1/** 2 * @license AngularJS v1.4.7 3 * (c) 2010-2015 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.4.7/' + 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 if (obj == null || isWindow(obj)) { 271 return false; 272 } 273
274 // Support: iOS 8.2 (not reproducible in simulator) 275 // "length" in obj used to prevent JIT error (gh-11508) 276 var length = "length" in Object(obj) && obj.length; 277 278 if (obj.nodeType === NODE_TYPE_ELEMENT && length) { 279 return true; 280 } 281 282 return isString(obj) || isArray(obj) || length === 0 || 283 typeof length === 'number' && length > 0 && (length - 1) in obj; 284} 285 286/** 287 * @ngdoc function 288 * @name angular.forEach 289 * @module ng 290 * @kind function 291 * 292 * @description 293 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 294 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 295 * is the value of an object property or an array element, `key` is the object property key or 296 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 297 * 298 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 299 * using the `hasOwnProperty` method. 300 * 301 * Unlike ES262's 302 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 303 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 304 * return the value provided. 305 * 306 ```js 307 var values = {name: 'misko', gender: 'male'}; 308 var log = []; 309 angular.forEach(values, function(value, key) { 310 this.push(key + ': ' + value); 311 }, log); 312 expect(log).toEqual(['name: misko', 'gender: male']); 313 ``` 314 * 315 * @param {Object|Array} obj Object to iterate over. 316 * @param {Function} iterator Iterator function. 317 * @param {Object=} context Object to become context (`this`) for the iterator function. 318 * @returns {Object|Array} Reference to `obj`. 319 */ 320 321function forEach(obj, iterator, context) { 322 var key, length; 323 if (obj) { 324 if (isFunction(obj)) { 325 for (key in obj) { 326 // Need to check if hasOwnProperty exists, 327 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 328 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 329 iterator.call(context, obj[key], key, obj); 330 } 331 } 332 } else if (isArray(obj) || isArrayLike(obj)) { 333 var isPrimitive = typeof obj !== 'object'; 334 for (key = 0, length = obj.length; key < length; key++) { 335 if (isPrimitive || key in obj) { 336 iterator.call(context, obj[key], key, obj); 337 } 338 } 339 } else if (obj.forEach && obj.forEach !== forEach) { 340 obj.forEach(iterator, context, obj); 341 } else if (isBlankObject(obj)) { 342 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 343 for (key in obj) { 344 iterator.call(context, obj[key], key, obj); 345 } 346 } else if (typeof obj.hasOwnProperty === 'function') { 347 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed 348 for (key in obj) { 349 if (obj.hasOwnProperty(key)) { 350 iterator.call(context, obj[key], key, obj); 351 } 352 } 353 } else { 354 // Slow path for objects which do not have a method `hasOwnProperty` 355 for (key in obj) { 356 if (hasOwnProperty.call(obj, key)) { 357 iterator.call(context, obj[key], key, obj); 358 } 359 } 360 } 361 } 362 return obj; 363} 364 365function forEachSorted(obj, iterator, context) { 366 var keys = Object.keys(obj).sort(); 367 for (var i = 0; i < keys.length; i++) { 368 iterator.call(context, obj[keys[i]], keys[i]); 369 } 370 return keys; 371} 372 373 374/** 375 * when using forEach the params are value, key, but it is often useful to have key, value. 376 * @param {function(string, *)} iteratorFn 377 * @returns {function(*, string)} 378 */ 379function reverseParams(iteratorFn) { 380 return function(value, key) { iteratorFn(key, value); }; 381} 382 383/** 384 * A consistent way of creating unique IDs in angular. 385 * 386 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before 387 * we hit number precision issues in JavaScript. 388 * 389 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M 390 * 391 * @returns {number}
vendor: 8,821 bytes, lines 391-758
391 an unique alpha-numeric string 392 */ 393function nextUid() { 394 return ++uid; 395} 396 397 398/** 399 * Set or clear the hashkey for an object. 400 * @param obj object 401 * @param h the hashkey (!truthy to delete the hashkey) 402 */ 403function setHashKey(obj, h) { 404 if (h) { 405 obj.$$hashKey = h; 406 } else { 407 delete obj.$$hashKey; 408 } 409} 410 411 412function baseExtend(dst, objs, deep) { 413 var h = dst.$$hashKey; 414 415 for (var i = 0, ii = objs.length; i < ii; ++i) { 416 var obj = objs[i]; 417 if (!isObject(obj) && !isFunction(obj)) continue; 418 var keys = Object.keys(obj); 419 for (var j = 0, jj = keys.length; j < jj; j++) { 420 var key = keys[j]; 421 var src = obj[key]; 422 423 if (deep && isObject(src)) { 424 if (isDate(src)) { 425 dst[key] = new Date(src.valueOf()); 426 } else if (isRegExp(src)) { 427 dst[key] = new RegExp(src); 428 } else { 429 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; 430 baseExtend(dst[key], [src], true); 431 } 432 } else { 433 dst[key] = src; 434 } 435 } 436 } 437 438 setHashKey(dst, h); 439 return dst; 440} 441 442/** 443 * @ngdoc function 444 * @name angular.extend 445 * @module ng 446 * @kind function 447 * 448 * @description 449 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 450 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 451 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. 452 * 453 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use 454 * {@link angular.merge} for this. 455 * 456 * @param {Object} dst Destination object. 457 * @param {...Object} src Source object(s). 458 * @returns {Object} Reference to `dst`. 459 */ 460function extend(dst) { 461 return baseExtend(dst, slice.call(arguments, 1), false); 462} 463 464 465/** 466* @ngdoc function 467* @name angular.merge 468* @module ng 469* @kind function 470* 471* @description 472* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 473* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 474* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. 475* 476* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source 477* objects, performing a deep copy. 478* 479* @param {Object} dst Destination object. 480* @param {...Object} src Source object(s). 481* @returns {Object} Reference to `dst`. 482*/ 483function merge(dst) { 484 return baseExtend(dst, slice.call(arguments, 1), true); 485} 486 487 488 489function toInt(str) { 490 return parseInt(str, 10); 491} 492 493 494function inherit(parent, extra) { 495 return extend(Object.create(parent), extra); 496} 497 498/** 499 * @ngdoc function 500 * @name angular.noop 501 * @module ng 502 * @kind function 503 * 504 * @description 505 * A function that performs no operations. This function can be useful when writing code in the 506 * functional style. 507 ```js 508 function foo(callback) { 509 var result = calculateResult(); 510 (callback || angular.noop)(result); 511 } 512 ``` 513 */ 514function noop() {} 515noop.$inject = []; 516 517 518/** 519 * @ngdoc function 520 * @name angular.identity 521 * @module ng 522 * @kind function 523 * 524 * @description 525 * A function that returns its first argument. This function is useful when writing code in the 526 * functional style. 527 * 528 ```js 529 function transformer(transformationFn, value) { 530 return (transformationFn || angular.identity)(value); 531 }; 532 ``` 533 * @param {*} value to be returned. 534 * @returns {*} the value passed in. 535 */ 536function identity($) {return $;} 537identity.$inject = []; 538 539 540function valueFn(value) {return function() {return value;};} 541 542function hasCustomToString(obj) { 543 return isFunction(obj.toString) && obj.toString !== Object.prototype.toString; 544} 545 546 547/** 548 * @ngdoc function 549 * @name angular.isUndefined 550 * @module ng 551 * @kind function 552 * 553 * @description 554 * Determines if a reference is undefined. 555 * 556 * @param {*} value Reference to check. 557 * @returns {boolean} True if `value` is undefined. 558 */ 559function isUndefined(value) {return typeof value === 'undefined';} 560 561 562/** 563 * @ngdoc function 564 * @name angular.isDefined 565 * @module ng 566 * @kind function 567 * 568 * @description 569 * Determines if a reference is defined. 570 * 571 * @param {*} value Reference to check. 572 * @returns {boolean} True if `value` is defined. 573 */ 574function isDefined(value) {return typeof value !== 'undefined';} 575 576 577/** 578 * @ngdoc function 579 * @name angular.isObject 580 * @module ng 581 * @kind function 582 * 583 * @description 584 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 585 * considered to be objects. Note that JavaScript arrays are objects. 586 * 587 * @param {*} value Reference to check. 588 * @returns {boolean} True if `value` is an `Object` but not `null`. 589 */ 590function isObject(value) { 591 // http://jsperf.com/isobject4 592 return value !== null && typeof value === 'object'; 593} 594 595 596/** 597 * Determine if a value is an object with a null prototype 598 * 599 * @returns {boolean} True if `value` is an `Object` with a null prototype 600 */ 601function isBlankObject(value) { 602 return value !== null && typeof value === 'object' && !getPrototypeOf(value); 603} 604 605 606/** 607 * @ngdoc function 608 * @name angular.isString 609 * @module ng 610 * @kind function 611 * 612 * @description 613 * Determines if a reference is a `String`. 614 * 615 * @param {*} value Reference to check. 616 * @returns {boolean} True if `value` is a `String`. 617 */ 618function isString(value) {return typeof value === 'string';} 619 620 621/** 622 * @ngdoc function 623 * @name angular.isNumber 624 * @module ng 625 * @kind function 626 * 627 * @description 628 * Determines if a reference is a `Number`. 629 * 630 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. 631 * 632 * If you wish to exclude these then you can use the native 633 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) 634 * method. 635 * 636 * @param {*} value Reference to check. 637 * @returns {boolean} True if `value` is a `Number`. 638 */ 639function isNumber(value) {return typeof value === 'number';} 640 641 642/** 643 * @ngdoc function 644 * @name angular.isDate 645 * @module ng 646 * @kind function 647 * 648 * @description 649 * Determines if a value is a date. 650 * 651 * @param {*} value Reference to check. 652 * @returns {boolean} True if `value` is a `Date`. 653 */ 654function isDate(value) { 655 return toString.call(value) === '[object Date]'; 656} 657 658 659/** 660 * @ngdoc function 661 * @name angular.isArray 662 * @module ng 663 * @kind function 664 * 665 * @description 666 * Determines if a reference is an `Array`. 667 * 668 * @param {*} value Reference to check. 669 * @returns {boolean} True if `value` is an `Array`. 670 */ 671var isArray = Array.isArray; 672 673/** 674 * @ngdoc function 675 * @name angular.isFunction 676 * @module ng 677 * @kind function 678 * 679 * @description 680 * Determines if a reference is a `Function`. 681 * 682 * @param {*} value Reference to check. 683 * @returns {boolean} True if `value` is a `Function`. 684 */ 685function isFunction(value) {return typeof value === 'function';} 686 687 688/** 689 * Determines if a value is a regular expression object. 690 * 691 * @private 692 * @param {*} value Reference to check. 693 * @returns {boolean} True if `value` is a `RegExp`. 694 */ 695function isRegExp(value) { 696 return toString.call(value) === '[object RegExp]'; 697} 698 699 700/** 701 * Checks if `obj` is a window object. 702 * 703 * @private 704 * @param {*} obj Object to check 705 * @returns {boolean} True if `obj` is a window obj. 706 */ 707function isWindow(obj) { 708 return obj && obj.window === obj; 709} 710 711 712function isScope(obj) { 713 return obj && obj.$evalAsync && obj.$watch; 714} 715 716 717function isFile(obj) { 718 return toString.call(obj) === '[object File]'; 719} 720 721 722function isFormData(obj) { 723 return toString.call(obj) === '[object FormData]'; 724} 725 726 727function isBlob(obj) { 728 return toString.call(obj) === '[object Blob]'; 729} 730 731 732function isBoolean(value) { 733 return typeof value === 'boolean'; 734} 735 736 737function isPromiseLike(obj) { 738 return obj && isFunction(obj.then); 739} 740 741 742var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/; 743function isTypedArray(value) { 744 return TYPED_ARRAY_REGEXP.test(toString.call(value)); 745} 746 747 748var trim = function(value) { 749 return isString(value) ? value.trim() : value; 750}; 751 752// Copied from: 753// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 754// Prereq: s is a string. 755var escapeForRegexp = function(s) { 756 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 757 replace(/\x08/g, '\\x08'); 758};
759 760 761/** 762 * @ngdoc function 763 * @name angular.isElement 764 * @module ng 765 * @kind function 766 * 767 * @description 768 * Determines if a reference is a DOM element (or wrapped jQuery element). 769 * 770 * @param {*} value Reference to check. 771 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 772 */ 773function isElement(node) { 774 return !!(node && 775 (node.nodeName // we are a direct element 776 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 777} 778 779/** 780 * @param str 'key1,key2,...' 781 * @returns {object} in the form of {key1:true, key2:true, ...} 782 */ 783function makeMap(str) { 784 var obj = {}, items = str.split(","), i; 785 for (i = 0; i < items.length; i++) { 786 obj[items[i]] = true; 787 } 788 return obj; 789} 790 791 792function nodeName_(element) { 793 return lowercase(element.nodeName || (element[0] && element[0].nodeName)); 794} 795 796function includes(array, obj) { 797 return Array.prototype.indexOf.call(array, obj) != -1; 798} 799 800function arrayRemove(array, value) { 801 var index = array.indexOf(value); 802 if (index >= 0) { 803 array.splice(index, 1); 804 } 805 return index; 806} 807 808/** 809 * @ngdoc function 810 * @name angular.copy 811 * @module ng 812 * @kind function 813 * 814 * @description 815 * Creates a deep copy of `source`, which should be an object or an array. 816 * 817 * * If no destination is supplied, a copy of the object or array is created. 818 * * If a destination is provided, all of its elements (for arrays) or properties (for objects) 819 * are deleted and then all elements/properties from the source are copied to it. 820 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 821 * * If `source` is identical to 'destination' an exception will be thrown. 822 * 823 * @param {*} source The source that will be used to make a copy. 824 * Can be any type, including primitives, `null`, and `undefined`. 825 * @param {(Object|Array)=} destination Destination into which the source is copied. If 826 * provided, must be of the same type as `source`. 827 * @returns {*} The copy or updated `destination`, if `destination` was specified. 828 * 829 * @example 830 <example module="copyExample"> 831 <file name="index.html">
832 <div ng-controller="ExampleController"> 833 <form novalidate class="simple-form"> 834 Name: <input type="text" ng-model="user.name" /><br /> 835 E-mail: <input type="email" ng-model="user.email" /><br /> 836 Gender: <input type="radio" ng-model="user.gender" value="male" />male 837 <input type="radio" ng-model="user.gender" value="female" />female<br /> 838 <button ng-click="reset()">RESET</button> 839 <button ng-click="update(user)">SAVE</button> 840 </form> 841 <pre>form = {{user | json}}</pre> 842 <pre>master = {{master | json}}</pre> 843 </div> 844 845 <script> 846 angular.module('copyExample', []) 847 .controller('ExampleController', ['$scope', function($scope) { 848 $scope.master= {}; 849 850 $scope.update = function(user) { 851 // Example with 1 argument 852 $scope.master= angular.copy(user); 853 }; 854 855 $scope.reset = function() { 856 // Example with 2 arguments 857 angular.copy($scope.master, $scope.user); 858 }; 859 860 $scope.reset(); 861 }]); 862 </script> 863 </file> 864 </example> 865 */ 866function copy(source, destination, stackSource, stackDest) { 867 if (isWindow(source) || isScope(source)) { 868 throw ngMinErr('cpws', 869 "Can't copy! Making copies of Window or Scope instances is not supported."); 870 } 871 if (isTypedArray(destination)) { 872 throw ngMinErr('cpta', 873 "Can't copy! TypedArray destination cannot be mutated."); 874 } 875 876 if (!destination) { 877 destination = source; 878 if (isObject(source)) { 879 var index; 880 if (stackSource && (index = stackSource.indexOf(source)) !== -1) { 881 return stackDest[index]; 882 } 883 884 // TypedArray, Date and RegExp have specific copy functionality and must be 885 // pushed onto the stack before returning. 886 // Array and other objects create the base object and recurse to copy child 887 // objects. The array/object will be pushed onto the stack when recursed. 888 if (isArray(source)) { 889 return copy(source, [], stackSource, stackDest); 890 } else if (isTypedArray(source)) { 891 destination = new source.constructor(source); 892 } else if (isDate(source)) { 893 destination = new Date(source.getTime()); 894 } else if (isRegExp(source)) { 895 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); 896 destination.lastIndex = source.lastIndex; 897 } else if (isFunction(source.cloneNode)) { 898 destination = source.cloneNode(true); 899 } else { 900 var emptyObject = Object.create(getPrototypeOf(source)); 901 return copy(source, emptyObject, stackSource, stackDest); 902 } 903 904 if (stackDest) { 905 stackSource.push(source); 906 stackDest.push(destination); 907 } 908 } 909 } else { 910 if (source === destination) throw ngMinErr('cpi', 911 "Can't copy! Source and destination are identical."); 912 913 stackSource = stackSource || []; 914 stackDest = stackDest || []; 915 916 if (isObject(source)) { 917 stackSource.push(source); 918 stackDest.push(destination); 919 } 920 921 var result, key; 922 if (isArray(source)) { 923 destination.length = 0; 924 for (var i = 0; i < source.length; i++) { 925 destination.push(copy(source[i], null, stackSource, stackDest)); 926 } 927 } else { 928 var h = destination.$$hashKey; 929 if (isArray(destination)) { 930 destination.length = 0; 931 } else { 932 forEach(destination, function(value, key) { 933 delete destination[key]; 934 }); 935 } 936 if (isBlankObject(source)) { 937 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 938 for (key in source) { 939 destination[key] = copy(source[key], null, stackSource, stackDest); 940 } 941 } else if (source && typeof source.hasOwnProperty === 'function') { 942 // Slow path, which must rely on hasOwnProperty 943 for (key in source) { 944 if (source.hasOwnProperty(key)) { 945 destination[key] = copy(source[key], null, stackSource, stackDest); 946 } 947 } 948 } else { 949 // Slowest path --- hasOwnProperty can't be called as a method 950 for (key in source) { 951 if (hasOwnProperty.call(source, key)) { 952 destination[key] = copy(source[key], null, stackSource, stackDest); 953 } 954 } 955 } 956 setHashKey(destination,h); 957 } 958 } 959 return destination; 960} 961 962/** 963 * Creates a shallow copy of an object, an array or a primitive. 964 * 965 * Assumes that there are no proto properties for objects. 966 */ 967function shallowCopy(src, dst) { 968 if (isArray(src)) { 969 dst = dst || []; 970 971 for (var i = 0, ii = src.length; i < ii; i++) { 972 dst[i] = src[i];
vendor: 14,750 bytes, lines 973-1440
973 } 974 } else if (isObject(src)) { 975 dst = dst || {}; 976 977 for (var key in src) { 978 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 979 dst[key] = src[key]; 980 } 981 } 982 } 983 984 return dst || src; 985} 986 987 988/** 989 * @ngdoc function 990 * @name angular.equals 991 * @module ng 992 * @kind function 993 * 994 * @description 995 * Determines if two objects or two values are equivalent. Supports value types, regular 996 * expressions, arrays and objects. 997 * 998 * Two objects or values are considered equivalent if at least one of the following is true: 999 * 1000 * * Both objects or values pass `===` comparison. 1001 * * Both objects or values are of the same type and all of their properties are equal by 1002 * comparing them with `angular.equals`. 1003 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 1004 * * Both values represent the same regular expression (In JavaScript, 1005 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 1006 * representation matches). 1007 * 1008 * During a property comparison, properties of `function` type and properties with names 1009 * that begin with `$` are ignored. 1010 * 1011 * Scope and DOMWindow objects are being compared only by identify (`===`). 1012 * 1013 * @param {*} o1 Object or value to compare. 1014 * @param {*} o2 Object or value to compare. 1015 * @returns {boolean} True if arguments are equal. 1016 */ 1017function equals(o1, o2) { 1018 if (o1 === o2) return true; 1019 if (o1 === null || o2 === null) return false; 1020 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 1021 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 1022 if (t1 == t2) { 1023 if (t1 == 'object') { 1024 if (isArray(o1)) { 1025 if (!isArray(o2)) return false; 1026 if ((length = o1.length) == o2.length) { 1027 for (key = 0; key < length; key++) { 1028 if (!equals(o1[key], o2[key])) return false; 1029 } 1030 return true; 1031 } 1032 } else if (isDate(o1)) { 1033 if (!isDate(o2)) return false; 1034 return equals(o1.getTime(), o2.getTime()); 1035 } else if (isRegExp(o1)) { 1036 return isRegExp(o2) ? o1.toString() == o2.toString() : false; 1037 } else { 1038 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || 1039 isArray(o2) || isDate(o2) || isRegExp(o2)) return false; 1040 keySet = createMap(); 1041 for (key in o1) { 1042 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 1043 if (!equals(o1[key], o2[key])) return false; 1044 keySet[key] = true; 1045 } 1046 for (key in o2) { 1047 if (!(key in keySet) && 1048 key.charAt(0) !== '$' && 1049 isDefined(o2[key]) && 1050 !isFunction(o2[key])) return false; 1051 } 1052 return true; 1053 } 1054 } 1055 } 1056 return false; 1057} 1058 1059var csp = function() { 1060 if (!isDefined(csp.rules)) { 1061 1062 1063 var ngCspElement = (document.querySelector('[ng-csp]') || 1064 document.querySelector('[data-ng-csp]')); 1065 1066 if (ngCspElement) { 1067 var ngCspAttribute = ngCspElement.getAttribute('ng-csp') || 1068 ngCspElement.getAttribute('data-ng-csp'); 1069 csp.rules = { 1070 noUnsafeEval: !ngCspAttribute || (ngCspAttribute.indexOf('no-unsafe-eval') !== -1), 1071 noInlineStyle: !ngCspAttribute || (ngCspAttribute.indexOf('no-inline-style') !== -1) 1072 }; 1073 } else { 1074 csp.rules = { 1075 noUnsafeEval: noUnsafeEval(), 1076 noInlineStyle: false 1077 }; 1078 } 1079 } 1080 1081 return csp.rules; 1082 1083 function noUnsafeEval() { 1084 try { 1085 /* jshint -W031, -W054 */ 1086 new Function(''); 1087 /* jshint +W031, +W054 */ 1088 return false; 1089 } catch (e) { 1090 return true; 1091 } 1092 } 1093}; 1094 1095/** 1096 * @ngdoc directive 1097 * @module ng 1098 * @name ngJq 1099 * 1100 * @element ANY 1101 * @param {string=} ngJq the name of the library available under `window` 1102 * to be used for angular.element 1103 * @description 1104 * Use this directive to force the angular.element library. This should be 1105 * used to force either jqLite by leaving ng-jq blank or setting the name of 1106 * the jquery variable under window (eg. jQuery). 1107 * 1108 * Since angular looks for this directive when it is loaded (doesn't wait for the 1109 * DOMContentLoaded event), it must be placed on an element that comes before the script 1110 * which loads angular. Also, only the first instance of `ng-jq` will be used and all 1111 * others ignored. 1112 * 1113 * @example 1114 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. 1115 ```html 1116 <!doctype html> 1117 <html ng-app ng-jq> 1118 ... 1119 ... 1120 </html> 1121 ``` 1122 * @example 1123 * This example shows how to use a jQuery based library of a different name. 1124 * The library name must be available at the top most 'window'. 1125 ```html 1126 <!doctype html> 1127 <html ng-app ng-jq="jQueryLib"> 1128 ... 1129 ... 1130 </html> 1131 ``` 1132 */ 1133var jq = function() { 1134 if (isDefined(jq.name_)) return jq.name_; 1135 var el; 1136 var i, ii = ngAttrPrefixes.length, prefix, name; 1137 for (i = 0; i < ii; ++i) { 1138 prefix = ngAttrPrefixes[i]; 1139 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) { 1140 name = el.getAttribute(prefix + 'jq'); 1141 break; 1142 } 1143 } 1144 1145 return (jq.name_ = name); 1146}; 1147 1148function concat(array1, array2, index) { 1149 return array1.concat(slice.call(array2, index)); 1150} 1151 1152function sliceArgs(args, startIndex) { 1153 return slice.call(args, startIndex || 0); 1154} 1155 1156 1157/* jshint -W101 */ 1158/** 1159 * @ngdoc function 1160 * @name angular.bind 1161 * @module ng 1162 * @kind function 1163 * 1164 * @description 1165 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1166 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1167 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1168 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1169 * 1170 * @param {Object} self Context which `fn` should be evaluated in. 1171 * @param {function()} fn Function to be bound. 1172 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1173 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1174 */ 1175/* jshint +W101 */ 1176function bind(self, fn) { 1177 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1178 if (isFunction(fn) && !(fn instanceof RegExp)) { 1179 return curryArgs.length 1180 ? function() { 1181 return arguments.length 1182 ? fn.apply(self, concat(curryArgs, arguments, 0)) 1183 : fn.apply(self, curryArgs); 1184 } 1185 : function() { 1186 return arguments.length 1187 ? fn.apply(self, arguments) 1188 : fn.call(self); 1189 }; 1190 } else { 1191 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1192 return fn; 1193 } 1194} 1195 1196 1197function toJsonReplacer(key, value) { 1198 var val = value; 1199 1200 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 1201 val = undefined; 1202 } else if (isWindow(value)) { 1203 val = '$WINDOW'; 1204 } else if (value && document === value) { 1205 val = '$DOCUMENT'; 1206 } else if (isScope(value)) { 1207 val = '$SCOPE'; 1208 } 1209 1210 return val; 1211} 1212 1213 1214/** 1215 * @ngdoc function 1216 * @name angular.toJson 1217 * @module ng 1218 * @kind function 1219 * 1220 * @description 1221 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 1222 * stripped since angular uses this notation internally. 1223 * 1224 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1225 * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace. 1226 * If set to an integer, the JSON output will contain that many spaces per indentation. 1227 * @returns {string|undefined} JSON-ified string representing `obj`. 1228 */ 1229function toJson(obj, pretty) { 1230 if (typeof obj === 'undefined') return undefined; 1231 if (!isNumber(pretty)) { 1232 pretty = pretty ? 2 : null; 1233 } 1234 return JSON.stringify(obj, toJsonReplacer, pretty); 1235} 1236 1237 1238/** 1239 * @ngdoc function 1240 * @name angular.fromJson 1241 * @module ng 1242 * @kind function 1243 * 1244 * @description 1245 * Deserializes a JSON string. 1246 * 1247 * @param {string} json JSON string to deserialize. 1248 * @returns {Object|Array|string|number} Deserialized JSON string. 1249 */ 1250function fromJson(json) { 1251 return isString(json) 1252 ? JSON.parse(json) 1253 : json; 1254} 1255 1256 1257function timezoneToOffset(timezone, fallback) { 1258 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 1259 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 1260} 1261 1262 1263function addDateMinutes(date, minutes) { 1264 date = new Date(date.getTime()); 1265 date.setMinutes(date.getMinutes() + minutes); 1266 return date; 1267} 1268 1269 1270function convertTimezoneToLocal(date, timezone, reverse) { 1271 reverse = reverse ? -1 : 1; 1272 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); 1273 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset())); 1274} 1275 1276 1277/** 1278 * @returns {string} Returns the string representation of the element. 1279 */ 1280function startingTag(element) { 1281 element = jqLite(element).clone(); 1282 try { 1283 // turns out IE does not let you set .html() on elements which 1284 // are not allowed to have children. So we just ignore it. 1285 element.empty(); 1286 } catch (e) {} 1287 var elemHtml = jqLite('<div>').append(element).html(); 1288 try { 1289 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 1290 elemHtml. 1291 match(/^(<[^>]+>)/)[1]. 1292 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1293 } catch (e) { 1294 return lowercase(elemHtml); 1295 } 1296 1297} 1298 1299 1300///////////////////////////////////////////////// 1301 1302/** 1303 * Tries to decode the URI component without throwing an exception. 1304 * 1305 * @private 1306 * @param str value potential URI component to check. 1307 * @returns {boolean} True if `value` can be decoded 1308 * with the decodeURIComponent function. 1309 */ 1310function tryDecodeURIComponent(value) { 1311 try { 1312 return decodeURIComponent(value); 1313 } catch (e) { 1314 // Ignore any invalid uri component 1315 } 1316} 1317 1318 1319/** 1320 * Parses an escaped url query string into key-value pairs. 1321 * @returns {Object.<string,boolean|Array>} 1322 */ 1323function parseKeyValue(/**string*/keyValue) { 1324 var obj = {}; 1325 forEach((keyValue || "").split('&'), function(keyValue) { 1326 var splitPoint, key, val; 1327 if (keyValue) { 1328 key = keyValue = keyValue.replace(/\+/g,'%20'); 1329 splitPoint = keyValue.indexOf('='); 1330 if (splitPoint !== -1) { 1331 key = keyValue.substring(0, splitPoint); 1332 val = keyValue.substring(splitPoint + 1); 1333 } 1334 key = tryDecodeURIComponent(key); 1335 if (isDefined(key)) { 1336 val = isDefined(val) ? tryDecodeURIComponent(val) : true; 1337 if (!hasOwnProperty.call(obj, key)) { 1338 obj[key] = val; 1339 } else if (isArray(obj[key])) { 1340 obj[key].push(val); 1341 } else { 1342 obj[key] = [obj[key],val]; 1343 } 1344 } 1345 } 1346 }); 1347 return obj; 1348} 1349 1350function toKeyValue(obj) { 1351 var parts = []; 1352 forEach(obj, function(value, key) { 1353 if (isArray(value)) { 1354 forEach(value, function(arrayValue) { 1355 parts.push(encodeUriQuery(key, true) + 1356 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1357 }); 1358 } else { 1359 parts.push(encodeUriQuery(key, true) + 1360 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1361 } 1362 }); 1363 return parts.length ? parts.join('&') : ''; 1364} 1365 1366 1367/** 1368 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1369 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1370 * segments: 1371 * segment = *pchar 1372 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1373 * pct-encoded = "%" HEXDIG HEXDIG 1374 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1375 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1376 * / "*" / "+" / "," / ";" / "=" 1377 */ 1378function encodeUriSegment(val) { 1379 return encodeUriQuery(val, true). 1380 replace(/%26/gi, '&'). 1381 replace(/%3D/gi, '='). 1382 replace(/%2B/gi, '+'); 1383} 1384 1385 1386/** 1387 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1388 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1389 * encoded per http://tools.ietf.org/html/rfc3986: 1390 * query = *( pchar / "/" / "?" ) 1391 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1392 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1393 * pct-encoded = "%" HEXDIG HEXDIG 1394 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1395 * / "*" / "+" / "," / ";" / "=" 1396 */ 1397function encodeUriQuery(val, pctEncodeSpaces) { 1398 return encodeURIComponent(val). 1399 replace(/%40/gi, '@'). 1400 replace(/%3A/gi, ':'). 1401 replace(/%24/g, '$'). 1402 replace(/%2C/gi, ','). 1403 replace(/%3B/gi, ';'). 1404 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1405} 1406 1407var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 1408 1409function getNgAttribute(element, ngAttr) { 1410 var attr, i, ii = ngAttrPrefixes.length; 1411 for (i = 0; i < ii; ++i) { 1412 attr = ngAttrPrefixes[i] + ngAttr; 1413 if (isString(attr = element.getAttribute(attr))) { 1414 return attr; 1415 } 1416 } 1417 return null; 1418} 1419 1420/** 1421 * @ngdoc directive 1422 * @name ngApp 1423 * @module ng 1424 * 1425 * @element ANY 1426 * @param {angular.Module} ngApp an optional application 1427 * {@link angular.module module} name to load. 1428 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 1429 * created in "strict-di" mode. This means that the application will fail to invoke functions which 1430 * do not use explicit function annotation (and are thus unsuitable for minification), as described 1431 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 1432 * tracking down the root of these bugs. 1433 * 1434 * @description 1435 * 1436 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1437 * designates the **root element** of the application and is typically placed near the root element 1438 * of the page - e.g. on the `<body>` or `<html>` tags. 1439 * 1440 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngAp
1440p` 1441 * found in the document will be used to define the root element to auto-bootstrap as an 1442 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1443 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1444 * 1445 * You can specify an **AngularJS module** to be used as the root module for the application. This 1446 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 1447 * should contain the application code needed or have dependencies on other modules that will 1448 * contain the code. See {@link angular.module} for more information. 1449 * 1450 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1451 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1452 * would not be resolved to `3`. 1453 * 1454 * `ngApp` is the easiest, and most common way to bootstrap an application. 1455 * 1456 <example module="ngAppDemo"> 1457 <file name="index.html"> 1458 <div ng-controller="ngAppDemoController"> 1459 I can add: {{a}} + {{b}} = {{ a+b }} 1460 </div> 1461 </file> 1462 <file name="script.js"> 1463 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1464 $scope.a = 1; 1465 $scope.b = 2; 1466 }); 1467 </file> 1468 </example> 1469 * 1470 * Using `ngStrictDi`, you would see something like this: 1471 * 1472 <example ng-app-included="true"> 1473 <file name="index.html"> 1474 <div ng-app="ngAppStrictDemo" ng-strict-di> 1475 <div ng-controller="GoodController1"> 1476 I can add: {{a}} + {{b}} = {{ a+b }} 1477 1478 <p>This renders because the controller does not fail to 1479 instantiate, by using explicit annotation style (see 1480 script.js for details) 1481 </p> 1482 </div> 1483 1484 <div ng-controller="GoodController2"> 1485 Name: <input ng-model="name"><br /> 1486 Hello, {{name}}! 1487 1488 <p>This renders because the controller does not fail to 1489 instantiate, by using explicit annotation style 1490 (see script.js for details) 1491 </p> 1492 </div> 1493 1494 <div ng-controller="BadController"> 1495 I can add: {{a}} + {{b}} = {{ a+b }} 1496 1497 <p>The controller could not be instantiated, due to relying 1498 on automatic function annotations (which are disabled in 1499 strict mode). As such, the content of this section is not 1500 interpolated, and there should be an error in your web console. 1501 </p> 1502 </div> 1503 </div> 1504 </file> 1505 <file name="script.js"> 1506 angular.module('ngAppStrictDemo', []) 1507 // BadController will fail to instantiate, due to relying on automatic function annotation, 1508 // rather than an explicit annotation 1509 .controller('BadController', function($scope) { 1510 $scope.a = 1; 1511 $scope.b = 2; 1512 }) 1513 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 1514 // due to using explicit annotations using the array style and $inject property, respectively. 1515 .controller('GoodController1', ['$scope', function($scope) { 1516 $scope.a = 1; 1517 $scope.b = 2; 1518 }]) 1519 .controller('GoodController2', GoodController2); 1520 function GoodController2($scope) { 1521 $scope.name = "World"; 1522 } 1523 GoodController2.$inject = ['$scope']; 1524 </file> 1525 <file name="style.css"> 1526 div[ng-controller] { 1527 margin-bottom: 1em; 1528 -webkit-border-radius: 4px; 1529 border-radius: 4px; 1530 border: 1px solid; 1531 padding: .5em; 1532 } 1533 div[ng-controller^=Good] { 1534 border-color: #d6e9c6; 1535 background-color: #dff0d8; 1536 color: #3c763d; 1537 } 1538 div[ng-controller^=Bad] { 1539 border-color: #ebccd1; 1540 background-color: #f2dede; 1541 color: #a94442; 1542 margin-bottom: 0; 1543 } 1544 </file> 1545 </example> 1546 */ 1547function angularInit(element, bootstrap) { 1548 var appElement, 1549 module, 1550 config = {}; 1551 1552 // The element `element` has priority over any other element 1553 forEach(ngAttrPrefixes, function(prefix) { 1554 var name = prefix + 'app'; 1555 1556 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 1557 appElement = element; 1558 module = element.getAttribute(name); 1559 } 1560 }); 1561 forEach(ngAttrPrefixes, function(prefix) { 1562 var name = prefix + 'app'; 1563 var candidate; 1564 1565 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 1566 appElement = candidate; 1567 module = candidate.getAttribute(name); 1568 } 1569 }); 1570 if (appElement) { 1571 config.strictDi = getNgAttribute(appElement, "strict-di") !== null; 1572 bootstrap(appElement, module ? [module] : [], config); 1573 } 1574} 1575 1576/** 1577 * @ngdoc function 1578 * @name angular.bootstrap 1579 * @module ng 1580 * @description 1581 * Use this function to manually start up angular application. 1582 * 1583 * See: {@link guide/bootstrap Bootstrap} 1584 *
1585 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually. 1586 * They must use {@link ng.directive:ngApp ngApp}. 1587 * 1588 * Angular will detect if it has been loaded into the browser more than once and only allow the 1589 * first loaded script to be bootstrapped and will report a warning to the browser console for 1590 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1591 * multiple instances of Angular try to work on the DOM. 1592 * 1593 * ```html 1594 * <!doctype html> 1595 * <html> 1596 * <body> 1597 * <div ng-controller="WelcomeController"> 1598 * {{greeting}} 1599 * </div> 1600 * 1601 * <script src="angular.js"></script> 1602 * <script> 1603 * var app = angular.module('demo', []) 1604 * .controller('WelcomeController', function($scope) { 1605 * $scope.greeting = 'Welcome!'; 1606 * }); 1607 * angular.bootstrap(document, ['demo']); 1608 * </script> 1609 * </body> 1610 * </html> 1611 * ``` 1612 * 1613 * @param {DOMElement} element DOM element which is the root of angular application. 1614 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1615 * Each item in the array should be the name of a predefined module or a (DI annotated) 1616 * function that will be invoked by the injector as a `config` block. 1617 * See: {@link angular.module modules} 1618 * @param {Object=} config an object for defining configuration options for the application. The 1619 * following keys are supported: 1620 * 1621 * * `strictDi` - disable automatic function annotation for the application. This is meant to 1622 * assist in finding bugs which break minified code. Defaults to `false`. 1623 * 1624 * @returns {auto.$injector} Returns the newly created injector for this app. 1625 */ 1626function bootstrap(element, modules, config) { 1627 if (!isObject(config)) config = {}; 1628 var defaultConfig = { 1629 strictDi: false 1630 }; 1631 config = extend(defaultConfig, config); 1632 var doBootstrap = function() { 1633 element = jqLite(element); 1634 1635 if (element.injector()) { 1636 var tag = (element[0] === document) ? 'document' : startingTag(element); 1637 //Encode angle brackets to prevent input from being sanitized to empty string #8683 1638 throw ngMinErr( 1639 'btstrpd', 1640 "App Already Bootstrapped with this Element '{0}'", 1641 tag.replace(/</,'<').replace(/>/,'>')); 1642 } 1643 1644 modules = modules || []; 1645 modules.unshift(['$provide', function($provide) { 1646 $provide.value('$rootElement', element); 1647 }]); 1648 1649 if (config.debugInfoEnabled) { 1650 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 1651 modules.push(['$compileProvider', function($compileProvider) { 1652 $compileProvider.debugInfoEnabled(true); 1653 }]); 1654 } 1655 1656 modules.unshift('ng'); 1657 var injector = createInjector(modules, config.strictDi); 1658 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 1659 function bootstrapApply(scope, element, compile, injector) { 1660 scope.$apply(function() { 1661 element.data('$injector', injector); 1662 compile(element)(scope); 1663 }); 1664 }] 1665 ); 1666 return injector; 1667 }; 1668 1669 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 1670 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1671 1672 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 1673 config.debugInfoEnabled = true; 1674 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 1675 } 1676 1677 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1678 return doBootstrap(); 1679 } 1680 1681 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1682 angular.resumeBootstrap = function(extraModules) { 1683 forEach(extraModules, function(module) { 1684 modules.push(module); 1685 }); 1686 return doBootstrap(); 1687 }; 1688 1689 if (isFunction(angular.resumeDeferredBootstrap)) { 1690 angular.resumeDeferredBootstrap(); 1691 } 1692} 1693 1694/** 1695 * @ngdoc function 1696 * @name angular.reloadWithDebugInfo 1697 * @module ng 1698 * @description 1699 * Use this function to reload the current application with debug information turned on. 1700 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 1701 * 1702 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 1703 */ 1704function reloadWithDebugInfo() { 1705 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 1706 window.location.reload(); 1707} 1708 1709/** 1710 * @name angular.getTestability 1711 * @module ng 1712 * @description 1713 * Get the testability service for the instance of Angular on the given 1714 * element. 1715 * @param {DOMElement} element DOM element which is the root of angular application. 1716 */ 1717function getTestability(rootElement) { 1718 var injector = angular.element(rootElement).injector(); 1719 if (!injector) { 1720 throw ngMinErr('test', 1721 'no injector found for element argument to getTestability'); 1722 } 1723 return injector.get('$$testability'); 1724} 1725 1726var SNAKE_CASE_REGEXP = /[A-Z]/g; 1727function snake_case(name, separator) { 1728 separator = separator || '_'; 1729 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1730 return (pos ? separator : '') + letter.toLowerCase(); 1731 }); 1732} 1733 1734var bindJQueryFired = false; 1735var skipDestroyOnNextJQueryCleanData; 1736function bindJQuery() { 1737 var originalCleanData; 1738 1739 if (bindJQueryFired) { 1740 return; 1741 } 1742 1743 // bind to jQuery if present; 1744 var jqName = jq(); 1745 jQuery = isUndefined(jqName) ? window.jQuery : // use jQuery (if present) 1746 !jqName ? undefined : // use jqLite 1747 window[jqName]; // use jQuery specified by `ngJq` 1748 1749 // Use jQuery if it exists with proper functionality, otherwise default to us. 1750 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. 1751 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older 1752 // versions. It will not work for sure with jQuery <1.7, though. 1753 if (jQuery && jQuery.fn.on) { 1754 jqLite = jQuery; 1755 extend(jQuery.fn, { 1756 scope: JQLitePrototype.scope, 1757 isolateScope: JQLitePrototype.isolateScope, 1758 controller: JQLitePrototype.controller, 1759 injector: JQLitePrototype.injector, 1760 inheritedData: JQLitePrototype.inheritedData 1761 });
vendor: 8,342 bytes, lines 1762-2024
1762 1763 // All nodes removed from the DOM via various jQuery APIs like .remove() 1764 // are passed through jQuery.cleanData. Monkey-patch this method to fire 1765 // the $destroy event on all removed nodes. 1766 originalCleanData = jQuery.cleanData; 1767 jQuery.cleanData = function(elems) { 1768 var events; 1769 if (!skipDestroyOnNextJQueryCleanData) { 1770 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 1771 events = jQuery._data(elem, "events"); 1772 if (events && events.$destroy) { 1773 jQuery(elem).triggerHandler('$destroy'); 1774 } 1775 } 1776 } else { 1777 skipDestroyOnNextJQueryCleanData = false; 1778 } 1779 originalCleanData(elems); 1780 }; 1781 } else { 1782 jqLite = JQLite; 1783 } 1784 1785 angular.element = jqLite; 1786 1787 // Prevent double-proxying. 1788 bindJQueryFired = true; 1789} 1790 1791/** 1792 * throw error if the argument is falsy. 1793 */ 1794function assertArg(arg, name, reason) { 1795 if (!arg) { 1796 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1797 } 1798 return arg; 1799} 1800 1801function assertArgFn(arg, name, acceptArrayAnnotation) { 1802 if (acceptArrayAnnotation && isArray(arg)) { 1803 arg = arg[arg.length - 1]; 1804 } 1805 1806 assertArg(isFunction(arg), name, 'not a function, got ' + 1807 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1808 return arg; 1809} 1810 1811/** 1812 * throw error if the name given is hasOwnProperty 1813 * @param {String} name the name to test 1814 * @param {String} context the context in which the name is used, such as module or directive 1815 */ 1816function assertNotHasOwnProperty(name, context) { 1817 if (name === 'hasOwnProperty') { 1818 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1819 } 1820} 1821 1822/** 1823 * Return the value accessible from the object by path. Any undefined traversals are ignored 1824 * @param {Object} obj starting object 1825 * @param {String} path path to traverse 1826 * @param {boolean} [bindFnToScope=true] 1827 * @returns {Object} value as accessible by path 1828 */ 1829//TODO(misko): this function needs to be removed 1830function getter(obj, path, bindFnToScope) { 1831 if (!path) return obj; 1832 var keys = path.split('.'); 1833 var key; 1834 var lastInstance = obj; 1835 var len = keys.length; 1836 1837 for (var i = 0; i < len; i++) { 1838 key = keys[i]; 1839 if (obj) { 1840 obj = (lastInstance = obj)[key]; 1841 } 1842 } 1843 if (!bindFnToScope && isFunction(obj)) { 1844 return bind(lastInstance, obj); 1845 } 1846 return obj; 1847} 1848 1849/** 1850 * Return the DOM siblings between the first and last node in the given array. 1851 * @param {Array} array like object 1852 * @returns {Array} the inputted object or a jqLite collection containing the nodes 1853 */ 1854function getBlockNodes(nodes) { 1855 // TODO(perf): update `nodes` instead of creating a new object? 1856 var node = nodes[0]; 1857 var endNode = nodes[nodes.length - 1]; 1858 var blockNodes; 1859 1860 for (var i = 1; node !== endNode && (node = node.nextSibling); i++) { 1861 if (blockNodes || nodes[i] !== node) { 1862 if (!blockNodes) { 1863 blockNodes = jqLite(slice.call(nodes, 0, i)); 1864 } 1865 blockNodes.push(node); 1866 } 1867 } 1868 1869 return blockNodes || nodes; 1870} 1871 1872 1873/** 1874 * Creates a new object without a prototype. This object is useful for lookup without having to 1875 * guard against prototypically inherited properties via hasOwnProperty. 1876 * 1877 * Related micro-benchmarks: 1878 * - http://jsperf.com/object-create2 1879 * - http://jsperf.com/proto-map-lookup/2 1880 * - http://jsperf.com/for-in-vs-object-keys2 1881 * 1882 * @returns {Object} 1883 */ 1884function createMap() { 1885 return Object.create(null); 1886} 1887 1888var NODE_TYPE_ELEMENT = 1; 1889var NODE_TYPE_ATTRIBUTE = 2; 1890var NODE_TYPE_TEXT = 3; 1891var NODE_TYPE_COMMENT = 8; 1892var NODE_TYPE_DOCUMENT = 9; 1893var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 1894 1895/** 1896 * @ngdoc type 1897 * @name angular.Module 1898 * @module ng 1899 * @description 1900 * 1901 * Interface for configuring angular {@link angular.module modules}. 1902 */ 1903 1904function setupModuleLoader(window) { 1905 1906 var $injectorMinErr = minErr('$injector'); 1907 var ngMinErr = minErr('ng'); 1908 1909 function ensure(obj, name, factory) { 1910 return obj[name] || (obj[name] = factory()); 1911 } 1912 1913 var angular = ensure(window, 'angular', Object); 1914 1915 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1916 angular.$$minErr = angular.$$minErr || minErr; 1917 1918 return ensure(angular, 'module', function() { 1919 /** @type {Object.<string, angular.Module>} */ 1920 var modules = {}; 1921 1922 /** 1923 * @ngdoc function 1924 * @name angular.module 1925 * @module ng 1926 * @description 1927 * 1928 * The `angular.module` is a global place for creating, registering and retrieving Angular 1929 * modules. 1930 * All modules (angular core or 3rd party) that should be available to an application must be 1931 * registered using this mechanism. 1932 * 1933 * Passing one argument retrieves an existing {@link angular.Module}, 1934 * whereas passing more than one argument creates a new {@link angular.Module} 1935 * 1936 * 1937 * # Module 1938 * 1939 * A module is a collection of services, directives, controllers, filters, and configuration information. 1940 * `angular.module` is used to configure the {@link auto.$injector $injector}. 1941 * 1942 * ```js 1943 * // Create a new module 1944 * var myModule = angular.module('myModule', []); 1945 * 1946 * // register a new service 1947 * myModule.value('appName', 'MyCoolApp'); 1948 * 1949 * // configure existing services inside initialization blocks. 1950 * myModule.config(['$locationProvider', function($locationProvider) { 1951 * // Configure existing providers 1952 * $locationProvider.hashPrefix('!'); 1953 * }]); 1954 * ``` 1955 * 1956 * Then you can create an injector and load your modules like this: 1957 * 1958 * ```js 1959 * var injector = angular.injector(['ng', 'myModule']) 1960 * ``` 1961 * 1962 * However it's more likely that you'll just use 1963 * {@link ng.directive:ngApp ngApp} or 1964 * {@link angular.bootstrap} to simplify this process for you. 1965 * 1966 * @param {!string} name The name of the module to create or retrieve. 1967 * @param {!Array.<string>=} requires If specified then new module is being created. If 1968 * unspecified then the module is being retrieved for further configuration. 1969 * @param {Function=} configFn Optional configuration function for the module. Same as 1970 * {@link angular.Module#config Module#config()}. 1971 * @returns {module} new module with the {@link angular.Module} api. 1972 */ 1973 return function module(name, requires, configFn) { 1974 var assertNotHasOwnProperty = function(name, context) { 1975 if (name === 'hasOwnProperty') { 1976 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 1977 } 1978 }; 1979 1980 assertNotHasOwnProperty(name, 'module'); 1981 if (requires && modules.hasOwnProperty(name)) { 1982 modules[name] = null; 1983 } 1984 return ensure(modules, name, function() { 1985 if (!requires) { 1986 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 1987 "the module name or forgot to load it. If registering a module ensure that you " + 1988 "specify the dependencies as the second argument.", name); 1989 } 1990 1991 /** @type {!Array.<Array.<*>>} */ 1992 var invokeQueue = []; 1993 1994 /** @type {!Array.<Function>} */ 1995 var configBlocks = []; 1996 1997 /** @type {!Array.<Function>} */ 1998 var runBlocks = []; 1999 2000 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 2001 2002 /** @type {angular.Module} */ 2003 var moduleInstance = { 2004 // Private state 2005 _invokeQueue: invokeQueue, 2006 _configBlocks: configBlocks, 2007 _runBlocks: runBlocks, 2008 2009 /** 2010 * @ngdoc property 2011 * @name angular.Module#requires 2012 * @module ng 2013 * 2014 * @description 2015 * Holds the list of modules which the injector will load before the current module is 2016 * loaded. 2017 */ 2018 requires: requires, 2019 2020 /** 2021 * @ngdoc property 2022 * @name angular.Module#name 2023 * @module ng 2024 *
2025 * @description 2026 * Name of the module. 2027 */ 2028 name: name, 2029 2030 2031 /** 2032 * @ngdoc method 2033 * @name angular.Module#provider 2034 * @module ng 2035 * @param {string} name service name 2036 * @param {Function} providerType Construction function for creating new instance of the 2037 * service. 2038 * @description 2039 * See {@link auto.$provide#provider $provide.provider()}. 2040 */ 2041 provider: invokeLaterAndSetModuleName('$provide', 'provider'), 2042 2043 /** 2044 * @ngdoc method 2045 * @name angular.Module#factory 2046 * @module ng 2047 * @param {string} name service name 2048 * @param {Function} providerFunction Function for creating new instance of the service. 2049 * @description 2050 * See {@link auto.$provide#factory $provide.factory()}. 2051 */ 2052 factory: invokeLaterAndSetModuleName('$provide', 'factory'), 2053 2054 /** 2055 * @ngdoc method 2056 * @name angular.Module#service 2057 * @module ng 2058 * @param {string} name service name 2059 * @param {Function} constructor A constructor function that will be instantiated. 2060 * @description 2061 * See {@link auto.$provide#service $provide.service()}. 2062 */ 2063 service: invokeLaterAndSetModuleName('$provide', 'service'), 2064 2065 /** 2066 * @ngdoc method 2067 * @name angular.Module#value 2068 * @module ng 2069 * @param {string} name service name 2070 * @param {*} object Service instance object. 2071 * @description 2072 * See {@link auto.$provide#value $provide.value()}. 2073 */ 2074 value: invokeLater('$provide', 'value'), 2075 2076 /** 2077 * @ngdoc method 2078 * @name angular.Module#constant 2079 * @module ng 2080 * @param {string} name constant name 2081 * @param {*} object Constant value. 2082 * @description 2083 * Because the constant are fixed, they get applied before other provide methods. 2084 * See {@link auto.$provide#constant $provide.constant()}. 2085 */ 2086 constant: invokeLater('$provide', 'constant', 'unshift'), 2087 2088 /** 2089 * @ngdoc method 2090 * @name angular.Module#decorator 2091 * @module ng 2092 * @param {string} The name of the service to decorate. 2093 * @param {Function} This function will be invoked when the service needs to be 2094 * instantiated and should return the decorated service instance. 2095 * @description 2096 * See {@link auto.$provide#decorator $provide.decorator()}. 2097 */ 2098 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), 2099 2100 /** 2101 * @ngdoc method 2102 * @name angular.Module#animation 2103 * @module ng 2104 * @param {string} name animation name 2105 * @param {Function} animationFactory Factory function for creating new instance of an 2106 * animation. 2107 * @description 2108 * 2109 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 2110 * 2111 * 2112 * Defines an animation hook that can be later used with 2113 * {@link $animate $animate} service and directives that use this service. 2114 * 2115 * ```js 2116 * module.animation('.animation-name', function($inject1, $inject2) { 2117 * return { 2118 * eventName : function(element, done) { 2119 * //code to run the animation 2120 * //once complete, then run done() 2121 * return function cancellationFunction(element) { 2122 * //code to cancel the animation 2123 * } 2124 * } 2125 * } 2126 * }) 2127 * ``` 2128 * 2129 * See {@link ng.$animateProvider#register $animateProvider.register()} and 2130 * {@link ngAnimate ngAnimate module} for more information. 2131 */ 2132 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), 2133 2134 /** 2135 * @ngdoc method 2136 * @name angular.Module#filter 2137 * @module ng 2138 * @param {string} name Filter name - this must be a valid angular expression identifier 2139 * @param {Function} filterFactory Factory function for creating new instance of filter.
2140 * @description 2141 * See {@link ng.$filterProvider#register $filterProvider.register()}. 2142 * 2143 * <div class="alert alert-warning"> 2144 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 2145 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 2146 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 2147 * (`myapp_subsection_filterx`). 2148 * </div> 2149 */ 2150 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), 2151 2152 /** 2153 * @ngdoc method 2154 * @name angular.Module#controller 2155 * @module ng 2156 * @param {string|Object} name Controller name, or an object map of controllers where the 2157 * keys are the names and the values are the constructors. 2158 * @param {Function} constructor Controller constructor function. 2159 * @description 2160 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 2161 */ 2162 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), 2163 2164 /** 2165 * @ngdoc method 2166 * @name angular.Module#directive 2167 * @module ng 2168 * @param {string|Object} name Directive name, or an object map of directives where the 2169 * keys are the names and the values are the factories. 2170 * @param {Function} directiveFactory Factory function for creating new instance of 2171 * directives. 2172 * @description 2173 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 2174 */ 2175 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), 2176 2177 /** 2178 * @ngdoc method 2179 * @name angular.Module#config 2180 * @module ng 2181 * @param {Function} configFn Execute this function on module load. Useful for service 2182 * configuration. 2183 * @description 2184 * Use this method to register work which needs to be performed on module loading. 2185 * For more about how to configure services, see 2186 * {@link providers#provider-recipe Provider Recipe}. 2187 */ 2188 config: config, 2189 2190 /** 2191 * @ngdoc method 2192 * @name angular.Module#run 2193 * @module ng 2194 * @param {Function} initializationFn Execute this function after injector creation. 2195 * Useful for application initialization. 2196 * @description 2197 * Use this method to register work which should be performed when the injector is done 2198 * loading all modules. 2199 */ 2200 run: function(block) { 2201 runBlocks.push(block); 2202 return this; 2203 } 2204 }; 2205 2206 if (configFn) { 2207 config(configFn); 2208 } 2209 2210 return moduleInstance; 2211 2212 /** 2213 * @param {string} provider 2214 * @param {string} method 2215 * @param {String=} insertMethod 2216 * @returns {angular.Module} 2217 */ 2218 function invokeLater(provider, method, insertMethod, queue) { 2219 if (!queue) queue = invokeQueue; 2220 return function() { 2221 queue[insertMethod || 'push']([provider, method, arguments]); 2222 return moduleInstance; 2223 }; 2224 } 2225 2226 /** 2227 * @param {string} provider 2228 * @param {string} method 2229 * @returns {angular.Module} 2230 */ 2231 function invokeLaterAndSetModuleName(provider, method) { 2232 return function(recipeName, factoryFunction) { 2233 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; 2234 invokeQueue.push([provider, method, arguments]); 2235 return moduleInstance; 2236 }; 2237 } 2238 }); 2239 }; 2240 }); 2241 2242} 2243 2244/* global: toDebugString: true */ 2245 2246function serializeObject(obj) { 2247 var seen = []; 2248 2249 return JSON.stringify(obj, function(key, val) { 2250 val = toJsonReplacer(key, val); 2251 if (isObject(val)) { 2252 2253 if (seen.indexOf(val) >= 0) return '...'; 2254 2255 seen.push(val); 2256 } 2257 return val; 2258 }); 2259} 2260 2261function toDebugString(obj) { 2262 if (typeof obj === 'function') { 2263 return obj.toString().replace(/ \{[\s\S]*$/, ''); 2264 } else if (isUndefined(obj)) { 2265 return 'undefined'; 2266 } else if (typeof obj !== 'string') { 2267 return serializeObject(obj); 2268 } 2269 return obj;
vendor: 6,462 bytes, lines 2270-2490
2270} 2271 2272/* global angularModule: true, 2273 version: true, 2274 2275 $CompileProvider, 2276 2277 htmlAnchorDirective, 2278 inputDirective, 2279 inputDirective, 2280 formDirective, 2281 scriptDirective, 2282 selectDirective, 2283 styleDirective, 2284 optionDirective, 2285 ngBindDirective, 2286 ngBindHtmlDirective, 2287 ngBindTemplateDirective, 2288 ngClassDirective, 2289 ngClassEvenDirective, 2290 ngClassOddDirective, 2291 ngCloakDirective, 2292 ngControllerDirective, 2293 ngFormDirective, 2294 ngHideDirective, 2295 ngIfDirective, 2296 ngIncludeDirective, 2297 ngIncludeFillContentDirective, 2298 ngInitDirective, 2299 ngNonBindableDirective, 2300 ngPluralizeDirective, 2301 ngRepeatDirective, 2302 ngShowDirective, 2303 ngStyleDirective, 2304 ngSwitchDirective, 2305 ngSwitchWhenDirective, 2306 ngSwitchDefaultDirective, 2307 ngOptionsDirective, 2308 ngTranscludeDirective, 2309 ngModelDirective, 2310 ngListDirective, 2311 ngChangeDirective, 2312 patternDirective, 2313 patternDirective, 2314 requiredDirective, 2315 requiredDirective, 2316 minlengthDirective, 2317 minlengthDirective, 2318 maxlengthDirective, 2319 maxlengthDirective, 2320 ngValueDirective, 2321 ngModelOptionsDirective, 2322 ngAttributeAliasDirectives, 2323 ngEventDirectives, 2324 2325 $AnchorScrollProvider, 2326 $AnimateProvider, 2327 $CoreAnimateCssProvider, 2328 $$CoreAnimateQueueProvider, 2329 $$CoreAnimateRunnerProvider, 2330 $BrowserProvider, 2331 $CacheFactoryProvider, 2332 $ControllerProvider, 2333 $DocumentProvider, 2334 $ExceptionHandlerProvider, 2335 $FilterProvider, 2336 $$ForceReflowProvider, 2337 $InterpolateProvider, 2338 $IntervalProvider, 2339 $$HashMapProvider, 2340 $HttpProvider, 2341 $HttpParamSerializerProvider, 2342 $HttpParamSerializerJQLikeProvider, 2343 $HttpBackendProvider, 2344 $xhrFactoryProvider, 2345 $LocationProvider, 2346 $LogProvider, 2347 $ParseProvider, 2348 $RootScopeProvider, 2349 $QProvider, 2350 $$QProvider, 2351 $$SanitizeUriProvider, 2352 $SceProvider, 2353 $SceDelegateProvider, 2354 $SnifferProvider, 2355 $TemplateCacheProvider, 2356 $TemplateRequestProvider, 2357 $$TestabilityProvider, 2358 $TimeoutProvider, 2359 $$RAFProvider, 2360 $WindowProvider, 2361 $$jqLiteProvider, 2362 $$CookieReaderProvider 2363*/ 2364 2365 2366/** 2367 * @ngdoc object 2368 * @name angular.version 2369 * @module ng 2370 * @description 2371 * An object that contains information about the current AngularJS version. 2372 * 2373 * This object has the following properties: 2374 * 2375 * - `full` â `{string}` â Full version string, such as "0.9.18". 2376 * - `major` â `{number}` â Major version number, such as "0". 2377 * - `minor` â `{number}` â Minor version number, such as "9". 2378 * - `dot` â `{number}` â Dot version number, such as "18". 2379 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 2380 */ 2381var version = { 2382 full: '1.4.7', // all of these placeholder strings will be replaced by grunt's 2383 major: 1, // package task 2384 minor: 4, 2385 dot: 7, 2386 codeName: 'dark-luminescence' 2387}; 2388 2389 2390function publishExternalAPI(angular) { 2391 extend(angular, { 2392 'bootstrap': bootstrap, 2393 'copy': copy, 2394 'extend': extend, 2395 'merge': merge, 2396 'equals': equals, 2397 'element': jqLite, 2398 'forEach': forEach, 2399 'injector': createInjector, 2400 'noop': noop, 2401 'bind': bind, 2402 'toJson': toJson, 2403 'fromJson': fromJson, 2404 'identity': identity, 2405 'isUndefined': isUndefined, 2406 'isDefined': isDefined, 2407 'isString': isString, 2408 'isFunction': isFunction, 2409 'isObject': isObject, 2410 'isNumber': isNumber, 2411 'isElement': isElement, 2412 'isArray': isArray, 2413 'version': version, 2414 'isDate': isDate, 2415 'lowercase': lowercase, 2416 'uppercase': uppercase, 2417 'callbacks': {counter: 0}, 2418 'getTestability': getTestability, 2419 '$$minErr': minErr, 2420 '$$csp': csp, 2421 'reloadWithDebugInfo': reloadWithDebugInfo 2422 }); 2423 2424 angularModule = setupModuleLoader(window); 2425 2426 angularModule('ng', ['ngLocale'], ['$provide', 2427 function ngModule($provide) { 2428 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2429 $provide.provider({ 2430 $$sanitizeUri: $$SanitizeUriProvider 2431 }); 2432 $provide.provider('$compile', $CompileProvider). 2433 directive({ 2434 a: htmlAnchorDirective, 2435 input: inputDirective, 2436 textarea: inputDirective, 2437 form: formDirective, 2438 script: scriptDirective, 2439 select: selectDirective, 2440 style: styleDirective, 2441 option: optionDirective, 2442 ngBind: ngBindDirective, 2443 ngBindHtml: ngBindHtmlDirective, 2444 ngBindTemplate: ngBindTemplateDirective, 2445 ngClass: ngClassDirective, 2446 ngClassEven: ngClassEvenDirective, 2447 ngClassOdd: ngClassOddDirective, 2448 ngCloak: ngCloakDirective, 2449 ngController: ngControllerDirective, 2450 ngForm: ngFormDirective, 2451 ngHide: ngHideDirective, 2452 ngIf: ngIfDirective, 2453 ngInclude: ngIncludeDirective, 2454 ngInit: ngInitDirective, 2455 ngNonBindable: ngNonBindableDirective, 2456 ngPluralize: ngPluralizeDirective, 2457 ngRepeat: ngRepeatDirective, 2458 ngShow: ngShowDirective, 2459 ngStyle: ngStyleDirective, 2460 ngSwitch: ngSwitchDirective, 2461 ngSwitchWhen: ngSwitchWhenDirective, 2462 ngSwitchDefault: ngSwitchDefaultDirective, 2463 ngOptions: ngOptionsDirective, 2464 ngTransclude: ngTranscludeDirective, 2465 ngModel: ngModelDirective, 2466 ngList: ngListDirective, 2467 ngChange: ngChangeDirective, 2468 pattern: patternDirective, 2469 ngPattern: patternDirective, 2470 required: requiredDirective, 2471 ngRequired: requiredDirective, 2472 minlength: minlengthDirective, 2473 ngMinlength: minlengthDirective, 2474 maxlength: maxlengthDirective, 2475 ngMaxlength: maxlengthDirective, 2476 ngValue: ngValueDirective, 2477 ngModelOptions: ngModelOptionsDirective 2478 }). 2479 directive({ 2480 ngInclude: ngIncludeFillContentDirective 2481 }). 2482 directive(ngAttributeAliasDirectives). 2483 directive(ngEventDirectives); 2484 $provide.provider({ 2485 $anchorScroll: $AnchorScrollProvider, 2486 $animate: $AnimateProvider, 2487 $animateCss: $CoreAnimateCssProvider, 2488 $$animateQueue: $$CoreAnimateQueueProvider, 2489 $$AnimateRunner: $$CoreAnimateRunnerProvider, 2490 $browser: $BrowserProvider,
2491 $cacheFactory: $CacheFactoryProvider, 2492 $controller: $ControllerProvider, 2493 $document: $DocumentProvider, 2494 $exceptionHandler: $ExceptionHandlerProvider, 2495 $filter: $FilterProvider, 2496 $$forceReflow: $$ForceReflowProvider, 2497 $interpolate: $InterpolateProvider, 2498 $interval: $IntervalProvider, 2499 $http: $HttpProvider, 2500 $httpParamSerializer: $HttpParamSerializerProvider, 2501 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, 2502 $httpBackend: $HttpBackendProvider, 2503 $xhrFactory: $xhrFactoryProvider, 2504 $location: $LocationProvider, 2505 $log: $LogProvider, 2506 $parse: $ParseProvider, 2507 $rootScope: $RootScopeProvider, 2508 $q: $QProvider, 2509 $$q: $$QProvider, 2510 $sce: $SceProvider, 2511 $sceDelegate: $SceDelegateProvider, 2512 $sniffer: $SnifferProvider, 2513 $templateCache: $TemplateCacheProvider, 2514 $templateRequest: $TemplateRequestProvider, 2515 $$testability: $$TestabilityProvider, 2516 $timeout: $TimeoutProvider, 2517 $window: $WindowProvider, 2518 $$rAF: $$RAFProvider, 2519 $$jqLite: $$jqLiteProvider, 2520 $$HashMap: $$HashMapProvider, 2521 $$cookieReader: $$CookieReaderProvider 2522 }); 2523 } 2524 ]); 2525} 2526 2527/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2528 * Any commits to this file should be reviewed with security in mind. * 2529 * Changes to this file can potentially create security vulnerabilities. * 2530 * An approval from 2 Core members with history of modifying * 2531 * this file is required. * 2532 * * 2533 * Does the change somehow allow for arbitrary javascript to be executed? * 2534 * Or allows for someone to change the prototype of built-in objects? * 2535 * Or gives undesired access to variables likes document or window? * 2536 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 2537 2538/* global JQLitePrototype: true, 2539 addEventListenerFn: true, 2540 removeEventListenerFn: true, 2541 BOOLEAN_ATTR: true, 2542 ALIASED_ATTR: true, 2543*/ 2544 2545////////////////////////////////// 2546//JQLite 2547////////////////////////////////// 2548 2549/** 2550 * @ngdoc function 2551 * @name angular.element 2552 * @module ng 2553 * @kind function 2554 * 2555 * @description 2556 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2557 * 2558 * If jQuery is available, `angular.element` is an alias for the 2559 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2560 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." 2561 * 2562 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows 2563 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most 2564 * commonly needed functionality with the goal of having a very small footprint.</div> 2565 * 2566 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. 2567 * 2568 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or 2569 * jqLite; they are never raw DOM references.</div> 2570 * 2571 * ## Angular's jqLite 2572 * jqLite provides only the following jQuery methods: 2573 * 2574 * - [`addClass()`](http://api.jquery.com/addClass/) 2575 * - [`after()`](http://api.jquery.com/after/) 2576 * - [`append()`](http://api.jquery.com/append/) 2577 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 2578 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2579 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2580 * - [`clone()`](http://api.jquery.com/clone/) 2581 * - [`contents()`](http://api.jquery.com/contents/) 2582 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'. 2583 * - [`data()`](http://api.jquery.com/data/) 2584 * - [`detach()`](http://api.jquery.com/detach/) 2585 * - [`empty()`](http://api.jquery.com/empty/) 2586 * - [`eq()`](http://api.jquery.com/eq/) 2587 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2588 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2589 * - [`html()`](http://api.jquery.com/html/) 2590 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2591 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2592 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces, selectors or event object as parameter 2593 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2594 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2595 * - [`prepend()`](http://api.jquery.com/prepend/) 2596 * - [`prop()`](http://api.jquery.com/prop/) 2597 * - [`ready()`](http://api.jquery.com/ready/) 2598 * - [`remove()`](http://api.jquery.com/remove/) 2599 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2600 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2601 * - [`removeData()`](http://api.jquery.com/removeData/) 2602 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2603 * - [`text()`](http://api.jquery.com/text/) 2604 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2605 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2606 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces or event object as parameter 2607 * - [`val()`](http://api.jquery.com/val/) 2608 * - [`wrap()`](http://api.jquery.com/wrap/) 2609 * 2610 * ## jQuery/jqLite Extras 2611 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2612 * 2613 * ### Events 2614 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
2614pis and fires this event 2615 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2616 * element before it is removed. 2617 * 2618 * ### Methods 2619 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2620 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2621 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2622 * `'ngModel'`). 2623 * - `injector()` - retrieves the injector of the current element or its parent. 2624 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2625 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 2626 * be enabled. 2627 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2628 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 2629 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2630 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 2631 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2632 * parent element is reached. 2633 * 2634 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2635 * @returns {Object} jQuery object. 2636 */ 2637 2638JQLite.expando = 'ng339'; 2639 2640var jqCache = JQLite.cache = {}, 2641 jqId = 1, 2642 addEventListenerFn = function(element, type, fn) { 2643 element.addEventListener(type, fn, false); 2644 }, 2645 removeEventListenerFn = function(element, type, fn) { 2646 element.removeEventListener(type, fn, false); 2647 }; 2648 2649/* 2650 * !!! This is an undocumented "private" function !!! 2651 */ 2652JQLite._data = function(node) { 2653 //jQuery always returns an object on cache miss 2654 return this.cache[node[this.expando]] || {}; 2655}; 2656 2657function jqNextId() { return ++jqId; } 2658 2659 2660var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2661var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2662var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; 2663var jqLiteMinErr = minErr('jqLite'); 2664 2665/** 2666 * Converts snake_case to camelCase. 2667 * Also there is special case for Moz prefix starting with upper case letter. 2668 * @param name Name to normalize 2669 */ 2670function camelCase(name) { 2671 return name. 2672 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2673 return offset ? letter.toUpperCase() : letter; 2674 }). 2675 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2676} 2677 2678var SINGLE_TAG_REGEXP = /^<([\w-]+)\s*\/?>(?:<\/\1>|)$/; 2679var HTML_REGEXP = /<|&#?\w+;/; 2680var TAG_NAME_REGEXP = /<([\w:-]+)/; 2681var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi; 2682 2683var wrapMap = { 2684 'option': [1, '<select multiple="multiple">', '</select>'], 2685 2686 'thead': [1, '<table>', '</table>'], 2687 'col': [2, '<table><colgroup>', '</colgroup></table>'], 2688 'tr': [2, '<table><tbody>', '</tbody></table>'], 2689 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2690 '_default': [0, "", ""] 2691}; 2692 2693wrapMap.optgroup = wrapMap.option; 2694wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2695wrapMap.th = wrapMap.td; 2696 2697 2698function jqLiteIsTextNode(html) { 2699 return !HTML_REGEXP.test(html); 2700} 2701 2702function jqLiteAcceptsData(node) { 2703 // The window object can accept data but has no nodeType 2704 // Otherwise we are only interested in elements (1) and documents (9) 2705 var nodeType = node.nodeType; 2706 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 2707} 2708 2709function jqLiteHasData(node) { 2710 for (var key in jqCache[node.ng339]) { 2711 return true; 2712 } 2713 return false; 2714} 2715 2716function jqLiteBuildFragment(html, context) { 2717 var tmp, tag, wrap, 2718 fragment = context.createDocumentFragment(), 2719 nodes = [], i; 2720 2721 if (jqLiteIsTextNode(html)) { 2722 // Convert non-html into a text node 2723 nodes.push(context.createTextNode(html)); 2724 } else { 2725 // Convert html into DOM nodes 2726 tmp = tmp || fragment.appendChild(context.createElement("div")); 2727 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2728 wrap = wrapMap[tag] || wrapMap._default; 2729 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2730 2731 // Descend through wrappers to the right content 2732 i = wrap[0]; 2733 while (i--) { 2734 tmp = tmp.lastChild; 2735 } 2736 2737 nodes = concat(nodes, tmp.childNodes); 2738 2739 tmp = fragment.firstChild; 2740 tmp.textContent = ""; 2741 } 2742 2743 // Remove wrapper from fragment 2744 fragment.textContent = ""; 2745 fragment.innerHTML = ""; // Clear inner HTML 2746 forEach(nodes, function(node) { 2747 fragment.appendChild(node); 2748 }); 2749 2750 return fragment; 2751} 2752 2753function jqLiteParseHTML(html, context) { 2754 context = context || document; 2755 var parsed; 2756 2757 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2758 return [context.createElement(parsed[1])]; 2759 } 2760 2761 if ((parsed = jqLiteBuildFragment(html, context))) { 2762 return parsed.childNodes; 2763 } 2764 2765 return []; 2766} 2767 2768///////////////////////////////////////////// 2769function JQLite(element) { 2770 if (element instanceof JQLite) { 2771 return element; 2772 } 2773 2774 var argIsString; 2775 2776 if (isString(element)) { 2777 element = trim(element); 2778 argIsString = true; 2779 } 2780 if (!(this instanceof JQLite)) { 2781 if (argIsString && element.charAt(0) != '<') { 2782 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2783 } 2784 return new JQLite(element); 2785 } 2786 2787 if (argIsString) { 2788 jqLiteAddNodes(this, jqLiteParseHTML(element)); 2789 } else { 2790 jqLiteAddNodes(this, element); 2791 } 2792} 2793 2794function jqLiteClone(element) { 2795 return element.cloneNode(true); 2796} 2797 2798function jqLiteDealoc(element, onlyDescendants) { 2799 if (!onlyDescendants) jqLiteRemoveData(element); 2800 2801 if (element.querySelectorAll) { 2802 var descendants = element.querySelectorAll('*');
2803 for (var i = 0, l = descendants.length; i < l; i++) { 2804 jqLiteRemoveData(descendants[i]); 2805 } 2806 } 2807} 2808 2809function jqLiteOff(element, type, fn, unsupported) { 2810 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2811 2812 var expandoStore = jqLiteExpandoStore(element); 2813 var events = expandoStore && expandoStore.events; 2814 var handle = expandoStore && expandoStore.handle; 2815 2816 if (!handle) return; //no listeners registered 2817 2818 if (!type) { 2819 for (type in events) { 2820 if (type !== '$destroy') { 2821 removeEventListenerFn(element, type, handle); 2822 } 2823 delete events[type]; 2824 } 2825 } else { 2826 forEach(type.split(' '), function(type) { 2827 if (isDefined(fn)) { 2828 var listenerFns = events[type]; 2829 arrayRemove(listenerFns || [], fn); 2830 if (listenerFns && listenerFns.length > 0) { 2831 return; 2832 } 2833 } 2834 2835 removeEventListenerFn(element, type, handle); 2836 delete events[type]; 2837 }); 2838 } 2839} 2840 2841function jqLiteRemoveData(element, name) { 2842 var expandoId = element.ng339; 2843 var expandoStore = expandoId && jqCache[expandoId]; 2844 2845 if (expandoStore) { 2846 if (name) { 2847 delete expandoStore.data[name]; 2848 return; 2849 } 2850 2851 if (expandoStore.handle) { 2852 if (expandoStore.events.$destroy) { 2853 expandoStore.handle({}, '$destroy'); 2854 } 2855 jqLiteOff(element); 2856 } 2857 delete jqCache[expandoId]; 2858 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 2859 } 2860} 2861 2862 2863function jqLiteExpandoStore(element, createIfNecessary) { 2864 var expandoId = element.ng339, 2865 expandoStore = expandoId && jqCache[expandoId]; 2866 2867 if (createIfNecessary && !expandoStore) { 2868 element.ng339 = expandoId = jqNextId(); 2869 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 2870 } 2871 2872 return expandoStore; 2873} 2874 2875 2876function jqLiteData(element, key, value) { 2877 if (jqLiteAcceptsData(element)) { 2878 2879 var isSimpleSetter = isDefined(value); 2880 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 2881 var massGetter = !key; 2882 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 2883 var data = expandoStore && expandoStore.data; 2884 2885 if (isSimpleSetter) { // data('key', value) 2886 data[key] = value; 2887 } else { 2888 if (massGetter) { // data() 2889 return data; 2890 } else { 2891 if (isSimpleGetter) { // data('key') 2892 // don't force creation of expandoStore if it doesn't exist yet 2893 return data && data[key]; 2894 } else { // mass-setter: data({key1: val1, key2: val2}) 2895 extend(data, key); 2896 } 2897 } 2898 } 2899 } 2900} 2901 2902function jqLiteHasClass(element, selector) { 2903 if (!element.getAttribute) return false; 2904 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2905 indexOf(" " + selector + " ") > -1); 2906} 2907 2908function jqLiteRemoveClass(element, cssClasses) { 2909 if (cssClasses && element.setAttribute) { 2910 forEach(cssClasses.split(' '), function(cssClass) { 2911 element.setAttribute('class', trim( 2912 (" " + (element.getAttribute('class') || '') + " ") 2913 .replace(/[\n\t]/g, " ") 2914 .replace(" " + trim(cssClass) + " ", " ")) 2915 ); 2916 }); 2917 } 2918} 2919 2920function jqLiteAddClass(element, cssClasses) { 2921 if (cssClasses && element.setAttribute) { 2922 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 2923 .replace(/[\n\t]/g, " "); 2924 2925 forEach(cssClasses.split(' '), function(cssClass) { 2926 cssClass = trim(cssClass); 2927 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 2928 existingClasses += cssClass + ' '; 2929 } 2930 }); 2931 2932 element.setAttribute('class', trim(existingClasses)); 2933 } 2934} 2935 2936 2937function jqLiteAddNodes(root, elements) { 2938 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 2939 2940 if (elements) { 2941 2942 // if a Node (the most common case) 2943 if (elements.nodeType) { 2944 root[root.length++] = elements; 2945 } else { 2946 var length = elements.length; 2947 2948 // if an Array or NodeList and not a Window 2949 if (typeof length === 'number' && elements.window !== elements) { 2950 if (length) { 2951 for (var i = 0; i < length; i++) { 2952 root[root.length++] = elements[i]; 2953 } 2954 } 2955 } else { 2956 root[root.length++] = elements; 2957 } 2958 } 2959 } 2960} 2961 2962 2963function jqLiteController(element, name) { 2964 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 2965} 2966 2967function jqLiteInheritedData(element, name, value) { 2968 // if element is the document object work with the html element instead 2969 // this makes $(document).scope() possible 2970 if (element.nodeType == NODE_TYPE_DOCUMENT) { 2971 element = element.documentElement; 2972 } 2973 var names = isArray(name) ? name : [name]; 2974 2975 while (element) { 2976 for (var i = 0, ii = names.length; i < ii; i++) { 2977 if (isDefined(value = jqLite.data(element, names[i]))) return value; 2978 } 2979 2980 // If dealing with a document fragment node with a host element, and no parent, use the host 2981 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 2982 // to lookup parent controllers. 2983 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 2984 } 2985} 2986 2987function jqLiteEmpty(element) { 2988 jqLiteDealoc(element, true); 2989 while (element.firstChild) { 2990 element.removeChild(element.firstChild); 2991 } 2992} 2993 2994function jqLiteRemove(element, keepData) { 2995 if (!keepData) jqLiteDealoc(element); 2996 var parent = element.parentNode; 2997 if (parent) parent.removeChild(element); 2998} 2999 3000 3001function jqLiteDocumentLoaded(action, win) { 3002 win = win || window; 3003 if (win.document.readyState === 'complete') { 3004 // Force the action to be run async for consistent behaviour 3005 // from the action's point of view 3006 // i.e. it will definitely not be in a $apply 3007 win.setTimeout(action); 3008 } else { 3009 // No need to unbind this handler as load is only ever called once 3010 jqLite(win).on('load', action); 3011 } 3012} 3013 3014////////////////////////////////////////// 3015// Functions which are declared directly.
vendor: 9,140 bytes, lines 3016-3317
3016////////////////////////////////////////// 3017var JQLitePrototype = JQLite.prototype = { 3018 ready: function(fn) { 3019 var fired = false; 3020 3021 function trigger() { 3022 if (fired) return; 3023 fired = true; 3024 fn(); 3025 } 3026 3027 // check if document is already loaded 3028 if (document.readyState === 'complete') { 3029 setTimeout(trigger); 3030 } else { 3031 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 3032 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 3033 // jshint -W064 3034 JQLite(window).on('load', trigger); // fallback to window.onload for others 3035 // jshint +W064 3036 } 3037 }, 3038 toString: function() { 3039 var value = []; 3040 forEach(this, function(e) { value.push('' + e);}); 3041 return '[' + value.join(', ') + ']'; 3042 }, 3043 3044 eq: function(index) { 3045 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 3046 }, 3047 3048 length: 0, 3049 push: push, 3050 sort: [].sort, 3051 splice: [].splice 3052}; 3053 3054////////////////////////////////////////// 3055// Functions iterating getter/setters. 3056// these functions return self on setter and 3057// value on get. 3058////////////////////////////////////////// 3059var BOOLEAN_ATTR = {}; 3060forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 3061 BOOLEAN_ATTR[lowercase(value)] = value; 3062}); 3063var BOOLEAN_ELEMENTS = {}; 3064forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 3065 BOOLEAN_ELEMENTS[value] = true; 3066}); 3067var ALIASED_ATTR = { 3068 'ngMinlength': 'minlength', 3069 'ngMaxlength': 'maxlength', 3070 'ngMin': 'min', 3071 'ngMax': 'max', 3072 'ngPattern': 'pattern' 3073}; 3074 3075function getBooleanAttrName(element, name) { 3076 // check dom last since we will most likely fail on name 3077 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 3078 3079 // booleanAttr is here twice to minimize DOM access 3080 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 3081} 3082 3083function getAliasedAttrName(name) { 3084 return ALIASED_ATTR[name]; 3085} 3086 3087forEach({ 3088 data: jqLiteData, 3089 removeData: jqLiteRemoveData, 3090 hasData: jqLiteHasData 3091}, function(fn, name) { 3092 JQLite[name] = fn; 3093}); 3094 3095forEach({ 3096 data: jqLiteData, 3097 inheritedData: jqLiteInheritedData, 3098 3099 scope: function(element) { 3100 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3101 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 3102 }, 3103 3104 isolateScope: function(element) { 3105 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3106 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 3107 }, 3108 3109 controller: jqLiteController, 3110 3111 injector: function(element) { 3112 return jqLiteInheritedData(element, '$injector'); 3113 }, 3114 3115 removeAttr: function(element, name) { 3116 element.removeAttribute(name); 3117 }, 3118 3119 hasClass: jqLiteHasClass, 3120 3121 css: function(element, name, value) { 3122 name = camelCase(name); 3123 3124 if (isDefined(value)) { 3125 element.style[name] = value; 3126 } else { 3127 return element.style[name]; 3128 } 3129 }, 3130 3131 attr: function(element, name, value) { 3132 var nodeType = element.nodeType; 3133 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { 3134 return; 3135 } 3136 var lowercasedName = lowercase(name); 3137 if (BOOLEAN_ATTR[lowercasedName]) { 3138 if (isDefined(value)) { 3139 if (!!value) { 3140 element[name] = true; 3141 element.setAttribute(name, lowercasedName); 3142 } else { 3143 element[name] = false; 3144 element.removeAttribute(lowercasedName); 3145 } 3146 } else { 3147 return (element[name] || 3148 (element.attributes.getNamedItem(name) || noop).specified) 3149 ? lowercasedName 3150 : undefined; 3151 } 3152 } else if (isDefined(value)) { 3153 element.setAttribute(name, value); 3154 } else if (element.getAttribute) { 3155 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 3156 // some elements (e.g. Document) don't have get attribute, so return undefined 3157 var ret = element.getAttribute(name, 2); 3158 // normalize non-existing attributes to undefined (as jQuery) 3159 return ret === null ? undefined : ret; 3160 } 3161 }, 3162 3163 prop: function(element, name, value) { 3164 if (isDefined(value)) { 3165 element[name] = value; 3166 } else { 3167 return element[name]; 3168 } 3169 }, 3170 3171 text: (function() { 3172 getText.$dv = ''; 3173 return getText; 3174 3175 function getText(element, value) { 3176 if (isUndefined(value)) { 3177 var nodeType = element.nodeType; 3178 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 3179 } 3180 element.textContent = value; 3181 } 3182 })(), 3183 3184 val: function(element, value) { 3185 if (isUndefined(value)) { 3186 if (element.multiple && nodeName_(element) === 'select') { 3187 var result = []; 3188 forEach(element.options, function(option) { 3189 if (option.selected) { 3190 result.push(option.value || option.text); 3191 } 3192 }); 3193 return result.length === 0 ? null : result; 3194 } 3195 return element.value; 3196 } 3197 element.value = value; 3198 }, 3199 3200 html: function(element, value) { 3201 if (isUndefined(value)) { 3202 return element.innerHTML; 3203 } 3204 jqLiteDealoc(element, true); 3205 element.innerHTML = value; 3206 }, 3207 3208 empty: jqLiteEmpty 3209}, function(fn, name) { 3210 /** 3211 * Properties: writes return selection, reads return first value 3212 */ 3213 JQLite.prototype[name] = function(arg1, arg2) { 3214 var i, key; 3215 var nodeCount = this.length; 3216 3217 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 3218 // in a way that survives minification. 3219 // jqLiteEmpty takes no arguments but is a setter. 3220 if (fn !== jqLiteEmpty && 3221 (isUndefined((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2))) { 3222 if (isObject(arg1)) { 3223 3224 // we are a write, but the object properties are the key/values 3225 for (i = 0; i < nodeCount; i++) { 3226 if (fn === jqLiteData) { 3227 // data() takes the whole object in jQuery 3228 fn(this[i], arg1); 3229 } else { 3230 for (key in arg1) { 3231 fn(this[i], key, arg1[key]); 3232 } 3233 } 3234 } 3235 // return self for chaining 3236 return this; 3237 } else { 3238 // we are a read, so read the first child. 3239 // TODO: do we still need this? 3240 var value = fn.$dv; 3241 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 3242 var jj = (isUndefined(value)) ? Math.min(nodeCount, 1) : nodeCount; 3243 for (var j = 0; j < jj; j++) { 3244 var nodeValue = fn(this[j], arg1, arg2); 3245 value = value ? value + nodeValue : nodeValue; 3246 } 3247 return value; 3248 } 3249 } else { 3250 // we are a write, so apply to all children 3251 for (i = 0; i < nodeCount; i++) { 3252 fn(this[i], arg1, arg2); 3253 } 3254 // return self for chaining 3255 return this; 3256 } 3257 }; 3258}); 3259 3260function createEventHandler(element, events) { 3261 var eventHandler = function(event, type) { 3262 // jQuery specific api 3263 event.isDefaultPrevented = function() { 3264 return event.defaultPrevented; 3265 }; 3266 3267 var eventFns = events[type || event.type]; 3268 var eventFnsLength = eventFns ? eventFns.length : 0; 3269 3270 if (!eventFnsLength) return; 3271 3272 if (isUndefined(event.immediatePropagationStopped)) { 3273 var originalStopImmediatePropagation = event.stopImmediatePropagation; 3274 event.stopImmediatePropagation = function() { 3275 event.immediatePropagationStopped = true; 3276 3277 if (event.stopPropagation) { 3278 event.stopPropagation(); 3279 } 3280 3281 if (originalStopImmediatePropagation) { 3282 originalStopImmediatePropagation.call(event); 3283 } 3284 }; 3285 } 3286 3287 event.isImmediatePropagationStopped = function() { 3288 return event.immediatePropagationStopped === true; 3289 }; 3290 3291 // Copy event handlers in case event handlers array is modified during execution. 3292 if ((eventFnsLength > 1)) { 3293 eventFns = shallowCopy(eventFns); 3294 } 3295 3296 for (var i = 0; i < eventFnsLength; i++) { 3297 if (!event.isImmediatePropagationStopped()) { 3298 eventFns[i].call(element, event); 3299 } 3300 } 3301 }; 3302 3303 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 3304 // events on `element` 3305 eventHandler.elem = element; 3306 return eventHandler; 3307} 3308 3309////////////////////////////////////////// 3310// Functions iterating traversal. 3311// These functions chain results into a single 3312// selector. 3313////////////////////////////////////////// 3314forEach({ 3315 removeData: jqLiteRemoveData, 3316 3317 on: function jqLiteOn(element, type, fn, unsupported) {
3318 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 3319 3320 // Do not add event handlers to non-elements because they will not be cleaned up. 3321 if (!jqLiteAcceptsData(element)) { 3322 return; 3323 } 3324 3325 var expandoStore = jqLiteExpandoStore(element, true); 3326 var events = expandoStore.events; 3327 var handle = expandoStore.handle; 3328 3329 if (!handle) { 3330 handle = expandoStore.handle = createEventHandler(element, events); 3331 } 3332 3333 // http://jsperf.com/string-indexof-vs-split 3334 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 3335 var i = types.length; 3336 3337 while (i--) { 3338 type = types[i]; 3339 var eventFns = events[type]; 3340 3341 if (!eventFns) { 3342 events[type] = []; 3343 3344 if (type === 'mouseenter' || type === 'mouseleave') { 3345 // Refer to jQuery's implementation of mouseenter & mouseleave 3346 // Read about mouseenter and mouseleave: 3347 // http://www.quirksmode.org/js/events_mouse.html#link8 3348 3349 jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) { 3350 var target = this, related = event.relatedTarget; 3351 // For mousenter/leave call the handler if related is outside the target. 3352 // NB: No relatedTarget if the mouse left/entered the browser window 3353 if (!related || (related !== target && !target.contains(related))) { 3354 handle(event, type); 3355 } 3356 }); 3357 3358 } else { 3359 if (type !== '$destroy') { 3360 addEventListenerFn(element, type, handle); 3361 } 3362 } 3363 eventFns = events[type]; 3364 } 3365 eventFns.push(fn); 3366 } 3367 }, 3368 3369 off: jqLiteOff, 3370 3371 one: function(element, type, fn) { 3372 element = jqLite(element); 3373 3374 //add the listener twice so that when it is called 3375 //you can remove the original function and still be 3376 //able to call element.off(ev, fn) normally 3377 element.on(type, function onFn() { 3378 element.off(type, fn); 3379 element.off(type, onFn); 3380 }); 3381 element.on(type, fn); 3382 }, 3383 3384 replaceWith: function(element, replaceNode) { 3385 var index, parent = element.parentNode; 3386 jqLiteDealoc(element); 3387 forEach(new JQLite(replaceNode), function(node) { 3388 if (index) { 3389 parent.insertBefore(node, index.nextSibling); 3390 } else { 3391 parent.replaceChild(node, element); 3392 } 3393 index = node; 3394 }); 3395 }, 3396 3397 children: function(element) { 3398 var children = []; 3399 forEach(element.childNodes, function(element) { 3400 if (element.nodeType === NODE_TYPE_ELEMENT) { 3401 children.push(element); 3402 } 3403 }); 3404 return children; 3405 }, 3406 3407 contents: function(element) { 3408 return element.contentDocument || element.childNodes || []; 3409 }, 3410 3411 append: function(element, node) { 3412 var nodeType = element.nodeType; 3413 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 3414 3415 node = new JQLite(node); 3416 3417 for (var i = 0, ii = node.length; i < ii; i++) { 3418 var child = node[i]; 3419 element.appendChild(child); 3420 } 3421 }, 3422 3423 prepend: function(element, node) { 3424 if (element.nodeType === NODE_TYPE_ELEMENT) { 3425 var index = element.firstChild; 3426 forEach(new JQLite(node), function(child) { 3427 element.insertBefore(child, index); 3428 }); 3429 } 3430 }, 3431 3432 wrap: function(element, wrapNode) { 3433 wrapNode = jqLite(wrapNode).eq(0).clone()[0]; 3434 var parent = element.parentNode; 3435 if (parent) { 3436 parent.replaceChild(wrapNode, element); 3437 } 3438 wrapNode.appendChild(element); 3439 }, 3440 3441 remove: jqLiteRemove, 3442 3443 detach: function(element) { 3444 jqLiteRemove(element, true); 3445 }, 3446 3447 after: function(element, newElement) { 3448 var index = element, parent = element.parentNode; 3449 newElement = new JQLite(newElement); 3450 3451 for (var i = 0, ii = newElement.length; i < ii; i++) { 3452 var node = newElement[i]; 3453 parent.insertBefore(node, index.nextSibling); 3454 index = node; 3455 } 3456 }, 3457 3458 addClass: jqLiteAddClass, 3459 removeClass: jqLiteRemoveClass, 3460 3461 toggleClass: function(element, selector, condition) { 3462 if (selector) {
vendor: 4,371 bytes, lines 3463-3601
3463 forEach(selector.split(' '), function(className) { 3464 var classCondition = condition; 3465 if (isUndefined(classCondition)) { 3466 classCondition = !jqLiteHasClass(element, className); 3467 } 3468 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 3469 }); 3470 } 3471 }, 3472 3473 parent: function(element) { 3474 var parent = element.parentNode; 3475 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 3476 }, 3477 3478 next: function(element) { 3479 return element.nextElementSibling; 3480 }, 3481 3482 find: function(element, selector) { 3483 if (element.getElementsByTagName) { 3484 return element.getElementsByTagName(selector); 3485 } else { 3486 return []; 3487 } 3488 }, 3489 3490 clone: jqLiteClone, 3491 3492 triggerHandler: function(element, event, extraParameters) { 3493 3494 var dummyEvent, eventFnsCopy, handlerArgs; 3495 var eventName = event.type || event; 3496 var expandoStore = jqLiteExpandoStore(element); 3497 var events = expandoStore && expandoStore.events; 3498 var eventFns = events && events[eventName]; 3499 3500 if (eventFns) { 3501 // Create a dummy event to pass to the handlers 3502 dummyEvent = { 3503 preventDefault: function() { this.defaultPrevented = true; }, 3504 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 3505 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 3506 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 3507 stopPropagation: noop, 3508 type: eventName, 3509 target: element 3510 }; 3511 3512 // If a custom event was provided then extend our dummy event with it 3513 if (event.type) { 3514 dummyEvent = extend(dummyEvent, event); 3515 } 3516 3517 // Copy event handlers in case event handlers array is modified during execution. 3518 eventFnsCopy = shallowCopy(eventFns); 3519 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 3520 3521 forEach(eventFnsCopy, function(fn) { 3522 if (!dummyEvent.isImmediatePropagationStopped()) { 3523 fn.apply(element, handlerArgs); 3524 } 3525 }); 3526 } 3527 } 3528}, function(fn, name) { 3529 /** 3530 * chaining functions 3531 */ 3532 JQLite.prototype[name] = function(arg1, arg2, arg3) { 3533 var value; 3534 3535 for (var i = 0, ii = this.length; i < ii; i++) { 3536 if (isUndefined(value)) { 3537 value = fn(this[i], arg1, arg2, arg3); 3538 if (isDefined(value)) { 3539 // any function which returns a value needs to be wrapped 3540 value = jqLite(value); 3541 } 3542 } else { 3543 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3544 } 3545 } 3546 return isDefined(value) ? value : this; 3547 }; 3548 3549 // bind legacy bind/unbind to on/off 3550 JQLite.prototype.bind = JQLite.prototype.on; 3551 JQLite.prototype.unbind = JQLite.prototype.off; 3552}); 3553 3554 3555// Provider for private $$jqLite service 3556function $$jqLiteProvider() { 3557 this.$get = function $$jqLite() { 3558 return extend(JQLite, { 3559 hasClass: function(node, classes) { 3560 if (node.attr) node = node[0]; 3561 return jqLiteHasClass(node, classes); 3562 }, 3563 addClass: function(node, classes) { 3564 if (node.attr) node = node[0]; 3565 return jqLiteAddClass(node, classes); 3566 }, 3567 removeClass: function(node, classes) { 3568 if (node.attr) node = node[0]; 3569 return jqLiteRemoveClass(node, classes); 3570 } 3571 }); 3572 }; 3573} 3574 3575/** 3576 * Computes a hash of an 'obj'. 3577 * Hash of a: 3578 * string is string 3579 * number is number as string 3580 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3581 * that is also assigned to the $$hashKey property of the object. 3582 * 3583 * @param obj 3584 * @returns {string} hash string such that the same input will have the same hash string. 3585 * The resulting string key is in 'type:hashKey' format. 3586 */ 3587function hashKey(obj, nextUidFn) { 3588 var key = obj && obj.$$hashKey; 3589 3590 if (key) { 3591 if (typeof key === 'function') { 3592 key = obj.$$hashKey(); 3593 } 3594 return key; 3595 } 3596 3597 var objType = typeof obj; 3598 if (objType == 'function' || (objType == 'object' && obj !== null)) { 3599 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 3600 } else { 3601 key = objType + ':' + obj;
vendor: 1,877 bytes, lines 3602-3677
3602 } 3603 3604 return key; 3605} 3606 3607/** 3608 * HashMap which can use objects as keys 3609 */ 3610function HashMap(array, isolatedUid) { 3611 if (isolatedUid) { 3612 var uid = 0; 3613 this.nextUid = function() { 3614 return ++uid; 3615 }; 3616 } 3617 forEach(array, this.put, this); 3618} 3619HashMap.prototype = { 3620 /** 3621 * Store key value pair 3622 * @param key key to store can be any type 3623 * @param value value to store can be any type 3624 */ 3625 put: function(key, value) { 3626 this[hashKey(key, this.nextUid)] = value; 3627 }, 3628 3629 /** 3630 * @param key 3631 * @returns {Object} the value for the key 3632 */ 3633 get: function(key) { 3634 return this[hashKey(key, this.nextUid)]; 3635 }, 3636 3637 /** 3638 * Remove the key/value pair 3639 * @param key 3640 */ 3641 remove: function(key) { 3642 var value = this[key = hashKey(key, this.nextUid)]; 3643 delete this[key]; 3644 return value; 3645 } 3646}; 3647 3648var $$HashMapProvider = [function() { 3649 this.$get = [function() { 3650 return HashMap; 3651 }]; 3652}]; 3653 3654/** 3655 * @ngdoc function 3656 * @module ng 3657 * @name angular.injector 3658 * @kind function 3659 * 3660 * @description 3661 * Creates an injector object that can be used for retrieving services as well as for 3662 * dependency injection (see {@link guide/di dependency injection}). 3663 * 3664 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 3665 * {@link angular.module}. The `ng` module must be explicitly added. 3666 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 3667 * disallows argument name annotation inference. 3668 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 3669 * 3670 * @example 3671 * Typical usage 3672 * ```js 3673 * // create an injector 3674 * var $injector = angular.injector(['ng']); 3675 * 3676 * // use the injector to kick off your application 3677 * // use the type inference to auto inject arguments, or use impl
3677icit injection 3678 * $injector.invoke(function($rootScope, $compile, $document) { 3679 * $compile($document)($rootScope); 3680 * $rootScope.$digest(); 3681 * }); 3682 * ``` 3683 * 3684 * Sometimes you want to get access to the injector of a currently running Angular app 3685 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3686 * application has been bootstrapped. You can do this using the extra `injector()` added 3687 * to JQuery/jqLite elements. See {@link angular.element}. 3688 * 3689 * *This is fairly rare but could be the case if a third party library is injecting the 3690 * markup.* 3691 * 3692 * In the following example a new block of HTML containing a `ng-controller` 3693 * directive is added to the end of the document body by JQuery. We then compile and link 3694 * it into the current AngularJS scope. 3695 * 3696 * ```js 3697 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3698 * $(document.body).append($div); 3699 * 3700 * angular.element(document).injector().invoke(function($compile) { 3701 * var scope = angular.element($div).scope(); 3702 * $compile($div)(scope); 3703 * }); 3704 * ``` 3705 */ 3706 3707 3708/** 3709 * @ngdoc module 3710 * @name auto 3711 * @description 3712 * 3713 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3714 */ 3715 3716var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m; 3717var FN_ARG_SPLIT = /,/; 3718var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3719var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3720var $injectorMinErr = minErr('$injector'); 3721 3722function anonFn(fn) { 3723 // For anonymous functions, showing at the very least the function signature can help in 3724 // debugging. 3725 var fnText = fn.toString().replace(STRIP_COMMENTS, ''), 3726 args = fnText.match(FN_ARGS); 3727 if (args) { 3728 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 3729 } 3730 return 'fn'; 3731} 3732 3733function annotate(fn, strictDi, name) { 3734 var $inject, 3735 fnText, 3736 argDecl, 3737 last; 3738 3739 if (typeof fn === 'function') { 3740 if (!($inject = fn.$inject)) { 3741 $inject = []; 3742 if (fn.length) { 3743 if (strictDi) { 3744 if (!isString(name) || !name) { 3745 name = fn.name || anonFn(fn); 3746 } 3747 throw $injectorMinErr('strictdi', 3748 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 3749 } 3750 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3751 argDecl = fnText.match(FN_ARGS); 3752 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 3753 arg.replace(FN_ARG, function(all, underscore, name) { 3754 $inject.push(name); 3755 }); 3756 }); 3757 } 3758 fn.$inject = $inject; 3759 } 3760 } else if (isArray(fn)) { 3761 last = fn.length - 1; 3762 assertArgFn(fn[last], 'fn'); 3763 $inject = fn.slice(0, last); 3764 } else { 3765 assertArgFn(fn, 'fn', true); 3766 } 3767 return $inject; 3768} 3769 3770/////////////////////////////////////// 3771 3772/** 3773 * @ngdoc service 3774 * @name $injector 3775 * 3776 * @description 3777 * 3778 * `$injector` is used to retrieve object instances as defined by 3779 * {@link auto.$provide provider}, instantiate types, invoke methods, 3780 * and load modules. 3781 * 3782 * The following always holds true: 3783 * 3784 * ```js 3785 * var $injector = angular.injector(); 3786 * expect($injector.get('$injector')).toBe($injector); 3787 * expect($injector.invoke(function($injector) { 3788 * return $injector; 3789 * })).toBe($injector); 3790 * ``` 3791 * 3792 * # Injection Function Annotation 3793 * 3794 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3795 * following are all valid ways of annotating function with injection arguments and are equivalent. 3796 * 3797 * ```js 3798 * // inferred (only works if code not minified/obfuscated) 3799 * $injector.invoke(function(serviceA){}); 3800 * 3801 * // annotated 3802 * function explicit(serviceA) {}; 3803 * explicit.$inject = ['serviceA']; 3804 * $injector.invoke(explicit); 3805 * 3806 * // inline 3807 * $injector.invoke(['serviceA', function(serviceA){}]); 3808 * ``` 3809 * 3810 * ## Inference 3811 * 3812 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3813 * can then be parsed and the function arguments can be extracted. This method of discovering 3814 * annotations is disallowed when the injector is in strict mode. 3815 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 3816 * argument names. 3817 * 3818 * ## `$inject` Annotation 3819 * By adding an `$inject` property onto a function the injection parameters can be specified. 3820 * 3821 * ## Inline 3822 * As an array of injection names, where the last item in the array is the function to call. 3823 */ 3824 3825/** 3826 * @ngdoc method 3827 * @name $injector#get 3828 * 3829 * @description 3830 * Return an instance of the service. 3831 * 3832 * @param {string} name The name of the instance to retrieve. 3833 * @param {string=} caller An optional string to provide the origin of the function call for error messages. 3834 * @return {*} The instance. 3835 */ 3836 3837/** 3838 * @ngdoc method 3839 * @name $injector#invoke 3840 * 3841 * @description 3842 * Invoke the method and supply the method arguments from the `$injector`. 3843 * 3844 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are 3845 * injected according to the {@link guide/di $inject Annotation} rules. 3846 * @param {Object=} self The `this` for the invoked method. 3847 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3848 * object first, before the `$injector` is consulted. 3849 * @returns {*} the value returned by the invoked `fn` function. 3850 */ 3851 3852/** 3853 * @ngdoc method 3854 * @name $injector#has 3855 * 3856 * @description 3857 * Allows the user to query if the particular service exists. 3858 * 3859 * @param {string} name Name of the service to query. 3860 * @returns {boolean} `true` if injector has given service. 3861 */ 3862 3863/** 3864 * @ngdoc method 3865 * @name $injector#instantiate 3866 * @description 3867 * Create a new instance of JS type. The method takes a constructor function, invokes the new 3868 * operator, and supplies all of the arguments to the constructor function as specified by the 3869 * constructor annotation. 3870 * 3871 * @param {Function} Type Annotated constructor function. 3872 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3873 * object first, before the `$injector` is consulted. 3874 * @returns {Object} new instance of `Type`. 3875 */ 3876 3877/** 3878 * @ngdoc method 3879 * @name $injector#annotate 3880 * 3881 * @description 3882 * Returns an array of service names which the function is requesting for injection. This API is 3883 * used by the injector to determine which services need to be injected into the function when the 3884 * function is invoked. There are three ways in which the function can be annotated with the needed 3885 * dependencies. 3886 * 3887 * # Argument names 3888 * 3889 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3890 * by converting the function into a string using `toString()` method and extracting the argument 3891 * names. 3892 * ```js 3893 * // Given 3894 * function MyController($scope, $route) { 3895 * // ... 3896 * } 3897 * 3898 * // Then 3899 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3900 * ``` 3901 * 3902 * You can disallow this method by using strict injection mode. 3903 * 3904 * This method does not work with code minification / obfuscation. For this reason the following 3905 * annotation strategies are supported. 3906 * 3907 * # The `$inject` property 3908 * 3909 * If a function has an `$inject` property and its value is an array of strings, then the strings 3910 * represent names of services to be injected into the function. 3911 * ```js 3912 * // Given 3913 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 3914 * // ... 3915 * } 3916 * // Define function dependencies 3917 * MyController['$inject'] = ['$scope', '$route']; 3918 * 3919 * // Then 3920 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3921 * ``` 3922 * 3923 * # The array notation 3924 * 3925 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 3926 * is very inconvenient. In these situations using the array notation to specify the dependencies in 3927 * a way that survives minification is a better choice: 3928 * 3929 * ```js 3930 * // We wish to write this (not minification / obfuscation safe) 3931 * injector.invoke(function($compile, $rootScope) { 3932 * // ... 3933 * }); 3934 * 3935 * // We are forced to write break inlining 3936 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 3937 * // ... 3938 * }; 3939 * tmpFn.$inject = ['$compile', '$rootScope']; 3940 * injector.invoke(tmpFn); 3941 * 3942 * // To better support inline function the inline annotation is supported 3943 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 3944 * // ... 3945 * }]); 3946 * 3947 * // Therefore 3948 * expect(injector.annotate( 3949 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 3950 * ).toEqual(['$compile', '$rootScope']); 3951 * ``` 3952 * 3953 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 3954 * be retrieved as described above. 3955 * 3956 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 3957 * 3958 * @returns {Array.<string>} The names of the services which the function requires. 3959 */ 3960 3961 3962 3963 3964/** 3965 * @ngdoc service 3966 * @name $provide 3967 * 3968 * @description 3969 * 3970 * The {@link auto.$provide $provide} service has a number of methods for registering components 3971 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 3972 * {@link angular.Module}. 3973 * 3974 * An Angular **service** is a singleton object created by a **service factory**. These **service 3975 * factories** are functions which, in turn, are created by a **service provider**. 3976 * The **service providers** are constructor functions. When instantiated they must contain a 3977 * property called `$get`, which holds the **service factory** function. 3978 * 3979 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 3980 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 3981 * function to get the instance of the **service**. 3982 * 3983 * Often services have no configuration options and there is no need to add methods to the service 3984 * provider. The provider will be no more than a constructor function with a `$get` property. For 3985 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 3986 * services without specifying a provider. 3987 * 3988 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 3989 * {@link auto.$injector $injector} 3990 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 3991 * providers and services. 3992 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 3993 * services, not providers. 3994 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 3995 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 3996 * given factory function. 3997 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 3998 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 3999 * a new object using the given constructor function. 4000 * 4001 * See the individual methods for more information and examples. 4002 */ 4003 4004/** 4005 * @ngdoc method 4006 * @name $provide#provider 4007 * @description 4008 * 4009 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 4010 * are constructor functions, whose instances are responsible for "providing" a factory for a 4011 * service. 4012 * 4013 * Service provider names start with the name of the service they provide followed by `Provider`. 4014 * For example, the {@link ng.$log $log} service has a provider called 4015 * {@link ng.$logProvider $logProvider}. 4016 * 4017 * Service provider objects can have additional methods which allow configuration of the provider 4018 * and its service. Importantly, you can configure what kind of service is created by the `$get` 4019 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 4020 * method {@link ng.$logProvider#debugEnabled debugEnabled} 4021 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 4022 * console or not. 4023 * 4024 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 4025 'Provider'` key. 4026 * @param {(Object|function())} provider If the provider is: 4027 * 4028 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 4029 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 4030 * - `Constructor`: a new instance of the provider will be created using 4031 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 4032 * 4033 * @returns {Object} registered provider instance 4034 4035 * @example 4036 * 4037 * The following example shows how to create a simple event tracking service and register it using 4038 * {@link auto.$provide#provider $provide.provider()}. 4039 * 4040 * ```js 4041 * // Define the eventTracker provider 4042 * function EventTrackerProvider() { 4043 * var trackingUrl = '/track'; 4044 * 4045 * // A provider method for configuring where the tracked events should been saved 4046 * this.setTrackingUrl = function(url) { 4047 * trackingUrl = url; 4048 * }; 4049 * 4050 * // The service factory function 4051 * this.$get = ['$http', function($http) { 4052 * var trackedEvents = {}; 4053 * return { 4054 * // Call this to track an event 4055 * event: function(event) { 4056 * var count = trackedEvents[event] || 0; 4057 * count += 1; 4058 * trackedEvents[event] = count; 4059 * return count; 4060 * }, 4061 * // Call this to save the tracked events to the trackingUrl 4062 * save: function() { 4063 * $http.post(trackingUrl, trackedEvents); 4064 * } 4065 * }; 4066 * }]; 4067 * } 4068 * 4069 * describe('eventTracker', function() { 4070 * var postSpy; 4071 * 4072 * beforeEach(module(function($provide) { 4073 * // Register the eventTracker provider 4074 * $provide.provider('eventTracker', EventTrackerProvider); 4075 * })); 4076 * 4077 * beforeEach(module(function(eventTrackerProvider) { 4078 * // Configure eventTracker provider 4079 * eventTrackerProvider.setTrackingUrl('/custom-track'); 4080 * })); 4081 * 4082 * it('tracks events', inject(function(eventTracker) { 4083 * expect(eventTracker.event('login')).toEqual(1); 4084 * expect(eventTracker.event('login')).toEqual(2); 4085 * })); 4086 * 4087 * it('saves to the tracking url', inject(function(eventTracker, $http) { 4088 * postSpy = spyOn($http, 'post'); 4089 * eventTracker.event('login'); 4090 * eventTracker.save(); 4091 * expect(postSpy).toHaveBeenCalled(); 4092 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 4093 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 4094 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 4095 * })); 4096 * }); 4097 * ``` 4098 */ 4099 4100/** 4101 * @ngdoc method 4102 * @name $provide#factory 4103 * @description 4104 * 4105 * Register a **service factory**, which will be called to return the service instance. 4106 * This is short for registering a service where its provider consists of only a `$get` property, 4107 * which is the given service factory function. 4108 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 4109 * configure your service in a provider. 4110 * 4111 * @param {string} name The name of the instance. 4112 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. 4113 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. 4114 * @returns {Object} registered provider instance 4115 * 4116 * @example 4117 * Here is an example of registering a service 4118 * ```js 4119 * $provide.factory('ping', ['$http', function($http) { 4120 * return function ping() { 4121 * return $http.send('/ping'); 4122 * }; 4123 * }]); 4124 * ``` 4125 * You would then inject and use this service like this: 4126 * ```js 4127 * someModule.controller('Ctrl', ['ping', function(ping) { 4128 * ping(); 4129 * }]); 4130 * ``` 4131 */ 4132 4133 4134/** 4135 * @ngdoc method 4136 * @name $provide#service 4137 * @description 4138 * 4139 * Register a **service constructor**, which will be invoked with `new` to create the service 4140 * instance. 4141 * This is short for registering a service where its provider's `$get` property is the service 4142 * constructor function that will be used to instantiate the service instance. 4143 * 4144 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 4145 * as a type/class. 4146 * 4147 * @param {string} name The name of the instance. 4148 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) 4149 * that will be instantiated. 4150 * @returns {Object} registered provider instance 4151 * 4152 * @example 4153 * Here is an example of registering a service using 4154 * {@link auto.$provide#service $provide.service(class)}. 4155 * ```js 4156 * var Ping = function($http) { 4157 * this.$http = $http; 4158 * }; 4159 * 4160 * Ping.$inject = ['$http']; 4161 * 4162 * Ping.prototype.send = function() { 4163 * return this.$http.get('/ping'); 4164 * }; 4165 * $provide.service('ping', Ping); 4166 * ``` 4167 * You would then inject and use this service like this: 4168 * ```js 4169 * someModule.controller('Ctrl', ['ping', function(ping) { 4170 * ping.send(); 4171 * }]); 4172 * ``` 4173 */ 4174 4175 4176/** 4177 * @ngdoc method 4178 * @name $provide#value 4179 * @description 4180 * 4181 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 4182 * number, an array, an object or a function. This is short for registering a service where its 4183 * provider's `$get` property is a factory function that takes no arguments and returns the **value 4184 * service**. 4185 * 4186 * Value services are similar to constant services, except that they cannot be injected into a 4187 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 4188 * an Angular 4189 * {@link auto.$provide#decorator decorator}. 4190 * 4191 * @param {string} name The name of the instance. 4192 * @param {*} value The value. 4193 * @returns {Object} registered provider instance 4194 * 4195 * @example 4196 * Here are some examples of creating value services. 4197 * ```js 4198 * $provide.value('ADMIN_USER', 'admin'); 4199 *
4200 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 4201 * 4202 * $provide.value('halfOf', function(value) { 4203 * return value / 2; 4204 * }); 4205 * ``` 4206 */ 4207 4208 4209/** 4210 * @ngdoc method 4211 * @name $provide#constant 4212 * @description 4213 * 4214 * Register a **constant service**, such as a string, a number, an array, an object or a function, 4215 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be 4216 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 4217 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 4218 * 4219 * @param {string} name The name of the constant. 4220 * @param {*} value The constant value. 4221 * @returns {Object} registered instance 4222 * 4223 * @example 4224 * Here a some examples of creating constants: 4225 * ```js 4226 * $provide.constant('SHARD_HEIGHT', 306); 4227 * 4228 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 4229 * 4230 * $provide.constant('double', function(value) { 4231 * return value * 2; 4232 * }); 4233 * ``` 4234 */ 4235 4236 4237/** 4238 * @ngdoc method 4239 * @name $provide#decorator 4240 * @description 4241 * 4242 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 4243 * intercepts the creation of a service, allowing it to override or modify the behaviour of the 4244 * service. The object returned by the decorator may be the original service, or a new service 4245 * object which replaces or wraps and delegates to the original service. 4246 * 4247 * @param {string} name The name of the service to decorate. 4248 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be 4249 * instantiated and should return the decorated service instance. The function is called using 4250 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 4251 * Local injection arguments: 4252 * 4253 * * `$delegate` - The original service instance, which can be monkey patched, configured, 4254 * decorated or delegated to. 4255 * 4256 * @example 4257 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 4258 * calls to {@link ng.$log#error $log.warn()}. 4259 * ```js 4260 * $provide.decorator('$log', ['$delegate', function($delegate) { 4261 * $delegate.warn = $delegate.error; 4262 * return $delegate; 4263 * }]); 4264 * ``` 4265 */ 4266 4267 4268function createInjector(modulesToLoad, strictDi) { 4269 strictDi = (strictDi === true); 4270 var INSTANTIATING = {}, 4271 providerSuffix = 'Provider', 4272 path = [], 4273 loadedModules = new HashMap([], true), 4274 providerCache = { 4275 $provide: { 4276 provider: supportObject(provider), 4277 factory: supportObject(factory), 4278 service: supportObject(service), 4279 value: supportObject(value), 4280 constant: supportObject(constant), 4281 decorator: decorator 4282 } 4283 }, 4284 providerInjector = (providerCache.$injector = 4285 createInternalInjector(providerCache, function(serviceName, caller) { 4286 if (angular.isString(caller)) { 4287 path.push(caller); 4288 } 4289 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 4290 })), 4291 instanceCache = {}, 4292 instanceInjector = (instanceCache.$injector = 4293 createInternalInjector(instanceCache, function(serviceName, caller) { 4294 var provider = providerInjector.get(serviceName + providerSuffix, caller); 4295 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); 4296 })); 4297 4298 4299 forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); }); 4300 4301 return instanceInjector; 4302 4303 //////////////////////////////////// 4304 // $provider 4305 //////////////////////////////////// 4306 4307 function supportObject(delegate) { 4308 return function(key, value) { 4309 if (isObject(key)) { 4310 forEach(key, reverseParams(delegate)); 4311 } else { 4312 return delegate(key, value); 4313 } 4314 }; 4315 } 4316 4317 function provider(name, provider_) { 4318 assertNotHasOwnProperty(name, 'service'); 4319 if (isFunction(provider_) || isArray(provider_)) { 4320 provider_ = providerInjector.instantiate(provider_); 4321 } 4322 if (!provider_.$get) { 4323 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 4324 } 4325 return providerCache[name + providerSuffix] = provider_; 4326 } 4327 4328 function enforceReturnValue(name, factory) { 4329 return function enforcedReturnValue() { 4330 var result = instanceInjector.invoke(factory, this); 4331 if (isUndefined(result)) { 4332 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); 4333 } 4334 return result; 4335 }; 4336 } 4337 4338 function factory(name, factoryFn, enforce) { 4339 return provider(name, { 4340 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 4341 });
vendor: 4,556 bytes, lines 4342-4475
4342 } 4343 4344 function service(name, constructor) { 4345 return factory(name, ['$injector', function($injector) { 4346 return $injector.instantiate(constructor); 4347 }]); 4348 } 4349 4350 function value(name, val) { return factory(name, valueFn(val), false); } 4351 4352 function constant(name, value) { 4353 assertNotHasOwnProperty(name, 'constant'); 4354 providerCache[name] = value; 4355 instanceCache[name] = value; 4356 } 4357 4358 function decorator(serviceName, decorFn) { 4359 var origProvider = providerInjector.get(serviceName + providerSuffix), 4360 orig$get = origProvider.$get; 4361 4362 origProvider.$get = function() { 4363 var origInstance = instanceInjector.invoke(orig$get, origProvider); 4364 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 4365 }; 4366 } 4367 4368 //////////////////////////////////// 4369 // Module Loading 4370 //////////////////////////////////// 4371 function loadModules(modulesToLoad) { 4372 assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array'); 4373 var runBlocks = [], moduleFn; 4374 forEach(modulesToLoad, function(module) { 4375 if (loadedModules.get(module)) return; 4376 loadedModules.put(module, true); 4377 4378 function runInvokeQueue(queue) { 4379 var i, ii; 4380 for (i = 0, ii = queue.length; i < ii; i++) { 4381 var invokeArgs = queue[i], 4382 provider = providerInjector.get(invokeArgs[0]); 4383 4384 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 4385 } 4386 } 4387 4388 try { 4389 if (isString(module)) { 4390 moduleFn = angularModule(module); 4391 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 4392 runInvokeQueue(moduleFn._invokeQueue); 4393 runInvokeQueue(moduleFn._configBlocks); 4394 } else if (isFunction(module)) { 4395 runBlocks.push(providerInjector.invoke(module)); 4396 } else if (isArray(module)) { 4397 runBlocks.push(providerInjector.invoke(module)); 4398 } else { 4399 assertArgFn(module, 'module'); 4400 } 4401 } catch (e) { 4402 if (isArray(module)) { 4403 module = module[module.length - 1]; 4404 } 4405 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 4406 // Safari & FF's stack traces don't contain error.message content 4407 // unlike those of Chrome and IE 4408 // So if stack doesn't contain message, we create a new string that contains both. 4409 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 4410 /* jshint -W022 */ 4411 e = e.message + '\n' + e.stack; 4412 } 4413 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 4414 module, e.stack || e.message || e); 4415 } 4416 }); 4417 return runBlocks; 4418 } 4419 4420 //////////////////////////////////// 4421 // internal Injector 4422 //////////////////////////////////// 4423 4424 function createInternalInjector(cache, factory) { 4425 4426 function getService(serviceName, caller) { 4427 if (cache.hasOwnProperty(serviceName)) { 4428 if (cache[serviceName] === INSTANTIATING) { 4429 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 4430 serviceName + ' <- ' + path.join(' <- ')); 4431 } 4432 return cache[serviceName]; 4433 } else { 4434 try { 4435 path.unshift(serviceName); 4436 cache[serviceName] = INSTANTIATING; 4437 return cache[serviceName] = factory(serviceName, caller); 4438 } catch (err) { 4439 if (cache[serviceName] === INSTANTIATING) { 4440 delete cache[serviceName]; 4441 } 4442 throw err; 4443 } finally { 4444 path.shift(); 4445 } 4446 } 4447 } 4448 4449 function invoke(fn, self, locals, serviceName) { 4450 if (typeof locals === 'string') { 4451 serviceName = locals; 4452 locals = null; 4453 } 4454 4455 var args = [], 4456 $inject = createInjector.$$annotate(fn, strictDi, serviceName), 4457 length, i, 4458 key; 4459 4460 for (i = 0, length = $inject.length; i < length; i++) { 4461 key = $inject[i]; 4462 if (typeof key !== 'string') { 4463 throw $injectorMinErr('itkn', 4464 'Incorrect injection token! Expected service name as string, got {0}', key); 4465 } 4466 args.push( 4467 locals && locals.hasOwnProperty(key) 4468 ? locals[key] 4469 : getService(key, serviceName) 4470 ); 4471 } 4472 if (isArray(fn)) { 4473 fn = fn[length]; 4474 } 4475
vendor: 4,918 bytes, lines 4476-4591
4476 // http://jsperf.com/angularjs-invoke-apply-vs-switch 4477 // #5388 4478 return fn.apply(self, args); 4479 } 4480 4481 function instantiate(Type, locals, serviceName) { 4482 // Check if Type is annotated and use just the given function at n-1 as parameter 4483 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 4484 // Object creation: http://jsperf.com/create-constructor/2 4485 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null); 4486 var returnedValue = invoke(Type, instance, locals, serviceName); 4487 4488 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 4489 } 4490 4491 return { 4492 invoke: invoke, 4493 instantiate: instantiate, 4494 get: getService, 4495 annotate: createInjector.$$annotate, 4496 has: function(name) { 4497 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 4498 } 4499 }; 4500 } 4501} 4502 4503createInjector.$$annotate = annotate; 4504 4505/** 4506 * @ngdoc provider 4507 * @name $anchorScrollProvider 4508 * 4509 * @description 4510 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 4511 * {@link ng.$location#hash $location.hash()} changes. 4512 */ 4513function $AnchorScrollProvider() { 4514 4515 var autoScrollingEnabled = true; 4516 4517 /** 4518 * @ngdoc method 4519 * @name $anchorScrollProvider#disableAutoScrolling 4520 * 4521 * @description 4522 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 4523 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 4524 * Use this method to disable automatic scrolling. 4525 * 4526 * If automatic scrolling is disabled, one must explicitly call 4527 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 4528 * current hash. 4529 */ 4530 this.disableAutoScrolling = function() { 4531 autoScrollingEnabled = false; 4532 }; 4533 4534 /** 4535 * @ngdoc service 4536 * @name $anchorScroll 4537 * @kind function 4538 * @requires $window 4539 * @requires $location 4540 * @requires $rootScope 4541 * 4542 * @description 4543 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the 4544 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified 4545 * in the 4546 * [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). 4547 * 4548 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 4549 * match any anchor whenever it changes. This can be disabled by calling 4550 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 4551 * 4552 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 4553 * vertical scroll-offset (either fixed or dynamic). 4554 * 4555 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of 4556 * {@link ng.$location#hash $location.hash()} will be used. 4557 * 4558 * @property {(number|function|jqLite)} yOffset 4559 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 4560 * positioned elements at the top of the page, such as navbars, headers etc. 4561 * 4562 * `yOffset` can be specified in various ways: 4563 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 4564 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 4565 * a number representing the offset (in pixels).<br /><br /> 4566 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 4567 * the top of the page to the element's bottom will be used as offset.<br /> 4568 * **Note**: The element will be taken into account only as long as its `position` is set to 4569 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust 4570 * their height and/or positioning according to the viewport's size. 4571 * 4572 * <br /> 4573 * <div class="alert alert-warning"> 4574 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 4575 * not some child element. 4576 * </div> 4577 * 4578 * @example 4579 <example module="anchorScrollExample"> 4580 <file name="index.html"> 4581 <div id="scrollArea" ng-controller="ScrollController"> 4582 <a ng-click="gotoBottom()">Go to bottom</a> 4583 <a id="bottom"></a> You're at the bottom! 4584 </div> 4585 </file> 4586 <file name="script.js"> 4587 angular.module('anchorScrollExample', []) 4588 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 4589 function ($scope, $location, $anchorScroll) { 4590 $scope.gotoBottom = function() { 4591 // set the location.hash to the id of
4592 // the element you wish to scroll to. 4593 $location.hash('bottom'); 4594 4595 // call $anchorScroll() 4596 $anchorScroll(); 4597 }; 4598 }]); 4599 </file> 4600 <file name="style.css"> 4601 #scrollArea { 4602 height: 280px; 4603 overflow: auto; 4604 } 4605 4606 #bottom { 4607 display: block; 4608 margin-top: 2000px; 4609 } 4610 </file> 4611 </example> 4612 * 4613 * <hr /> 4614 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 4615 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 4616 * 4617 * @example 4618 <example module="anchorScrollOffsetExample"> 4619 <file name="index.html"> 4620 <div class="fixed-header" ng-controller="headerCtrl"> 4621 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 4622 Go to anchor {{x}} 4623 </a> 4624 </div> 4625 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 4626 Anchor {{x}} of 5 4627 </div> 4628 </file> 4629 <file name="script.js"> 4630 angular.module('anchorScrollOffsetExample', []) 4631 .run(['$anchorScroll', function($anchorScroll) { 4632 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 4633 }]) 4634 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 4635 function ($anchorScroll, $location, $scope) {
vendor: 5,324 bytes, lines 4636-4805
4636 $scope.gotoAnchor = function(x) { 4637 var newHash = 'anchor' + x; 4638 if ($location.hash() !== newHash) { 4639 // set the $location.hash to `newHash` and 4640 // $anchorScroll will automatically scroll to it 4641 $location.hash('anchor' + x); 4642 } else { 4643 // call $anchorScroll() explicitly, 4644 // since $location.hash hasn't changed 4645 $anchorScroll(); 4646 } 4647 }; 4648 } 4649 ]); 4650 </file> 4651 <file name="style.css"> 4652 body { 4653 padding-top: 50px; 4654 } 4655 4656 .anchor { 4657 border: 2px dashed DarkOrchid; 4658 padding: 10px 10px 200px 10px; 4659 } 4660 4661 .fixed-header { 4662 background-color: rgba(0, 0, 0, 0.2); 4663 height: 50px; 4664 position: fixed; 4665 top: 0; left: 0; right: 0; 4666 } 4667 4668 .fixed-header > a { 4669 display: inline-block; 4670 margin: 5px 15px; 4671 } 4672 </file> 4673 </example> 4674 */ 4675 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 4676 var document = $window.document; 4677 4678 // Helper function to get first anchor from a NodeList 4679 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 4680 // and working in all supported browsers.) 4681 function getFirstAnchor(list) { 4682 var result = null; 4683 Array.prototype.some.call(list, function(element) { 4684 if (nodeName_(element) === 'a') { 4685 result = element; 4686 return true; 4687 } 4688 }); 4689 return result; 4690 } 4691 4692 function getYOffset() { 4693 4694 var offset = scroll.yOffset; 4695 4696 if (isFunction(offset)) { 4697 offset = offset(); 4698 } else if (isElement(offset)) { 4699 var elem = offset[0]; 4700 var style = $window.getComputedStyle(elem); 4701 if (style.position !== 'fixed') { 4702 offset = 0; 4703 } else { 4704 offset = elem.getBoundingClientRect().bottom; 4705 } 4706 } else if (!isNumber(offset)) { 4707 offset = 0; 4708 } 4709 4710 return offset; 4711 } 4712 4713 function scrollTo(elem) { 4714 if (elem) { 4715 elem.scrollIntoView(); 4716 4717 var offset = getYOffset(); 4718 4719 if (offset) { 4720 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 4721 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 4722 // top of the viewport. 4723 // 4724 // IF the number of pixels from the top of `elem` to the end of the page's content is less 4725 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 4726 // way down the page. 4727 // 4728 // This is often the case for elements near the bottom of the page. 4729 // 4730 // In such cases we do not need to scroll the whole `offset` up, just the difference between 4731 // the top of the element and the offset, which is enough to align the top of `elem` at the 4732 // desired position. 4733 var elemTop = elem.getBoundingClientRect().top; 4734 $window.scrollBy(0, elemTop - offset); 4735 } 4736 } else { 4737 $window.scrollTo(0, 0); 4738 } 4739 } 4740 4741 function scroll(hash) { 4742 hash = isString(hash) ? hash : $location.hash(); 4743 var elm; 4744 4745 // empty hash, scroll to the top of the page 4746 if (!hash) scrollTo(null); 4747 4748 // element with given id 4749 else if ((elm = document.getElementById(hash))) scrollTo(elm); 4750 4751 // first anchor with given name :-D 4752 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 4753 4754 // no element and hash == 'top', scroll to the top of the page 4755 else if (hash === 'top') scrollTo(null); 4756 } 4757 4758 // does not scroll when user clicks on anchor link that is currently on 4759 // (no url change, no $location.hash() change), browser native does scroll 4760 if (autoScrollingEnabled) { 4761 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 4762 function autoScrollWatchAction(newVal, oldVal) { 4763 // skip the initial scroll if $location.hash is empty 4764 if (newVal === oldVal && newVal === '') return; 4765 4766 jqLiteDocumentLoaded(function() { 4767 $rootScope.$evalAsync(scroll); 4768 }); 4769 }); 4770 } 4771 4772 return scroll; 4773 }]; 4774} 4775 4776var $animateMinErr = minErr('$animate'); 4777var ELEMENT_NODE = 1; 4778var NG_ANIMATE_CLASSNAME = 'ng-animate'; 4779 4780function mergeClasses(a,b) { 4781 if (!a && !b) return ''; 4782 if (!a) return b; 4783 if (!b) return a; 4784 if (isArray(a)) a = a.join(' '); 4785 if (isArray(b)) b = b.join(' '); 4786 return a + ' ' + b; 4787} 4788 4789function extractElementNode(element) { 4790 for (var i = 0; i < element.length; i++) { 4791 var elm = element[i]; 4792 if (elm.nodeType === ELEMENT_NODE) { 4793 return elm; 4794 } 4795 } 4796} 4797 4798function splitClasses(classes) { 4799 if (isString(classes)) { 4800 classes = classes.split(' '); 4801 } 4802 4803 // Use createMap() to prevent class assumptions involving property names in 4804 // Object.prototype 4805 var obj = createMap();
4806 forEach(classes, function(klass) { 4807 // sometimes the split leaves empty string values 4808 // incase extra spaces were applied to the options 4809 if (klass.length) { 4810 obj[klass] = true; 4811 } 4812 }); 4813 return obj; 4814} 4815 4816// if any other type of options value besides an Object value is 4817// passed into the $animate.method() animation then this helper code 4818// will be run which will ignore it. While this patch is not the 4819// greatest solution to this, a lot of existing plugins depend on 4820// $animate to either call the callback (< 1.2) or return a promise 4821// that can be changed. This helper function ensures that the options 4822// are wiped clean incase a callback function is provided. 4823function prepareAnimateOptions(options) { 4824 return isObject(options) 4825 ? options 4826 : {}; 4827} 4828 4829var $$CoreAnimateRunnerProvider = function() { 4830 this.$get = ['$q', '$$rAF', function($q, $$rAF) { 4831 function AnimateRunner() {} 4832 AnimateRunner.all = noop; 4833 AnimateRunner.chain = noop; 4834 AnimateRunner.prototype = { 4835 end: noop, 4836 cancel: noop, 4837 resume: noop, 4838 pause: noop, 4839 complete: noop, 4840 then: function(pass, fail) { 4841 return $q(function(resolve) { 4842 $$rAF(function() { 4843 resolve(); 4844 }); 4845 }).then(pass, fail); 4846 } 4847 }; 4848 return AnimateRunner; 4849 }]; 4850}; 4851 4852// this is prefixed with Core since it conflicts with 4853// the animateQueueProvider defined in ngAnimate/animateQueue.js 4854var $$CoreAnimateQueueProvider = function() { 4855 var postDigestQueue = new HashMap(); 4856 var postDigestElements = []; 4857 4858 this.$get = ['$$AnimateRunner', '$rootScope', 4859 function($$AnimateRunner, $rootScope) { 4860 return { 4861 enabled: noop, 4862 on: noop, 4863 off: noop, 4864 pin: noop, 4865 4866 push: function(element, event, options, domOperation) { 4867 domOperation && domOperation(); 4868 4869 options = options || {}; 4870 options.from && element.css(options.from); 4871 options.to && element.css(options.to); 4872 4873 if (options.addClass || options.removeClass) { 4874 addRemoveClassesPostDigest(element, options.addClass, options.removeClass); 4875 } 4876 4877 return new $$AnimateRunner(); // jshint ignore:line 4878 } 4879 }; 4880 4881 4882 function updateData(data, classes, value) { 4883 var changed = false; 4884 if (classes) { 4885 classes = isString(classes) ? classes.split(' ') : 4886 isArray(classes) ? classes : []; 4887 forEach(classes, function(className) { 4888 if (className) { 4889 changed = true; 4890 data[className] = value; 4891 } 4892 }); 4893 } 4894 return changed; 4895 } 4896 4897 function handleCSSClassChanges() { 4898 forEach(postDigestElements, function(element) { 4899 var data = postDigestQueue.get(element); 4900 if (data) { 4901 var existing = splitClasses(element.attr('class')); 4902 var toAdd = ''; 4903 var toRemove = ''; 4904 forEach(data, function(status, className) { 4905 var hasClass = !!existing[className]; 4906 if (status !== hasClass) { 4907 if (status) { 4908 toAdd += (toAdd.length ? ' ' : '') + className; 4909 } else { 4910 toRemove += (toRemove.length ? ' ' : '') + className; 4911 } 4912 } 4913 }); 4914 4915 forEach(element, function(elm) { 4916 toAdd && jqLiteAddClass(elm, toAdd); 4917 toRemove && jqLiteRemoveClass(elm, toRemove); 4918 }); 4919 postDigestQueue.remove(element); 4920 } 4921 }); 4922 postDigestElements.length = 0; 4923 } 4924 4925 4926 function addRemoveClassesPostDigest(element, add, remove) { 4927 var data = postDigestQueue.get(element) || {}; 4928 4929 var classesAdded = updateData(data, add, true); 4930 var classesRemoved = updateData(data, remove, false); 4931 4932 if (classesAdded || classesRemoved) { 4933 4934 postDigestQueue.put(element, data); 4935 postDigestElements.push(element); 4936 4937 if (postDigestElements.length === 1) { 4938 $rootScope.$$postDigest(handleCSSClassChanges); 4939 } 4940 } 4941 } 4942 }]; 4943}; 4944 4945/** 4946 * @ngdoc provider 4947 * @name $animateProvider 4948 * 4949 * @description 4950 * Default implementation of $animate that doesn't perform any animations, instead just 4951 * synchronously performs DOM updates and resolves the returned runner promise. 4952 * 4953 * In order to enable animations the `ngAnimate` module has to be loaded. 4954 * 4955 * To see the functional implementation check out `src/ngAnimate/animate.js`. 4956 */ 4957var $AnimateProvider = ['$provide', function($provide) { 4958 var provider = this; 4959 4960 this.$$registeredAnimations = Object.create(null); 4961 4962 /** 4963 * @ngdoc method 4964 * @name $animateProvider#register 4965 * 4966 * @description 4967 * Registers a new injectable animation factory function. The factory function produces the 4968 * animation object which contains callback functions for each event that is expected to be 4969 * animated. 4970 * 4971 * * `eventFn`: `function(element, ... , doneFunction, options)` 4972 * The element to animate, the `doneFunction` and the options fed into the animation. Depending 4973 * on the type of animation additional arguments will be injected into the animation function. The 4974 * list below explains the function signatures for the different animation methods: 4975 *
4976 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) 4977 * - addClass: function(element, addedClasses, doneFunction, options) 4978 * - removeClass: function(element, removedClasses, doneFunction, options) 4979 * - enter, leave, move: function(element, doneFunction, options) 4980 * - animate: function(element, fromStyles, toStyles, doneFunction, options) 4981 * 4982 * Make sure to trigger the `doneFunction` once the animation is fully complete. 4983 * 4984 * ```js 4985 * return { 4986 * //enter, leave, move signature 4987 * eventFn : function(element, done, options) { 4988 * //code to run the animation 4989 * //once complete, then run done() 4990 * return function endFunction(wasCancelled) { 4991 * //code to cancel the animation 4992 * } 4993 * } 4994 * } 4995 * ``` 4996 * 4997 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). 4998 * @param {Function} factory The factory function that will be executed to return the animation 4999 * object. 5000 */ 5001 this.register = function(name, factory) { 5002 if (name && name.charAt(0) !== '.') { 5003 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name); 5004 } 5005 5006 var key = name + '-animation'; 5007 provider.$$registeredAnimations[name.substr(1)] = key; 5008 $provide.factory(key, factory); 5009 }; 5010 5011 /** 5012 * @ngdoc method 5013 * @name $animateProvider#classNameFilter 5014 * 5015 * @description 5016 * Sets and/or returns the CSS class regular expression that is checked when performing 5017 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 5018 * therefore enable $animate to attempt to perform an animation on any element that is triggered. 5019 * When setting the `classNameFilter` value, animations will only be performed on elements 5020 * that successfully match the filter expression. This in turn can boost performance 5021 * for low-powered devices as well as applications containing a lot of structural operations. 5022 * @param {RegExp=} expression The className expression which will be checked against all animations 5023 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 5024 */ 5025 this.classNameFilter = function(expression) { 5026 if (arguments.length === 1) { 5027 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 5028 if (this.$$classNameFilter) { 5029 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)"); 5030 if (reservedRegex.test(this.$$classNameFilter.toString())) { 5031 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); 5032 5033 } 5034 } 5035 } 5036 return this.$$classNameFilter; 5037 }; 5038 5039 this.$get = ['$$animateQueue', function($$animateQueue) { 5040 function domInsert(element, parentElement, afterElement) { 5041 // if for some reason the previous element was removed 5042 // from the dom sometime before this code runs then let's 5043 // just stick to using the parent element as the anchor 5044 if (afterElement) { 5045 var afterNode = extractElementNode(afterElement); 5046 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { 5047 afterElement = null; 5048 } 5049 } 5050 afterElement ? afterElement.after(element) : parentElement.prepend(element); 5051 } 5052 5053 /** 5054 * @ngdoc service 5055 * @name $animate 5056 * @description The $animate service exposes a series of DOM utility methods that provide support 5057 * for animation hooks. The default behavior is the application of DOM operations, however, 5058 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting 5059 * to ensure that animation runs with the triggered DOM operation. 5060 * 5061 * By default $animate doesn't trigger an animations. This is because the `ngAnimate` module isn't 5062 * included and only when it is active then the animation hooks that `$animate` triggers will be 5063 * functional. Once active then all structural `ng-` directives will trigger animations as they perform 5064 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, 5065 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. 5066 * 5067 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. 5068 * 5069 * To learn more about enabling animation support, click here to visit the 5070 * {@link ngAnimate ngAnimate module page}. 5071 */ 5072 return { 5073 // we don't call it directly since non-existant arguments may 5074 // be interpreted as null within the sub enabled function 5075 5076 /** 5077 * 5078 * @ngdoc method 5079 * @name $animate#on 5080 * @kind function
5081 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) 5082 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback 5083 * is fired with the following params: 5084 * 5085 * ```js 5086 * $animate.on('enter', container, 5087 * function callback(element, phase) { 5088 * // cool we detected an enter animation within the container 5089 * } 5090 * ); 5091 * ``` 5092 * 5093 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) 5094 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself 5095 * as well as among its children 5096 * @param {Function} callback the callback function that will be fired when the listener is triggered 5097 * 5098 * The arguments present in the callback function are: 5099 * * `element` - The captured DOM element that the animation was fired on. 5100 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). 5101 */ 5102 on: $$animateQueue.on, 5103 5104 /** 5105 * 5106 * @ngdoc method 5107 * @name $animate#off 5108 * @kind function 5109 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method 5110 * can be used in three different ways depending on the arguments: 5111 * 5112 * ```js 5113 * // remove all the animation event listeners listening for `enter` 5114 * $animate.off('enter'); 5115 * 5116 * // remove all the animation event listeners listening for `enter` on the given element and its children 5117 * $animate.off('enter', container); 5118 * 5119 * // remove the event listener function provided by `listenerFn` that is set 5120 * // to listen for `enter` on the given `element` as well as its children 5121 * $animate.off('enter', container, callback); 5122 * ``` 5123 * 5124 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...) 5125 * @param {DOMElement=} container the container element the event listener was placed on 5126 * @param {Function=} callback the callback function that was registered as the listener 5127 */ 5128 off: $$animateQueue.off, 5129 5130 /** 5131 * @ngdoc method 5132 * @name $animate#pin 5133 * @kind function 5134 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists 5135 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the 5136 * element despite being outside the realm of the application or within another application. Say for example if the application 5137 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated 5138 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind 5139 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. 5140 * 5141 * Note that this feature is only active when the `ngAnimate` module is used. 5142 * 5143 * @param {DOMElement} element the external element that will be pinned 5144 * @param {DOMElement} parentElement the host parent element that will be associated with the external element 5145 */ 5146 pin: $$animateQueue.pin, 5147 5148 /** 5149 * 5150 * @ngdoc method 5151 * @name $animate#enabled 5152 * @kind function 5153 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This 5154 * function can be called in four ways: 5155 * 5156 * ```js 5157 * // returns true or false 5158 * $animate.enabled(); 5159 * 5160 * // changes the enabled state for all animations 5161 * $animate.enabled(false); 5162 * $animate.enabled(true); 5163 * 5164 * // returns true or false if animations are enabled for an element 5165 * $animate.enabled(element); 5166 * 5167 * // changes the enabled state for an element and its children 5168 * $animate.enabled(element, true); 5169 * $animate.enabled(element, false); 5170 * ``` 5171 * 5172 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state 5173 * @param {boolean=} enabled whether or not the animations will be enabled for the element 5174 * 5175 * @return {boolean} whether or not animations are enabled 5176 */ 5177 enabled: $$animateQueue.enabled, 5178 5179 /** 5180 * @ngdoc method 5181 * @name $animate#cancel 5182 * @kind function
5183 * @description Cancels the provided animation. 5184 * 5185 * @param {Promise} animationPromise The animation promise that is returned when an animation is started. 5186 */ 5187 cancel: function(runner) { 5188 runner.end && runner.end(); 5189 }, 5190 5191 /** 5192 * 5193 * @ngdoc method 5194 * @name $animate#enter 5195 * @kind function 5196 * @description Inserts the element into the DOM either after the `after` element (if provided) or 5197 * as the first child within the `parent` element and then triggers an animation. 5198 * A promise is returned that will be resolved during the next digest once the animation 5199 * has completed. 5200 * 5201 * @param {DOMElement} element the element which will be inserted into the DOM 5202 * @param {DOMElement} parent the parent element which will append the element as 5203 * a child (so long as the after element is not present) 5204 * @param {DOMElement=} after the sibling element after which the element will be appended 5205 * @param {object=} options an optional collection of options/styles that will be applied to the element 5206 * 5207 * @return {Promise} the animation callback promise 5208 */ 5209 enter: function(element, parent, after, options) { 5210 parent = parent && jqLite(parent); 5211 after = after && jqLite(after); 5212 parent = parent || after.parent(); 5213 domInsert(element, parent, after); 5214 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); 5215 }, 5216 5217 /** 5218 * 5219 * @ngdoc method 5220 * @name $animate#move 5221 * @kind function 5222 * @description Inserts (moves) the element into its new position in the DOM either after 5223 * the `after` element (if provided) or as the first child within the `parent` element 5224 * and then triggers an animation. A promise is returned that will be resolved 5225 * during the next digest once the animation has completed. 5226 * 5227 * @param {DOMElement} element the element which will be moved into the new DOM position 5228 * @param {DOMElement} parent the parent element which will append the element as 5229 * a child (so long as the after element is not present) 5230 * @param {DOMElement=} after the sibling element after which the element will be appended 5231 * @param {object=} options an optional collection of options/styles that will be applied to the element 5232 * 5233 * @return {Promise} the animation callback promise 5234 */ 5235 move: function(element, parent, after, options) { 5236 parent = parent && jqLite(parent); 5237 after = after && jqLite(after); 5238 parent = parent || after.parent(); 5239 domInsert(element, parent, after); 5240 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); 5241 }, 5242 5243 /** 5244 * @ngdoc method 5245 * @name $animate#leave 5246 * @kind function 5247 * @description Triggers an animation and then removes the element from the DOM. 5248 * When the function is called a promise is returned that will be resolved during the next 5249 * digest once the animation has completed. 5250 * 5251 * @param {DOMElement} element the element which will be removed from the DOM 5252 * @param {object=} options an optional collection of options/styles that will be applied to the element 5253 * 5254 * @return {Promise} the animation callback promise 5255 */ 5256 leave: function(element, options) { 5257 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { 5258 element.remove(); 5259 }); 5260 }, 5261 5262 /** 5263 * @ngdoc method 5264 * @name $animate#addClass 5265 * @kind function 5266 * 5267 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon 5268 * execution, the addClass operation will only be handled after the next digest and it will not trigger an 5269 * animation if element already contains the CSS class or if the class is removed at a later step. 5270 * Note that class-based animations are treated differently compared to structural animations 5271 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5272 * depending if CSS or JavaScript animations are used. 5273 * 5274 * @param {DOMElement} element the element which the CSS classes will be applied to 5275 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) 5276 * @param {object=} options an optional collection of options/styles that will be applied to the element 5277 * 5278 * @return {Promise} the animation callback promise 5279 */ 5280 addClass: function(element, className, options) { 5281 options = prepareAnimateOptions(options); 5282 options.addClass = mergeClasses(options.addclass, className); 5283 return $$animateQueue.push(element, 'addClass', options); 5284 }, 5285 5286 /** 5287 * @ngdoc method 5288 * @name $animate#removeClass 5289 * @kind function 5290 *
5291 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon 5292 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an 5293 * animation if element does not contain the CSS class or if the class is added at a later step. 5294 * Note that class-based animations are treated differently compared to structural animations 5295 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5296 * depending if CSS or JavaScript animations are used. 5297 * 5298 * @param {DOMElement} element the element which the CSS classes will be applied to 5299 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) 5300 * @param {object=} options an optional collection of options/styles that will be applied to the element 5301 * 5302 * @return {Promise} the animation callback promise 5303 */ 5304 removeClass: function(element, className, options) { 5305 options = prepareAnimateOptions(options); 5306 options.removeClass = mergeClasses(options.removeClass, className); 5307 return $$animateQueue.push(element, 'removeClass', options); 5308 }, 5309 5310 /** 5311 * @ngdoc method 5312 * @name $animate#setClass 5313 * @kind function 5314 * 5315 * @description Performs both the addition and removal of a CSS classes on an element and (during the process) 5316 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and 5317 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has 5318 * passed. Note that class-based animations are treated differently compared to structural animations 5319 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5320 * depending if CSS or JavaScript animations are used. 5321 * 5322 * @param {DOMElement} element the element which the CSS classes will be applied to 5323 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) 5324 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) 5325 * @param {object=} options an optional collection of options/styles that will be applied to the element 5326 * 5327 * @return {Promise} the animation callback promise 5328 */ 5329 setClass: function(element, add, remove, options) { 5330 options = prepareAnimateOptions(options); 5331 options.addClass = mergeClasses(options.addClass, add); 5332 options.removeClass = mergeClasses(options.removeClass, remove); 5333 return $$animateQueue.push(element, 'setClass', options); 5334 }, 5335 5336 /** 5337 * @ngdoc method 5338 * @name $animate#animate 5339 * @kind function 5340 * 5341 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. 5342 * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take 5343 * on the provided styles. For example, if a transition animation is set for the given className then the provided from and 5344 * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles 5345 * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter). 5346 * 5347 * @param {DOMElement} element the element which the CSS styles will be applied to 5348 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. 5349 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. 5350 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If 5351 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. 5352 * (Note that if no animation is detected then this value will not be appplied to the element.) 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 animate: function(element, from, to, className, options) { 5358 options = prepareAnimateOptions(options); 5359 options.from = options.from ? extend(options.from, from) : from; 5360 options.to = options.to ? extend(options.to, to) : to; 5361 5362 className = className || 'ng-inline-animate';
5363 options.tempClasses = mergeClasses(options.tempClasses, className); 5364 return $$animateQueue.push(element, 'animate', options); 5365 } 5366 }; 5367 }]; 5368}]; 5369 5370/** 5371 * @ngdoc service 5372 * @name $animateCss 5373 * @kind object 5374 * 5375 * @description 5376 * This is the core version of `$animateCss`. By default, only when the `ngAnimate` is included, 5377 * then the `$animateCss` service will actually perform animations. 5378 * 5379 * Click here {@link ngAnimate.$animateCss to read the documentation for $animateCss}. 5380 */ 5381var $CoreAnimateCssProvider = function() { 5382 this.$get = ['$$rAF', '$q', function($$rAF, $q) { 5383 5384 var RAFPromise = function() {}; 5385 RAFPromise.prototype = { 5386 done: function(cancel) { 5387 this.defer && this.defer[cancel === true ? 'reject' : 'resolve'](); 5388 }, 5389 end: function() { 5390 this.done(); 5391 }, 5392 cancel: function() { 5393 this.done(true); 5394 }, 5395 getPromise: function() { 5396 if (!this.defer) { 5397 this.defer = $q.defer(); 5398 } 5399 return this.defer.promise; 5400 }, 5401 then: function(f1,f2) { 5402 return this.getPromise().then(f1,f2); 5403 }, 5404 'catch': function(f1) { 5405 return this.getPromise()['catch'](f1); 5406 }, 5407 'finally': function(f1) { 5408 return this.getPromise()['finally'](f1); 5409 } 5410 }; 5411 5412 return function(element, options) { 5413 // there is no point in applying the styles since 5414 // there is no animation that goes on at all in 5415 // this version of $animateCss. 5416 if (options.cleanupStyles) { 5417 options.from = options.to = null; 5418 } 5419 5420 if (options.from) { 5421 element.css(options.from); 5422 options.from = null; 5423 } 5424 5425 var closed, runner = new RAFPromise(); 5426 return { 5427 start: run, 5428 end: run 5429 }; 5430 5431 function run() { 5432 $$rAF(function() { 5433 close(); 5434 if (!closed) { 5435 runner.done(); 5436 } 5437 closed = true; 5438 }); 5439 return runner; 5440 } 5441 5442 function close() { 5443 if (options.addClass) { 5444 element.addClass(options.addClass); 5445 options.addClass = null; 5446 } 5447 if (options.removeClass) { 5448 element.removeClass(options.removeClass); 5449 options.removeClass = null; 5450 } 5451 if (options.to) { 5452 element.css(options.to); 5453 options.to = null; 5454 } 5455 } 5456 }; 5457 }]; 5458}; 5459 5460/* global stripHash: true */ 5461 5462/** 5463 * ! This is a private undocumented service ! 5464 * 5465 * @name $browser 5466 * @requires $log 5467 * @description 5468 * This object has two goals: 5469 * 5470 * - hide all the global state in the browser caused by the window object 5471 * - abstract away all the browser specific features and inconsistencies 5472 * 5473 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 5474 * service, which can be used for convenient testing of the application without the interaction with 5475 * the real browser apis. 5476 */ 5477/** 5478 * @param {object} window The global window object. 5479 * @param {object} document jQuery wrapped document. 5480 * @param {object} $log window.console or an object with the same interface. 5481 * @param {object} $sniffer $sniffer service 5482 */ 5483function Browser(window, document, $log, $sniffer) { 5484 var self = this, 5485 rawDocument = document[0], 5486 location = window.location, 5487 history = window.history, 5488 setTimeout = window.setTimeout, 5489 clearTimeout = window.clearTimeout, 5490 pendingDeferIds = {}; 5491 5492 self.isMock = false; 5493 5494 var outstandingRequestCount = 0; 5495 var outstandingRequestCallbacks = []; 5496 5497 // TODO(vojta): remove this temporary api 5498 self.$$completeOutstandingRequest = completeOutstandingRequest; 5499 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 5500 5501 /** 5502 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 5503 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 5504 */ 5505 function completeOutstandingRequest(fn) { 5506 try { 5507 fn.apply(null, sliceArgs(arguments, 1)); 5508 } finally { 5509 outstandingRequestCount--; 5510 if (outstandingRequestCount === 0) { 5511 while (outstandingRequestCallbacks.length) { 5512 try { 5513 outstandingRequestCallbacks.pop()(); 5514 } catch (e) { 5515 $log.error(e); 5516 } 5517 } 5518 } 5519 } 5520 } 5521 5522 function getHash(url) { 5523 var index = url.indexOf('#'); 5524 return index === -1 ? '' : url.substr(index); 5525 } 5526 5527 /** 5528 * @private 5529 * Note: this method is used only by scenario runner 5530 * TODO(vojta): prefix this method with $$ ? 5531 * @param {function()} callback Function that will be called when no outstanding request 5532 */ 5533 self.notifyWhenNoOutstandingRequests = function(callback) { 5534 if (outstandingRequestCount === 0) { 5535 callback(); 5536 } else { 5537 outstandingRequestCallbacks.push(callback); 5538 } 5539 }; 5540 5541 ////////////////////////////////////////////////////////////// 5542 // URL API 5543 ////////////////////////////////////////////////////////////// 5544 5545 var cachedState, lastHistoryState, 5546 lastBrowserUrl = location.href, 5547 baseElement = document.find('base'), 5548 pendingLocation = null; 5549 5550 cacheState(); 5551 lastHistoryState = cachedState; 5552 5553 /** 5554 * @name $browser#url 5555 * 5556 * @description 5557 * GETTER: 5558 * Without any argument, this method just returns current value of location.href. 5559 * 5560 * SETTER: 5561 * With at least one argument, this method sets url to new value. 5562 * If html5 history api supported, pushState/replaceState is used, otherwise 5563 * location.href/location.replace is used. 5564 * Returns its own instance to allow chaining 5565 * 5566 * NOTE: this api is intended for use only by the $location service. Please use the 5567 * {@link ng.$location $location service} to change url. 5568 * 5569 * @param {string} url New url (when used as setter) 5570 * @param {boolean=} replace Should new url replace current history record? 5571 * @param {object=} state object to use with pushState/replaceState 5572 */ 5573 self.url = function(url, replace, state) { 5574 // In modern browsers `history.state` is `null` by default; treating it separately 5575 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 5576 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 5577 if (isUndefined(state)) { 5578 state = null; 5579 } 5580 5581 // Android Browser BFCache causes location, history reference to become stale. 5582 if (location !== window.location) location = window.location; 5583 if (history !== window.history) history = window.history;
5584 5585 // setter 5586 if (url) { 5587 var sameState = lastHistoryState === state; 5588 5589 // Don't change anything if previous and current URLs and states match. This also prevents 5590 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 5591 // See https://github.com/angular/angular.js/commit/ffb2701 5592 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 5593 return self; 5594 } 5595 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 5596 lastBrowserUrl = url; 5597 lastHistoryState = state; 5598 // Don't use history API if only the hash changed 5599 // due to a bug in IE10/IE11 which leads 5600 // to not firing a `hashchange` nor `popstate` event 5601 // in some cases (see #9143). 5602 if ($sniffer.history && (!sameBase || !sameState)) { 5603 history[replace ? 'replaceState' : 'pushState'](state, '', url); 5604 cacheState(); 5605 // Do the assignment again so that those two variables are referentially identical. 5606 lastHistoryState = cachedState; 5607 } else { 5608 if (!sameBase || pendingLocation) { 5609 pendingLocation = url; 5610 } 5611 if (replace) { 5612 location.replace(url); 5613 } else if (!sameBase) { 5614 location.href = url; 5615 } else { 5616 location.hash = getHash(url); 5617 } 5618 if (location.href !== url) { 5619 pendingLocation = url; 5620 } 5621 } 5622 return self; 5623 // getter 5624 } else { 5625 // - pendingLocation is needed as browsers don't allow to read out 5626 // the new location.href if a reload happened or if there is a bug like in iOS 9 (see 5627 // https://openradar.appspot.com/22186109). 5628 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 5629 return pendingLocation || location.href.replace(/%27/g,"'"); 5630 } 5631 }; 5632 5633 /** 5634 * @name $browser#state 5635 * 5636 * @description 5637 * This method is a getter. 5638 * 5639 * Return history.state or null if history.state is undefined. 5640 * 5641 * @returns {object} state 5642 */ 5643 self.state = function() { 5644 return cachedState; 5645 }; 5646 5647 var urlChangeListeners = [], 5648 urlChangeInit = false; 5649 5650 function cacheStateAndFireUrlChange() { 5651 pendingLocation = null; 5652 cacheState(); 5653 fireUrlChange(); 5654 } 5655 5656 function getCurrentState() { 5657 try { 5658 return history.state; 5659 } catch (e) { 5660 // MSIE can reportedly throw when there is no state (UNCONFIRMED). 5661 } 5662 } 5663 5664 // This variable should be used *only* inside the cacheState function. 5665 var lastCachedState = null; 5666 function cacheState() { 5667 // This should be the only place in $browser where `history.state` is read. 5668 cachedState = getCurrentState(); 5669 cachedState = isUndefined(cachedState) ? null : cachedState; 5670 5671 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 5672 if (equals(cachedState, lastCachedState)) { 5673 cachedState = lastCachedState; 5674 } 5675 lastCachedState = cachedState; 5676 } 5677 5678 function fireUrlChange() { 5679 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { 5680 return; 5681 } 5682 5683 lastBrowserUrl = self.url(); 5684 lastHistoryState = cachedState; 5685 forEach(urlChangeListeners, function(listener) { 5686 listener(self.url(), cachedState); 5687 }); 5688 } 5689 5690 /** 5691 * @name $browser#onUrlChange 5692 * 5693 * @description 5694 * Register callback function that will be called, when url changes. 5695 * 5696 * It's only called when the url is changed from outside of angular: 5697 * - user types different url into address bar 5698 * - user clicks on history (forward/back) button 5699 * - user clicks on a link 5700 * 5701 * It's not called when url is changed by $browser.url() method 5702 * 5703 * The listener gets called with new url as parameter. 5704 * 5705 * NOTE: this api is intended for use only by the $location service. Please use the 5706 * {@link ng.$location $location service} to monitor url changes in angular apps. 5707 * 5708 * @param {function(string)} listener Listener function to be called when url changes. 5709 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 5710 */ 5711 self.onUrlChange = function(callback) { 5712 // TODO(vojta): refactor to use node's syntax for events 5713 if (!urlChangeInit) { 5714 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 5715 // don't fire popstate when user change the address bar and don't fire hashchange when url 5716 // changed by push/replaceState 5717 5718 // html5 history api - popstate event 5719 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 5720 // hashchange event 5721 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 5722 5723 urlChangeInit = true; 5724 } 5725 5726 urlChangeListeners.push(callback); 5727 return callback; 5728 }; 5729 5730 /** 5731 * @private 5732 * Remove popstate and hashchange handler from window. 5733 * 5734 * NOTE: this api is intended for use only by $rootScope. 5735 */ 5736 self.$$applicationDestroyed = function() { 5737 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); 5738 }; 5739 5740 /** 5741 * Checks whether the url has changed outside of Angular. 5742 * Needs to be exported to be able to check for changes that have been done in sync, 5743 * as hashchange/popstate events fire in async. 5744 */ 5745 self.$$checkUrlChange = fireUrlChange; 5746 5747 ////////////////////////////////////////////////////////////// 5748 // Misc API 5749 ////////////////////////////////////////////////////////////// 5750 5751 /** 5752 * @name $browser#baseHref 5753 * 5754 * @description 5755 * Returns current <base href> 5756 * (always relative - without domain) 5757 * 5758 * @returns {string} The current base href 5759 */ 5760 self.baseHref = function() { 5761 var href = baseElement.attr('href'); 5762 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 5763 }; 5764 5765 /** 5766 * @name $browser#defer 5767 * @param {function()} fn A function, who's execution should be deferred. 5768 * @param {number=} [delay=0] of milliseconds to defer the function execution. 5769 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 5770 * 5771 * @description 5772 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 5773 * 5774 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 5775 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 5776 * via `$browser.defer.flush()`. 5777 * 5778 */ 5779 self.defer = function(fn, delay) { 5780 var timeoutId; 5781 outstandingRequestCount++; 5782 timeoutId = setTimeout(function() {
5783 delete pendingDeferIds[timeoutId]; 5784 completeOutstandingRequest(fn); 5785 }, delay || 0); 5786 pendingDeferIds[timeoutId] = true; 5787 return timeoutId; 5788 }; 5789 5790 5791 /** 5792 * @name $browser#defer.cancel 5793 * 5794 * @description 5795 * Cancels a deferred task identified with `deferId`. 5796 * 5797 * @param {*} deferId Token returned by the `$browser.defer` function. 5798 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 5799 * canceled. 5800 */ 5801 self.defer.cancel = function(deferId) { 5802 if (pendingDeferIds[deferId]) { 5803 delete pendingDeferIds[deferId]; 5804 clearTimeout(deferId); 5805 completeOutstandingRequest(noop); 5806 return true; 5807 } 5808 return false; 5809 }; 5810 5811} 5812 5813function $BrowserProvider() { 5814 this.$get = ['$window', '$log', '$sniffer', '$document', 5815 function($window, $log, $sniffer, $document) { 5816 return new Browser($window, $document, $log, $sniffer); 5817 }]; 5818} 5819 5820/** 5821 * @ngdoc service 5822 * @name $cacheFactory 5823 * 5824 * @description 5825 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 5826 * them. 5827 * 5828 * ```js 5829 * 5830 * var cache = $cacheFactory('cacheId'); 5831 * expect($cacheFactory.get('cacheId')).toBe(cache); 5832 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 5833 * 5834 * cache.put("key", "value"); 5835 * cache.put("another key", "another value"); 5836 * 5837 * // We've specified no options on creation 5838 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 5839 * 5840 * ``` 5841 * 5842 * 5843 * @param {string} cacheId Name or id of the newly created cache. 5844 * @param {object=} options Options object that specifies the cache behavior. Properties: 5845 * 5846 * - `{number=}` `capacity` â turns the cache into LRU cache. 5847 * 5848 * @returns {object} Newly created cache object with the following set of methods: 5849 * 5850 * - `{object}` `info()` â Returns id, size, and options of cache. 5851 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 5852 * it. 5853 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 5854 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 5855 * - `{void}` `removeAll()` â Removes all cached values. 5856 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 5857 * 5858 * @example 5859 <example module="cacheExampleApp"> 5860 <file name="index.html"> 5861 <div ng-controller="CacheController"> 5862 <input ng-model="newCacheKey" placeholder="Key"> 5863 <input ng-model="newCacheValue" placeholder="Value"> 5864 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 5865 5866 <p ng-if="keys.length">Cached Values</p> 5867 <div ng-repeat="key in keys"> 5868 <span ng-bind="key"></span> 5869 <span>: </span> 5870 <b ng-bind="cache.get(key)"></b> 5871 </div> 5872 5873 <p>Cache Info</p> 5874 <div ng-repeat="(key, value) in cache.info()"> 5875 <span ng-bind="key"></span> 5876 <span>: </span> 5877 <b ng-bind="value"></b> 5878 </div> 5879 </div> 5880 </file> 5881 <file name="script.js"> 5882 angular.module('cacheExampleApp', []). 5883 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 5884 $scope.keys = []; 5885 $scope.cache = $cacheFactory('cacheId'); 5886 $scope.put = function(key, value) { 5887 if (angular.isUndefined($scope.cache.get(key))) { 5888 $scope.keys.push(key); 5889 } 5890 $scope.cache.put(key, angular.isUndefined(value) ? null : value); 5891 }; 5892 }]); 5893 </file> 5894 <file name="style.css"> 5895 p { 5896 margin: 10px 0 3px; 5897 } 5898 </file> 5899 </example> 5900 */ 5901function $CacheFactoryProvider() { 5902 5903 this.$get = function() { 5904 var caches = {}; 5905 5906 function cacheFactory(cacheId, options) { 5907 if (cacheId in caches) { 5908 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 5909 } 5910 5911 var size = 0, 5912 stats = extend({}, options, {id: cacheId}), 5913 data = {}, 5914 capacity = (options && options.capacity) || Number.MAX_VALUE, 5915 lruHash = {}, 5916 freshEnd = null, 5917 staleEnd = null; 5918 5919 /** 5920 * @ngdoc type 5921 * @name $cacheFactory.Cache 5922 *
5923 * @description 5924 * A cache object used to store and retrieve data, primarily used by 5925 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 5926 * templates and other data. 5927 * 5928 * ```js 5929 * angular.module('superCache') 5930 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 5931 * return $cacheFactory('super-cache'); 5932 * }]); 5933 * ``` 5934 * 5935 * Example test: 5936 * 5937 * ```js 5938 * it('should behave like a cache', inject(function(superCache) { 5939 * superCache.put('key', 'value'); 5940 * superCache.put('another key', 'another value'); 5941 * 5942 * expect(superCache.info()).toEqual({ 5943 * id: 'super-cache', 5944 * size: 2 5945 * }); 5946 * 5947 * superCache.remove('another key'); 5948 * expect(superCache.get('another key')).toBeUndefined(); 5949 * 5950 * superCache.removeAll(); 5951 * expect(superCache.info()).toEqual({ 5952 * id: 'super-cache', 5953 * size: 0 5954 * }); 5955 * })); 5956 * ``` 5957 */ 5958 return caches[cacheId] = { 5959 5960 /** 5961 * @ngdoc method 5962 * @name $cacheFactory.Cache#put 5963 * @kind function 5964 * 5965 * @description 5966 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 5967 * retrieved later, and incrementing the size of the cache if the key was not already 5968 * present in the cache. If behaving like an LRU cache, it will also remove stale 5969 * entries from the set. 5970 * 5971 * It will not insert undefined values into the cache. 5972 * 5973 * @param {string} key the key under which the cached data is stored. 5974 * @param {*} value the value to store alongside the key. If it is undefined, the key 5975 * will not be stored. 5976 * @returns {*} the value stored. 5977 */ 5978 put: function(key, value) { 5979 if (isUndefined(value)) return; 5980 if (capacity < Number.MAX_VALUE) { 5981 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 5982 5983 refresh(lruEntry); 5984 } 5985 5986 if (!(key in data)) size++; 5987 data[key] = value; 5988 5989 if (size > capacity) { 5990 this.remove(staleEnd.key); 5991 } 5992 5993 return value; 5994 }, 5995 5996 /** 5997 * @ngdoc method 5998 * @name $cacheFactory.Cache#get 5999 * @kind function 6000 * 6001 * @description 6002 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 6003 * 6004 * @param {string} key the key of the data to be retrieved 6005 * @returns {*} the value stored. 6006 */ 6007 get: function(key) { 6008 if (capacity < Number.MAX_VALUE) { 6009 var lruEntry = lruHash[key]; 6010 6011 if (!lruEntry) return; 6012 6013 refresh(lruEntry); 6014 } 6015 6016 return data[key]; 6017 }, 6018 6019 6020 /** 6021 * @ngdoc method 6022 * @name $cacheFactory.Cache#remove 6023 * @kind function 6024 * 6025 * @description 6026 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 6027 * 6028 * @param {string} key the key of the entry to be removed 6029 */ 6030 remove: function(key) { 6031 if (capacity < Number.MAX_VALUE) { 6032 var lruEntry = lruHash[key]; 6033 6034 if (!lruEntry) return; 6035 6036 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 6037 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 6038 link(lruEntry.n,lruEntry.p); 6039 6040 delete lruHash[key]; 6041 } 6042 6043 delete data[key]; 6044 size--; 6045 }, 6046 6047 6048 /** 6049 * @ngdoc method 6050 * @name $cacheFactory.Cache#removeAll 6051 * @kind function 6052 * 6053 * @description 6054 * Clears the cache object of any entries. 6055 */ 6056 removeAll: function() { 6057 data = {}; 6058 size = 0; 6059 lruHash = {}; 6060 freshEnd = staleEnd = null; 6061 }, 6062 6063 6064 /** 6065 * @ngdoc method 6066 * @name $cacheFactory.Cache#destroy 6067 * @kind function 6068 *
6069 * @description 6070 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 6071 * removing it from the {@link $cacheFactory $cacheFactory} set. 6072 */ 6073 destroy: function() { 6074 data = null; 6075 stats = null; 6076 lruHash = null; 6077 delete caches[cacheId]; 6078 }, 6079 6080 6081 /** 6082 * @ngdoc method 6083 * @name $cacheFactory.Cache#info 6084 * @kind function 6085 * 6086 * @description 6087 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 6088 * 6089 * @returns {object} an object with the following properties: 6090 * <ul> 6091 * <li>**id**: the id of the cache instance</li> 6092 * <li>**size**: the number of entries kept in the cache instance</li> 6093 * <li>**...**: any additional properties from the options object when creating the 6094 * cache.</li> 6095 * </ul> 6096 */ 6097 info: function() { 6098 return extend({}, stats, {size: size}); 6099 } 6100 }; 6101 6102 6103 /** 6104 * makes the `entry` the freshEnd of the LRU linked list 6105 */ 6106 function refresh(entry) { 6107 if (entry != freshEnd) { 6108 if (!staleEnd) { 6109 staleEnd = entry; 6110 } else if (staleEnd == entry) { 6111 staleEnd = entry.n;
vendor: 5,797 bytes, lines 6112-6287
6112 } 6113 6114 link(entry.n, entry.p); 6115 link(entry, freshEnd); 6116 freshEnd = entry; 6117 freshEnd.n = null; 6118 } 6119 } 6120 6121 6122 /** 6123 * bidirectionally links two entries of the LRU linked list 6124 */ 6125 function link(nextEntry, prevEntry) { 6126 if (nextEntry != prevEntry) { 6127 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 6128 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 6129 } 6130 } 6131 } 6132 6133 6134 /** 6135 * @ngdoc method 6136 * @name $cacheFactory#info 6137 * 6138 * @description 6139 * Get information about all the caches that have been created 6140 * 6141 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 6142 */ 6143 cacheFactory.info = function() { 6144 var info = {}; 6145 forEach(caches, function(cache, cacheId) { 6146 info[cacheId] = cache.info(); 6147 }); 6148 return info; 6149 }; 6150 6151 6152 /** 6153 * @ngdoc method 6154 * @name $cacheFactory#get 6155 * 6156 * @description 6157 * Get access to a cache object by the `cacheId` used when it was created. 6158 * 6159 * @param {string} cacheId Name or id of a cache to access. 6160 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 6161 */ 6162 cacheFactory.get = function(cacheId) { 6163 return caches[cacheId]; 6164 }; 6165 6166 6167 return cacheFactory; 6168 }; 6169} 6170 6171/** 6172 * @ngdoc service 6173 * @name $templateCache 6174 * 6175 * @description 6176 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 6177 * can load templates directly into the cache in a `script` tag, or by consuming the 6178 * `$templateCache` service directly. 6179 * 6180 * Adding via the `script` tag: 6181 * 6182 * ```html 6183 * <script type="text/ng-template" id="templateId.html"> 6184 * <p>This is the content of the template</p> 6185 * </script> 6186 * ``` 6187 * 6188 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 6189 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, 6190 * element with ng-app attribute), otherwise the template will be ignored. 6191 * 6192 * Adding via the `$templateCache` service: 6193 * 6194 * ```js 6195 * var myApp = angular.module('myApp', []); 6196 * myApp.run(function($templateCache) { 6197 * $templateCache.put('templateId.html', 'This is the content of the template'); 6198 * }); 6199 * ``` 6200 * 6201 * To retrieve the template later, simply use it in your HTML: 6202 * ```html 6203 * <div ng-include=" 'templateId.html' "></div> 6204 * ``` 6205 * 6206 * or get it via Javascript: 6207 * ```js 6208 * $templateCache.get('templateId.html') 6209 * ``` 6210 * 6211 * See {@link ng.$cacheFactory $cacheFactory}. 6212 * 6213 */ 6214function $TemplateCacheProvider() { 6215 this.$get = ['$cacheFactory', function($cacheFactory) { 6216 return $cacheFactory('templates'); 6217 }]; 6218} 6219 6220/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 6221 * Any commits to this file should be reviewed with security in mind. * 6222 * Changes to this file can potentially create security vulnerabilities. * 6223 * An approval from 2 Core members with history of modifying * 6224 * this file is required. * 6225 * * 6226 * Does the change somehow allow for arbitrary javascript to be executed? * 6227 * Or allows for someone to change the prototype of built-in objects? * 6228 * Or gives undesired access to variables likes document or window? * 6229 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 6230 6231/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 6232 * 6233 * DOM-related variables: 6234 * 6235 * - "node" - DOM Node 6236 * - "element" - DOM Element or Node 6237 * - "$node" or "$element" - jqLite-wrapped node or element 6238 * 6239 * 6240 * Compiler related stuff: 6241 * 6242 * - "linkFn" - linking fn of a single directive 6243 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 6244 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 6245 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 6246 */ 6247 6248 6249/** 6250 * @ngdoc service 6251 * @name $compile 6252 * @kind function 6253 * 6254 * @description 6255 * Compiles an HTML string or DOM into a template and produces a template function, which 6256 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 6257 * 6258 * The compilation is a process of walking the DOM tree and matching DOM elements to 6259 * {@link ng.$compileProvider#directive directives}. 6260 * 6261 * <div class="alert alert-warning"> 6262 * **Note:** This document is an in-depth reference of all directive options. 6263 * For a gentle introduction to directives with examples of common use cases, 6264 * see the {@link guide/directive directive guide}. 6265 * </div> 6266 * 6267 * ## Comprehensive Directive API 6268 * 6269 * There are many different options for a directive. 6270 * 6271 * The difference resides in the return value of the factory function. 6272 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 6273 * or just the `postLink` function (all other properties will have the default values). 6274 * 6275 * <div class="alert alert-success"> 6276 * **Best Practice:** It's recommended to use the "directive definition object" form. 6277 * </div> 6278 * 6279 * Here's an example directive declared with a Directive Definition Object: 6280 * 6281 * ```js 6282 * var myModule = angular.module(...); 6283 * 6284 * myModule.directive('directiveName', function factory(injectables) { 6285 * var directiveDefinitionObject = { 6286 * priority: 0, 6287 * template: '<div></div>
6287', // or // function(tElement, tAttrs) { ... }, 6288 * // or 6289 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 6290 * transclude: false, 6291 * restrict: 'A', 6292 * templateNamespace: 'html', 6293 * scope: false, 6294 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 6295 * controllerAs: 'stringIdentifier', 6296 * bindToController: false, 6297 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 6298 * compile: function compile(tElement, tAttrs, transclude) { 6299 * return { 6300 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6301 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 6302 * } 6303 * // or 6304 * // return function postLink( ... ) { ... } 6305 * }, 6306 * // or 6307 * // link: { 6308 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6309 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 6310 * // } 6311 * // or 6312 * // link: function postLink( ... ) { ... } 6313 * }; 6314 * return directiveDefinitionObject; 6315 * }); 6316 * ``` 6317 * 6318 * <div class="alert alert-warning"> 6319 * **Note:** Any unspecified options will use the default value. You can see the default values below. 6320 * </div> 6321 * 6322 * Therefore the above can be simplified as: 6323 * 6324 * ```js 6325 * var myModule = angular.module(...); 6326 * 6327 * myModule.directive('directiveName', function factory(injectables) { 6328 * var directiveDefinitionObject = { 6329 * link: function postLink(scope, iElement, iAttrs) { ... } 6330 * }; 6331 * return directiveDefinitionObject; 6332 * // or 6333 * // return function postLink(scope, iElement, iAttrs) { ... } 6334 * }); 6335 * ``` 6336 * 6337 * 6338 * 6339 * ### Directive Definition Object 6340 * 6341 * The directive definition object provides instructions to the {@link ng.$compile 6342 * compiler}. The attributes are: 6343 * 6344 * #### `multiElement` 6345 * When this property is set to true, the HTML compiler will collect DOM nodes between 6346 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 6347 * together as the directive elements. It is recommended that this feature be used on directives 6348 * which are not strictly behavioural (such as {@link ngClick}), and which 6349 * do not manipulate or replace child nodes (such as {@link ngInclude}). 6350 * 6351 * #### `priority` 6352 * When there are multiple directives defined on a single DOM element, sometimes it 6353 * is necessary to specify the order in which the directives are applied. The `priority` is used 6354 * to sort the directives before their `compile` functions get called. Priority is defined as a 6355 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 6356 * are also run in priority order, but post-link functions are run in reverse order. The order 6357 * of directives with the same priority is undefined. The default priority is `0`. 6358 * 6359 * #### `terminal` 6360 * If set to true then the current `priority` will be the last set of directives 6361 * which will execute (any directives at the current priority will still execute 6362 * as the order of execution on same `priority` is undefined). Note that expressions 6363 * and other directives used in the directive's template will also be excluded from execution. 6364 * 6365 * #### `scope` 6366 * The scope property can be `true`, an object or a falsy value: 6367 * 6368 * * **falsy:** No scope will be created for the directive. The directive will use its parent's scope. 6369 * 6370 * * **`true`:** A new child scope that prototypically inherits from its parent will be created for 6371 * the directive's element. If multiple directives on the same element request a new scope, 6372 * only one new scope is created. The new scope rule does not apply for the root of the template 6373 * since the root of the template always gets a new scope. 6374 * 6375 * * **`{...}` (an object hash):** A new "isolate" scope is created for the directive's element. The 6376 * 'isolate' scope differs from normal scope in that it does not prototypically inherit from its parent 6377 * scope. This is useful when creating reusable components, which should not accidentally read or modify 6378 * data in the parent scope. 6379 * 6380 * The 'isolate' scope object hash defines a set of local scope properties derived from attributes on the 6381 * directive's element. These local properties are useful for aliasing values for templates. The keys in 6382 * the object hash map to the name of the property on the isolate scope; the values define how the property 6383 * is bound to the parent scope, via matching attributes on the directive's element: 6384 * 6385 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is 6386 * always a string since DOM attributes are strings. If no `attr` name is specified then the 6387 * attribute name is assumed to be the same as the local name. 6388 * Given `<widget my-attr="hello {{name}}">` and widget definition 6389 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 6390 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 6391 * `localName` property on the widget scope. The `name` is read from the parent scope (not 6392 * component scope). 6393 * 6394 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 6395 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 6396 * name is specified then the attribute name is assumed to be the same as the local name. 6397 * Given `<widget my-attr="parentModel">` and widget definition of 6398 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 6399 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 6400 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 6401 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 6402 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If 6403 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use 6404 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional). 6405 * 6406 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 6407 * If no `attr` name is specified then the attribute name is assumed to be the same as the 6408 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 6409 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 6410 * a function wrapper for the `count = count + value` expression. Often it's desirable to 6411 * pass data from the isolated scope via an expression to the parent scope, this can be 6412 * done by passing a map of local variable names and values into the expression wrapper fn. 6413 * For example, if the expression is `increment(amount)` then we can specify the amount value 6414 * by calling the `localFn` as `localFn({amount: 22})`. 6415 * 6416 * In general it's possible to apply more than one directive to one element, but there might be limitations 6417 * depending on the type of scope required by the directives. The following points will help explain these limitations. 6418 * For simplicity only two directives are taken into account, but it is also applicable for several directives: 6419 * 6420 * * **no scope** + **no scope** => Two directives which don't require their own scope will use their parent's scope 6421 * * **child scope** + **no scope** => Both directives will share one single child scope 6422 * * **child scope** + **child scope** => Both directives will share one single child scope 6423 * * **isolated scope** + **no scope** => The isolated directive will use it's own created isolated scope. The other directive will use
6424 * its parent's scope 6425 * * **isolated scope** + **child scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives cannot 6426 * be applied to the same element. 6427 * * **isolated scope** + **isolated scope** => **Won't work!** Only one scope can be related to one element. Therefore these directives 6428 * cannot be applied to the same element. 6429 * 6430 * 6431 * #### `bindToController` 6432 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will 6433 * allow a component to have its properties bound to the controller, rather than to scope. When the controller 6434 * is instantiated, the initial values of the isolate scope bindings are already available. 6435 * 6436 * #### `controller` 6437 * Controller constructor function. The controller is instantiated before the 6438 * pre-linking phase and can be accessed by other directives (see 6439 * `require` attribute). This allows the directives to communicate with each other and augment 6440 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 6441 * 6442 * * `$scope` - Current scope associated with the element 6443 * * `$element` - Current element 6444 * * `$attrs` - Current attributes object for the element 6445 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 6446 * `function([scope], cloneLinkingFn, futureParentElement)`. 6447 * * `scope`: optional argument to override the scope. 6448 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content. 6449 * * `futureParentElement`: 6450 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 6451 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 6452 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 6453 * and when the `cloneLinkinFn` is passed, 6454 * as those elements need to created and cloned in a special way when they are defined outside their 6455 * usual containers (e.g. like `<svg>`). 6456 * * See also the `directive.templateNamespace` property. 6457 * 6458 * 6459 * #### `require` 6460 * Require another directive and inject its controller as the fourth argument to the linking function. The 6461 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 6462 * injected argument will be an array in corresponding order. If no such directive can be 6463 * found, or if the directive does not have a controller, then an error is raised (unless no link function 6464 * is specified, in which case error checking is skipped). The name can be prefixed with: 6465 * 6466 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 6467 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 6468 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 6469 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 6470 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 6471 * `null` to the `link` fn if not found. 6472 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 6473 * `null` to the `link` fn if not found. 6474 * 6475 * 6476 * #### `controllerAs` 6477 * Identifier name for a reference to the controller in the directive's scope. 6478 * This allows the controller to be referenced from the directive template. This is especially 6479 * useful when a directive is used as component, i.e. with an `isolate` scope. It's also possible 6480 * to use it in a directive without an `isolate` / `new` scope, but you need to be aware that the 6481 * `controllerAs` reference might overwrite a property that already exists on the parent scope. 6482 * 6483 * 6484 * #### `restrict` 6485 * String of subset of `EACM` which restricts the directive to a specific directive 6486 * declaration style. If omitted, the defaults (elements and attributes) are used. 6487 * 6488 * * `E` - Element name (default): `<my-directive></my-directive>` 6489 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 6490 * * `C` - Class: `<div class="my-directive: exp;"></div>` 6491 * * `M` - Comment: `<!-- directive: my-directive exp -->` 6492 * 6493 * 6494 * #### `templateNamespace` 6495 * String representing the document type used by the markup in the template. 6496 * AngularJS needs this information as those elements need to be created and cloned 6497 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 6498 * 6499 * * `html` - All root nodes in the template are HTML. Root nodes may also be 6500 * top-level elements such as `<svg>` or `<math>`. 6501 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 6502 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 6503 * 6504 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 6505 * 6506 * #### `template` 6507 * HTML markup that may: 6508 * * Replace the contents of the directive's element (default). 6509 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 6510 * * Wrap the contents of the directive's element (if `transclude` is true). 6511 * 6512 * Value may be: 6513 * 6514 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 6515 * * A function which takes two arguments `tElement` and `tAttrs` (described
6515in the `compile` 6516 * function api below) and returns a string value. 6517 * 6518 * 6519 * #### `templateUrl` 6520 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 6521 * 6522 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 6523 * for later when the template has been resolved. In the meantime it will continue to compile and link 6524 * sibling and parent elements as though this element had not contained any directives. 6525 * 6526 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 6527 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 6528 * case when only one deeply nested directive has `templateUrl`. 6529 * 6530 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} 6531 * 6532 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 6533 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 6534 * a string value representing the url. In either case, the template URL is passed through {@link 6535 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 6536 * 6537 * 6538 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) 6539 * specify what the template should replace. Defaults to `false`. 6540 * 6541 * * `true` - the template will replace the directive's element. 6542 * * `false` - the template will replace the contents of the directive's element. 6543 * 6544 * The replacement process migrates all of the attributes / classes from the old element to the new 6545 * one. See the {@link guide/directive#template-expanding-directive 6546 * Directives Guide} for an example. 6547 * 6548 * There are very few scenarios where element replacement is required for the application function, 6549 * the main one being reusable custom components that are used within SVG contexts 6550 * (because SVG doesn't work with custom elements in the DOM tree). 6551 * 6552 * #### `transclude` 6553 * Extract the contents of the element where the directive appears and make it available to the directive. 6554 * The contents are compiled and provided to the directive as a **transclusion function**. See the 6555 * {@link $compile#transclusion Transclusion} section below. 6556 * 6557 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the 6558 * directive's element or the entire element: 6559 * 6560 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 6561 * * `'element'` - transclude the whole of the directive's element including any directives on this 6562 * element that defined at a lower priority than this directive. When used, the `template` 6563 * property is ignored. 6564 * 6565 * 6566 * #### `compile` 6567 * 6568 * ```js 6569 * function compile(tElement, tAttrs, transclude) { ... } 6570 * ``` 6571 * 6572 * The compile function deals with transforming the template DOM. Since most directives do not do 6573 * template transformation, it is not used often. The compile function takes the following arguments: 6574 * 6575 * * `tElement` - template element - The element where the directive has been declared. It is 6576 * safe to do template transformation on the element and child elements only. 6577 * 6578 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 6579 * between all directive compile functions. 6580 * 6581 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 6582 * 6583 * <div class="alert alert-warning"> 6584 * **Note:** The template instance and the link instance may be different objects if the template has 6585 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 6586 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration 6587 * should be done in a linking function rather than in a compile function. 6588 * </div> 6589 6590 * <div class="alert alert-warning"> 6591 * **Note:** The compile function cannot handle directives that recursively use themselves in their 6592 * own templates or compile functions. Compiling these directives results in an infinite loop and a 6593 * stack overflow errors. 6594 * 6595 * This can be avoided by manually using $compile in the postLink function to imperatively compile 6596 * a directive's template instead of relying on automatic template compilation via `template` or 6597 * `templateUrl` declaration or manual compilation inside the compile function. 6598 * </div> 6599 * 6600 * <div class="alert alert-danger"> 6601 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 6602 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 6603 * to the link function instead. 6604 * </div> 6605 6606 * A compile function can have a return value which can be either a function or an object. 6607 * 6608 * * returning a (post-link) function - is equivalent to registering the linking function via the 6609 * `link` property of the config object when the compile function is empty. 6610 * 6611 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 6612 * control when a linking function should be called during the linking phase. See info about 6613 * pre-linking and post-linking functions below. 6614 * 6615 * 6616 * #### `link` 6617 * This property is used only if the `compile` property is not defined. 6618 * 6619 * ```js 6620 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 6621 * ``` 6622 * 6623 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 6624 * executed after the template has been cloned. This is where most of the directive logic will be 6625 * put. 6626 * 6627 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 6628 * directive for registering {@link ng.$rootScope.Scope#$watch w
6628atches}. 6629 * 6630 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 6631 * manipulate the children of the element only in `postLink` function since the children have 6632 * already been linked. 6633 * 6634 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 6635 * between all directive linking functions. 6636 * 6637 * * `controller` - the directive's required controller instance(s) - Instances are shared 6638 * among all directives, which allows the directives to use the controllers as a communication 6639 * channel. The exact value depends on the directive's `require` property: 6640 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one 6641 * * `string`: the controller instance 6642 * * `array`: array of controller instances 6643 * 6644 * If a required controller cannot be found, and it is optional, the instance is `null`, 6645 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. 6646 * 6647 * Note that you can also require the directive's own controller - it will be made available like 6648 * any other controller. 6649 * 6650 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 6651 * This is the same as the `$transclude` 6652 * parameter of directive controllers, see there for details. 6653 * `function([scope], cloneLinkingFn, futureParentElement)`. 6654 * 6655 * #### Pre-linking function 6656 * 6657 * Executed before the child elements are linked. Not safe to do DOM transformation since the 6658 * compiler linking function will fail to locate the correct elements for linking. 6659 * 6660 * #### Post-linking function 6661 * 6662 * Executed after the child elements are linked. 6663 * 6664 * Note that child elements that contain `templateUrl` directives will not have been compiled 6665 * and linked since they are waiting for their template to load asynchronously and their own 6666 * compilation and linking has been suspended until that occurs. 6667 * 6668 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 6669 * for their async templates to be resolved. 6670 * 6671 * 6672 * ### Transclusion 6673 * 6674 * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and 6675 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 6676 * scope from where they were taken. 6677 * 6678 * Transclusion is used (often with {@link ngTransclude}) to insert the 6679 * original contents of a directive's element into a specified place in the template of the directive. 6680 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 6681 * content has access to the properties on the scope from which it was taken, even if the directive 6682 * has isolated scope. 6683 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 6684 * 6685 * This makes it possible for the widget to have private state for its template, while the transcluded 6686 * content has access to its originating scope. 6687 * 6688 * <div class="alert alert-warning"> 6689 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 6690 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 6691 * Testing Transclusion Directives}. 6692 * </div> 6693 * 6694 * #### Transclusion Functions 6695 * 6696 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 6697 * function** to the directive's `link` function and `controller`. This transclusion function is a special 6698 * **linking function** that will return the compiled contents linked to a new transclusion scope. 6699 * 6700 * <div class="alert alert-info"> 6701 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 6702 * ngTransclude will deal with it for us. 6703 * </div> 6704 * 6705 * If you want to manually control the insertion and removal of the transcluded content in your directive 6706 * then you must use this transclude function. When you call a transclude function it returns a a jq
6706Lite/JQuery 6707 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 6708 * 6709 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 6710 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 6711 * content and the `scope` is the newly created transclusion scope, to which the clone is bound. 6712 * 6713 * <div class="alert alert-info"> 6714 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function 6715 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 6716 * </div> 6717 * 6718 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 6719 * attach function**: 6720 * 6721 * ```js 6722 * var transcludedContent, transclusionScope; 6723 * 6724 * $transclude(function(clone, scope) { 6725 * element.append(clone); 6726 * transcludedContent = clone; 6727 * transclusionScope = scope; 6728 * }); 6729 * ``` 6730 * 6731 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 6732 * associated transclusion scope: 6733 * 6734 * ```js 6735 * transcludedContent.remove(); 6736 * transclusionScope.$destroy(); 6737 * ``` 6738 * 6739 * <div class="alert alert-info"> 6740 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 6741 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), 6742 * then you are also responsible for calling `$destroy` on the transclusion scope. 6743 * </div> 6744 * 6745 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 6746 * automatically destroy their transluded clones as necessary so you do not need to worry about this if 6747 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 6748 * 6749 * 6750 * #### Transclusion Scopes 6751 * 6752 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 6753 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 6754 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 6755 * was taken. 6756 * 6757 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 6758 * like this: 6759 * 6760 * ```html 6761 * <div ng-app> 6762 * <div isolate> 6763 * <div transclusion> 6764 * </div> 6765 * </div> 6766 * </div> 6767 * ``` 6768 * 6769 * The `$parent` scope hierarchy will look like this: 6770 * 6771 * ``` 6772 * - $rootScope 6773 * - isolate 6774 * - transclusion 6775 * ``` 6776 * 6777 * but the scopes will inherit prototypically from different scopes to their `$parent`. 6778 * 6779 * ``` 6780 * - $rootScope 6781 * - transclusion 6782 * - isolate 6783 * ``` 6784 * 6785 * 6786 * ### Attributes 6787 * 6788 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 6789 * `link()` or `compile()` functions. It has a variety of uses. 6790 * 6791 * accessing *Normalized attribute names:* 6792 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 6793 * the attributes object allows for normalized access to 6794 * the attributes. 6795 * 6796 * * *Directive inter-communication:* All directives share the same instance of the attributes 6797 * object which allows the directives to use the attributes object as inter directive 6798 * communication. 6799 * 6800 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 6801 * allowing other directives to read the interpolated value. 6802 * 6803 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 6804 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 6805 * the only way to easily get the actual value because during the linking phase the interpolation 6806 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 6807 * 6808 * ```js 6809 * function linkingFn(scope, elm, attrs, ctrl) { 6810 * // get the attribute value 6811 * console.log(attrs.ngModel); 6812 * 6813 * // change the attribute 6814 * attrs.$set('ngModel', 'new value'); 6815 * 6816 * // observe changes to interpolated attribute 6817 * attrs.$observe('ngModel', function(value) { 6818 * console.log('ngModel has changed value to ' + value); 6819 * }); 6820 * } 6821 * ``` 6822 * 6823 * ## Example 6824 * 6825 * <div class="alert alert-warning"> 6826 * **Note**: Typically directives are registered with `module.directive`. The example below is 6827 * to illustrate how `$compile` works. 6828 * </div> 6829 * 6830 <example module="compileExample"> 6831 <file name="index.html"> 6832 <script>
6833 angular.module('compileExample', [], function($compileProvider) { 6834 // configure new 'compile' directive by passing a directive 6835 // factory function. The factory function injects the '$compile' 6836 $compileProvider.directive('compile', function($compile) { 6837 // directive factory creates a link function 6838 return function(scope, element, attrs) { 6839 scope.$watch( 6840 function(scope) { 6841 // watch the 'compile' expression for changes 6842 return scope.$eval(attrs.compile); 6843 }, 6844 function(value) { 6845 // when the 'compile' expression changes 6846 // assign it into the current DOM 6847 element.html(value); 6848 6849 // compile the new DOM and link it to the current 6850 // scope. 6851 // NOTE: we only compile .childNodes so that 6852 // we don't get into infinite loop compiling ourselves 6853 $compile(element.contents())(scope); 6854 } 6855 ); 6856 }; 6857 }); 6858 }) 6859 .controller('GreeterController', ['$scope', function($scope) { 6860 $scope.name = 'Angular'; 6861 $scope.html = 'Hello {{name}}'; 6862 }]); 6863 </script> 6864 <div ng-controller="GreeterController"> 6865 <input ng-model="name"> <br/> 6866 <textarea ng-model="html"></textarea> <br/> 6867 <div compile="html"></div> 6868 </div> 6869 </file> 6870 <file name="protractor.js" type="protractor"> 6871 it('should auto compile', function() { 6872 var textarea = $('textarea'); 6873 var output = $('div[compile]'); 6874 // The initial state reads 'Hello Angular'. 6875 expect(output.getText()).toBe('Hello Angular'); 6876 textarea.clear(); 6877 textarea.sendKeys('{{name}}!'); 6878 expect(output.getText()).toBe('Angular!'); 6879 }); 6880 </file> 6881 </example> 6882 6883 * 6884 * 6885 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 6886 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 6887 * 6888 * <div class="alert alert-danger"> 6889 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 6890 * e.g. will not use the right outer scope. Please pass the transclude function as a 6891 * `parentBoundTranscludeFn` to the link function instead. 6892 * </div> 6893 * 6894 * @param {number} maxPriority only apply directives lower than given priority (Only effects the 6895 * root element(s), not their children) 6896 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 6897 * (a DOM element/tree) to a scope. Where: 6898 * 6899 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 6900 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 6901 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 6902 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 6903 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: 6904 * 6905 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 6906 * * `scope` - is the current scope with which the linking function is working with. 6907 * 6908 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 6909 * keys may be used to control linking behavior: 6910 * 6911 * * `parentBoundTranscludeFn` - the transclude function made available to 6912 * directives; if given, it will be passed through to the link functions of 6913 * directives found in `element` during compilation. 6914 * * `transcludeControllers` - an object hash with keys that map controller names 6915 * to controller instances; if given, it will make the controllers 6916 * available to directives. 6917 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 6918 * the cloned elements; only needed for transcludes that are allowed to contain non html 6919 * elements (e.g. SVG elements). See also the directive.controller property. 6920 * 6921 * Calling the linking function returns the element of the template. It is either the original 6922 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 6923 * 6924 * After linking the view is not updated until after a call to $digest which typically is done by 6925 * Angular automatically. 6926 * 6927 * If you need access to the bound view, there are two ways to do it: 6928 * 6929 * - If you are not asking the linking function to clone the template, create the DOM element(s) 6930 * before you send them to the compiler and keep this reference around. 6931 * ```js 6932 * var element = $compile('<p>{{total}}</p>')(scope); 6933 * ``` 6934 * 6935 * - if on the other hand, you need the element to be cloned, the view reference from the original 6936 * example would not point to the clone, but rather to the original template that was cloned. In 6937 * this case, you can access the clone via the cloneAttachFn: 6938 * ```js 6939 * var templateElement = angular.element('<p>{{total}}</p>'), 6940 * scope = ....; 6941 * 6942 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 6943 * //attach the clone to DOM document at the right place 6944 * }); 6945 * 6946 * //now we have reference to the cloned DOM via `clonedElement` 6947 * ``` 6948 * 6949 * 6950 * For information on how the compiler works, see the 6951 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 6952 */ 6953 6954var $compileMinErr = minErr('$compile'); 6955 6956/** 6957 * @ngdoc provider 6958 * @name $compileProvider 6959 * 6960 * @description 6961 */
vendor: 11,436 bytes, lines 6962-7248
6962$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 6963function $CompileProvider($provide, $$sanitizeUriProvider) { 6964 var hasDirectives = {}, 6965 Suffix = 'Directive', 6966 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, 6967 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, 6968 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 6969 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 6970 6971 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 6972 // The assumption is that future DOM event attribute names will begin with 6973 // 'on' and be composed of only English letters. 6974 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 6975 6976 function parseIsolateBindings(scope, directiveName, isController) { 6977 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; 6978 6979 var bindings = {}; 6980 6981 forEach(scope, function(definition, scopeName) { 6982 var match = definition.match(LOCAL_REGEXP); 6983 6984 if (!match) { 6985 throw $compileMinErr('iscp', 6986 "Invalid {3} for directive '{0}'." + 6987 " Definition: {... {1}: '{2}' ...}", 6988 directiveName, scopeName, definition, 6989 (isController ? "controller bindings definition" : 6990 "isolate scope definition")); 6991 } 6992 6993 bindings[scopeName] = { 6994 mode: match[1][0], 6995 collection: match[2] === '*', 6996 optional: match[3] === '?', 6997 attrName: match[4] || scopeName 6998 }; 6999 }); 7000 7001 return bindings; 7002 } 7003 7004 function parseDirectiveBindings(directive, directiveName) { 7005 var bindings = { 7006 isolateScope: null, 7007 bindToController: null 7008 }; 7009 if (isObject(directive.scope)) { 7010 if (directive.bindToController === true) { 7011 bindings.bindToController = parseIsolateBindings(directive.scope, 7012 directiveName, true); 7013 bindings.isolateScope = {}; 7014 } else { 7015 bindings.isolateScope = parseIsolateBindings(directive.scope, 7016 directiveName, false); 7017 } 7018 } 7019 if (isObject(directive.bindToController)) { 7020 bindings.bindToController = 7021 parseIsolateBindings(directive.bindToController, directiveName, true); 7022 } 7023 if (isObject(bindings.bindToController)) { 7024 var controller = directive.controller; 7025 var controllerAs = directive.controllerAs; 7026 if (!controller) { 7027 // There is no controller, there may or may not be a controllerAs property 7028 throw $compileMinErr('noctrl', 7029 "Cannot bind to controller without directive '{0}'s controller.", 7030 directiveName); 7031 } else if (!identifierForController(controller, controllerAs)) { 7032 // There is a controller, but no identifier or controllerAs property 7033 throw $compileMinErr('noident', 7034 "Cannot bind to controller without identifier for directive '{0}'.", 7035 directiveName); 7036 } 7037 } 7038 return bindings; 7039 } 7040 7041 function assertValidDirectiveName(name) { 7042 var letter = name.charAt(0); 7043 if (!letter || letter !== lowercase(letter)) { 7044 throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name); 7045 } 7046 if (name !== name.trim()) { 7047 throw $compileMinErr('baddir', 7048 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces", 7049 name); 7050 } 7051 } 7052 7053 /** 7054 * @ngdoc method 7055 * @name $compileProvider#directive 7056 * @kind function 7057 * 7058 * @description 7059 * Register a new directive with the compiler. 7060 * 7061 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 7062 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 7063 * names and the values are the factories. 7064 * @param {Function|Array} directiveFactory An injectable directive factory function. See 7065 * {@link guide/directive} for more info. 7066 * @returns {ng.$compileProvider} Self for chaining. 7067 */ 7068 this.directive = function registerDirective(name, directiveFactory) { 7069 assertNotHasOwnProperty(name, 'directive'); 7070 if (isString(name)) { 7071 assertValidDirectiveName(name); 7072 assertArg(directiveFactory, 'directiveFactory'); 7073 if (!hasDirectives.hasOwnProperty(name)) { 7074 hasDirectives[name] = []; 7075 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 7076 function($injector, $exceptionHandler) { 7077 var directives = []; 7078 forEach(hasDirectives[name], function(directiveFactory, index) { 7079 try { 7080 var directive = $injector.invoke(directiveFactory); 7081 if (isFunction(directive)) { 7082 directive = { compile: valueFn(directive) }; 7083 } else if (!directive.compile && directive.link) { 7084 directive.compile = valueFn(directive.link); 7085 } 7086 directive.priority = directive.priority || 0; 7087 directive.index = index; 7088 directive.name = directive.name || name; 7089 directive.require = directive.require || (directive.controller && directive.name); 7090 directive.restrict = directive.restrict || 'EA'; 7091 var bindings = directive.$$bindings = 7092 parseDirectiveBindings(directive, directive.name); 7093 if (isObject(bindings.isolateScope)) { 7094 directive.$$isolateBindings = bindings.isolateScope; 7095 } 7096 directive.$$moduleName = directiveFactory.$$moduleName; 7097 directives.push(directive); 7098 } catch (e) { 7099 $exceptionHandler(e); 7100 } 7101 }); 7102 return directives; 7103 }]); 7104 } 7105 hasDirectives[name].push(directiveFactory); 7106 } else { 7107 forEach(name, reverseParams(registerDirective)); 7108 } 7109 return this; 7110 }; 7111 7112 7113 /** 7114 * @ngdoc method 7115 * @name $compileProvider#aHrefSanitizationWhitelist 7116 * @kind function 7117 * 7118 * @description 7119 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 7120 * urls during a[href] sanitization. 7121 * 7122 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 7123 * 7124 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 7125 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 7126 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 7127 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 7128 * 7129 * @param {RegExp=} regexp New regexp to whitelist urls with. 7130 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 7131 * chaining otherwise. 7132 */ 7133 this.aHrefSanitizationWhitelist = function(regexp) { 7134 if (isDefined(regexp)) { 7135 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 7136 return this; 7137 } else { 7138 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 7139 } 7140 }; 7141 7142 7143 /** 7144 * @ngdoc method 7145 * @name $compileProvider#imgSrcSanitizationWhitelist 7146 * @kind function 7147 * 7148 * @description 7149 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 7150 * urls during img[src] sanitization. 7151 * 7152 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 7153 * 7154 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 7155 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 7156 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 7157 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 7158 * 7159 * @param {RegExp=} regexp New regexp to whitelist urls with. 7160 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 7161 * chaining otherwise. 7162 */ 7163 this.imgSrcSanitizationWhitelist = function(regexp) { 7164 if (isDefined(regexp)) { 7165 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 7166 return this; 7167 } else { 7168 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 7169 } 7170 }; 7171 7172 /** 7173 * @ngdoc method 7174 * @name $compileProvider#debugInfoEnabled 7175 * 7176 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 7177 * current debugInfoEnabled state 7178 * @returns {*} current value if used as getter or itself (chaining) if used as setter 7179 * 7180 * @kind function 7181 * 7182 * @description 7183 * Call this method to enable/disable various debug runtime information in the compiler such as adding 7184 * binding information and a reference to the current scope on to DOM elements. 7185 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 7186 * * `ng-binding` CSS class 7187 * * `$binding` data property containing an array of the binding expressions 7188 * 7189 * You may want to disable this in production for a significant performance boost. See 7190 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 7191 * 7192 * The default value is true. 7193 */ 7194 var debugInfoEnabled = true; 7195 this.debugInfoEnabled = function(enabled) { 7196 if (isDefined(enabled)) { 7197 debugInfoEnabled = enabled; 7198 return this; 7199 } 7200 return debugInfoEnabled; 7201 }; 7202 7203 this.$get = [ 7204 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 7205 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 7206 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 7207 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 7208 7209 var Attributes = function(element, attributesToCopy) { 7210 if (attributesToCopy) { 7211 var keys = Object.keys(attributesToCopy); 7212 var i, l, key; 7213 7214 for (i = 0, l = keys.length; i < l; i++) { 7215 key = keys[i]; 7216 this[key] = attributesToCopy[key]; 7217 } 7218 } else { 7219 this.$attr = {}; 7220 } 7221 7222 this.$$element = element; 7223 }; 7224 7225 Attributes.prototype = { 7226 /** 7227 * @ngdoc method 7228 * @name $compile.directive.Attributes#$normalize 7229 * @kind function 7230 * 7231 * @description 7232 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 7233 * `data-`) to its normalized, camelCase form. 7234 * 7235 * Also there is special case for Moz prefix starting with upper case letter. 7236 * 7237 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 7238 * 7239 * @param {string} name Name to normalize 7240 */ 7241 $normalize: directiveNormalize, 7242 7243 7244 /** 7245 * @ngdoc method 7246 * @name $compile.directive.Attributes#$addClass 7247 * @kind function 7248 *
7249 * @description 7250 * Adds the CSS class value specified by the classVal parameter to the element. If animations 7251 * are enabled then an animation will be triggered for the class addition. 7252 * 7253 * @param {string} classVal The className value that will be added to the element 7254 */ 7255 $addClass: function(classVal) { 7256 if (classVal && classVal.length > 0) { 7257 $animate.addClass(this.$$element, classVal); 7258 } 7259 }, 7260 7261 /** 7262 * @ngdoc method 7263 * @name $compile.directive.Attributes#$removeClass 7264 * @kind function 7265 * 7266 * @description 7267 * Removes the CSS class value specified by the classVal parameter from the element. If 7268 * animations are enabled then an animation will be triggered for the class removal. 7269 * 7270 * @param {string} classVal The className value that will be removed from the element 7271 */ 7272 $removeClass: function(classVal) { 7273 if (classVal && classVal.length > 0) { 7274 $animate.removeClass(this.$$element, classVal); 7275 } 7276 }, 7277 7278 /** 7279 * @ngdoc method 7280 * @name $compile.directive.Attributes#$updateClass 7281 * @kind function 7282 * 7283 * @description 7284 * Adds and removes the appropriate CSS class values to the element based on the difference 7285 * between the new and old CSS class values (specified as newClasses and oldClasses). 7286 * 7287 * @param {string} newClasses The current CSS className value 7288 * @param {string} oldClasses The former CSS className value 7289 */ 7290 $updateClass: function(newClasses, oldClasses) { 7291 var toAdd = tokenDifference(newClasses, oldClasses); 7292 if (toAdd && toAdd.length) { 7293 $animate.addClass(this.$$element, toAdd); 7294 } 7295 7296 var toRemove = tokenDifference(oldClasses, newClasses); 7297 if (toRemove && toRemove.length) { 7298 $animate.removeClass(this.$$element, toRemove); 7299 } 7300 }, 7301 7302 /** 7303 * Set a normalized attribute on the element in a way such that all directives 7304 * can share the attribute. This function properly handles boolean attributes. 7305 * @param {string} key Normalized key. (ie ngAttribute) 7306 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 7307 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 7308 * Defaults to true. 7309 * @param {string=} attrName Optional none normalized name. Defaults to key. 7310 */ 7311 $set: function(key, value, writeAttr, attrName) { 7312 // TODO: decide whether or not to throw an error if "class" 7313 //is set through this function since it may cause $updateClass to 7314 //become unstable. 7315 7316 var node = this.$$element[0], 7317 booleanKey = getBooleanAttrName(node, key), 7318 aliasedKey = getAliasedAttrName(key), 7319 observer = key, 7320 nodeName; 7321 7322 if (booleanKey) { 7323 this.$$element.prop(key, value); 7324 attrName = booleanKey; 7325 } else if (aliasedKey) { 7326 this[aliasedKey] = value; 7327 observer = aliasedKey; 7328 } 7329 7330 this[key] = value; 7331 7332 // translate normalized key to actual key 7333 if (attrName) { 7334 this.$attr[key] = attrName; 7335 } else { 7336 attrName = this.$attr[key]; 7337 if (!attrName) { 7338 this.$attr[key] = attrName = snake_case(key, '-'); 7339 } 7340 } 7341 7342 nodeName = nodeName_(this.$$element); 7343 7344 if ((nodeName === 'a' && key === 'href') || 7345 (nodeName === 'img' && key === 'src')) { 7346 // sanitize a[href] and img[src] values 7347 this[key] = value = $$sanitizeUri(value, key === 'src'); 7348 } else if (nodeName === 'img' && key === 'srcset') { 7349 // sanitize img[srcset] values 7350 var result = ""; 7351 7352 // first check if there are spaces because it's not the same pattern 7353 var trimmedSrcset = trim(value); 7354 // ( 999x ,| 999w ,| ,|, ) 7355 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 7356 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 7357 7358 // split srcset into tuple of uri and descriptor except for the last item 7359 var rawUris = trimmedSrcset.split(pattern); 7360 7361 // for each tuples 7362 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 7363 for (var i = 0; i < nbrUrisWith2parts; i++) { 7364 var innerIdx = i * 2; 7365 // sanitize the uri 7366 result += $$sanitizeUri(trim(rawUris[innerIdx]), true); 7367 // add the descriptor 7368 result += (" " + trim(rawUris[innerIdx + 1])); 7369 } 7370 7371 // split the last item into uri and descriptor 7372 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 7373 7374 // sanitize the last uri 7375 result += $$sanitizeUri(trim(lastTuple[0]), true); 7376 7377 // and add the last descriptor if any 7378 if (lastTuple.length === 2) { 7379 result += (" " + trim(lastTuple[1])); 7380 } 7381 this[key] = value = result; 7382 } 7383 7384 if (writeAttr !== false) { 7385 if (value === null || isUndefined(value)) { 7386 this.$$element.removeAttr(attrName); 7387 } else { 7388 this.$$element.attr(attrName, value); 7389 } 7390 } 7391 7392 // fire observers 7393 var $$observers = this.$$observers;
vendor: 11,170 bytes, lines 7394-7657
7394 $$observers && forEach($$observers[observer], function(fn) { 7395 try { 7396 fn(value); 7397 } catch (e) { 7398 $exceptionHandler(e); 7399 } 7400 }); 7401 }, 7402 7403 7404 /** 7405 * @ngdoc method 7406 * @name $compile.directive.Attributes#$observe 7407 * @kind function 7408 * 7409 * @description 7410 * Observes an interpolated attribute. 7411 * 7412 * The observer function will be invoked once during the next `$digest` following 7413 * compilation. The observer is then invoked whenever the interpolated value 7414 * changes. 7415 * 7416 * @param {string} key Normalized key. (ie ngAttribute) . 7417 * @param {function(interpolatedValue)} fn Function that will be called whenever 7418 the interpolated value of the attribute changes. 7419 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info. 7420 * @returns {function()} Returns a deregistration function for this observer. 7421 */ 7422 $observe: function(key, fn) { 7423 var attrs = this, 7424 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 7425 listeners = ($$observers[key] || ($$observers[key] = [])); 7426 7427 listeners.push(fn); 7428 $rootScope.$evalAsync(function() { 7429 if (!listeners.$$inter && attrs.hasOwnProperty(key) && !isUndefined(attrs[key])) { 7430 // no one registered attribute interpolation function, so lets call it manually 7431 fn(attrs[key]); 7432 } 7433 }); 7434 7435 return function() { 7436 arrayRemove(listeners, fn); 7437 }; 7438 } 7439 }; 7440 7441 7442 function safeAddClass($element, className) { 7443 try { 7444 $element.addClass(className); 7445 } catch (e) { 7446 // ignore, since it means that we are trying to set class on 7447 // SVG element, where class name is read-only. 7448 } 7449 } 7450 7451 7452 var startSymbol = $interpolate.startSymbol(), 7453 endSymbol = $interpolate.endSymbol(), 7454 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 7455 ? identity 7456 : function denormalizeTemplate(template) { 7457 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 7458 }, 7459 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 7460 7461 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 7462 var bindings = $element.data('$binding') || []; 7463 7464 if (isArray(binding)) { 7465 bindings = bindings.concat(binding); 7466 } else { 7467 bindings.push(binding); 7468 } 7469 7470 $element.data('$binding', bindings); 7471 } : noop; 7472 7473 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 7474 safeAddClass($element, 'ng-binding'); 7475 } : noop; 7476 7477 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 7478 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; 7479 $element.data(dataName, scope); 7480 } : noop; 7481 7482 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 7483 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 7484 } : noop; 7485 7486 return compile; 7487 7488 // ==== 7489 7490 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 7491 previousCompileContext) { 7492 if (!($compileNodes instanceof jqLite)) { 7493 // jquery always rewraps, whereas we need to preserve the original selector so that we can 7494 // modify it. 7495 $compileNodes = jqLite($compileNodes); 7496 } 7497 // We can not compile top level text elements since text nodes can be merged and we will 7498 // not be able to attach scope data to them, so we will wrap them in <span> 7499 forEach($compileNodes, function(node, index) { 7500 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) { 7501 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0]; 7502 } 7503 }); 7504 var compositeLinkFn = 7505 compileNodes($compileNodes, transcludeFn, $compileNodes, 7506 maxPriority, ignoreDirective, previousCompileContext); 7507 compile.$$addScopeClass($compileNodes); 7508 var namespace = null; 7509 return function publicLinkFn(scope, cloneConnectFn, options) { 7510 assertArg(scope, 'scope'); 7511 7512 options = options || {}; 7513 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 7514 transcludeControllers = options.transcludeControllers, 7515 futureParentElement = options.futureParentElement; 7516 7517 // When `parentBoundTranscludeFn` is passed, it is a 7518 // `controllersBoundTransclude` function (it was previously passed 7519 // as `transclude` to directive.link) so we must unwrap it to get 7520 // its `boundTranscludeFn` 7521 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 7522 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 7523 } 7524 7525 if (!namespace) { 7526 namespace = detectNamespaceForChildElements(futureParentElement); 7527 } 7528 var $linkNode; 7529 if (namespace !== 'html') { 7530 // When using a directive with replace:true and templateUrl the $compileNodes 7531 // (or a child element inside of them) 7532 // might change, so we need to recreate the namespace adapted compileNodes 7533 // for call to the link function. 7534 // Note: This will already clone the nodes... 7535 $linkNode = jqLite( 7536 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) 7537 ); 7538 } else if (cloneConnectFn) { 7539 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 7540 // and sometimes changes the structure of the DOM. 7541 $linkNode = JQLitePrototype.clone.call($compileNodes); 7542 } else { 7543 $linkNode = $compileNodes; 7544 } 7545 7546 if (transcludeControllers) { 7547 for (var controllerName in transcludeControllers) { 7548 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 7549 } 7550 } 7551 7552 compile.$$addScopeInfo($linkNode, scope); 7553 7554 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 7555 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 7556 return $linkNode; 7557 }; 7558 } 7559 7560 function detectNamespaceForChildElements(parentElement) { 7561 // TODO: Make this detect MathML as well... 7562 var node = parentElement && parentElement[0]; 7563 if (!node) { 7564 return 'html'; 7565 } else { 7566 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html'; 7567 } 7568 } 7569 7570 /** 7571 * Compile function matches each node in nodeList against the directives. Once all directives 7572 * for a particular node are collected their compile functions are executed. The compile 7573 * functions return values - the linking functions - are combined into a composite linking 7574 * function, which is the a linking function for the node. 7575 * 7576 * @param {NodeList} nodeList an array of nodes or NodeList to compile 7577 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 7578 * scope argument is auto-generated to the new child of the transcluded parent scope. 7579 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 7580 * the rootElement must be set the jqLite collection of the compile root. This is 7581 * needed so that the jqLite collection items can be replaced with widgets. 7582 * @param {number=} maxPriority Max directive priority. 7583 * @returns {Function} A composite linking function of all of the matched directives or null. 7584 */ 7585 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 7586 previousCompileContext) { 7587 var linkFns = [], 7588 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 7589 7590 for (var i = 0; i < nodeList.length; i++) { 7591 attrs = new Attributes(); 7592 7593 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 7594 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 7595 ignoreDirective); 7596 7597 nodeLinkFn = (directives.length) 7598 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 7599 null, [], [], previousCompileContext) 7600 : null; 7601 7602 if (nodeLinkFn && nodeLinkFn.scope) { 7603 compile.$$addScopeClass(attrs.$$element); 7604 } 7605 7606 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 7607 !(childNodes = nodeList[i].childNodes) || 7608 !childNodes.length) 7609 ? null 7610 : compileNodes(childNodes, 7611 nodeLinkFn ? ( 7612 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 7613 && nodeLinkFn.transclude) : transcludeFn); 7614 7615 if (nodeLinkFn || childLinkFn) { 7616 linkFns.push(i, nodeLinkFn, childLinkFn); 7617 linkFnFound = true; 7618 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 7619 } 7620 7621 //use the previous context only for the first element in the virtual group 7622 previousCompileContext = null; 7623 } 7624 7625 // return a linking function if we have found anything, null otherwise 7626 return linkFnFound ? compositeLinkFn : null; 7627 7628 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 7629 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 7630 var stableNodeList; 7631 7632 7633 if (nodeLinkFnFound) { 7634 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 7635 // offsets don't get screwed up 7636 var nodeListLength = nodeList.length; 7637 stableNodeList = new Array(nodeListLength); 7638 7639 // create a sparse array by only copying the elements which have a linkFn 7640 for (i = 0; i < linkFns.length; i+=3) { 7641 idx = linkFns[i]; 7642 stableNodeList[idx] = nodeList[idx]; 7643 } 7644 } else { 7645 stableNodeList = nodeList; 7646 } 7647 7648 for (i = 0, ii = linkFns.length; i < ii;) { 7649 node = stableNodeList[linkFns[i++]]; 7650 nodeLinkFn = linkFns[i++]; 7651 childLinkFn = linkFns[i++]; 7652 7653 if (nodeLinkFn) { 7654 if (nodeLinkFn.scope) { 7655 childScope = scope.$new(); 7656 compile.$$addScopeInfo(jqLite(node), childScope); 7657 var destroyBindings = nodeLinkFn.$$destroyBindings;
7658 if (destroyBindings) { 7659 nodeLinkFn.$$destroyBindings = null; 7660 childScope.$on('$destroyed', destroyBindings); 7661 } 7662 } else { 7663 childScope = scope; 7664 } 7665 7666 if (nodeLinkFn.transcludeOnThisElement) { 7667 childBoundTranscludeFn = createBoundTranscludeFn( 7668 scope, nodeLinkFn.transclude, parentBoundTranscludeFn); 7669 7670 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 7671 childBoundTranscludeFn = parentBoundTranscludeFn; 7672 7673 } else if (!parentBoundTranscludeFn && transcludeFn) { 7674 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 7675 7676 } else { 7677 childBoundTranscludeFn = null; 7678 } 7679 7680 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn, 7681 nodeLinkFn); 7682 7683 } else if (childLinkFn) { 7684 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 7685 } 7686 } 7687 } 7688 } 7689 7690 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 7691 7692 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 7693 7694 if (!transcludedScope) { 7695 transcludedScope = scope.$new(false, containingScope); 7696 transcludedScope.$$transcluded = true; 7697 } 7698 7699 return transcludeFn(transcludedScope, cloneFn, { 7700 parentBoundTranscludeFn: previousBoundTranscludeFn, 7701 transcludeControllers: controllers, 7702 futureParentElement: futureParentElement 7703 }); 7704 }; 7705 7706 return boundTranscludeFn; 7707 } 7708 7709 /** 7710 * Looks for directives on the given node and adds them to the directive collection which is 7711 * sorted. 7712 * 7713 * @param node Node to search. 7714 * @param directives An array to which the directives are added to. This array is sorted before 7715 * the function returns. 7716 * @param attrs The shared attrs object which is used to populate the normalized attributes. 7717 * @param {number=} maxPriority Max directive priority. 7718 */ 7719 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 7720 var nodeType = node.nodeType, 7721 attrsMap = attrs.$attr, 7722 match, 7723 className; 7724 7725 switch (nodeType) { 7726 case NODE_TYPE_ELEMENT: /* Element */ 7727 // use the node name: <directive> 7728 addDirective(directives, 7729 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); 7730 7731 // iterate over the attributes 7732 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 7733 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 7734 var attrStartName = false; 7735 var attrEndName = false; 7736 7737 attr = nAttrs[j]; 7738 name = attr.name; 7739 value = trim(attr.value); 7740 7741 // support ngAttr attribute binding 7742 ngAttrName = directiveNormalize(name); 7743 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 7744 name = name.replace(PREFIX_REGEXP, '') 7745 .substr(8).replace(/_(.)/g, function(match, letter) { 7746 return letter.toUpperCase(); 7747 }); 7748 } 7749 7750 var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); 7751 if (directiveIsMultiElement(directiveNName)) { 7752 if (ngAttrName === directiveNName + 'Start') { 7753 attrStartName = name; 7754 attrEndName = name.substr(0, name.length - 5) + 'end'; 7755 name = name.substr(0, name.length - 6); 7756 } 7757 } 7758 7759 nName = directiveNormalize(name.toLowerCase()); 7760 attrsMap[nName] = name; 7761 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 7762 attrs[nName] = value; 7763 if (getBooleanAttrName(node, nName)) { 7764 attrs[nName] = true; // presence means true 7765 } 7766 } 7767 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 7768 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 7769 attrEndName); 7770 } 7771 7772 // use class as directive
7773 className = node.className; 7774 if (isObject(className)) { 7775 // Maybe SVGAnimatedString 7776 className = className.animVal; 7777 } 7778 if (isString(className) && className !== '') { 7779 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 7780 nName = directiveNormalize(match[2]); 7781 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 7782 attrs[nName] = trim(match[3]); 7783 } 7784 className = className.substr(match.index + match[0].length); 7785 } 7786 } 7787 break; 7788 case NODE_TYPE_TEXT: /* Text Node */ 7789 if (msie === 11) { 7790 // Workaround for #11781 7791 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { 7792 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; 7793 node.parentNode.removeChild(node.nextSibling); 7794 } 7795 } 7796 addTextInterpolateDirective(directives, node.nodeValue); 7797 break; 7798 case NODE_TYPE_COMMENT: /* Comment */ 7799 try { 7800 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 7801 if (match) { 7802 nName = directiveNormalize(match[1]); 7803 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 7804 attrs[nName] = trim(match[2]); 7805 } 7806 } 7807 } catch (e) { 7808 // turns out that under some circumstances IE9 throws errors when one attempts to read 7809 // comment's node value. 7810 // Just ignore it and continue. (Can't seem to reproduce in test case.) 7811 } 7812 break; 7813 } 7814 7815 directives.sort(byPriority); 7816 return directives; 7817 } 7818 7819 /** 7820 * Given a node with an directive-start it collects all of the siblings until it finds 7821 * directive-end. 7822 * @param node 7823 * @param attrStart 7824 * @param attrEnd 7825 * @returns {*} 7826 */ 7827 function groupScan(node, attrStart, attrEnd) { 7828 var nodes = []; 7829 var depth = 0; 7830 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 7831 do { 7832 if (!node) { 7833 throw $compileMinErr('uterdir', 7834 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 7835 attrStart, attrEnd); 7836 } 7837 if (node.nodeType == NODE_TYPE_ELEMENT) { 7838 if (node.hasAttribute(attrStart)) depth++; 7839 if (node.hasAttribute(attrEnd)) depth--; 7840 } 7841 nodes.push(node); 7842 node = node.nextSibling; 7843 } while (depth > 0); 7844 } else { 7845 nodes.push(node); 7846 } 7847 7848 return jqLite(nodes); 7849 } 7850 7851 /** 7852 * Wrapper for linking function which converts normal linking function into a grouped 7853 * linking function. 7854 * @param linkFn 7855 * @param attrStart 7856 * @param attrEnd 7857 * @returns {Function} 7858 */ 7859 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 7860 return function(scope, element, attrs, controllers, transcludeFn) { 7861 element = groupScan(element[0], attrStart, attrEnd); 7862 return linkFn(scope, element, attrs, controllers, transcludeFn); 7863 }; 7864 } 7865 7866 /** 7867 * Once the directives have been collected, their compile functions are executed. This method 7868 * is responsible for inlining directive templates as well as terminating the application 7869 * of the directives if the terminal directive has been reached. 7870 * 7871 * @param {Array} directives Array of collected directives to execute their compile function. 7872 * this needs to be pre-sorted by priority order. 7873 * @param {Node} compileNode The raw DOM node to apply the compile functions to 7874 * @param {Object} templateAttrs The shared attribute function 7875 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 7876 * scope argument is auto-generated to the new 7877 * child of the transcluded parent scope. 7878 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 7879 * argument has the root jqLite array so that we can replace nodes 7880 * on it. 7881 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 7882 * compiling the transclusion. 7883 * @param {Array.<Function>} preLinkFns 7884 * @param {Array.<Function>} postLinkFns 7885 * @param {Object} previousCompileContext Context used for previous compilation of the current 7886 * node 7887 * @returns {Function} linkFn 7888 */ 7889 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 7890 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 7891 previousCompileContext) { 7892 previousCompileContext = previousCompileContext || {}; 7893 7894 var terminalPriority = -Number.MAX_VALUE, 7895 newScopeDirective = previousCompileContext.newScopeDirective, 7896 controllerDirectives = previousCompileContext.controllerDirectives, 7897 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 7898 templateDirective = previousCompileContext.templateDirective, 7899 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 7900 hasTranscludeDirective = false, 7901 hasTemplate = false, 7902 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 7903 $compileNode = templateAttrs.$$element = jqLite(compileNode), 7904 directive, 7905 directiveName, 7906 $template, 7907 replaceDirective = originalReplaceDirective, 7908 childTranscludeFn = transcludeFn, 7909 linkFn, 7910 directiveValue; 7911 7912 // executes all directives on the current element 7913 for (var i = 0, ii = directives.length; i < ii; i++) { 7914 directive = directives[i]; 7915 var attrStart = directive.$$start; 7916 var attrEnd = directive.$$end; 7917 7918 // collect multiblock sections 7919 if (attrStart) { 7920 $compileNode = groupScan(compileNode, attrStart, attrEnd); 7921 } 7922 $template = undefined; 7923 7924 if (terminalPriority > directive.priority) { 7925 break; // prevent further processing of directives 7926 } 7927 7928 if (directiveValue = directive.scope) { 7929 7930 // skip the check for directives with async templates, we'll check the derived sync 7931 // directive when the template arrives 7932 if (!directive.templateUrl) { 7933 if (isObject(directiveValue)) { 7934 // This directive is trying to add an isolated scope. 7935 // Check that there is no scope of any kind already 7936 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 7937 directive, $compileNode); 7938 newIsolateScopeDirective = directive; 7939 } else { 7940 // This directive is trying to add a child scope. 7941 // Check that there is no isolated scope already 7942 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 7943 $compileNode); 7944 } 7945 } 7946 7947 newScopeDirective = newScopeDirective || directive; 7948 } 7949 7950 directiveName = directive.name; 7951 7952 if (!directive.templateUrl && directive.controller) { 7953 directiveValue = directive.controller; 7954 controllerDirectives = controllerDirectives || createMap();
7955 assertNoDuplicate("'" + directiveName + "' controller", 7956 controllerDirectives[directiveName], directive, $compileNode); 7957 controllerDirectives[directiveName] = directive; 7958 } 7959 7960 if (directiveValue = directive.transclude) { 7961 hasTranscludeDirective = true; 7962 7963 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 7964 // This option should only be used by directives that know how to safely handle element transclusion, 7965 // where the transcluded nodes are added or replaced after linking. 7966 if (!directive.$$tlb) { 7967 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 7968 nonTlbTranscludeDirective = directive; 7969 } 7970 7971 if (directiveValue == 'element') { 7972 hasElementTranscludeDirective = true; 7973 terminalPriority = directive.priority; 7974 $template = $compileNode; 7975 $compileNode = templateAttrs.$$element = 7976 jqLite(document.createComment(' ' + directiveName + ': ' + 7977 templateAttrs[directiveName] + ' ')); 7978 compileNode = $compileNode[0]; 7979 replaceWith(jqCollection, sliceArgs($template), compileNode); 7980 7981 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 7982 replaceDirective && replaceDirective.name, { 7983 // Don't pass in: 7984 // - controllerDirectives - otherwise we'll create duplicates controllers 7985 // - newIsolateScopeDirective or templateDirective - comb
vendor: 10,483 bytes, lines 7985-8225
7985ining templates with 7986 // element transclusion doesn't make sense. 7987 // 7988 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 7989 // on the same element more than once. 7990 nonTlbTranscludeDirective: nonTlbTranscludeDirective 7991 }); 7992 } else { 7993 $template = jqLite(jqLiteClone(compileNode)).contents(); 7994 $compileNode.empty(); // clear contents 7995 childTranscludeFn = compile($template, transcludeFn); 7996 } 7997 } 7998 7999 if (directive.template) { 8000 hasTemplate = true; 8001 assertNoDuplicate('template', templateDirective, directive, $compileNode); 8002 templateDirective = directive; 8003 8004 directiveValue = (isFunction(directive.template)) 8005 ? directive.template($compileNode, templateAttrs) 8006 : directive.template; 8007 8008 directiveValue = denormalizeTemplate(directiveValue); 8009 8010 if (directive.replace) { 8011 replaceDirective = directive; 8012 if (jqLiteIsTextNode(directiveValue)) { 8013 $template = []; 8014 } else { 8015 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 8016 } 8017 compileNode = $template[0]; 8018 8019 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 8020 throw $compileMinErr('tplrt', 8021 "Template for directive '{0}' must have exactly one root element. {1}", 8022 directiveName, ''); 8023 } 8024 8025 replaceWith(jqCollection, $compileNode, compileNode); 8026 8027 var newTemplateAttrs = {$attr: {}}; 8028 8029 // combine directives from the original node and from the template: 8030 // - take the array of directives for this element 8031 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 8032 // - collect directives from the template and sort them by priority 8033 // - combine directives as: processed + template + unprocessed 8034 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 8035 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 8036 8037 if (newIsolateScopeDirective) { 8038 markDirectivesAsIsolate(templateDirectives); 8039 } 8040 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 8041 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 8042 8043 ii = directives.length; 8044 } else { 8045 $compileNode.html(directiveValue); 8046 } 8047 } 8048 8049 if (directive.templateUrl) { 8050 hasTemplate = true; 8051 assertNoDuplicate('template', templateDirective, directive, $compileNode); 8052 templateDirective = directive; 8053 8054 if (directive.replace) { 8055 replaceDirective = directive; 8056 } 8057 8058 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 8059 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 8060 controllerDirectives: controllerDirectives, 8061 newScopeDirective: (newScopeDirective !== directive) && newScopeDirective, 8062 newIsolateScopeDirective: newIsolateScopeDirective, 8063 templateDirective: templateDirective, 8064 nonTlbTranscludeDirective: nonTlbTranscludeDirective 8065 }); 8066 ii = directives.length; 8067 } else if (directive.compile) { 8068 try { 8069 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 8070 if (isFunction(linkFn)) { 8071 addLinkFns(null, linkFn, attrStart, attrEnd); 8072 } else if (linkFn) { 8073 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 8074 } 8075 } catch (e) { 8076 $exceptionHandler(e, startingTag($compileNode)); 8077 } 8078 } 8079 8080 if (directive.terminal) { 8081 nodeLinkFn.terminal = true; 8082 terminalPriority = Math.max(terminalPriority, directive.priority); 8083 } 8084 8085 } 8086 8087 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 8088 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 8089 nodeLinkFn.templateOnThisElement = hasTemplate; 8090 nodeLinkFn.transclude = childTranscludeFn; 8091 8092 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 8093 8094 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 8095 return nodeLinkFn; 8096 8097 //////////////////// 8098 8099 function addLinkFns(pre, post, attrStart, attrEnd) { 8100 if (pre) { 8101 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 8102 pre.require = directive.require; 8103 pre.directiveName = directiveName; 8104 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 8105 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 8106 } 8107 preLinkFns.push(pre); 8108 } 8109 if (post) { 8110 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 8111 post.require = directive.require; 8112 post.directiveName = directiveName; 8113 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 8114 post = cloneAndAnnotateFn(post, {isolateScope: true}); 8115 } 8116 postLinkFns.push(post); 8117 } 8118 } 8119 8120 8121 function getControllers(directiveName, require, $element, elementControllers) { 8122 var value; 8123 8124 if (isString(require)) { 8125 var match = require.match(REQUIRE_PREFIX_REGEXP); 8126 var name = require.substring(match[0].length); 8127 var inheritType = match[1] || match[3]; 8128 var optional = match[2] === '?'; 8129 8130 //If only parents then start at the parent element 8131 if (inheritType === '^^') { 8132 $element = $element.parent(); 8133 //Otherwise attempt getting the controller from elementControllers in case 8134 //the element is transcluded (and has no data) and to avoid .data if possible 8135 } else { 8136 value = elementControllers && elementControllers[name]; 8137 value = value && value.instance; 8138 } 8139 8140 if (!value) { 8141 var dataName = '$' + name + 'Controller'; 8142 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); 8143 } 8144 8145 if (!value && !optional) { 8146 throw $compileMinErr('ctreq', 8147 "Controller '{0}', required by directive '{1}', can't be found!", 8148 name, directiveName); 8149 } 8150 } else if (isArray(require)) { 8151 value = []; 8152 for (var i = 0, ii = require.length; i < ii; i++) { 8153 value[i] = getControllers(directiveName, require[i], $element, elementControllers); 8154 } 8155 } 8156 8157 return value || null; 8158 } 8159 8160 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) { 8161 var elementControllers = createMap(); 8162 for (var controllerKey in controllerDirectives) { 8163 var directive = controllerDirectives[controllerKey]; 8164 var locals = { 8165 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 8166 $element: $element, 8167 $attrs: attrs, 8168 $transclude: transcludeFn 8169 }; 8170 8171 var controller = directive.controller; 8172 if (controller == '@') { 8173 controller = attrs[directive.name]; 8174 } 8175 8176 var controllerInstance = $controller(controller, locals, true, directive.controllerAs); 8177 8178 // For directives with element transclusion the element is a comment, 8179 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 8180 // clean up (http://bugs.jquery.com/ticket/8335). 8181 // Instead, we save the controllers for the element in a local hash and attach to .data 8182 // later, once we have the actual element. 8183 elementControllers[directive.name] = controllerInstance; 8184 if (!hasElementTranscludeDirective) { 8185 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 8186 } 8187 } 8188 return elementControllers; 8189 } 8190 8191 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn, 8192 thisLinkFn) { 8193 var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element, 8194 attrs; 8195 8196 if (compileNode === linkNode) { 8197 attrs = templateAttrs; 8198 $element = templateAttrs.$$element; 8199 } else { 8200 $element = jqLite(linkNode); 8201 attrs = new Attributes($element, templateAttrs); 8202 } 8203 8204 if (newIsolateScopeDirective) { 8205 isolateScope = scope.$new(true); 8206 } 8207 8208 if (boundTranscludeFn) { 8209 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 8210 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 8211 transcludeFn = controllersBoundTransclude; 8212 transcludeFn.$$boundTransclude = boundTranscludeFn; 8213 } 8214 8215 if (controllerDirectives) { 8216 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope); 8217 } 8218 8219 if (newIsolateScopeDirective) { 8220 // Initialize isolate scope bindings for new isolate scope directive. 8221 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 8222 templateDirective === newIsolateScopeDirective.$$originalDirective))); 8223 compile.$$addScopeClass($element, true); 8224 isolateScope.$$isolateBindings = 8225 newIsolateScopeDirective.$$isolateBindings;
8226 initializeDirectiveBindings(scope, attrs, isolateScope, 8227 isolateScope.$$isolateBindings, 8228 newIsolateScopeDirective, isolateScope); 8229 } 8230 if (elementControllers) { 8231 // Initialize bindToController bindings for new/isolate scopes 8232 var scopeDirective = newIsolateScopeDirective || newScopeDirective; 8233 var bindings; 8234 var controllerForBindings; 8235 if (scopeDirective && elementControllers[scopeDirective.name]) { 8236 bindings = scopeDirective.$$bindings.bindToController; 8237 controller = elementControllers[scopeDirective.name]; 8238 8239 if (controller && controller.identifier && bindings) { 8240 controllerForBindings = controller; 8241 thisLinkFn.$$destroyBindings = 8242 initializeDirectiveBindings(scope, attrs, controller.instance, 8243 bindings, scopeDirective); 8244 } 8245 } 8246 for (i in elementControllers) { 8247 controller = elementControllers[i]; 8248 var controllerResult = controller(); 8249 8250 if (controllerResult !== controller.instance) { 8251 // If the controller constructor has a return value, overwrite the instance 8252 // from setupControllers and update the element data 8253 controller.instance = controllerResult; 8254 $element.data('$' + i + 'Controller', controllerResult); 8255 if (controller === controllerForBindings) { 8256 // Remove and re-install bindToController bindings 8257 thisLinkFn.$$destroyBindings(); 8258 thisLinkFn.$$destroyBindings = 8259 initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective); 8260 } 8261 } 8262 } 8263 } 8264 8265 // PRELINKING 8266 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 8267 linkFn = preLinkFns[i]; 8268 invokeLinkFn(linkFn, 8269 linkFn.isolateScope ? isolateScope : scope, 8270 $element, 8271 attrs, 8272 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 8273 transcludeFn 8274 ); 8275 } 8276 8277 // RECURSION 8278 // We only pass the isolate scope, if the isolate directive has a template, 8279 // otherwise the child elements do not belong to the isolate directive. 8280 var scopeToChild = scope; 8281 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 8282 scopeToChild = isolateScope; 8283 } 8284 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 8285 8286 // POSTLINKING 8287 for (i = postLinkFns.length - 1; i >= 0; i--) { 8288 linkFn = postLinkFns[i]; 8289 invokeLinkFn(linkFn, 8290 linkFn.isolateScope ? isolateScope : scope, 8291 $element, 8292 attrs, 8293 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 8294 transcludeFn 8295 ); 8296 } 8297 8298 // This is the function that is injected as `$transclude`. 8299 // Note: all arguments are optional! 8300 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) { 8301 var transcludeControllers; 8302 8303 // No scope passed in: 8304 if (!isScope(scope)) { 8305 futureParentElement = cloneAttachFn; 8306 cloneAttachFn = scope; 8307 scope = undefined; 8308 } 8309 8310 if (hasElementTranscludeDirective) { 8311 transcludeControllers = elementControllers; 8312 } 8313 if (!futureParentElement) { 8314 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 8315 } 8316 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 8317 } 8318 } 8319 } 8320 8321 function markDirectivesAsIsolate(directives) { 8322 // mark all directives as needing isolate scope. 8323 for (var j = 0, jj = directives.length; j < jj; j++) { 8324 directives[j] = inherit(directives[j], {$$isolateScope: true}); 8325 } 8326 } 8327 8328 /** 8329 * looks up the directive and decorates it with exception handling and proper parameters. We 8330 * call this the boundDirective. 8331 * 8332 * @param {string} name name of the directive to look up. 8333 * @param {string} location The directive must be found in specific format. 8334 * String containing any of theses characters: 8335 * 8336 * * `E`: element name 8337 * * `A': attribute 8338 * * `C`: class 8339 * * `M`: comment 8340 * @returns {boolean} true if directive was added. 8341 */ 8342 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 8343 endAttrName) { 8344 if (name === ignoreDirective) return null; 8345 var match = null; 8346 if (hasDirectives.hasOwnProperty(name)) { 8347 for (var directive, directives = $injector.get(name + Suffix), 8348 i = 0, ii = directives.length; i < ii; i++) { 8349 try { 8350 directive = directives[i]; 8351 if ((isUndefined(maxPriority) || maxPriority > directive.priority) && 8352 directive.restrict.indexOf(location) != -1) { 8353 if (startAttrName) { 8354 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 8355 } 8356 tDirectives.push(directive); 8357 match = directive; 8358 } 8359 } catch (e) { $exceptionHandler(e); } 8360 } 8361 } 8362 return match; 8363 } 8364 8365 8366 /** 8367 * looks up the directive and returns true if it is a multi-element directive, 8368 * and therefore requires DOM nodes between -start and -end markers to be grouped 8369 * together. 8370 * 8371 * @param {string} name name of the directive to look up. 8372 * @returns true if directive was registered as multi-element. 8373 */ 8374 function directiveIsMultiElement(name) { 8375 if (hasDirectives.hasOwnProperty(name)) { 8376 for (var directive, directives = $injector.get(name + Suffix), 8377 i = 0, ii = directives.length; i < ii; i++) { 8378 directive = directives[i]; 8379 if (directive.multiElement) { 8380 return true; 8381 } 8382 } 8383 } 8384 return false; 8385 } 8386 8387 /** 8388 * When the element is replaced with HTML template then the new attributes 8389 * on the template need to be merged with the existing attributes in the DOM. 8390 * The desired effect is to have both of the attributes present. 8391 * 8392 * @param {object} dst destination attributes (original DOM) 8393 * @param {object} src source attributes (from the directive template) 8394 */ 8395 function mergeTemplateAttributes(dst, src) { 8396 var srcAttr = src.$attr, 8397 dstAttr = dst.$attr, 8398 $element = dst.$$element; 8399 8400 // reapply the old attributes to the new element
8401 forEach(dst, function(value, key) { 8402 if (key.charAt(0) != '$') { 8403 if (src[key] && src[key] !== value) { 8404 value += (key === 'style' ? ';' : ' ') + src[key]; 8405 } 8406 dst.$set(key, value, true, srcAttr[key]); 8407 } 8408 }); 8409 8410 // copy the new attributes on the old attrs object 8411 forEach(src, function(value, key) { 8412 if (key == 'class') { 8413 safeAddClass($element, value); 8414 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 8415 } else if (key == 'style') { 8416 $element.attr('style', $element.attr('style') + ';' + value); 8417 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 8418 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 8419 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 8420 // have an attribute like "has-own-property" or "data-has-own-property", etc. 8421 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 8422 dst[key] = value; 8423 dstAttr[key] = srcAttr[key]; 8424 } 8425 }); 8426 } 8427 8428 8429 function compileTemplateUrl(directives, $compileNode, tAttrs, 8430 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 8431 var linkQueue = [], 8432 afterTemplateNodeLinkFn, 8433 afterTemplateChildLinkFn, 8434 beforeTemplateCompileNode = $compileNode[0], 8435 origAsyncDirective = directives.shift(), 8436 derivedSyncDirective = inherit(origAsyncDirective, { 8437 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 8438 }), 8439 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 8440 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 8441 : origAsyncDirective.templateUrl, 8442 templateNamespace = origAsyncDirective.templateNamespace; 8443 8444 $compileNode.empty(); 8445 8446 $templateRequest(templateUrl) 8447 .then(function(content) { 8448 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 8449 8450 content = denormalizeTemplate(content); 8451 8452 if (origAsyncDirective.replace) { 8453 if (jqLiteIsTextNode(content)) { 8454 $template = []; 8455 } else { 8456 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 8457 } 8458 compileNode = $template[0]; 8459 8460 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 8461 throw $compileMinErr('tplrt', 8462 "Template for directive '{0}' must have exactly one root element. {1}", 8463 origAsyncDirective.name, templateUrl); 8464 } 8465 8466 tempTemplateAttrs = {$attr: {}}; 8467 replaceWith($rootElement, $compileNode, compileNode); 8468 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 8469 8470 if (isObject(origAsyncDirective.scope)) { 8471 markDirectivesAsIsolate(templateDirectives); 8472 } 8473 directives = templateDirectives.concat(directives); 8474 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 8475 } else { 8476 compileNode = beforeTemplateCompileNode; 8477 $compileNode.html(content); 8478 } 8479 8480 directives.unshift(derivedSyncDirective); 8481 8482 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 8483 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 8484 previousCompileContext); 8485 forEach($rootElement, function(node, i) { 8486 if (node == compileNode) { 8487 $rootElement[i] = $compileNode[0]; 8488 } 8489 }); 8490 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 8491 8492 while (linkQueue.length) { 8493 var scope = linkQueue.shift(), 8494 beforeTemplateLinkNode = linkQueue.shift(), 8495 linkRootElement = linkQueue.shift(), 8496 boundTranscludeFn = linkQueue.shift(), 8497 linkNode = $compileNode[0]; 8498 8499 if (scope.$$destroyed) continue; 8500 8501 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
vendor: 5,406 bytes, lines 8502-8639
8502 var oldClasses = beforeTemplateLinkNode.className; 8503 8504 if (!(previousCompileContext.hasElementTranscludeDirective && 8505 origAsyncDirective.replace)) { 8506 // it was cloned therefore we have to clone as well. 8507 linkNode = jqLiteClone(compileNode); 8508 } 8509 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 8510 8511 // Copy in CSS classes from original node 8512 safeAddClass(jqLite(linkNode), oldClasses); 8513 } 8514 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 8515 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 8516 } else { 8517 childBoundTranscludeFn = boundTranscludeFn; 8518 } 8519 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 8520 childBoundTranscludeFn, afterTemplateNodeLinkFn); 8521 } 8522 linkQueue = null; 8523 }); 8524 8525 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 8526 var childBoundTranscludeFn = boundTranscludeFn; 8527 if (scope.$$destroyed) return; 8528 if (linkQueue) { 8529 linkQueue.push(scope, 8530 node, 8531 rootElement, 8532 childBoundTranscludeFn); 8533 } else { 8534 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 8535 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 8536 } 8537 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn, 8538 afterTemplateNodeLinkFn); 8539 } 8540 }; 8541 } 8542 8543 8544 /** 8545 * Sorting function for bound directives. 8546 */ 8547 function byPriority(a, b) { 8548 var diff = b.priority - a.priority; 8549 if (diff !== 0) return diff; 8550 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 8551 return a.index - b.index; 8552 } 8553 8554 function assertNoDuplicate(what, previousDirective, directive, element) { 8555 8556 function wrapModuleNameIfDefined(moduleName) { 8557 return moduleName ? 8558 (' (module: ' + moduleName + ')') : 8559 ''; 8560 } 8561 8562 if (previousDirective) { 8563 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', 8564 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), 8565 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); 8566 } 8567 } 8568 8569 8570 function addTextInterpolateDirective(directives, text) { 8571 var interpolateFn = $interpolate(text, true); 8572 if (interpolateFn) { 8573 directives.push({ 8574 priority: 0, 8575 compile: function textInterpolateCompileFn(templateNode) { 8576 var templateNodeParent = templateNode.parent(), 8577 hasCompileParent = !!templateNodeParent.length; 8578 8579 // When transcluding a template that has bindings in the root 8580 // we don't have a parent and thus need to add the class during linking fn. 8581 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 8582 8583 return function textInterpolateLinkFn(scope, node) { 8584 var parent = node.parent(); 8585 if (!hasCompileParent) compile.$$addBindingClass(parent); 8586 compile.$$addBindingInfo(parent, interpolateFn.expressions); 8587 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 8588 node[0].nodeValue = value; 8589 }); 8590 }; 8591 } 8592 }); 8593 } 8594 } 8595 8596 8597 function wrapTemplate(type, template) { 8598 type = lowercase(type || 'html'); 8599 switch (type) { 8600 case 'svg': 8601 case 'math': 8602 var wrapper = document.createElement('div'); 8603 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 8604 return wrapper.childNodes[0].childNodes; 8605 default: 8606 return template; 8607 } 8608 } 8609 8610 8611 function getTrustedContext(node, attrNormalizedName) { 8612 if (attrNormalizedName == "srcdoc") { 8613 return $sce.HTML; 8614 } 8615 var tag = nodeName_(node); 8616 // maction[xlink:href] can source SVG. It's not limited to <maction>. 8617 if (attrNormalizedName == "xlinkHref" || 8618 (tag == "form" && attrNormalizedName == "action") || 8619 (tag != "img" && (attrNormalizedName == "src" || 8620 attrNormalizedName == "ngSrc"))) { 8621 return $sce.RESOURCE_URL; 8622 } 8623 } 8624 8625 8626 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { 8627 var trustedContext = getTrustedContext(node, name); 8628 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; 8629 8630 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); 8631 8632 // no interpolation found -> ignore 8633 if (!interpolateFn) return; 8634 8635 8636 if (name === "multiple" && nodeName_(node) === "select") { 8637 throw $compileMinErr("selmulti", 8638 "Binding to the 'multiple' attribute is not supported. Element: {0}", 8639 startingTag(node));
8640 } 8641 8642 directives.push({ 8643 priority: 100, 8644 compile: function() { 8645 return { 8646 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 8647 var $$observers = (attr.$$observers || (attr.$$observers = createMap())); 8648 8649 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 8650 throw $compileMinErr('nodomevents', 8651 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 8652 "ng- versions (such as ng-click instead of onclick) instead."); 8653 } 8654 8655 // If the attribute has changed since last $interpolate()ed 8656 var newValue = attr[name]; 8657 if (newValue !== value) { 8658 // we need to interpolate again since the attribute value has been updated 8659 // (e.g. by another directive's compile function) 8660 // ensure unset/empty values make interpolateFn falsy 8661 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 8662 value = newValue; 8663 } 8664 8665 // if attribute was updated so that there is no interpolation going on we don't want to 8666 // register any observers 8667 if (!interpolateFn) return; 8668 8669 // initialize attr object so that it's ready in case we need the value for isolate 8670 // scope initialization, otherwise the value would not be available from isolate 8671 // directive's linking fn during linking phase 8672 attr[name] = interpolateFn(scope); 8673 8674 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 8675 (attr.$$observers && attr.$$observers[name].$$scope || scope). 8676 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 8677 //special case for class attribute addition + removal 8678 //so that class changes can tap into the animation 8679 //hooks provided by the $animate service. Be sure to 8680 //skip animations when the first digest occurs (when 8681 //both the new and the old values are the same) since 8682 //the CSS classes are the non-interpolated values 8683 if (name === 'class' && newValue != oldValue) { 8684 attr.$updateClass(newValue, oldValue); 8685 } else { 8686 attr.$set(name, newValue); 8687 } 8688 }); 8689 } 8690 }; 8691 } 8692 }); 8693 } 8694 8695 8696 /** 8697 * This is a special jqLite.replaceWith, which can replace items which 8698 * have no parents, provided that the containing jqLite collection is provided. 8699 * 8700 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 8701 * in the root of the tree. 8702 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 8703 * the shell, but replace its DOM node reference. 8704 * @param {Node} newNode The new DOM node. 8705 */ 8706 function replaceWith($rootElement, elementsToRemove, newNode) { 8707 var firstElementToRemove = elementsToRemove[0], 8708 removeCount = elementsToRemove.length, 8709 parent = firstElementToRemove.parentNode, 8710 i, ii; 8711 8712 if ($rootElement) { 8713 for (i = 0, ii = $rootElement.length; i < ii; i++) { 8714 if ($rootElement[i] == firstElementToRemove) { 8715 $rootElement[i++] = newNode; 8716 for (var j = i, j2 = j + removeCount - 1, 8717 jj = $rootElement.length; 8718 j < jj; j++, j2++) { 8719 if (j2 < jj) { 8720 $rootElement[j] = $rootElement[j2]; 8721 } else { 8722 delete $rootElement[j]; 8723 } 8724 } 8725 $rootElement.length -= removeCount - 1; 8726 8727 // If the replaced element is also the jQuery .context then replace it 8728 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 8729 // http://api.jquery.com/context/ 8730 if ($rootElement.context === firstElementToRemove) { 8731 $rootElement.context = newNode; 8732 } 8733 break; 8734 } 8735 } 8736 } 8737 8738 if (parent) { 8739 parent.replaceChild(newNode, firstElementToRemove); 8740 } 8741 8742 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it? 8743 var fragment = document.createDocumentFragment(); 8744 fragment.appendChild(firstElementToRemove); 8745 8746 if (jqLite.hasData(firstElementToRemove)) { 8747 // Copy over user data (that includes Angular's $scope etc.). Don't copy private 8748 // data here because there's no public interface in jQuery to do that and copying over 8749 // event listeners (which is the main use of private data) wouldn't work anyway. 8750 jqLite(newNode).data(jqLite(firstElementToRemove).data()); 8751 8752 // Remove data of the replaced element. We cannot just call .remove() 8753 // on the element it since that would deallocate scope that is needed 8754 // for the new node. Instead, remove the data "manually". 8755 if (!jQuery) { 8756 delete jqLite.cache[firstElementToRemove[jqLite.expando]]; 8757 } else { 8758 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after 8759 // the replaced element. The cleanData version monkey-patched by Angular would cause 8760 // the scope to be trashed and we do need the very same scope to work with the new 8761 // element. However, we cannot just cache the non-patched version and use it here as 8762 // that would break if another library patches the method after Angular does (one
8763 // example is jQuery UI). Instead, set a flag indicating scope destroying should be 8764 // skipped this one time. 8765 skipDestroyOnNextJQueryCleanData = true; 8766 jQuery.cleanData([firstElementToRemove]); 8767 } 8768 } 8769 8770 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 8771 var element = elementsToRemove[k]; 8772 jqLite(element).remove(); // must do this way to clean up expando 8773 fragment.appendChild(element); 8774 delete elementsToRemove[k]; 8775 } 8776 8777 elementsToRemove[0] = newNode; 8778 elementsToRemove.length = 1; 8779 } 8780 8781 8782 function cloneAndAnnotateFn(fn, annotation) { 8783 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 8784 } 8785 8786 8787 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { 8788 try { 8789 linkFn(scope, $element, attrs, controllers, transcludeFn); 8790 } catch (e) { 8791 $exceptionHandler(e, startingTag($element)); 8792 } 8793 } 8794 8795 8796 // Set up $watches for isolate scope and controller bindings. This process 8797 // only occurs for isolate scopes and new scopes with controllerAs. 8798 function initializeDirectiveBindings(scope, attrs, destination, bindings, 8799 directive, newScope) { 8800 var onNewScopeDestroyed; 8801 forEach(bindings, function(definition, scopeName) { 8802 var attrName = definition.attrName, 8803 optional = definition.optional, 8804 mode = definition.mode, // @, =, or & 8805 lastValue, 8806 parentGet, parentSet, compare; 8807 8808 switch (mode) { 8809 8810 case '@': 8811 if (!optional && !hasOwnProperty.call(attrs, attrName)) { 8812 destination[scopeName] = attrs[attrName] = void 0; 8813 } 8814 attrs.$observe(attrName, function(value) { 8815 if (isString(value)) { 8816 destination[scopeName] = value; 8817 } 8818 }); 8819 attrs.$$observers[attrName].$$scope = scope; 8820 if (isString(attrs[attrName])) { 8821 // If the attribute has been provided then we trigger an interpolation to ensure 8822 // the value is there for use in the link fn 8823 destination[scopeName] = $interpolate(attrs[attrName])(scope); 8824 } 8825 break; 8826 8827 case '=': 8828 if (!hasOwnProperty.call(attrs, attrName)) { 8829 if (optional) break; 8830 attrs[attrName] = void 0; 8831 } 8832 if (optional && !attrs[attrName]) break; 8833 8834 parentGet = $parse(attrs[attrName]); 8835 if (parentGet.literal) { 8836 compare = equals; 8837 } else { 8838 compare = function(a, b) { return a === b || (a !== a && b !== b); }; 8839 } 8840 parentSet = parentGet.assign || function() { 8841 // reset the change, or we will throw this exception on every $digest 8842 lastValue = destination[scopeName] = parentGet(scope); 8843 throw $compileMinErr('nonassign', 8844 "Expression '{0}' used with directive '{1}' is non-assignable!", 8845 attrs[attrName], directive.name); 8846 }; 8847 lastValue = destination[scopeName] = parentGet(scope); 8848 var parentValueWatch = function parentValueWatch(parentValue) { 8849 if (!compare(parentValue, destination[scopeName])) { 8850 // we are out of sync and need to copy 8851 if (!compare(parentValue, lastValue)) { 8852 // parent changed and it has precedence 8853 destination[scopeName] = parentValue; 8854 } else { 8855 // if the parent can be assigned then do so 8856 parentSet(scope, parentValue = destination[scopeName]); 8857 } 8858 } 8859 return lastValue = parentValue; 8860 }; 8861 parentValueWatch.$stateful = true; 8862 var unwatch; 8863 if (definition.collection) { 8864 unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 8865 } else { 8866 unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 8867 } 8868 onNewScopeDestroyed = (onNewScopeDestroyed || []); 8869 onNewScopeDestroyed.push(unwatch); 8870 break; 8871 8872 case '&': 8873 // Don't assign Object.prototype method to scope 8874 parentGet = attrs.hasOwnProperty(attrName) ? $parse(attrs[attrName]) : noop; 8875 8876 // Don't assign noop to destination if expression is not val
8876id 8877 if (parentGet === noop && optional) break; 8878 8879 destination[scopeName] = function(locals) { 8880 return parentGet(scope, locals); 8881 }; 8882 break; 8883 } 8884 }); 8885 var destroyBindings = onNewScopeDestroyed ? function destroyBindings() { 8886 for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) { 8887 onNewScopeDestroyed[i](); 8888 } 8889 } : noop; 8890 if (newScope && destroyBindings !== noop) { 8891 newScope.$on('$destroy', destroyBindings); 8892 return noop; 8893 } 8894 return destroyBindings; 8895 } 8896 }]; 8897} 8898 8899var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; 8900/** 8901 * Converts all accepted directives format into proper directive name. 8902 * @param name Name to normalize 8903 */ 8904function directiveNormalize(name) { 8905 return camelCase(name.replace(PREFIX_REGEXP, '')); 8906} 8907 8908/** 8909 * @ngdoc type 8910 * @name $compile.directive.Attributes 8911 * 8912 * @description 8913 * A shared object between directive compile / linking functions which contains normalized DOM 8914 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 8915 * needed since all of these are treated as equivalent in Angular: 8916 * 8917 * ``` 8918 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 8919 * ``` 8920 */ 8921 8922/** 8923 * @ngdoc property 8924 * @name $compile.directive.Attributes#$attr 8925 * 8926 * @description 8927 * A map of DOM element attribute names to the normalized name. This is 8928 * needed to do reverse lookup from normalized name back to actual name. 8929 */ 8930 8931 8932/** 8933 * @ngdoc method 8934 * @name $compile.directive.Attributes#$set 8935 * @kind function 8936 * 8937 * @description 8938 * Set DOM element attribute value. 8939 * 8940 * 8941 * @param {string} name Normalized element attribute name of the property to modify. The name is 8942 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 8943 * property to the original name. 8944 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 8945 */ 8946 8947 8948 8949/** 8950 * Closure compiler type information 8951 */ 8952 8953function nodesetLinkingFn( 8954 /* angular.Scope */ scope, 8955 /* NodeList */ nodeList, 8956 /* Element */ rootElement, 8957 /* function(Function) */ boundTranscludeFn 8958) {} 8959 8960function directiveLinkingFn( 8961 /* nodesetLinkingFn */ nodesetLinkingFn, 8962 /* angular.Scope */ scope, 8963 /* Node */ node, 8964 /* Element */ rootElement, 8965 /* function(Function) */ boundTranscludeFn 8966) {} 8967 8968function tokenDifference(str1, str2) { 8969 var values = '', 8970 tokens1 = str1.split(/\s+/), 8971 tokens2 = str2.split(/\s+/); 8972 8973 outer: 8974 for (var i = 0; i < tokens1.length; i++) { 8975 var token = tokens1[i]; 8976 for (var j = 0; j < tokens2.length; j++) { 8977 if (token == tokens2[j]) continue outer; 8978 } 8979 values += (values.length > 0 ? ' ' : '') + token; 8980 } 8981 return values; 8982} 8983 8984function removeComments(jqNodes) { 8985 jqNodes = jqLite(jqNodes); 8986 var i = jqNodes.length; 8987 8988 if (i <= 1) { 8989 return jqNodes; 8990 } 8991 8992 while (i--) { 8993 var node = jqNodes[i]; 8994 if (node.nodeType === NODE_TYPE_COMMENT) { 8995 splice.call(jqNodes, i, 1); 8996 } 8997 } 8998 return jqNodes; 8999} 9000 9001var $controllerMinErr = minErr('$controller'); 9002 9003 9004var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 9005function identifierForController(controller, ident) { 9006 if (ident && isString(ident)) return ident; 9007 if (isString(controller)) { 9008 var match = CNTRL_REG.exec(controller); 9009 if (match) return match[3]; 9010 } 9011} 9012 9013 9014/** 9015 * @ngdoc provider 9016 * @name $controllerProvider 9017 * @description 9018 * The {@link ng.$controller $controller service} is used by Angular to create new 9019 * controllers. 9020 * 9021 * This provider allows controller registration via the 9022 * {@link ng.$controllerProvider#register register} method. 9023 */ 9024function $ControllerProvider() { 9025 var controllers = {}, 9026 globals = false; 9027 9028 /** 9029 * @ngdoc method 9030 * @name $controllerProvider#register 9031 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 9032 * the names and the values are the constructors. 9033 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 9034 * annotations in the array notation). 9035 */ 9036 this.register = function(name, constructor) { 9037 assertNotHasOwnProperty(name, 'controller'); 9038 if (isObject(name)) { 9039 extend(controllers, name); 9040 } else { 9041 controllers[name] = constructor; 9042 } 9043 }; 9044 9045 /** 9046 * @ngdoc method 9047 * @name $controllerProvider#allowGlobals 9048 * @description If called, allows `$controller` to find controller constructors on `window` 9049 */ 9050 this.allowGlobals = function() { 9051 globals = true; 9052 }; 9053 9054 9055 this.$get = ['$injector', '$window', function($injector, $window) { 9056 9057 /** 9058 * @ngdoc service 9059 * @name $controller 9060 * @requires $injector 9061 * 9062 * @param {Function|string} constructor If called with a function then it's considered to be the 9063 * controller constructor function. Otherwise it's considered to be a string which is used 9064 * to retrieve the controller constructor using the following steps: 9065 * 9066 * * check if a controller with given name is registered via `$controllerProvider` 9067 * * check if evaluating the string on the current scope returns a constructor 9068 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global 9069 * `window` object (not recommended) 9070 * 9071 * The string can use the `controller as property` syntax, where the controller instance is published 9072 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 9073 * to work correctly. 9074 * 9075 * @param {Object} locals Injection locals for Controller. 9076 * @return {Object} Instance of given controller. 9077 *
vendor: 4,129 bytes, lines 9078-9182
9078 * @description 9079 * `$controller` service is responsible for instantiating controllers. 9080 * 9081 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 9082 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 9083 */ 9084 return function(expression, locals, later, ident) { 9085 // PRIVATE API: 9086 // param `later` --- indicates that the controller's constructor is invoked at a later time. 9087 // If true, $controller will allocate the object with the correct 9088 // prototype chain, but will not invoke the controller until a returned 9089 // callback is invoked. 9090 // param `ident` --- An optional label which overrides the label parsed from the controller 9091 // expression, if any. 9092 var instance, match, constructor, identifier; 9093 later = later === true; 9094 if (ident && isString(ident)) { 9095 identifier = ident; 9096 } 9097 9098 if (isString(expression)) { 9099 match = expression.match(CNTRL_REG); 9100 if (!match) { 9101 throw $controllerMinErr('ctrlfmt', 9102 "Badly formed controller string '{0}'. " + 9103 "Must match `__name__ as __id__` or `__name__`.", expression); 9104 } 9105 constructor = match[1], 9106 identifier = identifier || match[3]; 9107 expression = controllers.hasOwnProperty(constructor) 9108 ? controllers[constructor] 9109 : getter(locals.$scope, constructor, true) || 9110 (globals ? getter($window, constructor, true) : undefined); 9111 9112 assertArgFn(expression, constructor, true); 9113 } 9114 9115 if (later) { 9116 // Instantiate controller later: 9117 // This machinery is used to create an instance of the object before calling the 9118 // controller's constructor itself. 9119 // 9120 // This allows properties to be added to the controller before the constructor is 9121 // invoked. Primarily, this is used for isolate scope bindings in $compile. 9122 // 9123 // This feature is not intended for use by applications, and is thus not documented 9124 // publicly. 9125 // Object creation: http://jsperf.com/create-constructor/2 9126 var controllerPrototype = (isArray(expression) ? 9127 expression[expression.length - 1] : expression).prototype; 9128 instance = Object.create(controllerPrototype || null); 9129 9130 if (identifier) { 9131 addIdentifier(locals, identifier, instance, constructor || expression.name); 9132 } 9133 9134 var instantiate; 9135 return instantiate = extend(function() { 9136 var result = $injector.invoke(expression, instance, locals, constructor); 9137 if (result !== instance && (isObject(result) || isFunction(result))) { 9138 instance = result; 9139 if (identifier) { 9140 // If result changed, re-assign controllerAs value to scope. 9141 addIdentifier(locals, identifier, instance, constructor || expression.name); 9142 } 9143 } 9144 return instance; 9145 }, { 9146 instance: instance, 9147 identifier: identifier 9148 }); 9149 } 9150 9151 instance = $injector.instantiate(expression, locals, constructor); 9152 9153 if (identifier) { 9154 addIdentifier(locals, identifier, instance, constructor || expression.name); 9155 } 9156 9157 return instance; 9158 }; 9159 9160 function addIdentifier(locals, identifier, instance, name) { 9161 if (!(locals && isObject(locals.$scope))) { 9162 throw minErr('$controller')('noscp', 9163 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 9164 name, identifier); 9165 } 9166 9167 locals.$scope[identifier] = instance; 9168 } 9169 }]; 9170} 9171 9172/** 9173 * @ngdoc service 9174 * @name $document 9175 * @requires $window 9176 * 9177 * @description 9178 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 9179 * 9180 * @example 9181 <example module="documentExample"> 9182 <file name="index.html">
9183 <div ng-controller="ExampleController"> 9184 <p>$document title: <b ng-bind="title"></b></p> 9185 <p>window.document title: <b ng-bind="windowTitle"></b></p> 9186 </div> 9187 </file> 9188 <file name="script.js"> 9189 angular.module('documentExample', []) 9190 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 9191 $scope.title = $document[0].title; 9192 $scope.windowTitle = angular.element(window.document)[0].title; 9193 }]); 9194 </file> 9195 </example> 9196 */ 9197function $DocumentProvider() { 9198 this.$get = ['$window', function(window) { 9199 return jqLite(window.document); 9200 }]; 9201} 9202 9203/** 9204 * @ngdoc service 9205 * @name $exceptionHandler 9206 * @requires ng.$log 9207 * 9208 * @description 9209 * Any uncaught exception in angular expressions is delegated to this service. 9210 * The default implementation simply delegates to `$log.error` which logs it into 9211 * the browser console. 9212 * 9213 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 9214 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 9215 * 9216 * ## Example: 9217 * 9218 * ```js 9219 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { 9220 * return function(exception, cause) { 9221 * exception.message += ' (caused by "' + cause + '")'; 9222 * throw exception; 9223 * }; 9224 * }); 9225 * ``` 9226 * 9227 * This example will override the normal action of `$exceptionHandler`, to make angular 9228 * exceptions fail hard when they happen, instead of just logging to the console. 9229 * 9230 * <hr /> 9231 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 9232 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 9233 * (unless executed during a digest). 9234 * 9235 * If you wish, you can manually delegate exceptions, e.g. 9236 * `try { ... } catch(e) { $exceptionHandler(e); }` 9237 * 9238 * @param {Error} exception Exception associated with the error. 9239 * @param {string=} cause optional information about the context in which 9240 * the error was thrown. 9241 * 9242 */ 9243function $ExceptionHandlerProvider() { 9244 this.$get = ['$log', function($log) { 9245 return function(exception, cause) { 9246 $log.error.apply($log, arguments); 9247 }; 9248 }]; 9249} 9250 9251var $$ForceReflowProvider = function() { 9252 this.$get = ['$document', function($document) { 9253 return function(domNode) { 9254 //the line below will force the browser to perform a repaint so 9255 //that all the animated elements within the animation frame will 9256 //be properly updated and drawn on screen. This is required to 9257 //ensure that the preparation animation is properly flushed so that 9258 //the active state picks up from there. DO NOT REMOVE THIS LINE. 9259 //DO NOT OPTIMIZE THIS LINE. THE MINIFIER WILL REMOVE IT OTHERWISE WHICH 9260 //WILL RESULT IN AN UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND 9261 //WILL TAKE YEARS AWAY FROM YOUR LIFE. 9262 if (domNode) { 9263 if (!domNode.nodeType && domNode instanceof jqLite) { 9264 domNode = domNode[0]; 9265 } 9266 } else { 9267 domNode = $document[0].body; 9268 } 9269 return domNode.offsetWidth + 1; 9270 }; 9271 }]; 9272}; 9273 9274var APPLICATION_JSON = 'application/json'; 9275var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 9276var JSON_START = /^\[|^\{(?!\{)/; 9277var JSON_ENDS = { 9278 '[': /]$/, 9279 '{': /}$/ 9280}; 9281var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; 9282var $httpMinErr = minErr('$http'); 9283var $httpMinErrLegacyFn = function(method) { 9284 return function() { 9285 throw $httpMinErr('legacy', 'The method `{0}` on the promise returned from `$http` has been disabled.', method); 9286 }; 9287}; 9288 9289function serializeValue(v) { 9290 if (isObject(v)) { 9291 return isDate(v) ? v.toISOString() : toJson(v); 9292 } 9293 return v; 9294} 9295 9296 9297function $HttpParamSerializerProvider() { 9298 /** 9299 * @ngdoc service 9300 * @name $httpParamSerializer 9301 * @description 9302 * 9303 * Default {@link $http `$http`} params serializer that converts objects to strings 9304 * according to the following rules: 9305 * 9306 * * `{'foo': 'bar'}` results in `foo=bar` 9307 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) 9308 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) 9309 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object) 9310 * 9311 * Note that serializer will sort the request parameters alphabetically. 9312 * */ 9313 9314 this.$get = function() { 9315 return function ngParamSerializer(params) { 9316 if (!params) return ''; 9317 var parts = [];
vendor: 3,809 bytes, lines 9318-9444
9318 forEachSorted(params, function(value, key) { 9319 if (value === null || isUndefined(value)) return; 9320 if (isArray(value)) { 9321 forEach(value, function(v, k) { 9322 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); 9323 }); 9324 } else { 9325 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); 9326 } 9327 }); 9328 9329 return parts.join('&'); 9330 }; 9331 }; 9332} 9333 9334function $HttpParamSerializerJQLikeProvider() { 9335 /** 9336 * @ngdoc service 9337 * @name $httpParamSerializerJQLike 9338 * @description 9339 * 9340 * Alternative {@link $http `$http`} params serializer that follows 9341 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic. 9342 * The serializer will also sort the params alphabetically. 9343 * 9344 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: 9345 * 9346 * ```js 9347 * $http({ 9348 * url: myUrl, 9349 * method: 'GET', 9350 * params: myParams, 9351 * paramSerializer: '$httpParamSerializerJQLike' 9352 * }); 9353 * ``` 9354 * 9355 * It is also possible to set it as the default `paramSerializer` in the 9356 * {@link $httpProvider#defaults `$httpProvider`}. 9357 * 9358 * Additionally, you can inject the serializer and use it explicitly, for example to serialize 9359 * form data for submission: 9360 * 9361 * ```js 9362 * .controller(function($http, $httpParamSerializerJQLike) { 9363 * //... 9364 * 9365 * $http({ 9366 * url: myUrl, 9367 * method: 'POST', 9368 * data: $httpParamSerializerJQLike(myData), 9369 * headers: { 9370 * 'Content-Type': 'application/x-www-form-urlencoded' 9371 * } 9372 * }); 9373 * 9374 * }); 9375 * ``` 9376 * 9377 * */ 9378 this.$get = function() { 9379 return function jQueryLikeParamSerializer(params) { 9380 if (!params) return ''; 9381 var parts = []; 9382 serialize(params, '', true); 9383 return parts.join('&'); 9384 9385 function serialize(toSerialize, prefix, topLevel) { 9386 if (toSerialize === null || isUndefined(toSerialize)) return; 9387 if (isArray(toSerialize)) { 9388 forEach(toSerialize, function(value, index) { 9389 serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']'); 9390 }); 9391 } else if (isObject(toSerialize) && !isDate(toSerialize)) { 9392 forEachSorted(toSerialize, function(value, key) { 9393 serialize(value, prefix + 9394 (topLevel ? '' : '[') + 9395 key + 9396 (topLevel ? '' : ']')); 9397 }); 9398 } else { 9399 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); 9400 } 9401 } 9402 }; 9403 }; 9404} 9405 9406function defaultHttpResponseTransform(data, headers) { 9407 if (isString(data)) { 9408 // Strip json vulnerability protection prefix and trim whitespace 9409 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 9410 9411 if (tempData) { 9412 var contentType = headers('Content-Type'); 9413 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { 9414 data = fromJson(tempData); 9415 } 9416 } 9417 } 9418 9419 return data; 9420} 9421 9422function isJsonLike(str) { 9423 var jsonStart = str.match(JSON_START); 9424 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 9425} 9426 9427/** 9428 * Parse headers into key value object 9429 * 9430 * @param {string} headers Raw headers as a string 9431 * @returns {Object} Parsed headers as key value object 9432 */ 9433function parseHeaders(headers) { 9434 var parsed = createMap(), i; 9435 9436 function fillInParsed(key, val) { 9437 if (key) { 9438 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 9439 } 9440 } 9441 9442 if (isString(headers)) { 9443 forEach(headers.split('\n'), function(line) { 9444 i = line.indexOf(':');
vendor: 5,581 bytes, lines 9445-9612
9445 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); 9446 }); 9447 } else if (isObject(headers)) { 9448 forEach(headers, function(headerVal, headerKey) { 9449 fillInParsed(lowercase(headerKey), trim(headerVal)); 9450 }); 9451 } 9452 9453 return parsed; 9454} 9455 9456 9457/** 9458 * Returns a function that provides access to parsed headers. 9459 * 9460 * Headers are lazy parsed when first requested. 9461 * @see parseHeaders 9462 * 9463 * @param {(string|Object)} headers Headers to provide access to. 9464 * @returns {function(string=)} Returns a getter function which if called with: 9465 * 9466 * - if called with single an argument returns a single header value or null 9467 * - if called with no arguments returns an object containing all headers. 9468 */ 9469function headersGetter(headers) { 9470 var headersObj; 9471 9472 return function(name) { 9473 if (!headersObj) headersObj = parseHeaders(headers); 9474 9475 if (name) { 9476 var value = headersObj[lowercase(name)]; 9477 if (value === void 0) { 9478 value = null; 9479 } 9480 return value; 9481 } 9482 9483 return headersObj; 9484 }; 9485} 9486 9487 9488/** 9489 * Chain all given functions 9490 * 9491 * This function is used for both request and response transforming 9492 * 9493 * @param {*} data Data to transform. 9494 * @param {function(string=)} headers HTTP headers getter fn. 9495 * @param {number} status HTTP status code of the response. 9496 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 9497 * @returns {*} Transformed data. 9498 */ 9499function transformData(data, headers, status, fns) { 9500 if (isFunction(fns)) { 9501 return fns(data, headers, status); 9502 } 9503 9504 forEach(fns, function(fn) { 9505 data = fn(data, headers, status); 9506 }); 9507 9508 return data; 9509} 9510 9511 9512function isSuccess(status) { 9513 return 200 <= status && status < 300; 9514} 9515 9516 9517/** 9518 * @ngdoc provider 9519 * @name $httpProvider 9520 * @description 9521 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 9522 * */ 9523function $HttpProvider() { 9524 /** 9525 * @ngdoc property 9526 * @name $httpProvider#defaults 9527 * @description 9528 * 9529 * Object containing default values for all {@link ng.$http $http} requests. 9530 * 9531 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} 9532 * that will provide the cache for all requests who set their `cache` property to `true`. 9533 * If you set the `defaults.cache = false` then only requests that specify their own custom 9534 * cache object will be cached. See {@link $http#caching $http Caching} for more information. 9535 * 9536 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 9537 * Defaults value is `'XSRF-TOKEN'`. 9538 * 9539 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 9540 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 9541 * 9542 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 9543 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 9544 * setting default headers. 9545 * - **`defaults.headers.common`** 9546 * - **`defaults.headers.post`** 9547 * - **`defaults.headers.put`** 9548 * - **`defaults.headers.patch`** 9549 * 9550 * 9551 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function 9552 * used to the prepare string representation of request parameters (specified as an object). 9553 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. 9554 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. 9555 * 9556 **/ 9557 var defaults = this.defaults = { 9558 // transform incoming response data 9559 transformResponse: [defaultHttpResponseTransform], 9560 9561 // transform outgoing request data 9562 transformRequest: [function(d) { 9563 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 9564 }], 9565 9566 // default headers 9567 headers: { 9568 common: { 9569 'Accept': 'application/json, text/plain, */*' 9570 }, 9571 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 9572 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 9573 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 9574 }, 9575 9576 xsrfCookieName: 'XSRF-TOKEN', 9577 xsrfHeaderName: 'X-XSRF-TOKEN', 9578 9579 paramSerializer: '$httpParamSerializer' 9580 }; 9581 9582 var useApplyAsync = false; 9583 /** 9584 * @ngdoc method 9585 * @name $httpProvider#useApplyAsync 9586 * @description 9587 * 9588 * Configure $http service to combine processing of multiple http responses received at around 9589 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 9590 * significant performance improvement for bigger applications that make many HTTP requests 9591 * concurrently (common during application bootstrap). 9592 * 9593 * Defaults to false. If no value is specified, returns the current configured value. 9594 * 9595 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 9596 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 9597 * to load and share the same digest cycle. 9598 * 9599 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 9600 * otherwise, returns the current configured value. 9601 **/ 9602 this.useApplyAsync = function(value) { 9603 if (isDefined(value)) { 9604 useApplyAsync = !!value; 9605 return this; 9606 } 9607 return useApplyAsync; 9608 }; 9609 9610 var useLegacyPromise = true; 9611 /** 9612 * @ngdoc method
9613 * @name $httpProvider#useLegacyPromiseExtensions 9614 * @description 9615 * 9616 * Configure `$http` service to return promises without the shorthand methods `success` and `error`. 9617 * This should be used to make sure that applications work without these methods. 9618 * 9619 * Defaults to false. If no value is specified, returns the current configured value. 9620 * 9621 * @param {boolean=} value If true, `$http` will return a normal promise without the `success` and `error` methods. 9622 * 9623 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 9624 * otherwise, returns the current configured value. 9625 **/ 9626 this.useLegacyPromiseExtensions = function(value) { 9627 if (isDefined(value)) { 9628 useLegacyPromise = !!value; 9629 return this; 9630 } 9631 return useLegacyPromise; 9632 }; 9633 9634 /** 9635 * @ngdoc property 9636 * @name $httpProvider#interceptors 9637 * @description 9638 * 9639 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 9640 * pre-processing of request or postprocessing of responses. 9641 * 9642 * These service factories are ordered by request, i.e. they are applied in the same order as the 9643 * array, on request, but reverse order, on response. 9644 * 9645 * {@link ng.$http#interceptors Interceptors detailed info} 9646 **/ 9647 var interceptorFactories = this.interceptors = []; 9648 9649 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', 9650 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { 9651 9652 var defaultCache = $cacheFactory('$http'); 9653 9654 /** 9655 * Make sure that default param serializer is exposed as a function 9656 */ 9657 defaults.paramSerializer = isString(defaults.paramSerializer) ? 9658 $injector.get(defaults.paramSerializer) : defaults.paramSerializer; 9659 9660 /** 9661 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 9662 * The reversal is needed so that we can build up the interception chain around the 9663 * server request. 9664 */ 9665 var reversedInterceptors = []; 9666 9667 forEach(interceptorFactories, function(interceptorFactory) { 9668 reversedInterceptors.unshift(isString(interceptorFactory) 9669 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 9670 }); 9671 9672 /** 9673 * @ngdoc service 9674 * @kind function 9675 * @name $http 9676 * @requires ng.$httpBackend 9677 * @requires $cacheFactory 9678 * @requires $rootScope 9679 * @requires $q 9680 * @requires $injector 9681 * 9682 * @description 9683 * The `$http` service is a core Angular service that facilitates communication with the remote 9684 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 9685 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 9686 * 9687 * For unit testing applications that use `$http` service, see 9688 * {@link ngMock.$httpBackend $httpBackend mock}. 9689 * 9690 * For a higher level of abstraction, please check out the {@link ngResource.$resource 9691 * $resource} service. 9692 * 9693 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 9694 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 9695 * it is important to familiarize yourself with these APIs and the guarantees they provide. 9696 * 9697 * 9698 * ## General usage 9699 * The `$http` service is a function which takes a single argument â a {@link $http#usage configuration object} â 9700 * that is used to generate an HTTP request and returns a {@link ng.$q promise}. 9701 * 9702 * ```js 9703 * // Simple GET request example: 9704 * $http({ 9705 * method: 'GET', 9706 * url: '/someUrl' 9707 * }).then(function successCallback(response) { 9708 * // this callback will be called asynchronously 9709 * // when the response is available 9710 * }, function errorCallback(response) { 9711 * // called asynchronously if an error occurs 9712 * // or server returns response with an error status. 9713 * }); 9714 * ``` 9715 * 9716 * The response object has these properties: 9717 * 9718 * - **data** â `{string|Object}` â The response body transformed with the transform 9719 * functions. 9720 * - **status** â `{number}` â HTTP status code of the response. 9721 * - **headers** â `{function([headerName])}` â Header getter function. 9722 * - **config** â `{Object}` â The configuration object that was used to generate the request. 9723 * - **statusText** â `{string}` â HTTP status text of the response. 9724 * 9725 * A response status code between 200 and 299 is considered a success status and 9726 * will result in the success callback being called. Note that if the response is a redirect, 9727 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 9728 * called for such responses. 9729 * 9730 * 9731 * ## Shortcut methods 9732 * 9733 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 9734 * request data must be passed in for POST/PUT requests. An optional config can be passed as the 9735 * last argument. 9736 * 9737 * ```js 9738 * $http.get('/someUrl', config).then(successCallback, errorCallback); 9739 * $http.post('/someUrl', data, config).then(successCallback, errorCallback); 9740 * ``` 9741 * 9742 * Complete list of shortcut methods: 9743 * 9744 * - {@link ng.$http#get $http.get} 9745 * - {@link ng.$http#head $http.head} 9746 * - {@link ng.$http#post $http.post} 9747 * - {@link ng.$http#put $http.put} 9748 * - {@link ng.$http#delete $http.delete} 9749 * - {@link ng.$http#jsonp $http.jsonp} 9750 * - {@link ng.$http#patch $http.patch} 9751 * 9752 * 9753 * ## Writing Unit Tests that use $http 9754 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 9755 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 9756 * request using trained responses. 9757 * 9758 * ``` 9759 * $httpBackend.expectGET(...); 9760 * $http.get(...); 9761 * $httpBackend.flush(); 9762 * ``` 9763 * 9764 * ## Deprecation Notice 9765 * <div class="alert alert-danger"> 9766 * The `$http` legacy promise methods `success` and `error` have been deprecated. 9767 * Use the standard `then` method instead.
9768 * If {@link $httpProvider#useLegacyPromiseExtensions `$httpProvider.useLegacyPromiseExtensions`} is set to 9769 * `false` then these methods will throw {@link $http:legacy `$http/legacy`} error. 9770 * </div> 9771 * 9772 * ## Setting HTTP Headers 9773 * 9774 * The $http service will automatically add certain HTTP headers to all requests. These defaults 9775 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 9776 * object, which currently contains this default configuration: 9777 * 9778 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 9779 * - `Accept: application/json, text/plain, * / *` 9780 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 9781 * - `Content-Type: application/json` 9782 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 9783 * - `Content-Type: application/json` 9784 * 9785 * To add or overwrite these defaults, simply add or remove a property from these configuration 9786 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 9787 * with the lowercased HTTP method name as the key, e.g. 9788 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. 9789 * 9790 * The defaults can also be set at runtime via the `$http.defaults` object in the same 9791 * fashion. For example: 9792 * 9793 * ``` 9794 * module.run(function($http) { 9795 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' 9796 * }); 9797 * ``` 9798 * 9799 * In addition, you can supply a `headers` property in the config object passed when 9800 * calling `$http(config)`, which overrides the defaults without changing them globally. 9801 * 9802 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 9803 * Use the `headers` property, setting the desired header to `undefined`. For example: 9804 * 9805 * ```js 9806 * var req = { 9807 * method: 'POST', 9808 * url: 'http://example.com', 9809 * headers: { 9810 * 'Content-Type': undefined 9811 * }, 9812 * data: { test: 'test' } 9813 * } 9814 * 9815 * $http(req).then(function(){...}, function(){...}); 9816 * ``` 9817 * 9818 * ## Transforming Requests and Responses 9819 * 9820 * Both requests and responses can be transformed using transformation functions: `transformRequest` 9821 * and `transformResponse`. These properties can be a single function that returns 9822 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 9823 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 9824 * 9825 * ### Default Transformations 9826 * 9827 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 9828 * `defaults.transformResponse` properties. If a request does not provide its own transformations 9829 * then these will be applied. 9830 * 9831 * You can augment or replace the default transformations by modifying these properties by adding to or 9832 * replacing the array. 9833 * 9834 * Angular provides the following default transformations: 9835 * 9836 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): 9837 * 9838 * - If the `data` property of the request configuration object contains an object, serialize it 9839 * into JSON format. 9840 * 9841 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): 9842 * 9843 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 9844 * - If JSON response is detected, deserialize it using a JSON parser. 9845 * 9846 * 9847 * ### Overriding the Default Transformations Per Request 9848 * 9849 * If you wish override the request/response transformations only for a single request then provide 9850 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 9851 * into `$http`. 9852 * 9853 * Note that if you provide these properties on the config object the default transformations will be 9854 * overwritten. If you wish to augment the default transformations then you must include them in your 9855 * local transformation array. 9856 * 9857 * The following code demonstrates adding a new response transformation to be run after the default response 9858 * transformations have been run. 9859 * 9860 * ```js 9861 * function appendTransform(defaults, transform) { 9862 * 9863 * // We can't guarantee that the default transformation is an array 9864 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 9865 * 9866 * // Append the new transformation to the defaults 9867 * return defaults.concat(transform); 9868 * } 9869 * 9870 * $http({ 9871 * url: '...', 9872 * method: 'GET', 9873 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 9874 * return doTransform(value); 9875 * }) 9876 * }); 9877 * ``` 9878 * 9879 * 9880 * ## Caching 9881 *
9882 * To enable caching, set the request configuration `cache` property to `true` (to use default 9883 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 9884 * When the cache is enabled, `$http` stores the response from the server in the specified 9885 * cache. The next time the same request is made, the response is served from the cache without 9886 * sending a request to the server. 9887 * 9888 * Note that even if the response is served from cache, delivery of the data is asynchronous in 9889 * the same way that real requests are. 9890 * 9891 * If there are multiple GET requests for the same URL that should be cached using the same 9892 * cache, but the cache is not populated yet, only one request to the server will be made and 9893 * the remaining requests will be fulfilled using the response from the first request. 9894 * 9895 * You can change the default cache to a new object (built with 9896 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 9897 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set 9898 * their `cache` property to `true` will now use this cache object. 9899 * 9900 * If you set the default cache to `false` then only requests that specify their own custom 9901 * cache object will be cached. 9902 * 9903 * ## Interceptors 9904 * 9905 * Before you start creating interceptors, be sure to understand the 9906 * {@link ng.$q $q and deferred/promise APIs}. 9907 * 9908 * For purposes of global error handling, authentication, or any kind of synchronous or 9909 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 9910 * able to intercept requests before they are handed to the server and 9911 * responses before they are handed over to the application code that 9912 * initiated these requests. The interceptors leverage the {@link ng.$q 9913 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 9914 * 9915 * The interceptors are service factories that are registered with the `$httpProvider` by 9916 * adding them to the `$httpProvider.interceptors` array. The factory is called and 9917 * injected with dependencies (if specified) and returns the interceptor. 9918 * 9919 * There are two kinds of interceptors (and two kinds of rejection interceptors): 9920 * 9921 * * `request`: interceptors get called with a http {@link $http#usage config} object. The function is free to 9922 * modify the `config` object or create a new one. The function needs to return the `config` 9923 * object directly, or a promise containing the `config` or a new `config` object. 9924 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 9925 * resolved with a rejection. 9926 * * `response`: interceptors get called with http `response` object. The function is free to 9927 * modify the `response` object or create a new one. The function needs to return the `response` 9928 * object directly, or as a promise containing the `response` or a new `response` object. 9929 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 9930 * resolved with a rejection. 9931 * 9932 * 9933 * ```js 9934 * // register the interceptor as a service 9935 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 9936 * return { 9937 * // optional method 9938 * 'request': function(config) { 9939 * // do something on success 9940 * return config; 9941 * }, 9942 * 9943 * // optional method 9944 * 'requestError': function(rejection) { 9945 * // do something on error 9946 * if (canRecover(rejection)) { 9947 * return responseOrNewPromise 9948 * } 9949 * return $q.reject(rejection); 9950 * }, 9951 * 9952 * 9953 * 9954 * // optional method 9955 * 'response': function(response) { 9956 * // do something on success 9957 * return response; 9958 * }, 9959 * 9960 * // optional method 9961 * 'responseError': function(rejection) {
9962 * // do something on error 9963 * if (canRecover(rejection)) { 9964 * return responseOrNewPromise 9965 * } 9966 * return $q.reject(rejection); 9967 * } 9968 * }; 9969 * }); 9970 * 9971 * $httpProvider.interceptors.push('myHttpInterceptor'); 9972 * 9973 * 9974 * // alternatively, register the interceptor via an anonymous factory 9975 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 9976 * return { 9977 * 'request': function(config) { 9978 * // same as above 9979 * }, 9980 * 9981 * 'response': function(response) { 9982 * // same as above 9983 * } 9984 * }; 9985 * }); 9986 * ``` 9987 * 9988 * ## Security Considerations 9989 * 9990 * When designing web applications, consider security threats from: 9991 * 9992 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 9993 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 9994 * 9995 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 9996 * pre-configured with strategies that address these issues, but for this to work backend server 9997 * cooperation is required. 9998 * 9999 * ### JSON Vulnerability Protection 10000 * 10001 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 10002 * allows third party website to turn your JSON resource URL into 10003 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 10004 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 10005 * Angular will automatically strip the prefix before processing it as JSON. 10006 * 10007 * For example if your server needs to return: 10008 * ```js 10009 * ['one','two'] 10010 * ``` 10011 * 10012 * which is vulnerable to attack, your server can return: 10013 * ```js 10014 * )]}', 10015 * ['one','two'] 10016 * ``` 10017 * 10018 * Angular will strip the prefix, before processing the JSON. 10019 * 10020 * 10021 * ### Cross Site Request Forgery (XSRF) Protection 10022 * 10023 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which 10024 * an unauthorized site can gain your user's private data. Angular provides a mechanism 10025 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 10026 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 10027 * JavaScript that runs on your domain could read the cookie, your server can be assured that 10028 * the XHR came from JavaScript running on your domain. The header will not be set for 10029 * cross-domain requests. 10030 * 10031 * To take advantage of this, your server needs to set a token in a JavaScript readable session 10032 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 10033 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 10034 * that only JavaScript running on your domain could have sent the request. The token must be 10035 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 10036 * making up its own tokens). We recommend that the token is a digest of your site's 10037 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 10038 * for added security. 10039 * 10040 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 10041 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 10042 * or the per-request config object. 10043 * 10044 * In order to prevent collisions in environments where multiple Angular apps share the 10045 * same domain or subdomain, we recommend that each application uses unique cookie name. 10046 * 10047 * @param {object} config Object describing the request to be made and how it should be 10048 * processed. The object has following properties: 10049 * 10050 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 10051 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 10052 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be serialized 10053 * with the `paramSerializer` and appended as GET parameters. 10054 * - **data** â `{string|Object}` â Data to be sent as the request message data. 10055 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 10056 * HTTP headers to send to the server. If the return value of a function is null, the 10057 * header will not be sent. Functions accept a config object as an argument. 10058 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 10059 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 10060 * - **transformRequest** â 10061 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>
10061}` â 10062 * transform function or an array of such functions. The transform function takes the http 10063 * request body and headers and returns its transformed (typically serialized) version. 10064 * See {@link ng.$http#overriding-the-default-transformations-per-request 10065 * Overriding the Default Transformations} 10066 * - **transformResponse** â 10067 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` â 10068 * transform function or an array of such functions. The transform function takes the http 10069 * response body, headers and status and returns its transformed (typically deserialized) version. 10070 * See {@link ng.$http#overriding-the-default-transformations-per-request 10071 * Overriding the Default TransformationjqLiks} 10072 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to 10073 * prepare the string representation of request parameters (specified as an object). 10074 * If specified as string, it is interpreted as function registered with the 10075 * {@link $injector $injector}, which means you can create your own serializer 10076 * by registering it as a {@link auto.$provide#service service}. 10077 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; 10078 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} 10079 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 10080 * GET request, otherwise if a cache instance built with 10081 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 10082 * caching. 10083 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 10084 * that should abort the request when resolved. 10085 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 10086 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 10087 * for more information. 10088 * - **responseType** - `{string}` - see 10089 * [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype). 10090 * 10091 * @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object 10092 * when the request succeeds or fails. 10093 * 10094 * 10095 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 10096 * requests. This is primarily meant to be used for debugging purposes. 10097 * 10098 * 10099 * @example 10100<example module="httpExample"> 10101<file name="index.html"> 10102 <div ng-controller="FetchController"> 10103 <select ng-model="method" aria-label="Request method"> 10104 <option>GET</option> 10105 <option>JSONP</option> 10106 </select> 10107 <input type="text" ng-model="url" size="80" aria-label="URL" /> 10108 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 10109 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 10110 <button id="samplejsonpbtn" 10111 ng-click="updateModel('JSONP', 10112 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 10113 Sample JSONP 10114 </button> 10115 <button id="invalidjsonpbtn" 10116 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 10117 Invalid JSONP 10118 </button> 10119 <pre>http status code: {{status}}</pre> 10120 <pre>http response data: {{data}}</pre> 10121 </div> 10122</file> 10123<file name="script.js"> 10124 angular.module('httpExample', []) 10125 .controller('FetchController', ['$scope', '$http', '$templateCache', 10126 function($scope, $http, $templateCache) { 10127 $scope.method = 'GET'; 10128 $scope.url = 'http-hello.html'; 10129 10130 $scope.fetch = function() { 10131 $scope.code = null; 10132 $scope.response = null; 10133 10134 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 10135 then(function(response) { 10136 $scope.status = response.status; 10137 $scope.data = response.data; 10138 }, function(response) { 10139 $scope.data = response.data || "Request failed"; 10140 $scope.status = response.status; 10141 }); 10142 }; 10143 10144 $scope.updateModel = function(method, url) { 10145 $scope.method = method; 10146 $scope.url = url; 10147 }; 10148 }]); 10149</file> 10150<file name="http-hello.html"> 10151 Hello, $http! 10152</file> 10153<file name="protractor.js" type="protractor"> 10154 var status = element(by.binding('status')); 10155 var data = element(by.binding('data')); 10156 var fetchBtn = element(by.id('fetchbtn')); 10157 var sampleGetBtn = element(by.id('samplegetbtn')); 10158 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 10159 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 10160 10161 it('should make an xhr GET request', function() { 10162 sampleGetBtn.click(); 10163 fetchBtn.click(); 10164 expect(status.getText()).toMatch('200'); 10165 expect(data.getText()).toMatch(/Hello, \$http!/); 10166 }); 10167 10168// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 10169// it('should make a JSONP request to angularjs.org', function() { 10170// sampleJsonpBtn.click();
10171// fetchBtn.click(); 10172// expect(status.getText()).toMatch('200'); 10173// expect(data.getText()).toMatch(/Super Hero!/); 10174// }); 10175 10176 it('should make JSONP request to invalid URL and invoke the error handler', 10177 function() { 10178 invalidJsonpBtn.click(); 10179 fetchBtn.click(); 10180 expect(status.getText()).toMatch('0'); 10181 expect(data.getText()).toMatch('Request failed'); 10182 }); 10183</file> 10184</example> 10185 */ 10186 function $http(requestConfig) { 10187 10188 if (!angular.isObject(requestConfig)) { 10189 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 10190 } 10191 10192 var config = extend({ 10193 method: 'get', 10194 transformRequest: defaults.transformRequest, 10195 transformResponse: defaults.transformResponse, 10196 paramSerializer: defaults.paramSerializer 10197 }, requestConfig); 10198 10199 config.headers = mergeHeaders(requestConfig); 10200 config.method = uppercase(config.method); 10201 config.paramSerializer = isString(config.paramSerializer) ? 10202 $injector.get(config.paramSerializer) : config.paramSerializer; 10203 10204 var serverRequest = function(config) { 10205 var headers = config.headers; 10206 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 10207 10208 // strip content-type if data is undefined 10209 if (isUndefined(reqData)) { 10210 forEach(headers, function(value, header) { 10211 if (lowercase(header) === 'content-type') { 10212 delete headers[header]; 10213 } 10214 }); 10215 } 10216 10217 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 10218 config.withCredentials = defaults.withCredentials; 10219 } 10220 10221 // send request 10222 return sendReq(config, reqData).then(transformResponse, transformResponse); 10223 }; 10224 10225 var chain = [serverRequest, undefined]; 10226 var promise = $q.when(config); 10227 10228 // apply interceptors 10229 forEach(reversedInterceptors, function(interceptor) { 10230 if (interceptor.request || interceptor.requestError) { 10231 chain.unshift(interceptor.request, interceptor.requestError); 10232 } 10233 if (interceptor.response || interceptor.responseError) { 10234 chain.push(interceptor.response, interceptor.responseError); 10235 } 10236 }); 10237 10238 while (chain.length) { 10239 var thenFn = chain.shift(); 10240 var rejectFn = chain.shift(); 10241 10242 promise = promise.then(thenFn, rejectFn); 10243 } 10244 10245 if (useLegacyPromise) { 10246 promise.success = function(fn) { 10247 assertArgFn(fn, 'fn'); 10248 10249 promise.then(function(response) { 10250 fn(response.data, response.status, response.headers, config); 10251 }); 10252 return promise; 10253 }; 10254 10255 promise.error = function(fn) { 10256 assertArgFn(fn, 'fn'); 10257 10258 promise.then(null, function(response) { 10259 fn(response.data, response.status, response.headers, config); 10260 }); 10261 return promise; 10262 }; 10263 } else { 10264 promise.success = $httpMinErrLegacyFn('success'); 10265 promise.error = $httpMinErrLegacyFn('error'); 10266 } 10267 10268 return promise; 10269 10270 function transformResponse(response) { 10271 // make a copy since the response must be cacheable 10272 var resp = extend({}, response); 10273 if (!response.data) { 10274 resp.data = response.data; 10275 } else { 10276 resp.data = transformData(response.data, response.headers, response.status, config.transformResponse); 10277 } 10278 return (isSuccess(response.status)) 10279 ? resp 10280 : $q.reject(resp); 10281 } 10282 10283 function executeHeaderFns(headers, config) { 10284 var headerContent, processedHeaders = {}; 10285 10286 forEach(headers, function(headerFn, header) { 10287 if (isFunction(headerFn)) { 10288 headerContent = headerFn(config); 10289 if (headerContent != null) { 10290 processedHeaders[header] = headerContent; 10291 } 10292 } else { 10293 processedHeaders[header] = headerFn; 10294 } 10295 }); 10296 10297 return processedHeaders; 10298 } 10299 10300 function mergeHeaders(config) { 10301 var defHeaders = defaults.headers, 10302 reqHeaders = extend({}, config.headers), 10303 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 10304 10305 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 10306
vendor: 9,399 bytes, lines 10307-10607
10307 // using for-in instead of forEach to avoid unecessary iteration after header has been found 10308 defaultHeadersIteration: 10309 for (defHeaderName in defHeaders) { 10310 lowercaseDefHeaderName = lowercase(defHeaderName); 10311 10312 for (reqHeaderName in reqHeaders) { 10313 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 10314 continue defaultHeadersIteration; 10315 } 10316 } 10317 10318 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 10319 } 10320 10321 // execute if header value is a function for merged headers 10322 return executeHeaderFns(reqHeaders, shallowCopy(config)); 10323 } 10324 } 10325 10326 $http.pendingRequests = []; 10327 10328 /** 10329 * @ngdoc method 10330 * @name $http#get 10331 * 10332 * @description 10333 * Shortcut method to perform `GET` request. 10334 * 10335 * @param {string} url Relative or absolute URL specifying the destination of the request 10336 * @param {Object=} config Optional configuration object 10337 * @returns {HttpPromise} Future object 10338 */ 10339 10340 /** 10341 * @ngdoc method 10342 * @name $http#delete 10343 * 10344 * @description 10345 * Shortcut method to perform `DELETE` request. 10346 * 10347 * @param {string} url Relative or absolute URL specifying the destination of the request 10348 * @param {Object=} config Optional configuration object 10349 * @returns {HttpPromise} Future object 10350 */ 10351 10352 /** 10353 * @ngdoc method 10354 * @name $http#head 10355 * 10356 * @description 10357 * Shortcut method to perform `HEAD` request. 10358 * 10359 * @param {string} url Relative or absolute URL specifying the destination of the request 10360 * @param {Object=} config Optional configuration object 10361 * @returns {HttpPromise} Future object 10362 */ 10363 10364 /** 10365 * @ngdoc method 10366 * @name $http#jsonp 10367 * 10368 * @description 10369 * Shortcut method to perform `JSONP` request. 10370 * 10371 * @param {string} url Relative or absolute URL specifying the destination of the request. 10372 * The name of the callback should be the string `JSON_CALLBACK`. 10373 * @param {Object=} config Optional configuration object 10374 * @returns {HttpPromise} Future object 10375 */ 10376 createShortMethods('get', 'delete', 'head', 'jsonp'); 10377 10378 /** 10379 * @ngdoc method 10380 * @name $http#post 10381 * 10382 * @description 10383 * Shortcut method to perform `POST` request. 10384 * 10385 * @param {string} url Relative or absolute URL specifying the destination of the request 10386 * @param {*} data Request content 10387 * @param {Object=} config Optional configuration object 10388 * @returns {HttpPromise} Future object 10389 */ 10390 10391 /** 10392 * @ngdoc method 10393 * @name $http#put 10394 * 10395 * @description 10396 * Shortcut method to perform `PUT` request. 10397 * 10398 * @param {string} url Relative or absolute URL specifying the destination of the request 10399 * @param {*} data Request content 10400 * @param {Object=} config Optional configuration object 10401 * @returns {HttpPromise} Future object 10402 */ 10403 10404 /** 10405 * @ngdoc method 10406 * @name $http#patch 10407 * 10408 * @description 10409 * Shortcut method to perform `PATCH` request. 10410 * 10411 * @param {string} url Relative or absolute URL specifying the destination of the request 10412 * @param {*} data Request content 10413 * @param {Object=} config Optional configuration object 10414 * @returns {HttpPromise} Future object 10415 */ 10416 createShortMethodsWithData('post', 'put', 'patch'); 10417 10418 /** 10419 * @ngdoc property 10420 * @name $http#defaults 10421 * 10422 * @description 10423 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 10424 * default headers, withCredentials as well as request and response transformations. 10425 * 10426 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 10427 */ 10428 $http.defaults = defaults; 10429 10430 10431 return $http; 10432 10433 10434 function createShortMethods(names) { 10435 forEach(arguments, function(name) { 10436 $http[name] = function(url, config) { 10437 return $http(extend({}, config || {}, { 10438 method: name, 10439 url: url 10440 })); 10441 }; 10442 }); 10443 } 10444 10445 10446 function createShortMethodsWithData(name) { 10447 forEach(arguments, function(name) { 10448 $http[name] = function(url, data, config) { 10449 return $http(extend({}, config || {}, { 10450 method: name, 10451 url: url, 10452 data: data 10453 })); 10454 }; 10455 }); 10456 } 10457 10458 10459 /** 10460 * Makes the request. 10461 * 10462 * !!! ACCESSES CLOSURE VARS: 10463 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 10464 */ 10465 function sendReq(config, reqData) { 10466 var deferred = $q.defer(), 10467 promise = deferred.promise, 10468 cache, 10469 cachedResp, 10470 reqHeaders = config.headers, 10471 url = buildUrl(config.url, config.paramSerializer(config.params)); 10472 10473 $http.pendingRequests.push(config); 10474 promise.then(removePendingReq, removePendingReq); 10475 10476 10477 if ((config.cache || defaults.cache) && config.cache !== false && 10478 (config.method === 'GET' || config.method === 'JSONP')) { 10479 cache = isObject(config.cache) ? config.cache 10480 : isObject(defaults.cache) ? defaults.cache 10481 : defaultCache; 10482 } 10483 10484 if (cache) { 10485 cachedResp = cache.get(url); 10486 if (isDefined(cachedResp)) { 10487 if (isPromiseLike(cachedResp)) { 10488 // cached request has already been sent, but there is no response yet 10489 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 10490 } else { 10491 // serving from cache 10492 if (isArray(cachedResp)) { 10493 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 10494 } else { 10495 resolvePromise(cachedResp, 200, {}, 'OK'); 10496 } 10497 } 10498 } else { 10499 // put the promise for the non-transformed response into cache as a placeholder 10500 cache.put(url, promise); 10501 } 10502 } 10503 10504 10505 // if we won't have the response in cache, set the xsrf headers and 10506 // send the request to the backend 10507 if (isUndefined(cachedResp)) { 10508 var xsrfValue = urlIsSameOrigin(config.url) 10509 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] 10510 : undefined; 10511 if (xsrfValue) { 10512 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 10513 } 10514 10515 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 10516 config.withCredentials, config.responseType); 10517 } 10518 10519 return promise; 10520 10521 10522 /** 10523 * Callback registered to $httpBackend(): 10524 * - caches the response if desired 10525 * - resolves the raw $http promise 10526 * - calls $apply 10527 */ 10528 function done(status, response, headersString, statusText) { 10529 if (cache) { 10530 if (isSuccess(status)) { 10531 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 10532 } else { 10533 // remove promise from the cache 10534 cache.remove(url); 10535 } 10536 } 10537 10538 function resolveHttpPromise() { 10539 resolvePromise(response, status, headersString, statusText); 10540 } 10541 10542 if (useApplyAsync) { 10543 $rootScope.$applyAsync(resolveHttpPromise); 10544 } else { 10545 resolveHttpPromise(); 10546 if (!$rootScope.$$phase) $rootScope.$apply(); 10547 } 10548 } 10549 10550 10551 /** 10552 * Resolves the raw $http promise. 10553 */ 10554 function resolvePromise(response, status, headers, statusText) { 10555 //status: HTTP response status code, 0, -1 (aborted by timeout / promise) 10556 status = status >= -1 ? status : 0; 10557 10558 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 10559 data: response, 10560 status: status, 10561 headers: headersGetter(headers), 10562 config: config, 10563 statusText: statusText 10564 }); 10565 } 10566 10567 function resolvePromiseWithResult(result) { 10568 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); 10569 } 10570 10571 function removePendingReq() { 10572 var idx = $http.pendingRequests.indexOf(config); 10573 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 10574 } 10575 } 10576 10577 10578 function buildUrl(url, serializedParams) { 10579 if (serializedParams.length > 0) { 10580 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams; 10581 } 10582 return url; 10583 } 10584 }]; 10585} 10586 10587/** 10588 * @ngdoc service 10589 * @name $xhrFactory 10590 * 10591 * @description 10592 * Factory function used to create XMLHttpRequest objects. 10593 * 10594 * Replace or decorate this service to create your own custom XMLHttpRequest objects. 10595 * 10596 * ``` 10597 * angular.module('myApp', []) 10598 * .factory('$xhrFactory', function() { 10599 * return function createXhr(method, url) { 10600 * return new window.XMLHttpRequest({mozSystem: true}); 10601 * }; 10602 * }); 10603 * ``` 10604 * 10605 * @param {string} method HTTP method of the request (GET, POST, PUT, ..) 10606 * @param {string} url URL of the request. 10607 */
10608function $xhrFactoryProvider() { 10609 this.$get = function() { 10610 return function createXhr() { 10611 return new window.XMLHttpRequest(); 10612 }; 10613 }; 10614} 10615 10616/** 10617 * @ngdoc service 10618 * @name $httpBackend 10619 * @requires $window 10620 * @requires $document 10621 * @requires $xhrFactory 10622 * 10623 * @description 10624 * HTTP backend used by the {@link ng.$http service} that delegates to 10625 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 10626 * 10627 * You should never need to use this service directly, instead use the higher-level abstractions: 10628 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 10629 * 10630 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 10631 * $httpBackend} which can be trained with responses. 10632 */ 10633function $HttpBackendProvider() { 10634 this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) { 10635 return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]); 10636 }]; 10637} 10638 10639function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 10640 // TODO(vojta): fix the signature 10641 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 10642 $browser.$$incOutstandingRequestCount(); 10643 url = url || $browser.url(); 10644 10645 if (lowercase(method) == 'jsonp') { 10646 var callbackId = '_' + (callbacks.counter++).toString(36); 10647 callbacks[callbackId] = function(data) { 10648 callbacks[callbackId].data = data; 10649 callbacks[callbackId].called = true; 10650 }; 10651 10652 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 10653 callbackId, function(status, text) { 10654 completeRequest(callback, status, callbacks[callbackId].data, "", text); 10655 callbacks[callbackId] = noop; 10656 }); 10657 } else { 10658 10659 var xhr = createXhr(method, url); 10660 10661 xhr.open(method, url, true); 10662 forEach(headers, function(value, key) { 10663 if (isDefined(value)) { 10664 xhr.setRequestHeader(key, value); 10665 } 10666 }); 10667 10668 xhr.onload = function requestLoaded() { 10669 var statusText = xhr.statusText || ''; 10670 10671 // responseText is the old-school way of retrieving response (supported by IE9) 10672 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 10673 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 10674 10675 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 10676 var status = xhr.status === 1223 ? 204 : xhr.status; 10677 10678 // fix status code when it is 0 (0 status is undocumented). 10679 // Occurs when accessing file resources or on Android 4.1 stock browser 10680 // while retrieving files from application cache. 10681 if (status === 0) { 10682 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 10683 } 10684 10685 completeRequest(callback, 10686 status, 10687 response, 10688 xhr.getAllResponseHeaders(), 10689 statusText); 10690 }; 10691 10692 var requestError = function() { 10693 // The response is always empty 10694 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 10695 completeRequest(callback, -1, null, null, ''); 10696 }; 10697 10698 xhr.onerror = requestError; 10699 xhr.onabort = requestError; 10700 10701 if (withCredentials) { 10702 xhr.withCredentials = true; 10703 } 10704 10705 if (responseType) { 10706 try { 10707 xhr.responseType = responseType; 10708 } catch (e) { 10709 // WebKit added support for the json responseType value on 09/03/2013 10710 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 10711 // known to throw when setting the value "json" as the response type. Other older 10712 // browsers implementing the responseType 10713 // 10714 // The json response type can be ignored if not supported, because JSON payloads are 10715 // parsed on the client-side regardless. 10716 if (responseType !== 'json') { 10717 throw e; 10718 } 10719 } 10720 } 10721
10722 xhr.send(isUndefined(post) ? null : post); 10723 } 10724 10725 if (timeout > 0) { 10726 var timeoutId = $browserDefer(timeoutRequest, timeout); 10727 } else if (isPromiseLike(timeout)) { 10728 timeout.then(timeoutRequest); 10729 } 10730 10731 10732 function timeoutRequest() { 10733 jsonpDone && jsonpDone(); 10734 xhr && xhr.abort(); 10735 } 10736 10737 function completeRequest(callback, status, response, headersString, statusText) { 10738 // cancel timeout and subsequent timeout promise resolution 10739 if (isDefined(timeoutId)) { 10740 $browserDefer.cancel(timeoutId); 10741 } 10742 jsonpDone = xhr = null; 10743 10744 callback(status, response, headersString, statusText); 10745 $browser.$$completeOutstandingRequest(noop); 10746 } 10747 }; 10748 10749 function jsonpReq(url, callbackId, done) { 10750 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: 10751 // - fetches local scripts via XHR and evals them 10752 // - adds and immediately removes script elements from the document 10753 var script = rawDocument.createElement('script'), callback = null; 10754 script.type = "text/javascript"; 10755 script.src = url; 10756 script.async = true; 10757 10758 callback = function(event) { 10759 removeEventListenerFn(script, "load", callback); 10760 removeEventListenerFn(script, "error", callback); 10761 rawDocument.body.removeChild(script); 10762 script = null; 10763 var status = -1; 10764 var text = "unknown"; 10765 10766 if (event) { 10767 if (event.type === "load" && !callbacks[callbackId].called) { 10768 event = { type: "error" }; 10769 } 10770 text = event.type; 10771 status = event.type === "error" ? 404 : 200; 10772 } 10773 10774 if (done) { 10775 done(status, text); 10776 } 10777 }; 10778 10779 addEventListenerFn(script, "load", callback); 10780 addEventListenerFn(script, "error", callback); 10781 rawDocument.body.appendChild(script); 10782 return callback; 10783 } 10784} 10785 10786var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); 10787$interpolateMinErr.throwNoconcat = function(text) { 10788 throw $interpolateMinErr('noconcat', 10789 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 10790 "interpolations that concatenate multiple expressions when a trusted value is " + 10791 "required. See http://docs.angularjs.org/api/ng.$sce", text); 10792}; 10793 10794$interpolateMinErr.interr = function(text, err) { 10795 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); 10796}; 10797 10798/** 10799 * @ngdoc provider 10800 * @name $interpolateProvider 10801 * 10802 * @description 10803 *
10804 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 10805 * 10806 * @example 10807<example module="customInterpolationApp"> 10808<file name="index.html"> 10809<script> 10810 var customInterpolationApp = angular.module('customInterpolationApp', []); 10811 10812 customInterpolationApp.config(function($interpolateProvider) { 10813 $interpolateProvider.startSymbol('//'); 10814 $interpolateProvider.endSymbol('//'); 10815 }); 10816 10817 10818 customInterpolationApp.controller('DemoController', function() { 10819 this.label = "This binding is brought you by // interpolation symbols."; 10820 }); 10821</script> 10822<div ng-app="App" ng-controller="DemoController as demo"> 10823 //demo.label// 10824</div> 10825</file> 10826<file name="protractor.js" type="protractor"> 10827 it('should interpolate binding with custom symbols', function() { 10828 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 10829 }); 10830</file> 10831</example> 10832 */ 10833function $InterpolateProvider() { 10834 var startSymbol = '{{'; 10835 var endSymbol = '}}'; 10836 10837 /** 10838 * @ngdoc method 10839 * @name $interpolateProvider#startSymbol 10840 * @description 10841 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 10842 * 10843 * @param {string=} value new value to set the starting symbol to. 10844 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 10845 */ 10846 this.startSymbol = function(value) { 10847 if (value) { 10848 startSymbol = value; 10849 return this; 10850 } else { 10851 return startSymbol; 10852 } 10853 }; 10854 10855 /** 10856 * @ngdoc method 10857 * @name $interpolateProvider#endSymbol 10858 * @description 10859 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 10860 * 10861 * @param {string=} value new value to set the ending symbol to. 10862 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 10863 */ 10864 this.endSymbol = function(value) { 10865 if (value) { 10866 endSymbol = value; 10867 return this; 10868 } else { 10869 return endSymbol; 10870 } 10871 }; 10872 10873 10874 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 10875 var startSymbolLength = startSymbol.length, 10876 endSymbolLength = endSymbol.length, 10877 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 10878 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 10879 10880 function escape(ch) { 10881 return '\\\\\\' + ch; 10882 } 10883 10884 function unescapeText(text) { 10885 return text.replace(escapedStartRegexp, startSymbol). 10886 replace(escapedEndRegexp, endSymbol); 10887 } 10888 10889 function stringify(value) { 10890 if (value == null) { // null || undefined 10891 return ''; 10892 } 10893 switch (typeof value) { 10894 case 'string': 10895 break; 10896 case 'number': 10897 value = '' + value; 10898 break; 10899 default: 10900 value = toJson(value); 10901 } 10902 10903 return value; 10904 } 10905 10906 /** 10907 * @ngdoc service 10908 * @name $interpolate 10909 * @kind function 10910 * 10911 * @requires $parse 10912 * @requires $sce 10913 * 10914 * @description 10915 * 10916 * Compiles a string with markup into an interpolation function. This service is used by the 10917 * HTML {@link ng.$compile $compile} service for data binding. See 10918 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 10919 * interpolation markup. 10920 * 10921 * 10922 * ```js 10923 * var $interpolate = ...; // injected 10924 * var exp = $interpolate('Hello {{name | uppercase}}!'); 10925 * expect(exp({name:'Angular'})).toEqual('Hello ANGULAR!'); 10926 * ``` 10927 * 10928 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 10929 * `true`, the interpolation function will return `undefined` unless all embedded expressions 10930 * evaluate to a value other than `undefined`. 10931 * 10932 * ```js 10933 * var $interpolate = ...; // injected 10934 * var context = {greeting: 'Hello', name: undefined }; 10935 * 10936 * // default "forgiving" mode 10937 * var exp = $interpolate('{{greeting}} {{name}}!'); 10938 * expect(exp(context)).toEqual('Hello !'); 10939 * 10940 * // "allOrNothing" mode 10941 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 10942 * expect(exp(context)).toBeUndefined(); 10943 * context.name = 'Angular'; 10944 * expect(exp(context)).toEqual('Hello Angular!'); 10945 * ``` 10946 * 10947 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 10948 * 10949 * ####Escaped Interpolation 10950 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 10951 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 10952 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 10953 * or binding. 10954 * 10955 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 10956 * degree, while also enabling code examples to work without relying on the 10957 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 10958 * 10959 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 10960 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 10961 * interpolation start/end markers with their escaped counterparts.** 10962 * 10963 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 10964 * output when the $interpolate service processes the text. So, for HTML elements interpolated 10965 * by {@link ng.$compile $compile}
10965, or otherwise interpolated with the `mustHaveExpression` parameter 10966 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 10967 * this is typically useful only when user-data is used in rendering a template from the server, or 10968 * when otherwise untrusted data is used by a directive. 10969 * 10970 * <example> 10971 * <file name="index.html"> 10972 * <div ng-init="username='A user'"> 10973 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 10974 * </p> 10975 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 10976 * application, but fails to accomplish their task, because the server has correctly 10977 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 10978 * characters.</p> 10979 * <p>Instead, the result of the attempted script injection is visible, and can be removed 10980 * from the database by an administrator.</p> 10981 * </div> 10982 * </file> 10983 * </example> 10984 * 10985 * @param {string} text The text with markup to interpolate. 10986 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 10987 * embedded expression in order to return an interpolation function. Strings with no 10988 * embedded expression will return null for the interpolation function. 10989 * @param {string=} trustedContext when provided, the returned function passes the interpolated 10990 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 10991 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 10992 * provides Strict Contextual Escaping for details. 10993 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 10994 * unless all embedded expressions evaluate to a value other than `undefined`. 10995 * @returns {function(context)} an interpolation function which is used to compute the 10996 * interpolated string. The function has these parameters: 10997 * 10998 * - `context`: evaluation context for all expressions embedded in the interpolated text 10999 */ 11000 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 11001 allOrNothing = !!allOrNothing; 11002 var startIndex, 11003 endIndex, 11004 index = 0, 11005 expressions = [], 11006 parseFns = [], 11007 textLength = text.length, 11008 exp, 11009 concat = [], 11010 expressionPositions = []; 11011 11012 while (index < textLength) { 11013 if (((startIndex = text.indexOf(startSymbol, index)) != -1) && 11014 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { 11015 if (index !== startIndex) { 11016 concat.push(unescapeText(text.substring(index, startIndex))); 11017 } 11018 exp = text.substring(startIndex + startSymbolLength, endIndex); 11019 expressions.push(exp); 11020 parseFns.push($parse(exp, parseStringifyInterceptor)); 11021 index = endIndex + endSymbolLength; 11022 expressionPositions.push(concat.length); 11023 concat.push(''); 11024 } else { 11025 // we did not find an interpolation, so we have to add the remainder to the separators array 11026 if (index !== textLength) { 11027 concat.push(unescapeText(text.substring(index))); 11028 } 11029 break; 11030 } 11031 } 11032 11033 // Concatenating expressions makes it hard to reason about whether some combination of 11034 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 11035 // single expression be used for iframe[src], object[src], etc., we ensure that the value 11036 // that's used is assigned or constructed by some JS code somewhere that is more testable or 11037 // make it obvious that you bound the value to some user controlled value. This helps reduce 11038 // the load when auditing for XSS issues. 11039 if (trustedContext && concat.length > 1) { 11040 $interpolateMinErr.throwNoconcat(text); 11041 } 11042 11043 if (!mustHaveExpression || expressions.length) { 11044 var compute = function(values) { 11045 for (var i = 0, ii = expressions.length; i < ii; i++) { 11046 if (allOrNothing && isUndefined(values[i])) return; 11047 concat[expressionPositions[i]] = values[i]; 11048 } 11049 return concat.join(''); 11050 }; 11051 11052 var getValue = function(value) { 11053 return trustedContext ? 11054 $sce.getTrusted(trustedContext, value) : 11055 $sce.valueOf(value); 11056 }; 11057 11058 return extend(function interpolationFn(context) { 11059 var i = 0; 11060 var ii = expressions.length; 11061 var values = new Array(ii); 11062 11063 try { 11064 for (; i < ii; i++) { 11065 values[i] = parseFns[i](context); 11066 } 11067 11068 return compute(values); 11069 } catch (err) { 11070 $exceptionHandler($interpolateMinErr.interr(text, err));
vendor: 4,275 bytes, lines 11071-11181
11071 } 11072 11073 }, { 11074 // all of these properties are undocumented for now 11075 exp: text, //just for compatibility with regular watchers created via $watch 11076 expressions: expressions, 11077 $$watchDelegate: function(scope, listener) { 11078 var lastValue; 11079 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { 11080 var currValue = compute(values); 11081 if (isFunction(listener)) { 11082 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 11083 } 11084 lastValue = currValue; 11085 }); 11086 } 11087 }); 11088 } 11089 11090 function parseStringifyInterceptor(value) { 11091 try { 11092 value = getValue(value); 11093 return allOrNothing && !isDefined(value) ? value : stringify(value); 11094 } catch (err) { 11095 $exceptionHandler($interpolateMinErr.interr(text, err)); 11096 } 11097 } 11098 } 11099 11100 11101 /** 11102 * @ngdoc method 11103 * @name $interpolate#startSymbol 11104 * @description 11105 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 11106 * 11107 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 11108 * the symbol. 11109 * 11110 * @returns {string} start symbol. 11111 */ 11112 $interpolate.startSymbol = function() { 11113 return startSymbol; 11114 }; 11115 11116 11117 /** 11118 * @ngdoc method 11119 * @name $interpolate#endSymbol 11120 * @description 11121 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 11122 * 11123 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 11124 * the symbol. 11125 * 11126 * @returns {string} end symbol. 11127 */ 11128 $interpolate.endSymbol = function() { 11129 return endSymbol; 11130 }; 11131 11132 return $interpolate; 11133 }]; 11134} 11135 11136function $IntervalProvider() { 11137 this.$get = ['$rootScope', '$window', '$q', '$$q', 11138 function($rootScope, $window, $q, $$q) { 11139 var intervals = {}; 11140 11141 11142 /** 11143 * @ngdoc service 11144 * @name $interval 11145 * 11146 * @description 11147 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 11148 * milliseconds. 11149 * 11150 * The return value of registering an interval function is a promise. This promise will be 11151 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 11152 * run indefinitely if `count` is not defined. The value of the notification will be the 11153 * number of iterations that have run. 11154 * To cancel an interval, call `$interval.cancel(promise)`. 11155 * 11156 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 11157 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 11158 * time. 11159 * 11160 * <div class="alert alert-warning"> 11161 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 11162 * with them. In particular they are not automatically destroyed when a controller's scope or a 11163 * directive's element are destroyed. 11164 * You should take this into consideration and make sure to always cancel the interval at the 11165 * appropriate moment. See the example below for more details on how and when to do this. 11166 * </div> 11167 * 11168 * @param {function()} fn A function that should be called repeatedly. 11169 * @param {number} delay Number of milliseconds between each function call. 11170 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 11171 * indefinitely. 11172 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 11173 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 11174 * @param {...*=} Pass additional parameters to the executed function. 11175 * @returns {promise} A promise which will be notified on each iteration. 11176 * 11177 * @example 11178 * <example module="intervalExample"> 11179 * <file name="index.html"> 11180 * <script> 11181 * angular.module('intervalExample', [])
11182 * .controller('ExampleController', ['$scope', '$interval', 11183 * function($scope, $interval) { 11184 * $scope.format = 'M/d/yy h:mm:ss a'; 11185 * $scope.blood_1 = 100; 11186 * $scope.blood_2 = 120; 11187 * 11188 * var stop; 11189 * $scope.fight = function() { 11190 * // Don't start a new fight if we are already fighting 11191 * if ( angular.isDefined(stop) ) return; 11192 * 11193 * stop = $interval(function() { 11194 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 11195 * $scope.blood_1 = $scope.blood_1 - 3; 11196 * $scope.blood_2 = $scope.blood_2 - 4; 11197 * } else { 11198 * $scope.stopFight(); 11199 * } 11200 * }, 100); 11201 * }; 11202 * 11203 * $scope.stopFight = function() { 11204 * if (angular.isDefined(stop)) { 11205 * $interval.cancel(stop); 11206 * stop = undefined; 11207 * } 11208 * }; 11209 * 11210 * $scope.resetFight = function() { 11211 * $scope.blood_1 = 100; 11212 * $scope.blood_2 = 120; 11213 * }; 11214 * 11215 * $scope.$on('$destroy', function() { 11216 * // Make sure that the interval is destroyed too 11217 * $scope.stopFight(); 11218 * }); 11219 * }]) 11220 * // Register the 'myCurrentTime' directive factory method. 11221 * // We inject $interval and dateFilter service since the factory method is DI. 11222 * .directive('myCurrentTime', ['$interval', 'dateFilter', 11223 * function($interval, dateFilter) { 11224 * // return the directive link function. (compile function not needed) 11225 * return function(scope, element, attrs) { 11226 * var format, // date format 11227 * stopTime; // so that we can cancel the time updates 11228 * 11229 * // used to update the UI 11230 * function updateTime() { 11231 * element.text(dateFilter(new Date(), format)); 11232 * } 11233 * 11234 * // watch the expression, and update the UI on change. 11235 * scope.$watch(attrs.myCurrentTime, function(value) { 11236 * format = value; 11237 * updateTime(); 11238 * }); 11239 * 11240 * stopTime = $interval(updateTime, 1000); 11241 * 11242 * // listen on DOM destroy (removal) event, and cancel the next UI update 11243 * // to prevent updating time after the DOM element was removed. 11244 * element.on('$destroy', function() { 11245 * $interval.cancel(stopTime); 11246 * }); 11247 * } 11248 * }]); 11249 * </script> 11250 * 11251 * <div>
11252 * <div ng-controller="ExampleController"> 11253 * <label>Date format: <input ng-model="format"></label> <hr/> 11254 * Current time is: <span my-current-time="format"></span> 11255 * <hr/> 11256 * Blood 1 : <font color='red'>{{blood_1}}</font> 11257 * Blood 2 : <font color='red'>{{blood_2}}</font> 11258 * <button type="button" data-ng-click="fight()">Fight</button> 11259 * <button type="button" data-ng-click="stopFight()">StopFight</button> 11260 * <button type="button" data-ng-click="resetFight()">resetFight</button> 11261 * </div> 11262 * </div> 11263 * 11264 * </file> 11265 * </example> 11266 */ 11267 function interval(fn, delay, count, invokeApply) { 11268 var hasParams = arguments.length > 4, 11269 args = hasParams ? sliceArgs(arguments, 4) : [], 11270 setInterval = $window.setInterval, 11271 clearInterval = $window.clearInterval, 11272 iteration = 0, 11273 skipApply = (isDefined(invokeApply) && !invokeApply), 11274 deferred = (skipApply ? $$q : $q).defer(), 11275 promise = deferred.promise; 11276 11277 count = isDefined(count) ? count : 0; 11278 11279 promise.then(null, null, (!hasParams) ? fn : function() { 11280 fn.apply(null, args); 11281 }); 11282 11283 promise.$$intervalId = setInterval(function tick() { 11284 deferred.notify(iteration++); 11285 11286 if (count > 0 && iteration >= count) { 11287 deferred.resolve(iteration); 11288 clearInterval(promise.$$intervalId); 11289 delete intervals[promise.$$intervalId]; 11290 } 11291 11292 if (!skipApply) $rootScope.$apply(); 11293 11294 }, delay); 11295 11296 intervals[promise.$$intervalId] = deferred; 11297 11298 return promise; 11299 } 11300 11301 11302 /** 11303 * @ngdoc method 11304 * @name $interval#cancel 11305 * 11306 * @description 11307 * Cancels a task associated with the `promise`. 11308 * 11309 * @param {Promise=} promise returned by the `$interval` function. 11310 * @returns {boolean} Returns `true` if the task was successfully canceled. 11311 */ 11312 interval.cancel = function(promise) { 11313 if (promise && promise.$$intervalId in intervals) { 11314 intervals[promise.$$intervalId].reject('canceled'); 11315 $window.clearInterval(promise.$$intervalId); 11316 delete intervals[promise.$$intervalId]; 11317 return true; 11318 } 11319 return false; 11320 }; 11321 11322 return interval; 11323 }]; 11324} 11325 11326/** 11327 * @ngdoc service 11328 * @name $locale 11329 * 11330 * @description 11331 * $locale service provides localization rules for various Angular components. As of right now the 11332 * only public api is: 11333 * 11334 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 11335 */ 11336 11337var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 11338 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 11339var $locationMinErr = minErr('$location'); 11340 11341 11342/** 11343 * Encode path using encodeUriSegment, ignoring forward slashes 11344 * 11345 * @param {string} path Path to encode 11346 * @returns {string} 11347 */ 11348function encodePath(path) { 11349 var segments = path.split('/'), 11350 i = segments.length; 11351 11352 while (i--) { 11353 segments[i] = encodeUriSegment(segments[i]); 11354 } 11355 11356 return segments.join('/'); 11357} 11358 11359function parseAbsoluteUrl(absoluteUrl, locationObj) { 11360 var parsedUrl = urlResolve(absoluteUrl); 11361 11362 locationObj.$$protocol = parsedUrl.protocol; 11363 locationObj.$$host = parsedUrl.hostname; 11364 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 11365} 11366 11367 11368function parseAppUrl(relativeUrl, locationObj) { 11369 var prefixed = (relativeUrl.charAt(0) !== '/'); 11370 if (prefixed) { 11371 relativeUrl = '/' + relativeUrl; 11372 } 11373 var match = urlResolve(relativeUrl); 11374 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 11375 match.pathname.substring(1) : match.pathname); 11376 locationObj.$$search = parseKeyValue(match.search); 11377 locationObj.$$hash = decodeURIComponent(match.hash); 11378 11379 // make sure path starts with '/'; 11380 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 11381 locationObj.$$path = '/' + locationObj.$$path; 11382 } 11383} 11384 11385 11386/** 11387 * 11388 * @param {string} begin 11389 * @param {string} whole 11390 * @returns {string} returns text from whole after begin or undefined if it does not begin with 11391 * expected string. 11392 */ 11393function beginsWith(begin, whole) { 11394 if (whole.indexOf(begin) === 0) { 11395 return whole.substr(begin.length); 11396 } 11397} 11398 11399 11400function stripHash(url) { 11401 var index = url.indexOf('#'); 11402 return index == -1 ? url : url.substr(0, index); 11403} 11404 11405function trimEmptyHash(url) { 11406 return url.replace(/(#.+)|#$/, '$1'); 11407} 11408 11409 11410function stripFile(url) { 11411 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 11412} 11413 11414/* return the server only (scheme://host:port) */ 11415function serverBase(url) { 11416 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 11417} 11418 11419 11420/** 11421 * LocationHtml5Url represents an url 11422 * This object is exposed as $location service when HTML5 mode is enabled and supported 11423 * 11424 * @constructor 11425 * @param {string} appBase application base URL 11426 * @param {string} appBaseNoFile application base URL stripped of any filename 11427 * @param {string} basePrefix url path prefix 11428 */ 11429function LocationHtml5Url(appBase, appBaseNoFile, basePrefix) { 11430 this.$$html5 = true; 11431 basePrefix = basePrefix || ''; 11432 parseAbsoluteUrl(appBase, this); 11433 11434 11435 /** 11436 * Parse given html5 (regular) url string into properties 11437 * @param {string} url HTML5 url 11438 * @private 11439 */ 11440 this.$$parse = function(url) { 11441 var pathUrl = beginsWith(appBaseNoFile, url); 11442 if (!isString(pathUrl)) { 11443 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 11444 appBaseNoFile); 11445 } 11446 11447 parseAppUrl(pathUrl, this); 11448 11449 if (!this.$$path) { 11450 this.$$path = '/'; 11451 } 11452 11453 this.$$compose(); 11454 }; 11455 11456 /** 11457 * Compose url and update `absUrl` property 11458 * @private 11459 */ 11460 this.$$compose = function() {
vendor: 4,105 bytes, lines 11461-11583
11461 var search = toKeyValue(this.$$search), 11462 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11463 11464 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11465 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 11466 }; 11467 11468 this.$$parseLinkUrl = function(url, relHref) { 11469 if (relHref && relHref[0] === '#') { 11470 // special case for links to hash fragments: 11471 // keep the old url and only replace the hash fragment 11472 this.hash(relHref.slice(1)); 11473 return true; 11474 } 11475 var appUrl, prevAppUrl; 11476 var rewrittenUrl; 11477 11478 if (isDefined(appUrl = beginsWith(appBase, url))) { 11479 prevAppUrl = appUrl; 11480 if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) { 11481 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 11482 } else { 11483 rewrittenUrl = appBase + prevAppUrl; 11484 } 11485 } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) { 11486 rewrittenUrl = appBaseNoFile + appUrl; 11487 } else if (appBaseNoFile == url + '/') { 11488 rewrittenUrl = appBaseNoFile; 11489 } 11490 if (rewrittenUrl) { 11491 this.$$parse(rewrittenUrl); 11492 } 11493 return !!rewrittenUrl; 11494 }; 11495} 11496 11497 11498/** 11499 * LocationHashbangUrl represents url 11500 * This object is exposed as $location service when developer doesn't opt into html5 mode. 11501 * It also serves as the base class for html5 mode fallback on legacy browsers. 11502 * 11503 * @constructor 11504 * @param {string} appBase application base URL 11505 * @param {string} appBaseNoFile application base URL stripped of any filename 11506 * @param {string} hashPrefix hashbang prefix 11507 */ 11508function LocationHashbangUrl(appBase, appBaseNoFile, hashPrefix) { 11509 11510 parseAbsoluteUrl(appBase, this); 11511 11512 11513 /** 11514 * Parse given hashbang url into properties 11515 * @param {string} url Hashbang url 11516 * @private 11517 */ 11518 this.$$parse = function(url) { 11519 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 11520 var withoutHashUrl; 11521 11522 if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') { 11523 11524 // The rest of the url starts with a hash so we have 11525 // got either a hashbang path or a plain hash fragment 11526 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); 11527 if (isUndefined(withoutHashUrl)) { 11528 // There was no hashbang prefix so we just have a hash fragment 11529 withoutHashUrl = withoutBaseUrl; 11530 } 11531 11532 } else { 11533 // There was no hashbang path nor hash fragment: 11534 // If we are in HTML5 mode we use what is left as the path; 11535 // Otherwise we ignore what is left 11536 if (this.$$html5) { 11537 withoutHashUrl = withoutBaseUrl; 11538 } else { 11539 withoutHashUrl = ''; 11540 if (isUndefined(withoutBaseUrl)) { 11541 appBase = url; 11542 this.replace(); 11543 } 11544 } 11545 } 11546 11547 parseAppUrl(withoutHashUrl, this); 11548 11549 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 11550 11551 this.$$compose(); 11552 11553 /* 11554 * In Windows, on an anchor node on documents loaded from 11555 * the filesystem, the browser will return a pathname 11556 * prefixed with the drive name ('/C:/path') when a 11557 * pathname without a drive is set: 11558 * * a.setAttribute('href', '/foo') 11559 * * a.pathname === '/C:/foo' //true 11560 * 11561 * Inside of Angular, we're always using pathnames that 11562 * do not include drive names for routing. 11563 */ 11564 function removeWindowsDriveName(path, url, base) { 11565 /* 11566 Matches paths for file protocol on windows, 11567 such as /C:/foo/bar, and captures only /foo/bar. 11568 */ 11569 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 11570 11571 var firstPathSegmentMatch; 11572 11573 //Get the relative path from the input URL. 11574 if (url.indexOf(base) === 0) { 11575 url = url.replace(base, ''); 11576 } 11577 11578 // The input URL intentionally contains a first path segment that ends with a colon. 11579 if (windowsFilePathExp.exec(url)) { 11580 return path; 11581 } 11582 11583 firstPathSegmentMatch = windowsFilePathExp.exec(path);
11584 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 11585 } 11586 }; 11587 11588 /** 11589 * Compose hashbang url and update `absUrl` property 11590 * @private 11591 */ 11592 this.$$compose = function() { 11593 var search = toKeyValue(this.$$search), 11594 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11595 11596 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11597 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 11598 }; 11599 11600 this.$$parseLinkUrl = function(url, relHref) { 11601 if (stripHash(appBase) == stripHash(url)) { 11602 this.$$parse(url); 11603 return true; 11604 } 11605 return false; 11606 }; 11607} 11608 11609 11610/** 11611 * LocationHashbangUrl represents url 11612 * This object is exposed as $location service when html5 history api is enabled but the browser 11613 * does not support it. 11614 * 11615 * @constructor 11616 * @param {string} appBase application base URL 11617 * @param {string} appBaseNoFile application base URL stripped of any filename 11618 * @param {string} hashPrefix hashbang prefix 11619 */ 11620function LocationHashbangInHtml5Url(appBase, appBaseNoFile, hashPrefix) { 11621 this.$$html5 = true; 11622 LocationHashbangUrl.apply(this, arguments); 11623 11624 this.$$parseLinkUrl = function(url, relHref) { 11625 if (relHref && relHref[0] === '#') { 11626 // special case for links to hash fragments: 11627 // keep the old url and only replace the hash fragment 11628 this.hash(relHref.slice(1)); 11629 return true; 11630 } 11631 11632 var rewrittenUrl; 11633 var appUrl; 11634 11635 if (appBase == stripHash(url)) { 11636 rewrittenUrl = url; 11637 } else if ((appUrl = beginsWith(appBaseNoFile, url))) { 11638 rewrittenUrl = appBase + hashPrefix + appUrl; 11639 } else if (appBaseNoFile === url + '/') { 11640 rewrittenUrl = appBaseNoFile; 11641 } 11642 if (rewrittenUrl) { 11643 this.$$parse(rewrittenUrl); 11644 } 11645 return !!rewrittenUrl; 11646 }; 11647 11648 this.$$compose = function() { 11649 var search = toKeyValue(this.$$search), 11650 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11651 11652 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11653 // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#' 11654 this.$$absUrl = appBase + hashPrefix + this.$$url; 11655 }; 11656 11657} 11658 11659 11660var locationPrototype = { 11661 11662 /** 11663 * Are we in html5 mode? 11664 * @private 11665 */ 11666 $$html5: false, 11667 11668 /** 11669 * Has any change been replacing? 11670 * @private 11671 */ 11672 $$replace: false, 11673 11674 /** 11675 * @ngdoc method 11676 * @name $location#absUrl 11677 * 11678 * @description 11679 * This method is getter only. 11680 * 11681 * Return full url representation with all segments encoded according to rules specified in 11682 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 11683 * 11684 * 11685 * ```js 11686 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11687 * var absUrl = $location.absUrl(); 11688 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 11689 * ``` 11690 * 11691 * @return {string} full url 11692 */ 11693 absUrl: locationGetter('$$absUrl'), 11694 11695 /** 11696 * @ngdoc method 11697 * @name $location#url 11698 * 11699 * @description 11700 * This method is getter / setter. 11701 * 11702 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 11703 * 11704 * Change path, search and hash, when called with parameter and return `$location`. 11705 * 11706 * 11707 * ```js 11708 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11709 * var url = $location.url(); 11710 * // => "/some/path?foo=bar&baz=xoxo" 11711 * ``` 11712 * 11713 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 11714 * @return {string} url 11715 */ 11716 url: function(url) { 11717 if (isUndefined(url)) { 11718 return this.$$url; 11719 } 11720 11721 var match = PATH_MATCH.exec(url); 11722 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 11723 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 11724 this.hash(match[5] || ''); 11725 11726 return this; 11727 }, 11728 11729 /** 11730 * @ngdoc method 11731 * @name $location#protocol 11732 * 11733 * @description 11734 * This method is getter only. 11735 * 11736 * Return protocol of current url. 11737 * 11738 * 11739 * ```js
11740 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11741 * var protocol = $location.protocol(); 11742 * // => "http" 11743 * ``` 11744 * 11745 * @return {string} protocol of current url 11746 */ 11747 protocol: locationGetter('$$protocol'), 11748 11749 /** 11750 * @ngdoc method 11751 * @name $location#host 11752 * 11753 * @description 11754 * This method is getter only. 11755 * 11756 * Return host of current url. 11757 * 11758 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. 11759 * 11760 * 11761 * ```js 11762 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11763 * var host = $location.host(); 11764 * // => "example.com" 11765 * 11766 * // given url http://user:[email protected]:8080/#/some/path?foo=bar&baz=xoxo 11767 * host = $location.host(); 11768 * // => "example.com" 11769 * host = location.host; 11770 * // => "example.com:8080" 11771 * ``` 11772 * 11773 * @return {string} host of current url. 11774 */ 11775 host: locationGetter('$$host'), 11776 11777 /** 11778 * @ngdoc method 11779 * @name $location#port 11780 * 11781 * @description 11782 * This method is getter only. 11783 * 11784 * Return port of current url. 11785 * 11786 * 11787 * ```js 11788 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11789 * var port = $location.port(); 11790 * // => 80 11791 * ``` 11792 * 11793 * @return {Number} port 11794 */ 11795 port: locationGetter('$$port'), 11796 11797 /** 11798 * @ngdoc method 11799 * @name $location#path 11800 * 11801 * @description 11802 * This method is getter / setter. 11803 * 11804 * Return path of current url when called without any parameter. 11805 * 11806 * Change path when called with parameter and return `$location`. 11807 * 11808 * Note: Path should always begin with forward slash (/), this method will add the forward slash 11809 * if it is missing. 11810 * 11811 * 11812 * ```js 11813 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11814 * var path = $location.path(); 11815 * // => "/some/path" 11816 * ``` 11817 * 11818 * @param {(string|number)=} path New path 11819 * @return {string} path 11820 */ 11821 path: locationGetterSetter('$$path', function(path) { 11822 path = path !== null ? path.toString() : ''; 11823 return path.charAt(0) == '/' ? path : '/' + path; 11824 }), 11825 11826 /** 11827 * @ngdoc method 11828 * @name $location#search 11829 * 11830 * @description 11831 * This method is getter / setter. 11832 * 11833 * Return search part (as object) of current url when called without any parameter. 11834 * 11835 * Change search part when called with parameter and return `$location`. 11836 * 11837 * 11838 * ```js 11839 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11840 * var searchObject = $location.search(); 11841 * // => {foo: 'bar', baz: 'xoxo'} 11842 * 11843 * // set foo to 'yipee' 11844 * $location.search('foo', 'yipee'); 11845 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 11846 * ``` 11847 * 11848 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 11849 * hash object. 11850 * 11851 * When called with a single argument the method acts as a setter, setting the `search` component 11852 * of `$location` to the specified value. 11853 * 11854 * If the argument is a hash object containing an array of values, these values will be encoded 11855 * as duplicate search parameters in the url. 11856 * 11857 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 11858 * will override only a single search property. 11859 * 11860 * If `paramValue` is an array, it will override the property of the `search` component of 11861 * `$location` specified via the first argument. 11862 * 11863 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 11864 * 11865 * If `paramValue` is `true`, the property specified via the first argument will be added with no 11866 * value nor trailing equal sign. 11867 * 11868 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 11869 * one or more arguments returns `$location` object itself. 11870 */ 11871 search: function(search, paramValue) { 11872 switch (arguments.length) { 11873 case 0: 11874 return this.$$search; 11875 case 1: 11876 if (isString(search) || isNumber(search)) { 11877 search = search.toString(); 11878 this.$$search = parseKeyValue(search); 11879 } else if (isObject(search)) { 11880 search = copy(search, {}); 11881 // remove object undefined or null properties
vendor: 17,974 bytes, lines 11882-12414
11882 forEach(search, function(value, key) { 11883 if (value == null) delete search[key]; 11884 }); 11885 11886 this.$$search = search; 11887 } else { 11888 throw $locationMinErr('isrcharg', 11889 'The first argument of the `$location#search()` call must be a string or an object.'); 11890 } 11891 break; 11892 default: 11893 if (isUndefined(paramValue) || paramValue === null) { 11894 delete this.$$search[search]; 11895 } else { 11896 this.$$search[search] = paramValue; 11897 } 11898 } 11899 11900 this.$$compose(); 11901 return this; 11902 }, 11903 11904 /** 11905 * @ngdoc method 11906 * @name $location#hash 11907 * 11908 * @description 11909 * This method is getter / setter. 11910 * 11911 * Return hash fragment when called without any parameter. 11912 * 11913 * Change hash fragment when called with parameter and return `$location`. 11914 * 11915 * 11916 * ```js 11917 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 11918 * var hash = $location.hash(); 11919 * // => "hashValue" 11920 * ``` 11921 * 11922 * @param {(string|number)=} hash New hash fragment 11923 * @return {string} hash 11924 */ 11925 hash: locationGetterSetter('$$hash', function(hash) { 11926 return hash !== null ? hash.toString() : ''; 11927 }), 11928 11929 /** 11930 * @ngdoc method 11931 * @name $location#replace 11932 * 11933 * @description 11934 * If called, all changes to $location during current `$digest` will be replacing current history 11935 * record, instead of adding new one. 11936 */ 11937 replace: function() { 11938 this.$$replace = true; 11939 return this; 11940 } 11941}; 11942 11943forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 11944 Location.prototype = Object.create(locationPrototype); 11945 11946 /** 11947 * @ngdoc method 11948 * @name $location#state 11949 * 11950 * @description 11951 * This method is getter / setter. 11952 * 11953 * Return the history state object when called without any parameter. 11954 * 11955 * Change the history state object when called with one parameter and return `$location`. 11956 * The state object is later passed to `pushState` or `replaceState`. 11957 * 11958 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 11959 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 11960 * older browsers (like IE9 or Android < 4.0), don't use this method. 11961 * 11962 * @param {object=} state State object for pushState or replaceState 11963 * @return {object} state 11964 */ 11965 Location.prototype.state = function(state) { 11966 if (!arguments.length) { 11967 return this.$$state; 11968 } 11969 11970 if (Location !== LocationHtml5Url || !this.$$html5) { 11971 throw $locationMinErr('nostate', 'History API state support is available only ' + 11972 'in HTML5 mode and only in browsers supporting HTML5 History API'); 11973 } 11974 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 11975 // but we're changing the $$state reference to $browser.state() during the $digest 11976 // so the modification window is narrow. 11977 this.$$state = isUndefined(state) ? null : state; 11978 11979 return this; 11980 }; 11981}); 11982 11983 11984function locationGetter(property) { 11985 return function() { 11986 return this[property]; 11987 }; 11988} 11989 11990 11991function locationGetterSetter(property, preprocess) { 11992 return function(value) { 11993 if (isUndefined(value)) { 11994 return this[property]; 11995 } 11996 11997 this[property] = preprocess(value); 11998 this.$$compose(); 11999 12000 return this; 12001 }; 12002} 12003 12004 12005/** 12006 * @ngdoc service 12007 * @name $location 12008 * 12009 * @requires $rootElement 12010 * 12011 * @description 12012 * The $location service parses the URL in the browser address bar (based on the 12013 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 12014 * available to your application. Changes to the URL in the address bar are reflected into 12015 * $location service and changes to $location are reflected into the browser address bar. 12016 * 12017 * **The $location service:** 12018 * 12019 * - Exposes the current URL in the browser address bar, so you can 12020 * - Watch and observe the URL. 12021 * - Change the URL. 12022 * - Synchronizes the URL with the browser when the user 12023 * - Changes the address bar. 12024 * - Clicks the back or forward button (or clicks a History link). 12025 * - Clicks on a link. 12026 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 12027 * 12028 * For more information see {@link guide/$location Developer Guide: Using $location} 12029 */ 12030 12031/** 12032 * @ngdoc provider 12033 * @name $locationProvider 12034 * @description 12035 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 12036 */ 12037function $LocationProvider() { 12038 var hashPrefix = '', 12039 html5Mode = { 12040 enabled: false, 12041 requireBase: true, 12042 rewriteLinks: true 12043 }; 12044 12045 /** 12046 * @ngdoc method 12047 * @name $locationProvider#hashPrefix 12048 * @description 12049 * @param {string=} prefix Prefix for hash part (containing path and search) 12050 * @returns {*} current value if used as getter or itself (chaining) if used as setter 12051 */ 12052 this.hashPrefix = function(prefix) { 12053 if (isDefined(prefix)) { 12054 hashPrefix = prefix; 12055 return this; 12056 } else { 12057 return hashPrefix; 12058 } 12059 }; 12060 12061 /** 12062 * @ngdoc method 12063 * @name $locationProvider#html5Mode 12064 * @description 12065 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 12066 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 12067 * properties: 12068 * - **enabled** â `{boolean}` â (default: false) If true, will rely on `history.pushState` to 12069 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 12070 * support `pushState`. 12071 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 12072 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 12073 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 12074 * See the {@link guide/$location $location guide for more information} 12075 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, 12076 * enables/disables url rewriting for relative links. 12077 * 12078 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 12079 */ 12080 this.html5Mode = function(mode) { 12081 if (isBoolean(mode)) { 12082 html5Mode.enabled = mode; 12083 return this; 12084 } else if (isObject(mode)) { 12085 12086 if (isBoolean(mode.enabled)) { 12087 html5Mode.enabled = mode.enabled; 12088 } 12089 12090 if (isBoolean(mode.requireBase)) { 12091 html5Mode.requireBase = mode.requireBase; 12092 } 12093 12094 if (isBoolean(mode.rewriteLinks)) { 12095 html5Mode.rewriteLinks = mode.rewriteLinks; 12096 } 12097 12098 return this; 12099 } else { 12100 return html5Mode; 12101 } 12102 }; 12103 12104 /** 12105 * @ngdoc event 12106 * @name $location#$locationChangeStart 12107 * @eventType broadcast on root scope 12108 * @description 12109 * Broadcasted before a URL will change. 12110 * 12111 * This change can be prevented by calling 12112 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 12113 * details about event object. Upon successful change 12114 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 12115 * 12116 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 12117 * the browser supports the HTML5 History API. 12118 * 12119 * @param {Object} angularEvent Synthetic event object. 12120 * @param {string} newUrl New URL 12121 * @param {string=} oldUrl URL that was before it was changed. 12122 * @param {string=} newState New history state object 12123 * @param {string=} oldState History state object that was before it was changed. 12124 */ 12125 12126 /** 12127 * @ngdoc event 12128 * @name $location#$locationChangeSuccess 12129 * @eventType broadcast on root scope 12130 * @description 12131 * Broadcasted after a URL was changed. 12132 * 12133 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 12134 * the browser supports the HTML5 History API. 12135 * 12136 * @param {Object} angularEvent Synthetic event object. 12137 * @param {string} newUrl New URL 12138 * @param {string=} oldUrl URL that was before it was changed. 12139 * @param {string=} newState New history state object 12140 * @param {string=} oldState History state object that was before it was changed. 12141 */ 12142 12143 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 12144 function($rootScope, $browser, $sniffer, $rootElement, $window) { 12145 var $location, 12146 LocationMode, 12147 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 12148 initialUrl = $browser.url(), 12149 appBase; 12150 12151 if (html5Mode.enabled) { 12152 if (!baseHref && html5Mode.requireBase) { 12153 throw $locationMinErr('nobase', 12154 "$location in HTML5 mode requires a <base> tag to be present!"); 12155 } 12156 appBase = serverBase(initialUrl) + (baseHref || '/'); 12157 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 12158 } else { 12159 appBase = stripHash(initialUrl); 12160 LocationMode = LocationHashbangUrl; 12161 } 12162 var appBaseNoFile = stripFile(appBase); 12163 12164 $location = new LocationMode(appBase, appBaseNoFile, '#' + hashPrefix); 12165 $location.$$parseLinkUrl(initialUrl, initialUrl); 12166 12167 $location.$$state = $browser.state(); 12168 12169 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 12170 12171 function setBrowserUrlWithFallback(url, replace, state) { 12172 var oldUrl = $location.url(); 12173 var oldState = $location.$$state; 12174 try { 12175 $browser.url(url, replace, state); 12176 12177 // Make sure $location.state() returns referentially identical (not just deeply equal) 12178 // state object; this makes possible quick checking if the state changed in the digest 12179 // loop. Checking deep equality would be too expensive. 12180 $location.$$state = $browser.state(); 12181 } catch (e) { 12182 // Restore old values if pushState fails 12183 $location.url(oldUrl); 12184 $location.$$state = oldState; 12185 12186 throw e; 12187 } 12188 } 12189 12190 $rootElement.on('click', function(event) { 12191 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 12192 // currently we open nice url link and redirect then 12193 12194 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; 12195 12196 var elm = jqLite(event.target); 12197 12198 // traverse the DOM up to find first A tag 12199 while (nodeName_(elm[0]) !== 'a') { 12200 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 12201 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 12202 } 12203 12204 var absHref = elm.prop('href'); 12205 // get the actual href attribute - see 12206 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 12207 var relHref = elm.attr('href') || elm.attr('xlink:href'); 12208 12209 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 12210 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 12211 // an animation. 12212 absHref = urlResolve(absHref.animVal).href; 12213 } 12214 12215 // Ignore when url is started with javascript: or mailto: 12216 if (IGNORE_URI_REGEXP.test(absHref)) return; 12217 12218 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 12219 if ($location.$$parseLinkUrl(absHref, relHref)) { 12220 // We do a preventDefault for all urls that are part of the angular application, 12221 // in html5mode and also without, so that we are able to abort navigation without 12222 // getting double entries in the location history. 12223 event.preventDefault(); 12224 // update location manually 12225 if ($location.absUrl() != $browser.url()) { 12226 $rootScope.$apply(); 12227 // hack to work around FF6 bug 684208 when scenario runner clicks on links 12228 $window.angular['ff-684208-preventDefault'] = true; 12229 } 12230 } 12231 } 12232 }); 12233 12234 12235 // rewrite hashbang url <> html5 url 12236 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) { 12237 $browser.url($location.absUrl(), true); 12238 } 12239 12240 var initializing = true; 12241 12242 // update $location when $browser url changes 12243 $browser.onUrlChange(function(newUrl, newState) { 12244 12245 if (isUndefined(beginsWith(appBaseNoFile, newUrl))) { 12246 // If we are navigating outside of the app then force a reload 12247 $window.location.href = newUrl; 12248 return; 12249 } 12250 12251 $rootScope.$evalAsync(function() { 12252 var oldUrl = $location.absUrl(); 12253 var oldState = $location.$$state; 12254 var defaultPrevented; 12255 12256 $location.$$parse(newUrl); 12257 $location.$$state = newState; 12258 12259 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 12260 newState, oldState).defaultPrevented; 12261 12262 // if the location was changed by a `$locationChangeStart` handler then stop 12263 // processing this location change 12264 if ($location.absUrl() !== newUrl) return; 12265 12266 if (defaultPrevented) { 12267 $location.$$parse(oldUrl); 12268 $location.$$state = oldState; 12269 setBrowserUrlWithFallback(oldUrl, false, oldState); 12270 } else { 12271 initializing = false; 12272 afterLocationChange(oldUrl, oldState); 12273 } 12274 }); 12275 if (!$rootScope.$$phase) $rootScope.$digest(); 12276 }); 12277 12278 // update browser 12279 $rootScope.$watch(function $locationWatch() { 12280 var oldUrl = trimEmptyHash($browser.url()); 12281 var newUrl = trimEmptyHash($location.absUrl()); 12282 var oldState = $browser.state(); 12283 var currentReplace = $location.$$replace; 12284 var urlOrStateChanged = oldUrl !== newUrl || 12285 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 12286 12287 if (initializing || urlOrStateChanged) { 12288 initializing = false; 12289 12290 $rootScope.$evalAsync(function() { 12291 var newUrl = $location.absUrl(); 12292 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 12293 $location.$$state, oldState).defaultPrevented; 12294 12295 // if the location was changed by a `$locationChangeStart` handler then stop 12296 // processing this location change 12297 if ($location.absUrl() !== newUrl) return; 12298 12299 if (defaultPrevented) { 12300 $location.$$parse(oldUrl); 12301 $location.$$state = oldState; 12302 } else { 12303 if (urlOrStateChanged) { 12304 setBrowserUrlWithFallback(newUrl, currentReplace, 12305 oldState === $location.$$state ? null : $location.$$state); 12306 } 12307 afterLocationChange(oldUrl, oldState); 12308 } 12309 }); 12310 } 12311 12312 $location.$$replace = false; 12313 12314 // we don't need to return anything because $evalAsync will make the digest loop dirty when 12315 // there is a change 12316 }); 12317 12318 return $location; 12319 12320 function afterLocationChange(oldUrl, oldState) { 12321 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 12322 $location.$$state, oldState); 12323 } 12324}]; 12325} 12326 12327/** 12328 * @ngdoc service 12329 * @name $log 12330 * @requires $window 12331 * 12332 * @description 12333 * Simple service for logging. Default implementation safely writes the message 12334 * into the browser's console (if present). 12335 * 12336 * The main purpose of this service is to simplify debugging and troubleshooting. 12337 * 12338 * The default is to log `debug` messages. You can use 12339 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 12340 * 12341 * @example 12342 <example module="logExample"> 12343 <file name="script.js"> 12344 angular.module('logExample', []) 12345 .controller('LogController', ['$scope', '$log', function($scope, $log) { 12346 $scope.$log = $log; 12347 $scope.message = 'Hello World!'; 12348 }]); 12349 </file> 12350 <file name="index.html"> 12351 <div ng-controller="LogController"> 12352 <p>Reload this page with open console, enter text and hit the log button...</p> 12353 <label>Message: 12354 <input type="text" ng-model="message" /></label> 12355 <button ng-click="$log.log(message)">log</button> 12356 <button ng-click="$log.warn(message)">warn</button> 12357 <button ng-click="$log.info(message)">info</button> 12358 <button ng-click="$log.error(message)">error</button> 12359 <button ng-click="$log.debug(message)">debug</button> 12360 </div> 12361 </file> 12362 </example> 12363 */ 12364 12365/** 12366 * @ngdoc provider 12367 * @name $logProvider 12368 * @description 12369 * Use the `$logProvider` to configure how the application logs messages 12370 */ 12371function $LogProvider() { 12372 var debug = true, 12373 self = this; 12374 12375 /** 12376 * @ngdoc method 12377 * @name $logProvider#debugEnabled 12378 * @description 12379 * @param {boolean=} flag enable or disable debug level messages 12380 * @returns {*} current value if used as getter or itself (chaining) if used as setter 12381 */ 12382 this.debugEnabled = function(flag) { 12383 if (isDefined(flag)) { 12384 debug = flag; 12385 return this; 12386 } else { 12387 return debug; 12388 } 12389 }; 12390 12391 this.$get = ['$window', function($window) { 12392 return { 12393 /** 12394 * @ngdoc method 12395 * @name $log#log 12396 * 12397 * @description 12398 * Write a log message 12399 */ 12400 log: consoleLog('log'), 12401 12402 /** 12403 * @ngdoc method 12404 * @name $log#info 12405 * 12406 * @description 12407 * Write an information message 12408 */ 12409 info: consoleLog('info'), 12410 12411 /** 12412 * @ngdoc method 12413 * @name $log#warn 12414 *
12415 * @description 12416 * Write a warning message 12417 */ 12418 warn: consoleLog('warn'), 12419 12420 /** 12421 * @ngdoc method 12422 * @name $log#error 12423 * 12424 * @description 12425 * Write an error message 12426 */ 12427 error: consoleLog('error'), 12428 12429 /** 12430 * @ngdoc method 12431 * @name $log#debug 12432 * 12433 * @description 12434 * Write a debug message 12435 */ 12436 debug: (function() { 12437 var fn = consoleLog('debug'); 12438 12439 return function() { 12440 if (debug) { 12441 fn.apply(self, arguments); 12442 } 12443 }; 12444 }()) 12445 }; 12446 12447 function formatError(arg) { 12448 if (arg instanceof Error) { 12449 if (arg.stack) { 12450 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 12451 ? 'Error: ' + arg.message + '\n' + arg.stack 12452 : arg.stack; 12453 } else if (arg.sourceURL) { 12454 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 12455 } 12456 } 12457 return arg; 12458 } 12459 12460 function consoleLog(type) { 12461 var console = $window.console || {}, 12462 logFn = console[type] || console.log || noop, 12463 hasApply = false; 12464 12465 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 12466 // The reason behind this is that console.log has type "object" in IE8... 12467 try { 12468 hasApply = !!logFn.apply; 12469 } catch (e) {} 12470 12471 if (hasApply) { 12472 return function() { 12473 var args = []; 12474 forEach(arguments, function(arg) { 12475 args.push(formatError(arg)); 12476 }); 12477 return logFn.apply(console, args); 12478 }; 12479 } 12480 12481 // we are IE which either doesn't have window.console => this is noop and we do nothing, 12482 // or we are IE where console.log doesn't have apply so we log at least first 2 args 12483 return function(arg1, arg2) { 12484 logFn(arg1, arg2 == null ? '' : arg2); 12485 }; 12486 } 12487 }]; 12488} 12489 12490/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 12491 * Any commits to this file should be reviewed with security in mind. * 12492 * Changes to this file can potentially create security vulnerabilities. * 12493 * An approval from 2 Core members with history of modifying * 12494 * this file is required. * 12495 * * 12496 * Does the change somehow allow for arbitrary javascript to be executed? * 12497 * Or allows for someone to change the prototype of built-in objects? * 12498 * Or gives undesired access to variables likes document or window? * 12499 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 12500 12501var $parseMinErr = minErr('$parse'); 12502 12503// Sandboxing Angular Expressions 12504// ------------------------------ 12505// Angular expressions are generally considered safe because these expressions only have direct 12506// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by 12507// obtaining a reference to native JS functions such as the Function constructor. 12508// 12509// As an example, consider the following Angular expression: 12510// 12511// {}.toString.constructor('alert("evil JS code")') 12512// 12513// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 12514// against the expression language, but not to prevent exploits that were enabled by exposing 12515// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good 12516// practice and therefore we are not even trying to protect against interaction with an object 12517// explicitly exposed in this way. 12518// 12519// In general, it is not possible to access a Window object from an angular expression unless a 12520// window or some DOM object that has a reference to window is published onto a Scope. 12521// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 12522// native objects. 12523// 12524// See https://docs.angularjs.org/guide/security 12525 12526 12527function ensureSafeMemberName(name, fullExpression) { 12528 if (name === "__defineGetter__" || name === "__defineSetter__" 12529 || name === "__lookupGetter__" || name === "__lookupSetter__" 12530 || name === "__proto__") { 12531 throw $parseMinErr('isecfld', 12532 'Attempting to access a disallowed field in Angular expressions! ' 12533 + 'Expression: {0}', fullExpression); 12534 } 12535 return name; 12536} 12537 12538function getStringValue(name, fullExpression) { 12539 // From the JavaScript docs: 12540 // Property names must be strings. This means that non-string objects cannot be used 12541 // as keys in an object. Any non-string object, including a number, is typecasted 12542 // into a string via the toString method. 12543 // 12544 // So, to ensure that we are checking the same `name` that JavaScript would use, 12545 // we cast it to a string, if possible. 12546 // Doing `name + ''` can cause a repl error if the result to `toString` is not a string, 12547 // this is, this will handle objects that misbehave. 12548 name = name + ''; 12549 if (!isString(name)) { 12550 throw $parseMinErr('iseccst', 12551 'Cannot convert object to primitive value! ' 12552 + 'Expression: {0}', fullExpression); 12553 } 12554 return name; 12555} 12556 12557function ensureSafeObject(obj, fullExpression) { 12558 // nifty check if obj is Function that is fast and works across iframes and other contexts 12559 if (obj) { 12560 if (obj.constructor === obj) { 12561 throw $parseMinErr('isecfn', 12562 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 12563 fullExpression); 12564 } else if (// isWindow(obj) 12565 obj.window === obj) { 12566 throw $parseMinErr('isecwindow', 12567 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 12568 fullExpression); 12569 } else if (// isElement(obj) 12570 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 12571 throw $parseMinErr('isecdom', 12572 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 12573 fullExpression); 12574 } else if (// block Object so that we can't get hold of dangerous Object.* methods 12575 obj === Object) { 12576 throw $parseMinErr('isecobj', 12577 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 12578 fullExpression); 12579 } 12580 } 12581 return obj;
12582} 12583 12584var CALL = Function.prototype.call; 12585var APPLY = Function.prototype.apply; 12586var BIND = Function.prototype.bind; 12587 12588function ensureSafeFunction(obj, fullExpression) { 12589 if (obj) { 12590 if (obj.constructor === obj) { 12591 throw $parseMinErr('isecfn', 12592 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 12593 fullExpression); 12594 } else if (obj === CALL || obj === APPLY || obj === BIND) { 12595 throw $parseMinErr('isecff', 12596 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 12597 fullExpression); 12598 } 12599 } 12600} 12601 12602function ensureSafeAssignContext(obj, fullExpression) { 12603 if (obj) { 12604 if (obj === (0).constructor || obj === (false).constructor || obj === ''.constructor || 12605 obj === {}.constructor || obj === [].constructor || obj === Function.constructor) { 12606 throw $parseMinErr('isecaf', 12607 'Assigning to a constructor is disallowed! Expression: {0}', fullExpression); 12608 } 12609 } 12610} 12611 12612var OPERATORS = createMap(); 12613forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); 12614var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 12615 12616 12617///////////////////////////////////////// 12618 12619 12620/** 12621 * @constructor 12622 */ 12623var Lexer = function(options) { 12624 this.options = options; 12625}; 12626 12627Lexer.prototype = { 12628 constructor: Lexer, 12629 12630 lex: function(text) { 12631 this.text = text; 12632 this.index = 0; 12633 this.tokens = []; 12634 12635 while (this.index < this.text.length) { 12636 var ch = this.text.charAt(this.index); 12637 if (ch === '"' || ch === "'") { 12638 this.readString(ch); 12639 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 12640 this.readNumber(); 12641 } else if (this.isIdent(ch)) { 12642 this.readIdent(); 12643 } else if (this.is(ch, '(){}[].,;:?')) { 12644 this.tokens.push({index: this.index, text: ch}); 12645 this.index++; 12646 } else if (this.isWhitespace(ch)) { 12647 this.index++; 12648 } else { 12649 var ch2 = ch + this.peek(); 12650 var ch3 = ch2 + this.peek(2); 12651 var op1 = OPERATORS[ch];
vendor: 48,426 bytes, lines 12652-14123
12652 var op2 = OPERATORS[ch2]; 12653 var op3 = OPERATORS[ch3]; 12654 if (op1 || op2 || op3) { 12655 var token = op3 ? ch3 : (op2 ? ch2 : ch); 12656 this.tokens.push({index: this.index, text: token, operator: true}); 12657 this.index += token.length; 12658 } else { 12659 this.throwError('Unexpected next character ', this.index, this.index + 1); 12660 } 12661 } 12662 } 12663 return this.tokens; 12664 }, 12665 12666 is: function(ch, chars) { 12667 return chars.indexOf(ch) !== -1; 12668 }, 12669 12670 peek: function(i) { 12671 var num = i || 1; 12672 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 12673 }, 12674 12675 isNumber: function(ch) { 12676 return ('0' <= ch && ch <= '9') && typeof ch === "string"; 12677 }, 12678 12679 isWhitespace: function(ch) { 12680 // IE treats non-breaking space as \u00A0 12681 return (ch === ' ' || ch === '\r' || ch === '\t' || 12682 ch === '\n' || ch === '\v' || ch === '\u00A0'); 12683 }, 12684 12685 isIdent: function(ch) { 12686 return ('a' <= ch && ch <= 'z' || 12687 'A' <= ch && ch <= 'Z' || 12688 '_' === ch || ch === '$'); 12689 }, 12690 12691 isExpOperator: function(ch) { 12692 return (ch === '-' || ch === '+' || this.isNumber(ch)); 12693 }, 12694 12695 throwError: function(error, start, end) { 12696 end = end || this.index; 12697 var colStr = (isDefined(start) 12698 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 12699 : ' ' + end); 12700 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 12701 error, colStr, this.text); 12702 }, 12703 12704 readNumber: function() { 12705 var number = ''; 12706 var start = this.index; 12707 while (this.index < this.text.length) { 12708 var ch = lowercase(this.text.charAt(this.index)); 12709 if (ch == '.' || this.isNumber(ch)) { 12710 number += ch; 12711 } else { 12712 var peekCh = this.peek(); 12713 if (ch == 'e' && this.isExpOperator(peekCh)) { 12714 number += ch; 12715 } else if (this.isExpOperator(ch) && 12716 peekCh && this.isNumber(peekCh) && 12717 number.charAt(number.length - 1) == 'e') { 12718 number += ch; 12719 } else if (this.isExpOperator(ch) && 12720 (!peekCh || !this.isNumber(peekCh)) && 12721 number.charAt(number.length - 1) == 'e') { 12722 this.throwError('Invalid exponent'); 12723 } else { 12724 break; 12725 } 12726 } 12727 this.index++; 12728 } 12729 this.tokens.push({ 12730 index: start, 12731 text: number, 12732 constant: true, 12733 value: Number(number) 12734 }); 12735 }, 12736 12737 readIdent: function() { 12738 var start = this.index; 12739 while (this.index < this.text.length) { 12740 var ch = this.text.charAt(this.index); 12741 if (!(this.isIdent(ch) || this.isNumber(ch))) { 12742 break; 12743 } 12744 this.index++; 12745 } 12746 this.tokens.push({ 12747 index: start, 12748 text: this.text.slice(start, this.index), 12749 identifier: true 12750 }); 12751 }, 12752 12753 readString: function(quote) { 12754 var start = this.index; 12755 this.index++; 12756 var string = ''; 12757 var rawString = quote; 12758 var escape = false; 12759 while (this.index < this.text.length) { 12760 var ch = this.text.charAt(this.index); 12761 rawString += ch; 12762 if (escape) { 12763 if (ch === 'u') { 12764 var hex = this.text.substring(this.index + 1, this.index + 5); 12765 if (!hex.match(/[\da-f]{4}/i)) { 12766 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 12767 } 12768 this.index += 4; 12769 string += String.fromCharCode(parseInt(hex, 16)); 12770 } else { 12771 var rep = ESCAPE[ch]; 12772 string = string + (rep || ch); 12773 } 12774 escape = false; 12775 } else if (ch === '\\') { 12776 escape = true; 12777 } else if (ch === quote) { 12778 this.index++; 12779 this.tokens.push({ 12780 index: start, 12781 text: rawString, 12782 constant: true, 12783 value: string 12784 }); 12785 return; 12786 } else { 12787 string += ch; 12788 } 12789 this.index++; 12790 } 12791 this.throwError('Unterminated quote', start); 12792 } 12793}; 12794 12795var AST = function(lexer, options) { 12796 this.lexer = lexer; 12797 this.options = options; 12798}; 12799 12800AST.Program = 'Program'; 12801AST.ExpressionStatement = 'ExpressionStatement'; 12802AST.AssignmentExpression = 'AssignmentExpression'; 12803AST.ConditionalExpression = 'ConditionalExpression'; 12804AST.LogicalExpression = 'LogicalExpression'; 12805AST.BinaryExpression = 'BinaryExpression'; 12806AST.UnaryExpression = 'UnaryExpression'; 12807AST.CallExpression = 'CallExpression'; 12808AST.MemberExpression = 'MemberExpression'; 12809AST.Identifier = 'Identifier'; 12810AST.Literal = 'Literal'; 12811AST.ArrayExpression = 'ArrayExpression'; 12812AST.Property = 'Property'; 12813AST.ObjectExpression = 'ObjectExpression'; 12814AST.ThisExpression = 'ThisExpression'; 12815 12816// Internal use only 12817AST.NGValueParameter = 'NGValueParameter'; 12818 12819AST.prototype = { 12820 ast: function(text) { 12821 this.text = text; 12822 this.tokens = this.lexer.lex(text); 12823 12824 var value = this.program(); 12825 12826 if (this.tokens.length !== 0) { 12827 this.throwError('is an unexpected token', this.tokens[0]); 12828 } 12829 12830 return value; 12831 }, 12832 12833 program: function() { 12834 var body = []; 12835 while (true) { 12836 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 12837 body.push(this.expressionStatement()); 12838 if (!this.expect(';')) { 12839 return { type: AST.Program, body: body}; 12840 } 12841 } 12842 }, 12843 12844 expressionStatement: function() { 12845 return { type: AST.ExpressionStatement, expression: this.filterChain() }; 12846 }, 12847 12848 filterChain: function() { 12849 var left = this.expression(); 12850 var token; 12851 while ((token = this.expect('|'))) { 12852 left = this.filter(left); 12853 } 12854 return left; 12855 }, 12856 12857 expression: function() { 12858 return this.assignment(); 12859 }, 12860 12861 assignment: function() { 12862 var result = this.ternary(); 12863 if (this.expect('=')) { 12864 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; 12865 } 12866 return result; 12867 }, 12868 12869 ternary: function() { 12870 var test = this.logicalOR(); 12871 var alternate; 12872 var consequent; 12873 if (this.expect('?')) { 12874 alternate = this.expression(); 12875 if (this.consume(':')) { 12876 consequent = this.expression(); 12877 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; 12878 } 12879 } 12880 return test; 12881 }, 12882 12883 logicalOR: function() { 12884 var left = this.logicalAND(); 12885 while (this.expect('||')) { 12886 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; 12887 } 12888 return left; 12889 }, 12890 12891 logicalAND: function() { 12892 var left = this.equality(); 12893 while (this.expect('&&')) { 12894 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; 12895 } 12896 return left; 12897 }, 12898 12899 equality: function() { 12900 var left = this.relational(); 12901 var token; 12902 while ((token = this.expect('==','!=','===','!=='))) { 12903 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; 12904 } 12905 return left; 12906 }, 12907 12908 relational: function() { 12909 var left = this.additive(); 12910 var token; 12911 while ((token = this.expect('<', '>', '<=', '>='))) { 12912 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; 12913 } 12914 return left; 12915 }, 12916 12917 additive: function() { 12918 var left = this.multiplicative(); 12919 var token; 12920 while ((token = this.expect('+','-'))) { 12921 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; 12922 } 12923 return left; 12924 }, 12925 12926 multiplicative: function() { 12927 var left = this.unary(); 12928 var token; 12929 while ((token = this.expect('*','/','%'))) { 12930 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; 12931 } 12932 return left; 12933 }, 12934 12935 unary: function() { 12936 var token; 12937 if ((token = this.expect('+', '-', '!'))) { 12938 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; 12939 } else { 12940 return this.primary(); 12941 } 12942 }, 12943 12944 primary: function() { 12945 var primary; 12946 if (this.expect('(')) { 12947 primary = this.filterChain(); 12948 this.consume(')'); 12949 } else if (this.expect('[')) { 12950 primary = this.arrayDeclaration(); 12951 } else if (this.expect('{')) { 12952 primary = this.object(); 12953 } else if (this.constants.hasOwnProperty(this.peek().text)) { 12954 primary = copy(this.constants[this.consume().text]); 12955 } else if (this.peek().identifier) { 12956 primary = this.identifier(); 12957 } else if (this.peek().constant) { 12958 primary = this.constant(); 12959 } else { 12960 this.throwError('not a primary expression', this.peek()); 12961 } 12962 12963 var next; 12964 while ((next = this.expect('(', '[', '.'))) { 12965 if (next.text === '(') { 12966 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; 12967 this.consume(')'); 12968 } else if (next.text === '[') { 12969 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; 12970 this.consume(']'); 12971 } else if (next.text === '.') { 12972 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; 12973 } else { 12974 this.throwError('IMPOSSIBLE'); 12975 } 12976 } 12977 return primary; 12978 }, 12979 12980 filter: function(baseExpression) { 12981 var args = [baseExpression]; 12982 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; 12983 12984 while (this.expect(':')) { 12985 args.push(this.expression()); 12986 } 12987 12988 return result; 12989 }, 12990 12991 parseArguments: function() { 12992 var args = []; 12993 if (this.peekToken().text !== ')') { 12994 do { 12995 args.push(this.expression()); 12996 } while (this.expect(',')); 12997 } 12998 return args; 12999 }, 13000 13001 identifier: function() { 13002 var token = this.consume(); 13003 if (!token.identifier) { 13004 this.throwError('is not a valid identifier', token); 13005 } 13006 return { type: AST.Identifier, name: token.text }; 13007 }, 13008 13009 constant: function() { 13010 // TODO check that it is a constant 13011 return { type: AST.Literal, value: this.consume().value }; 13012 }, 13013 13014 arrayDeclaration: function() { 13015 var elements = []; 13016 if (this.peekToken().text !== ']') { 13017 do { 13018 if (this.peek(']')) { 13019 // Support trailing commas per ES5.1. 13020 break; 13021 } 13022 elements.push(this.expression()); 13023 } while (this.expect(',')); 13024 } 13025 this.consume(']'); 13026 13027 return { type: AST.ArrayExpression, elements: elements }; 13028 }, 13029 13030 object: function() { 13031 var properties = [], property; 13032 if (this.peekToken().text !== '}') { 13033 do { 13034 if (this.peek('}')) { 13035 // Support trailing commas per ES5.1. 13036 break; 13037 } 13038 property = {type: AST.Property, kind: 'init'}; 13039 if (this.peek().constant) { 13040 property.key = this.constant(); 13041 } else if (this.peek().identifier) { 13042 property.key = this.identifier(); 13043 } else { 13044 this.throwError("invalid key", this.peek()); 13045 } 13046 this.consume(':'); 13047 property.value = this.expression(); 13048 properties.push(property); 13049 } while (this.expect(',')); 13050 } 13051 this.consume('}'); 13052 13053 return {type: AST.ObjectExpression, properties: properties }; 13054 }, 13055 13056 throwError: function(msg, token) { 13057 throw $parseMinErr('syntax', 13058 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 13059 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 13060 }, 13061 13062 consume: function(e1) { 13063 if (this.tokens.length === 0) { 13064 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 13065 } 13066 13067 var token = this.expect(e1); 13068 if (!token) { 13069 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 13070 } 13071 return token; 13072 }, 13073 13074 peekToken: function() { 13075 if (this.tokens.length === 0) { 13076 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 13077 } 13078 return this.tokens[0]; 13079 }, 13080 13081 peek: function(e1, e2, e3, e4) { 13082 return this.peekAhead(0, e1, e2, e3, e4); 13083 }, 13084 13085 peekAhead: function(i, e1, e2, e3, e4) { 13086 if (this.tokens.length > i) { 13087 var token = this.tokens[i]; 13088 var t = token.text; 13089 if (t === e1 || t === e2 || t === e3 || t === e4 || 13090 (!e1 && !e2 && !e3 && !e4)) { 13091 return token; 13092 } 13093 } 13094 return false; 13095 }, 13096 13097 expect: function(e1, e2, e3, e4) { 13098 var token = this.peek(e1, e2, e3, e4); 13099 if (token) { 13100 this.tokens.shift(); 13101 return token; 13102 } 13103 return false; 13104 }, 13105 13106 13107 /* `undefined` is not a constant, it is an identifier, 13108 * but using it as an identifier is not supported 13109 */ 13110 constants: { 13111 'true': { type: AST.Literal, value: true }, 13112 'false': { type: AST.Literal, value: false }, 13113 'null': { type: AST.Literal, value: null }, 13114 'undefined': {type: AST.Literal, value: undefined }, 13115 'this': {type: AST.ThisExpression } 13116 } 13117}; 13118 13119function ifDefined(v, d) { 13120 return typeof v !== 'undefined' ? v : d; 13121} 13122 13123function plusFn(l, r) { 13124 if (typeof l === 'undefined') return r; 13125 if (typeof r === 'undefined') return l; 13126 return l + r; 13127} 13128 13129function isStateless($filter, filterName) { 13130 var fn = $filter(filterName); 13131 return !fn.$stateful; 13132} 13133 13134function findConstantAndWatchExpressions(ast, $filter) { 13135 var allConstants; 13136 var argsToWatch; 13137 switch (ast.type) { 13138 case AST.Program: 13139 allConstants = true; 13140 forEach(ast.body, function(expr) { 13141 findConstantAndWatchExpressions(expr.expression, $filter); 13142 allConstants = allConstants && expr.expression.constant; 13143 }); 13144 ast.constant = allConstants; 13145 break; 13146 case AST.Literal: 13147 ast.constant = true; 13148 ast.toWatch = []; 13149 break; 13150 case AST.UnaryExpression: 13151 findConstantAndWatchExpressions(ast.argument, $filter); 13152 ast.constant = ast.argument.constant; 13153 ast.toWatch = ast.argument.toWatch; 13154 break; 13155 case AST.BinaryExpression: 13156 findConstantAndWatchExpressions(ast.left, $filter); 13157 findConstantAndWatchExpressions(ast.right, $filter); 13158 ast.constant = ast.left.constant && ast.right.constant; 13159 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); 13160 break; 13161 case AST.LogicalExpression: 13162 findConstantAndWatchExpressions(ast.left, $filter); 13163 findConstantAndWatchExpressions(ast.right, $filter); 13164 ast.constant = ast.left.constant && ast.right.constant; 13165 ast.toWatch = ast.constant ? [] : [ast]; 13166 break; 13167 case AST.ConditionalExpression: 13168 findConstantAndWatchExpressions(ast.test, $filter); 13169 findConstantAndWatchExpressions(ast.alternate, $filter); 13170 findConstantAndWatchExpressions(ast.consequent, $filter); 13171 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; 13172 ast.toWatch = ast.constant ? [] : [ast]; 13173 break; 13174 case AST.Identifier: 13175 ast.constant = false; 13176 ast.toWatch = [ast]; 13177 break; 13178 case AST.MemberExpression: 13179 findConstantAndWatchExpressions(ast.object, $filter); 13180 if (ast.computed) { 13181 findConstantAndWatchExpressions(ast.property, $filter); 13182 } 13183 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); 13184 ast.toWatch = [ast]; 13185 break; 13186 case AST.CallExpression: 13187 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false; 13188 argsToWatch = []; 13189 forEach(ast.arguments, function(expr) { 13190 findConstantAndWatchExpressions(expr, $filter); 13191 allConstants = allConstants && expr.constant; 13192 if (!expr.constant) { 13193 argsToWatch.push.apply(argsToWatch, expr.toWatch); 13194 } 13195 }); 13196 ast.constant = allConstants; 13197 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast]; 13198 break; 13199 case AST.AssignmentExpression: 13200 findConstantAndWatchExpressions(ast.left, $filter); 13201 findConstantAndWatchExpressions(ast.right, $filter); 13202 ast.constant = ast.left.constant && ast.right.constant; 13203 ast.toWatch = [ast]; 13204 break; 13205 case AST.ArrayExpression: 13206 allConstants = true; 13207 argsToWatch = []; 13208 forEach(ast.elements, function(expr) { 13209 findConstantAndWatchExpressions(expr, $filter); 13210 allConstants = allConstants && expr.constant; 13211 if (!expr.constant) { 13212 argsToWatch.push.apply(argsToWatch, expr.toWatch); 13213 } 13214 }); 13215 ast.constant = allConstants; 13216 ast.toWatch = argsToWatch; 13217 break; 13218 case AST.ObjectExpression: 13219 allConstants = true; 13220 argsToWatch = []; 13221 forEach(ast.properties, function(property) { 13222 findConstantAndWatchExpressions(property.value, $filter); 13223 allConstants = allConstants && property.value.constant; 13224 if (!property.value.constant) { 13225 argsToWatch.push.apply(argsToWatch, property.value.toWatch); 13226 } 13227 }); 13228 ast.constant = allConstants; 13229 ast.toWatch = argsToWatch; 13230 break; 13231 case AST.ThisExpression: 13232 ast.constant = false; 13233 ast.toWatch = []; 13234 break; 13235 } 13236} 13237 13238function getInputs(body) { 13239 if (body.length != 1) return; 13240 var lastExpression = body[0].expression; 13241 var candidate = lastExpression.toWatch; 13242 if (candidate.length !== 1) return candidate; 13243 return candidate[0] !== lastExpression ? candidate : undefined; 13244} 13245 13246function isAssignable(ast) { 13247 return ast.type === AST.Identifier || ast.type === AST.MemberExpression; 13248} 13249 13250function assignableAST(ast) { 13251 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { 13252 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; 13253 } 13254} 13255 13256function isLiteral(ast) { 13257 return ast.body.length === 0 || 13258 ast.body.length === 1 && ( 13259 ast.body[0].expression.type === AST.Literal || 13260 ast.body[0].expression.type === AST.ArrayExpression || 13261 ast.body[0].expression.type === AST.ObjectExpression); 13262} 13263 13264function isConstant(ast) { 13265 return ast.constant; 13266} 13267 13268function ASTCompiler(astBuilder, $filter) { 13269 this.astBuilder = astBuilder; 13270 this.$filter = $filter; 13271} 13272 13273ASTCompiler.prototype = { 13274 compile: function(expression, expensiveChecks) { 13275 var self = this; 13276 var ast = this.astBuilder.ast(expression); 13277 this.state = { 13278 nextId: 0, 13279 filters: {}, 13280 expensiveChecks: expensiveChecks, 13281 fn: {vars: [], body: [], own: {}}, 13282 assign: {vars: [], body: [], own: {}}, 13283 inputs: [] 13284 }; 13285 findConstantAndWatchExpressions(ast, self.$filter); 13286 var extra = ''; 13287 var assignable; 13288 this.stage = 'assign'; 13289 if ((assignable = assignableAST(ast))) { 13290 this.state.computing = 'assign'; 13291 var result = this.nextId(); 13292 this.recurse(assignable, result); 13293 this.return_(result); 13294 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); 13295 } 13296 var toWatch = getInputs(ast.body); 13297 self.stage = 'inputs'; 13298 forEach(toWatch, function(watch, key) { 13299 var fnKey = 'fn' + key; 13300 self.state[fnKey] = {vars: [], body: [], own: {}}; 13301 self.state.computing = fnKey; 13302 var intoId = self.nextId(); 13303 self.recurse(watch, intoId); 13304 self.return_(intoId); 13305 self.state.inputs.push(fnKey); 13306 watch.watchId = key; 13307 }); 13308 this.state.computing = 'fn'; 13309 this.stage = 'main'; 13310 this.recurse(ast); 13311 var fnString = 13312 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. 13313 // This is a workaround for this until we do a better job at only removing the prefix only when we should. 13314 '"' + this.USE + ' ' + this.STRICT + '";\n' + 13315 this.filterPrefix() + 13316 'var fn=' + this.generateFunction('fn', 's,l,a,i') + 13317 extra + 13318 this.watchFns() + 13319 'return fn;'; 13320 13321 /* jshint -W054 */ 13322 var fn = (new Function('$filter', 13323 'ensureSafeMemberName', 13324 'ensureSafeObject', 13325 'ensureSafeFunction', 13326 'getStringValue', 13327 'ensureSafeAssignContext', 13328 'ifDefined', 13329 'plus', 13330 'text', 13331 fnString))( 13332 this.$filter, 13333 ensureSafeMemberName, 13334 ensureSafeObject, 13335 ensureSafeFunction, 13336 getStringValue, 13337 ensureSafeAssignContext, 13338 ifDefined, 13339 plusFn, 13340 expression); 13341 /* jshint +W054 */ 13342 this.state = this.stage = undefined; 13343 fn.literal = isLiteral(ast); 13344 fn.constant = isConstant(ast); 13345 return fn; 13346 }, 13347 13348 USE: 'use', 13349 13350 STRICT: 'strict', 13351 13352 watchFns: function() { 13353 var result = []; 13354 var fns = this.state.inputs; 13355 var self = this; 13356 forEach(fns, function(name) { 13357 result.push('var ' + name + '=' + self.generateFunction(name, 's')); 13358 }); 13359 if (fns.length) { 13360 result.push('fn.inputs=[' + fns.join(',') + '];'); 13361 } 13362 return result.join(''); 13363 }, 13364 13365 generateFunction: function(name, params) { 13366 return 'function(' + params + '){' + 13367 this.varsPrefix(name) + 13368 this.body(name) + 13369 '};'; 13370 }, 13371 13372 filterPrefix: function() { 13373 var parts = []; 13374 var self = this; 13375 forEach(this.state.filters, function(id, filter) { 13376 parts.push(id + '=$filter(' + self.escape(filter) + ')'); 13377 }); 13378 if (parts.length) return 'var ' + parts.join(',') + ';'; 13379 return ''; 13380 }, 13381 13382 varsPrefix: function(section) { 13383 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; 13384 }, 13385 13386 body: function(section) { 13387 return this.state[section].body.join(''); 13388 }, 13389 13390 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 13391 var left, right, self = this, args, expression; 13392 recursionFn = recursionFn || noop; 13393 if (!skipWatchIdCheck && isDefined(ast.watchId)) { 13394 intoId = intoId || this.nextId(); 13395 this.if_('i', 13396 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), 13397 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) 13398 ); 13399 return; 13400 } 13401 switch (ast.type) { 13402 case AST.Program: 13403 forEach(ast.body, function(expression, pos) { 13404 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); 13405 if (pos !== ast.body.length - 1) { 13406 self.current().body.push(right, ';'); 13407 } else { 13408 self.return_(right); 13409 } 13410 }); 13411 break; 13412 case AST.Literal: 13413 expression = this.escape(ast.value); 13414 this.assign(intoId, expression); 13415 recursionFn(expression); 13416 break; 13417 case AST.UnaryExpression: 13418 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); 13419 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; 13420 this.assign(intoId, expression); 13421 recursionFn(expression); 13422 break; 13423 case AST.BinaryExpression: 13424 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); 13425 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); 13426 if (ast.operator === '+') { 13427 expression = this.plus(left, right); 13428 } else if (ast.operator === '-') { 13429 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); 13430 } else { 13431 expression = '(' + left + ')' + ast.operator + '(' + right + ')'; 13432 } 13433 this.assign(intoId, expression); 13434 recursionFn(expression); 13435 break; 13436 case AST.LogicalExpression: 13437 intoId = intoId || this.nextId(); 13438 self.recurse(ast.left, intoId); 13439 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); 13440 recursionFn(intoId); 13441 break; 13442 case AST.ConditionalExpression: 13443 intoId = intoId || this.nextId(); 13444 self.recurse(ast.test, intoId); 13445 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); 13446 recursionFn(intoId); 13447 break; 13448 case AST.Identifier: 13449 intoId = intoId || this.nextId(); 13450 if (nameId) { 13451 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); 13452 nameId.computed = false; 13453 nameId.name = ast.name; 13454 } 13455 ensureSafeMemberName(ast.name); 13456 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), 13457 function() { 13458 self.if_(self.stage === 'inputs' || 's', function() { 13459 if (create && create !== 1) { 13460 self.if_( 13461 self.not(self.nonComputedMember('s', ast.name)), 13462 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); 13463 } 13464 self.assign(intoId, self.nonComputedMember('s', ast.name)); 13465 }); 13466 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) 13467 ); 13468 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { 13469 self.addEnsureSafeObject(intoId); 13470 } 13471 recursionFn(intoId); 13472 break; 13473 case AST.MemberExpression: 13474 left = nameId && (nameId.context = this.nextId()) || this.nextId(); 13475 intoId = intoId || this.nextId(); 13476 self.recurse(ast.object, left, undefined, function() { 13477 self.if_(self.notNull(left), function() { 13478 if (ast.computed) { 13479 right = self.nextId(); 13480 self.recurse(ast.property, right); 13481 self.getStringValue(right); 13482 self.addEnsureSafeMemberName(right); 13483 if (create && create !== 1) { 13484 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); 13485 } 13486 expression = self.ensureSafeObject(self.computedMember(left, right)); 13487 self.assign(intoId, expression); 13488 if (nameId) { 13489 nameId.computed = true; 13490 nameId.name = right; 13491 } 13492 } else { 13493 ensureSafeMemberName(ast.property.name); 13494 if (create && create !== 1) { 13495 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); 13496 } 13497 expression = self.nonComputedMember(left, ast.property.name); 13498 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { 13499 expression = self.ensureSafeObject(expression); 13500 } 13501 self.assign(intoId, expression); 13502 if (nameId) { 13503 nameId.computed = false; 13504 nameId.name = ast.property.name; 13505 } 13506 } 13507 }, function() { 13508 self.assign(intoId, 'undefined'); 13509 }); 13510 recursionFn(intoId); 13511 }, !!create); 13512 break; 13513 case AST.CallExpression: 13514 intoId = intoId || this.nextId(); 13515 if (ast.filter) { 13516 right = self.filter(ast.callee.name); 13517 args = []; 13518 forEach(ast.arguments, function(expr) { 13519 var argument = self.nextId(); 13520 self.recurse(expr, argument); 13521 args.push(argument); 13522 }); 13523 expression = right + '(' + args.join(',') + ')'; 13524 self.assign(intoId, expression); 13525 recursionFn(intoId); 13526 } else { 13527 right = self.nextId(); 13528 left = {}; 13529 args = []; 13530 self.recurse(ast.callee, right, left, function() { 13531 self.if_(self.notNull(right), function() { 13532 self.addEnsureSafeFunction(right); 13533 forEach(ast.arguments, function(expr) { 13534 self.recurse(expr, self.nextId(), undefined, function(argument) { 13535 args.push(self.ensureSafeObject(argument)); 13536 }); 13537 }); 13538 if (left.name) { 13539 if (!self.state.expensiveChecks) { 13540 self.addEnsureSafeObject(left.context); 13541 } 13542 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; 13543 } else { 13544 expression = right + '(' + args.join(',') + ')'; 13545 } 13546 expression = self.ensureSafeObject(expression); 13547 self.assign(intoId, expression); 13548 }, function() { 13549 self.assign(intoId, 'undefined'); 13550 }); 13551 recursionFn(intoId); 13552 }); 13553 } 13554 break; 13555 case AST.AssignmentExpression: 13556 right = this.nextId(); 13557 left = {}; 13558 if (!isAssignable(ast.left)) { 13559 throw $parseMinErr('lval', 'Trying to assing a value to a non l-value'); 13560 } 13561 this.recurse(ast.left, undefined, left, function() { 13562 self.if_(self.notNull(left.context), function() { 13563 self.recurse(ast.right, right); 13564 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); 13565 self.addEnsureSafeAssignContext(left.context); 13566 expression = self.member(left.context, left.name, left.computed) + ast.operator + right; 13567 self.assign(intoId, expression); 13568 recursionFn(intoId || expression); 13569 }); 13570 }, 1); 13571 break; 13572 case AST.ArrayExpression: 13573 args = []; 13574 forEach(ast.elements, function(expr) { 13575 self.recurse(expr, self.nextId(), undefined, function(argument) { 13576 args.push(argument); 13577 }); 13578 }); 13579 expression = '[' + args.join(',') + ']'; 13580 this.assign(intoId, expression); 13581 recursionFn(expression); 13582 break; 13583 case AST.ObjectExpression: 13584 args = []; 13585 forEach(ast.properties, function(property) { 13586 self.recurse(property.value, self.nextId(), undefined, function(expr) { 13587 args.push(self.escape( 13588 property.key.type === AST.Identifier ? property.key.name : 13589 ('' + property.key.value)) + 13590 ':' + expr); 13591 }); 13592 }); 13593 expression = '{' + args.join(',') + '}'; 13594 this.assign(intoId, expression); 13595 recursionFn(expression); 13596 break; 13597 case AST.ThisExpression: 13598 this.assign(intoId, 's'); 13599 recursionFn('s'); 13600 break; 13601 case AST.NGValueParameter: 13602 this.assign(intoId, 'v'); 13603 recursionFn('v'); 13604 break; 13605 } 13606 }, 13607 13608 getHasOwnProperty: function(element, property) { 13609 var key = element + '.' + property; 13610 var own = this.current().own; 13611 if (!own.hasOwnProperty(key)) { 13612 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); 13613 } 13614 return own[key]; 13615 }, 13616 13617 assign: function(id, value) { 13618 if (!id) return; 13619 this.current().body.push(id, '=', value, ';'); 13620 return id; 13621 }, 13622 13623 filter: function(filterName) { 13624 if (!this.state.filters.hasOwnProperty(filterName)) { 13625 this.state.filters[filterName] = this.nextId(true); 13626 } 13627 return this.state.filters[filterName]; 13628 }, 13629 13630 ifDefined: function(id, defaultValue) { 13631 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; 13632 }, 13633 13634 plus: function(left, right) { 13635 return 'plus(' + left + ',' + right + ')'; 13636 }, 13637 13638 return_: function(id) { 13639 this.current().body.push('return ', id, ';'); 13640 }, 13641 13642 if_: function(test, alternate, consequent) { 13643 if (test === true) { 13644 alternate(); 13645 } else { 13646 var body = this.current().body; 13647 body.push('if(', test, '){'); 13648 alternate(); 13649 body.push('}'); 13650 if (consequent) { 13651 body.push('else{'); 13652 consequent(); 13653 body.push('}'); 13654 } 13655 } 13656 }, 13657 13658 not: function(expression) { 13659 return '!(' + expression + ')'; 13660 }, 13661 13662 notNull: function(expression) { 13663 return expression + '!=null'; 13664 }, 13665 13666 nonComputedMember: function(left, right) { 13667 return left + '.' + right; 13668 }, 13669 13670 computedMember: function(left, right) { 13671 return left + '[' + right + ']'; 13672 }, 13673 13674 member: function(left, right, computed) { 13675 if (computed) return this.computedMember(left, right); 13676 return this.nonComputedMember(left, right); 13677 }, 13678 13679 addEnsureSafeObject: function(item) { 13680 this.current().body.push(this.ensureSafeObject(item), ';'); 13681 }, 13682 13683 addEnsureSafeMemberName: function(item) { 13684 this.current().body.push(this.ensureSafeMemberName(item), ';'); 13685 }, 13686 13687 addEnsureSafeFunction: function(item) { 13688 this.current().body.push(this.ensureSafeFunction(item), ';'); 13689 }, 13690 13691 addEnsureSafeAssignContext: function(item) { 13692 this.current().body.push(this.ensureSafeAssignContext(item), ';'); 13693 }, 13694 13695 ensureSafeObject: function(item) { 13696 return 'ensureSafeObject(' + item + ',text)'; 13697 }, 13698 13699 ensureSafeMemberName: function(item) { 13700 return 'ensureSafeMemberName(' + item + ',text)'; 13701 }, 13702 13703 ensureSafeFunction: function(item) { 13704 return 'ensureSafeFunction(' + item + ',text)'; 13705 }, 13706 13707 getStringValue: function(item) { 13708 this.assign(item, 'getStringValue(' + item + ',text)'); 13709 }, 13710 13711 ensureSafeAssignContext: function(item) { 13712 return 'ensureSafeAssignContext(' + item + ',text)'; 13713 }, 13714 13715 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 13716 var self = this; 13717 return function() { 13718 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); 13719 }; 13720 }, 13721 13722 lazyAssign: function(id, value) { 13723 var self = this; 13724 return function() { 13725 self.assign(id, value); 13726 }; 13727 }, 13728 13729 stringEscapeRegex: /[^ a-zA-Z0-9]/g, 13730 13731 stringEscapeFn: function(c) { 13732 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 13733 }, 13734 13735 escape: function(value) { 13736 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'"; 13737 if (isNumber(value)) return value.toString(); 13738 if (value === true) return 'true'; 13739 if (value === false) return 'false'; 13740 if (value === null) return 'null'; 13741 if (typeof value === 'undefined') return 'undefined'; 13742 13743 throw $parseMinErr('esc', 'IMPOSSIBLE'); 13744 }, 13745 13746 nextId: function(skip, init) { 13747 var id = 'v' + (this.state.nextId++); 13748 if (!skip) { 13749 this.current().vars.push(id + (init ? '=' + init : '')); 13750 } 13751 return id; 13752 }, 13753 13754 current: function() { 13755 return this.state[this.state.computing]; 13756 } 13757}; 13758 13759 13760function ASTInterpreter(astBuilder, $filter) { 13761 this.astBuilder = astBuilder; 13762 this.$filter = $filter; 13763} 13764 13765ASTInterpreter.prototype = { 13766 compile: function(expression, expensiveChecks) { 13767 var self = this; 13768 var ast = this.astBuilder.ast(expression); 13769 this.expression = expression; 13770 this.expensiveChecks = expensiveChecks; 13771 findConstantAndWatchExpressions(ast, self.$filter); 13772 var assignable; 13773 var assign; 13774 if ((assignable = assignableAST(ast))) { 13775 assign = this.recurse(assignable); 13776 } 13777 var toWatch = getInputs(ast.body); 13778 var inputs; 13779 if (toWatch) { 13780 inputs = []; 13781 forEach(toWatch, function(watch, key) { 13782 var input = self.recurse(watch); 13783 watch.input = input; 13784 inputs.push(input); 13785 watch.watchId = key; 13786 }); 13787 } 13788 var expressions = []; 13789 forEach(ast.body, function(expression) { 13790 expressions.push(self.recurse(expression.expression)); 13791 }); 13792 var fn = ast.body.length === 0 ? function() {} : 13793 ast.body.length === 1 ? expressions[0] : 13794 function(scope, locals) { 13795 var lastValue; 13796 forEach(expressions, function(exp) { 13797 lastValue = exp(scope, locals); 13798 }); 13799 return lastValue; 13800 }; 13801 if (assign) { 13802 fn.assign = function(scope, value, locals) { 13803 return assign(scope, locals, value); 13804 }; 13805 } 13806 if (inputs) { 13807 fn.inputs = inputs; 13808 } 13809 fn.literal = isLiteral(ast); 13810 fn.constant = isConstant(ast); 13811 return fn; 13812 }, 13813 13814 recurse: function(ast, context, create) { 13815 var left, right, self = this, args, expression; 13816 if (ast.input) { 13817 return this.inputs(ast.input, ast.watchId); 13818 } 13819 switch (ast.type) { 13820 case AST.Literal: 13821 return this.value(ast.value, context); 13822 case AST.UnaryExpression: 13823 right = this.recurse(ast.argument); 13824 return this['unary' + ast.operator](right, context); 13825 case AST.BinaryExpression: 13826 left = this.recurse(ast.left); 13827 right = this.recurse(ast.right); 13828 return this['binary' + ast.operator](left, right, context); 13829 case AST.LogicalExpression: 13830 left = this.recurse(ast.left); 13831 right = this.recurse(ast.right); 13832 return this['binary' + ast.operator](left, right, context); 13833 case AST.ConditionalExpression: 13834 return this['ternary?:']( 13835 this.recurse(ast.test), 13836 this.recurse(ast.alternate), 13837 this.recurse(ast.consequent), 13838 context 13839 ); 13840 case AST.Identifier: 13841 ensureSafeMemberName(ast.name, self.expression); 13842 return self.identifier(ast.name, 13843 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), 13844 context, create, self.expression); 13845 case AST.MemberExpression: 13846 left = this.recurse(ast.object, false, !!create); 13847 if (!ast.computed) { 13848 ensureSafeMemberName(ast.property.name, self.expression); 13849 right = ast.property.name; 13850 } 13851 if (ast.computed) right = this.recurse(ast.property); 13852 return ast.computed ? 13853 this.computedMember(left, right, context, create, self.expression) : 13854 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); 13855 case AST.CallExpression: 13856 args = []; 13857 forEach(ast.arguments, function(expr) { 13858 args.push(self.recurse(expr)); 13859 }); 13860 if (ast.filter) right = this.$filter(ast.callee.name); 13861 if (!ast.filter) right = this.recurse(ast.callee, true); 13862 return ast.filter ? 13863 function(scope, locals, assign, inputs) { 13864 var values = []; 13865 for (var i = 0; i < args.length; ++i) { 13866 values.push(args[i](scope, locals, assign, inputs)); 13867 } 13868 var value = right.apply(undefined, values, inputs); 13869 return context ? {context: undefined, name: undefined, value: value} : value; 13870 } : 13871 function(scope, locals, assign, inputs) { 13872 var rhs = right(scope, locals, assign, inputs); 13873 var value; 13874 if (rhs.value != null) { 13875 ensureSafeObject(rhs.context, self.expression); 13876 ensureSafeFunction(rhs.value, self.expression); 13877 var values = []; 13878 for (var i = 0; i < args.length; ++i) { 13879 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); 13880 } 13881 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); 13882 } 13883 return context ? {value: value} : value; 13884 }; 13885 case AST.AssignmentExpression: 13886 left = this.recurse(ast.left, true, 1); 13887 right = this.recurse(ast.right); 13888 return function(scope, locals, assign, inputs) { 13889 var lhs = left(scope, locals, assign, inputs); 13890 var rhs = right(scope, locals, assign, inputs); 13891 ensureSafeObject(lhs.value, self.expression); 13892 ensureSafeAssignContext(lhs.context); 13893 lhs.context[lhs.name] = rhs; 13894 return context ? {value: rhs} : rhs; 13895 }; 13896 case AST.ArrayExpression: 13897 args = []; 13898 forEach(ast.elements, function(expr) { 13899 args.push(self.recurse(expr)); 13900 }); 13901 return function(scope, locals, assign, inputs) { 13902 var value = []; 13903 for (var i = 0; i < args.length; ++i) { 13904 value.push(args[i](scope, locals, assign, inputs)); 13905 } 13906 return context ? {value: value} : value; 13907 }; 13908 case AST.ObjectExpression: 13909 args = []; 13910 forEach(ast.properties, function(property) { 13911 args.push({key: property.key.type === AST.Identifier ? 13912 property.key.name : 13913 ('' + property.key.value), 13914 value: self.recurse(property.value) 13915 }); 13916 }); 13917 return function(scope, locals, assign, inputs) { 13918 var value = {}; 13919 for (var i = 0; i < args.length; ++i) { 13920 value[args[i].key] = args[i].value(scope, locals, assign, inputs); 13921 } 13922 return context ? {value: value} : value; 13923 }; 13924 case AST.ThisExpression: 13925 return function(scope) { 13926 return context ? {value: scope} : scope; 13927 }; 13928 case AST.NGValueParameter: 13929 return function(scope, locals, assign, inputs) { 13930 return context ? {value: assign} : assign; 13931 }; 13932 } 13933 }, 13934 13935 'unary+': function(argument, context) { 13936 return function(scope, locals, assign, inputs) { 13937 var arg = argument(scope, locals, assign, inputs); 13938 if (isDefined(arg)) { 13939 arg = +arg; 13940 } else { 13941 arg = 0; 13942 } 13943 return context ? {value: arg} : arg; 13944 }; 13945 }, 13946 'unary-': function(argument, context) { 13947 return function(scope, locals, assign, inputs) { 13948 var arg = argument(scope, locals, assign, inputs); 13949 if (isDefined(arg)) { 13950 arg = -arg; 13951 } else { 13952 arg = 0; 13953 } 13954 return context ? {value: arg} : arg; 13955 }; 13956 }, 13957 'unary!': function(argument, context) { 13958 return function(scope, locals, assign, inputs) { 13959 var arg = !argument(scope, locals, assign, inputs); 13960 return context ? {value: arg} : arg; 13961 }; 13962 }, 13963 'binary+': function(left, right, context) { 13964 return function(scope, locals, assign, inputs) { 13965 var lhs = left(scope, locals, assign, inputs); 13966 var rhs = right(scope, locals, assign, inputs); 13967 var arg = plusFn(lhs, rhs); 13968 return context ? {value: arg} : arg; 13969 }; 13970 }, 13971 'binary-': function(left, right, context) { 13972 return function(scope, locals, assign, inputs) { 13973 var lhs = left(scope, locals, assign, inputs); 13974 var rhs = right(scope, locals, assign, inputs); 13975 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); 13976 return context ? {value: arg} : arg; 13977 }; 13978 }, 13979 'binary*': function(left, right, context) { 13980 return function(scope, locals, assign, inputs) { 13981 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); 13982 return context ? {value: arg} : arg; 13983 }; 13984 }, 13985 'binary/': function(left, right, context) { 13986 return function(scope, locals, assign, inputs) { 13987 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); 13988 return context ? {value: arg} : arg; 13989 }; 13990 }, 13991 'binary%': function(left, right, context) { 13992 return function(scope, locals, assign, inputs) { 13993 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); 13994 return context ? {value: arg} : arg; 13995 }; 13996 }, 13997 'binary===': function(left, right, context) { 13998 return function(scope, locals, assign, inputs) { 13999 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); 14000 return context ? {value: arg} : arg; 14001 }; 14002 }, 14003 'binary!==': function(left, right, context) { 14004 return function(scope, locals, assign, inputs) { 14005 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); 14006 return context ? {value: arg} : arg; 14007 }; 14008 }, 14009 'binary==': function(left, right, context) { 14010 return function(scope, locals, assign, inputs) { 14011 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); 14012 return context ? {value: arg} : arg; 14013 }; 14014 }, 14015 'binary!=': function(left, right, context) { 14016 return function(scope, locals, assign, inputs) { 14017 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); 14018 return context ? {value: arg} : arg; 14019 }; 14020 }, 14021 'binary<': function(left, right, context) { 14022 return function(scope, locals, assign, inputs) { 14023 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); 14024 return context ? {value: arg} : arg; 14025 }; 14026 }, 14027 'binary>': function(left, right, context) { 14028 return function(scope, locals, assign, inputs) { 14029 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); 14030 return context ? {value: arg} : arg; 14031 }; 14032 }, 14033 'binary<=': function(left, right, context) { 14034 return function(scope, locals, assign, inputs) { 14035 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); 14036 return context ? {value: arg} : arg; 14037 }; 14038 }, 14039 'binary>=': function(left, right, context) { 14040 return function(scope, locals, assign, inputs) { 14041 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); 14042 return context ? {value: arg} : arg; 14043 }; 14044 }, 14045 'binary&&': function(left, right, context) { 14046 return function(scope, locals, assign, inputs) { 14047 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); 14048 return context ? {value: arg} : arg; 14049 }; 14050 }, 14051 'binary||': function(left, right, context) { 14052 return function(scope, locals, assign, inputs) { 14053 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); 14054 return context ? {value: arg} : arg; 14055 }; 14056 }, 14057 'ternary?:': function(test, alternate, consequent, context) { 14058 return function(scope, locals, assign, inputs) { 14059 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); 14060 return context ? {value: arg} : arg; 14061 }; 14062 }, 14063 value: function(value, context) { 14064 return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; 14065 }, 14066 identifier: function(name, expensiveChecks, context, create, expression) { 14067 return function(scope, locals, assign, inputs) { 14068 var base = locals && (name in locals) ? locals : scope; 14069 if (create && create !== 1 && base && !(base[name])) { 14070 base[name] = {}; 14071 } 14072 var value = base ? base[name] : undefined; 14073 if (expensiveChecks) { 14074 ensureSafeObject(value, expression); 14075 } 14076 if (context) { 14077 return {context: base, name: name, value: value}; 14078 } else { 14079 return value; 14080 } 14081 }; 14082 }, 14083 computedMember: function(left, right, context, create, expression) { 14084 return function(scope, locals, assign, inputs) { 14085 var lhs = left(scope, locals, assign, inputs); 14086 var rhs; 14087 var value; 14088 if (lhs != null) { 14089 rhs = right(scope, locals, assign, inputs); 14090 rhs = getStringValue(rhs); 14091 ensureSafeMemberName(rhs, expression); 14092 if (create && create !== 1 && lhs && !(lhs[rhs])) { 14093 lhs[rhs] = {}; 14094 } 14095 value = lhs[rhs]; 14096 ensureSafeObject(value, expression); 14097 } 14098 if (context) { 14099 return {context: lhs, name: rhs, value: value}; 14100 } else { 14101 return value; 14102 } 14103 }; 14104 }, 14105 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { 14106 return function(scope, locals, assign, inputs) { 14107 var lhs = left(scope, locals, assign, inputs); 14108 if (create && create !== 1 && lhs && !(lhs[right])) { 14109 lhs[right] = {}; 14110 } 14111 var value = lhs != null ? lhs[right] : undefined; 14112 if (expensiveChecks || isPossiblyDangerousMemberName(right)) { 14113 ensureSafeObject(value, expression); 14114 } 14115 if (context) { 14116 return {context: lhs, name: right, value: value}; 14117 } else { 14118 return value; 14119 } 14120 }; 14121 }, 14122 inputs: function(input, watchId) { 14123 return function(scope, value, locals, inputs) {
14124 if (inputs) return inputs[watchId]; 14125 return input(scope, value, locals); 14126 }; 14127 } 14128}; 14129 14130/** 14131 * @constructor 14132 */ 14133var Parser = function(lexer, $filter, options) { 14134 this.lexer = lexer; 14135 this.$filter = $filter; 14136 this.options = options; 14137 this.ast = new AST(this.lexer); 14138 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : 14139 new ASTCompiler(this.ast, $filter); 14140}; 14141 14142Parser.prototype = { 14143 constructor: Parser, 14144 14145 parse: function(text) { 14146 return this.astCompiler.compile(text, this.options.expensiveChecks); 14147 } 14148}; 14149 14150var getterFnCacheDefault = createMap(); 14151var getterFnCacheExpensive = createMap(); 14152 14153function isPossiblyDangerousMemberName(name) { 14154 return name == 'constructor'; 14155} 14156 14157var objectValueOf = Object.prototype.valueOf; 14158 14159function getValueOf(value) { 14160 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 14161} 14162 14163/////////////////////////////////// 14164 14165/** 14166 * @ngdoc service 14167 * @name $parse 14168 * @kind function 14169 * 14170 * @description 14171 * 14172 * Converts Angular {@link guide/expression expression} into a function. 14173 * 14174 * ```js 14175 * var getter = $parse('user.name'); 14176 * var setter = getter.assign; 14177 * var context = {user:{name:'angular'}}; 14178 * var locals = {user:{name:'local'}}; 14179 * 14180 * expect(getter(context)).toEqual('angular'); 14181 * setter(context, 'newValue'); 14182 * expect(context.user.name).toEqual('newValue'); 14183 * expect(getter(context, locals)).toEqual('local'); 14184 * ``` 14185 * 14186 * 14187 * @param {string} expression String expression to compile. 14188 * @returns {function(context, locals)} a function which represents the compiled expression: 14189 * 14190 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14191 * are evaluated against (typically a scope object). 14192 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14193 * `context`. 14194 * 14195 * The returned function also has the following properties: 14196 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 14197 * literal. 14198 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 14199 * constant literals. 14200 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 14201 * set to a function to change its value on the given context. 14202 * 14203 */ 14204 14205 14206/** 14207 * @ngdoc provider 14208 * @name $parseProvider 14209 * 14210 * @description 14211 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 14212 * service. 14213 */ 14214function $ParseProvider() { 14215 var cacheDefault = createMap(); 14216 var cacheExpensive = createMap(); 14217 14218 this.$get = ['$filter', function($filter) { 14219 var noUnsafeEval = csp().noUnsafeEval; 14220 var $parseOptions = { 14221 csp: noUnsafeEval, 14222 expensiveChecks: false 14223 }, 14224 $parseOptionsExpensive = { 14225 csp: noUnsafeEval, 14226 expensiveChecks: true 14227 }; 14228 14229 return function $parse(exp, interceptorFn, expensiveChecks) { 14230 var parsedExpression, oneTime, cacheKey; 14231 14232 switch (typeof exp) { 14233 case 'string': 14234 exp = exp.trim(); 14235 cacheKey = exp; 14236 14237 var cache = (expensiveChecks ? cacheExpensive : cacheDefault); 14238 parsedExpression = cache[cacheKey]; 14239 14240 if (!parsedExpression) { 14241 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 14242 oneTime = true; 14243 exp = exp.substring(2); 14244 } 14245 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; 14246 var lexer = new Lexer(parseOptions); 14247 var parser = new Parser(lexer, $filter, parseOptions); 14248 parsedExpression = parser.parse(exp); 14249 if (parsedExpression.constant) { 14250 parsedExpression.$$watchDelegate = constantWatchDelegate; 14251 } else if (oneTime) { 14252 parsedExpression.$$watchDelegate = parsedExpression.literal ? 14253 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; 14254 } else if (parsedExpression.inputs) { 14255 parsedExpression.$$watchDelegate = inputsWatchDelegate; 14256 } 14257 cache[cacheKey] = parsedExpression; 14258 } 14259 return addInterceptor(parsedExpression, interceptorFn); 14260 14261 case 'function': 14262 return addInterceptor(exp, interceptorFn); 14263 14264 default: 14265 return noop; 14266 } 14267 }; 14268 14269 function expressionInputDirtyCheck(newValue, oldValueOfValue) { 14270 14271 if (newValue == null || oldValueOfValue == null) { // null/undefined 14272 return newValue === oldValueOfValue; 14273 } 14274 14275 if (typeof newValue === 'object') { 14276 14277 // attempt to convert the value to a primitive type 14278 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 14279 // be cheaply dirty-checked 14280 newValue = getValueOf(newValue); 14281 14282 if (typeof newValue === 'object') { 14283 // objects/arrays are not supported - deep-watching them would be too expensive 14284 return false; 14285 } 14286 14287 // fall-through to the primitive equality check 14288 } 14289 14290 //Primitive or NaN 14291 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 14292 } 14293 14294 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 14295 var inputExpressions = parsedExpression.inputs; 14296 var lastResult; 14297 14298 if (inputExpressions.length === 1) { 14299 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails 14300 inputExpressions = inputExpressions[0]; 14301 return scope.$watch(function expressionInputWatch(scope) { 14302 var newInputValue = inputExpressions(scope); 14303 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { 14304 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); 14305 oldInputValueOf = newInputValue && getValueOf(newInputValue); 14306 } 14307 return lastResult; 14308 }, listener, objectEquality, prettyPrintExpression); 14309 } 14310 14311 var oldInputValueOfValues = []; 14312 var oldInputValues = []; 14313 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 14314 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 14315 oldInputValues[i] = null; 14316 } 14317 14318 return scope.$watch(function expressionInputsWatch(scope) { 14319 var changed = false; 14320 14321 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 14322 var newInputValue = inputExpressions[i](scope); 14323 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { 14324 oldInputValues[i] = newInputValue; 14325 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 14326 } 14327 } 14328 14329 if (changed) { 14330 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); 14331 } 14332 14333 return lastResult; 14334 }, listener, objectEquality, prettyPrintExpression); 14335 } 14336 14337 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) {
14338 var unwatch, lastValue; 14339 return unwatch = scope.$watch(function oneTimeWatch(scope) { 14340 return parsedExpression(scope); 14341 }, function oneTimeListener(value, old, scope) { 14342 lastValue = value; 14343 if (isFunction(listener)) { 14344 listener.apply(this, arguments); 14345 } 14346 if (isDefined(value)) { 14347 scope.$$postDigest(function() { 14348 if (isDefined(lastValue)) { 14349 unwatch(); 14350 } 14351 }); 14352 } 14353 }, objectEquality); 14354 } 14355 14356 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14357 var unwatch, lastValue; 14358 return unwatch = scope.$watch(function oneTimeWatch(scope) { 14359 return parsedExpression(scope); 14360 }, function oneTimeListener(value, old, scope) { 14361 lastValue = value; 14362 if (isFunction(listener)) { 14363 listener.call(this, value, old, scope); 14364 } 14365 if (isAllDefined(value)) { 14366 scope.$$postDigest(function() { 14367 if (isAllDefined(lastValue)) unwatch(); 14368 }); 14369 } 14370 }, objectEquality); 14371 14372 function isAllDefined(value) { 14373 var allDefined = true; 14374 forEach(value, function(val) { 14375 if (!isDefined(val)) allDefined = false; 14376 }); 14377 return allDefined; 14378 } 14379 } 14380 14381 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14382 var unwatch; 14383 return unwatch = scope.$watch(function constantWatch(scope) { 14384 return parsedExpression(scope); 14385 }, function constantListener(value, old, scope) { 14386 if (isFunction(listener)) { 14387 listener.apply(this, arguments); 14388 } 14389 unwatch(); 14390 }, objectEquality); 14391 } 14392 14393 function addInterceptor(parsedExpression, interceptorFn) { 14394 if (!interceptorFn) return parsedExpression; 14395 var watchDelegate = parsedExpression.$$watchDelegate; 14396 14397 var regularWatch = 14398 watchDelegate !== oneTimeLiteralWatchDelegate && 14399 watchDelegate !== oneTimeWatchDelegate; 14400 14401 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { 14402 var value = parsedExpression(scope, locals, assign, inputs); 14403 return interceptorFn(value, scope, locals); 14404 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { 14405 var value = parsedExpression(scope, locals, assign, inputs); 14406 var result = interceptorFn(value, scope, locals); 14407 // we only return the interceptor's result if the 14408 // initial value is defined (for bind-once) 14409 return isDefined(value) ? result : value; 14410 }; 14411 14412 // Propagate $$watchDelegates other then inputsWatchDelegate 14413 if (parsedExpression.$$watchDelegate && 14414 parsedExpression.$$watchDelegate !== inputsWatchDelegate) { 14415 fn.$$watchDelegate = parsedExpression.$$watchDelegate; 14416 } else if (!interceptorFn.$stateful) { 14417 // If there is an interceptor, but no watchDelegate then treat the interceptor like 14418 // we treat filters - it is assumed to be a pure function unless flagged with $stateful 14419 fn.$$watchDelegate = inputsWatchDelegate; 14420 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; 14421 } 14422 14423 return fn; 14424 } 14425 }]; 14426} 14427 14428/** 14429 * @ngdoc service 14430 * @name $q 14431 * @requires $rootScope 14432 * 14433 * @description 14434 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 14435 * when they are done processing. 14436 * 14437 * This is an implementation of promises/deferred objects inspired by 14438 * [Kris Kowal's Q](https://github.com/kriskowal/q). 14439 * 14440 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 14441 * implementations, and the other which resembles ES6 promises to some degree. 14442 * 14443 * # $q constructor 14444 * 14445 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 14446 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony, 14447 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 14448 * 14449 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are 14450 * available yet.
14451 * 14452 * It can be used like so: 14453 * 14454 * ```js 14455 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 14456 * // are available in the current lexical scope (they could have been injected or passed in). 14457 * 14458 * function asyncGreet(name) { 14459 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 14460 * return $q(function(resolve, reject) { 14461 * setTimeout(function() { 14462 * if (okToGreet(name)) { 14463 * resolve('Hello, ' + name + '!'); 14464 * } else { 14465 * reject('Greeting ' + name + ' is not allowed.'); 14466 * } 14467 * }, 1000); 14468 * }); 14469 * } 14470 * 14471 * var promise = asyncGreet('Robin Hood'); 14472 * promise.then(function(greeting) { 14473 * alert('Success: ' + greeting); 14474 * }, function(reason) { 14475 * alert('Failed: ' + reason); 14476 * }); 14477 * ``` 14478 * 14479 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 14480 * 14481 * However, the more traditional CommonJS-style usage is still available, and documented below. 14482 * 14483 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 14484 * interface for interacting with an object that represents the result of an action that is 14485 * performed asynchronously, and may or may not be finished at any given point in time. 14486 * 14487 * From the perspective of dealing with error handling, deferred and promise APIs are to 14488 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 14489 * 14490 * ```js 14491 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 14492 * // are available in the current lexical scope (they could have been injected or passed in). 14493 * 14494 * function asyncGreet(name) { 14495 * var deferred = $q.defer(); 14496 * 14497 * setTimeout(function() { 14498 * deferred.notify('About to greet ' + name + '.'); 14499 * 14500 * if (okToGreet(name)) { 14501 * deferred.resolve('Hello, ' + name + '!'); 14502 * } else { 14503 * deferred.reject('Greeting ' + name + ' is not allowed.'); 14504 * } 14505 * }, 1000); 14506 * 14507 * return deferred.promise; 14508 * } 14509 * 14510 * var promise = asyncGreet('Robin Hood'); 14511 * promise.then(function(greeting) { 14512 * alert('Success: ' + greeting); 14513 * }, function(reason) { 14514 * alert('Failed: ' + reason); 14515 * }, function(update) { 14516 * alert('Got notification: ' + update); 14517 * }); 14518 * ``` 14519 * 14520 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 14521 * comes in the way of guarantees that promise and deferred APIs make, see 14522 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 14523 * 14524 * Additionally the promise api allows for composition that is very hard to do with the 14525 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 14526 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 14527 * section on serial or parallel joining of promises. 14528 * 14529 * # The Deferred API 14530 * 14531 * A new instance of deferred is constructed by calling `$q.defer()`. 14532 * 14533 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 14534 * that can be used for signaling the successful or unsuccessful completion, as well as the status 14535 * of the task. 14536 * 14537 * **Methods** 14538 * 14539 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 14540 * constructed via `$q.reject`, the promise will be rejected instead. 14541 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 14542 * resolving it with a rejection constructed via `$q.reject`. 14543 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 14544 * multiple times before the promise is either resolved or rejected. 14545 * 14546 * **Properties** 14547 * 14548 * - promise â `{Promise}` â promise object associated with this deferred. 14549 * 14550 * 14551 * # The Promise API 14552 * 14553 * A new promise instance is created when a deferred instance is created and can be retrieved by 14554 * calling `deferred.promise`. 14555 * 14556 * The purpose of the promise object is to allow for interested parties to get access to the result 14557 * of the deferred task when it completes. 14558 * 14559 * **Methods** 14560 * 14561 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 14562 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 14563 * as soon as the result is available. The callbacks are called with a single argument: the result 14564 * or rejection reason. Additionally, the notify callback may be called zero or more times to 14565 * provide a progress indication, before the promise is resolved or rejected. 14566 * 14567 * This method *returns a new promise* which is resolved or rejected via the return value of the 14568 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14569 * with the value which is resolved in that promise using 14570 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). 14571 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be 14572 * resolved or rejected from the notifyCallback method. 14573 * 14574 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 14575 * 14576 * - `finally(callback, notifyCallback)` â allows you to observe either the fulfillment or rejection of a promise, 14577 * but to do so without modifying the final value. This is useful to release resources or do some 14578 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 14579 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 14580 * more information. 14581 * 14582 * # Chaining promises 14583 * 14584 * Because calling the `then` method of a promise returns a new derived promise, it is easily 14585 * possible to create a chain of promises: 14586 * 14587 * ```js 14588 * promiseB = promiseA.then(function(result) { 14589 * return result + 1; 14590 * }); 14591 * 14592 * // promiseB will be resolved immediately after promiseA is resolved and its value 14593 * // will be the result of promiseA incremented by 1 14594 * ``` 14595 * 14596 * It is possible to create chains of any length and since a promise can be resolved with another 14597 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 14598 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 14599 * $http's response interceptors. 14600 * 14601 * 14602 * # Differences between Kris Kowal's Q and $q 14603 * 14604 * There are two main differences: 14605 * 14606 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 14607 * mechanism in angular, which means faster propagation of resolution or rejection into your 14608 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 14609 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14610 * all the important functionality needed for common async tasks. 14611 * 14612 * # Testing 14613 * 14614 * ```js 14615 * it('should simulate promise', inject(function($q, $rootScope) { 14616 * var deferred = $q.defer(); 14617 * var promise = deferred.promise; 14618 * var resolvedValue; 14619 * 14620 * promise.then(function(value) { resolvedValue = value; }); 14621 * expect(resolvedValue).toBeUndefined(); 14622 * 14623 * // Simulate resolving of promise 14624 * deferred.resolve(123); 14625 * // Note that the 'then' function does not get called synchronously. 14626 * // This is because we want the promise API to always be async, whether or not 14627 * // it got called synchronously or asynchronously. 14628 * expect(resolvedValue).toBeUndefined(); 14629 * 14630 * // Propagate promise resolution to 'then' functions using $apply(). 14631 * $rootScope.$apply(); 14632 * expect(resolvedValue).toEqual(123); 14633 * })); 14634 * ``` 14635 * 14636 * @param {function(function, function)} resolver Function which is responsible for resolving or 14637 * rejecting the newly created promise. The first parameter is a function which resolves the 14638 * promise, the second parameter is a function which rejects the promise. 14639 * 14640 * @returns {Promise} The newly created promise. 14641 */ 14642function $QProvider() { 14643 14644 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 14645 return qFactory(function(callback) { 14646 $rootScope.$evalAsync(callback); 14647 }, $exceptionHandler); 14648 }]; 14649} 14650 14651function $$QProvider() { 14652 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 14653 return qFactory(function(callback) { 14654 $browser.defer(callback); 14655 }, $exceptionHandler); 14656 }]; 14657} 14658 14659/** 14660 * Constructs a promise manager. 14661 * 14662 * @param {function(function)} nextTick Function for executing functions in the next turn. 14663 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 14664 * debugging purposes. 14665 * @returns {object} Promise manager. 14666 */ 14667function qFactory(nextTick, exceptionHandler) { 14668 var $qMinErr = minErr('$q', TypeError); 14669 function callOnce(self, resolveFn, rejectFn) { 14670 var called = false; 14671 function wrap(fn) { 14672 return function(value) { 14673 if (called) return; 14674 called = true; 14675 fn.call(self, value); 14676 }; 14677 } 14678 14679 return [wrap(resolveFn), wrap(rejectFn)]; 14680 } 14681 14682 /** 14683 * @ngdoc method 14684 * @name ng.$q#defer 14685 * @kind function 14686 * 14687 * @description 14688 * Creates a `Deferred` object which represents a task which will finish in the future. 14689 * 14690 * @returns {Deferred} Returns a new instance of deferred. 14691 */ 14692 var defer = function() { 14693 return new Deferred(); 14694 }; 14695 14696 function Promise() { 14697 this.$$state = { status: 0 }; 14698 } 14699 14700 extend(Promise.prototype, { 14701 then: function(onFulfilled, onRejected, progressBack) { 14702 if (isUndefined(onFulfilled) && isUndefined(onRejected) && isUndefined(progressBack)) { 14703 return this; 14704 } 14705 var result = new Deferred(); 14706 14707 this.$$state.pending = this.$$state.pending || []; 14708 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 14709 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 14710 14711 return result.promise; 14712 }, 14713 14714 "catch": function(callback) { 14715 return this.then(null, callback); 14716 }, 14717 14718 "finally": function(callback, progressBack) { 14719 return this.then(function(value) { 14720 return handleCallback(value, true, callback); 14721 }, function(error) { 14722 return handleCallback(error, false, callback); 14723 }, progressBack); 14724 } 14725 }); 14726 14727 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native 14728 function simpleBind(context, fn) { 14729 return function(value) { 14730 fn.call(context, value); 14731 }; 14732 } 14733 14734 function processQueue(state) { 14735 var fn, deferred, pending; 14736 14737 pending = state.pending; 14738 state.processScheduled = false; 14739 state.pending = undefined; 14740 for (var i = 0, ii = pending.length; i < ii; ++i) { 14741 deferred = pending[i][0]; 14742 fn = pending[i][state.status];
14743 try { 14744 if (isFunction(fn)) { 14745 deferred.resolve(fn(state.value)); 14746 } else if (state.status === 1) { 14747 deferred.resolve(state.value); 14748 } else { 14749 deferred.reject(state.value); 14750 } 14751 } catch (e) { 14752 deferred.reject(e); 14753 exceptionHandler(e); 14754 } 14755 } 14756 } 14757 14758 function scheduleProcessQueue(state) { 14759 if (state.processScheduled || !state.pending) return; 14760 state.processScheduled = true; 14761 nextTick(function() { processQueue(state); }); 14762 } 14763 14764 function Deferred() { 14765 this.promise = new Promise(); 14766 //Necessary to support unbound execution :/ 14767 this.resolve = simpleBind(this, this.resolve); 14768 this.reject = simpleBind(this, this.reject); 14769 this.notify = simpleBind(this, this.notify); 14770 } 14771 14772 extend(Deferred.prototype, { 14773 resolve: function(val) { 14774 if (this.promise.$$state.status) return; 14775 if (val === this.promise) { 14776 this.$$reject($qMinErr( 14777 'qcycle', 14778 "Expected promise to be resolved with value other than itself '{0}'", 14779 val)); 14780 } else { 14781 this.$$resolve(val); 14782 } 14783 14784 }, 14785 14786 $$resolve: function(val) { 14787 var then, fns; 14788 14789 fns = callOnce(this, this.$$resolve, this.$$reject); 14790 try { 14791 if ((isObject(val) || isFunction(val))) then = val && val.then; 14792 if (isFunction(then)) { 14793 this.promise.$$state.status = -1; 14794 then.call(val, fns[0], fns[1], this.notify); 14795 } else { 14796 this.promise.$$state.value = val; 14797 this.promise.$$state.status = 1; 14798 scheduleProcessQueue(this.promise.$$state); 14799 } 14800 } catch (e) { 14801 fns[1](e); 14802 exceptionHandler(e); 14803 } 14804 }, 14805 14806 reject: function(reason) { 14807 if (this.promise.$$state.status) return; 14808 this.$$reject(reason); 14809 }, 14810 14811 $$reject: function(reason) { 14812 this.promise.$$state.value = reason; 14813 this.promise.$$state.status = 2; 14814 scheduleProcessQueue(this.promise.$$state); 14815 }, 14816 14817 notify: function(progress) { 14818 var callbacks = this.promise.$$state.pending; 14819 14820 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { 14821 nextTick(function() { 14822 var callback, result; 14823 for (var i = 0, ii = callbacks.length; i < ii; i++) { 14824 result = callbacks[i][0]; 14825 callback = callbacks[i][3]; 14826 try { 14827 result.notify(isFunction(callback) ? callback(progress) : progress); 14828 } catch (e) { 14829 exceptionHandler(e); 14830 } 14831 } 14832 }); 14833 } 14834 } 14835 }); 14836 14837 /** 14838 * @ngdoc method 14839 * @name $q#reject 14840 * @kind function 14841 * 14842 * @description 14843 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 14844 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 14845 * a promise chain, you don't need to worry about it. 14846 * 14847 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 14848 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 14849 * a promise error callback and you want to forward the error to the promise derived from the 14850 * current promise, you have to "rethrow" the error by returning a rejection constructed via 14851 * `reject`. 14852 * 14853 * ```js 14854 * promiseB = promiseA.then(function(result) { 14855 * // success: do something and resolve promiseB 14856 * // with the old or a new result 14857 * return result; 14858 * }, function(reason) { 14859 * // error: handle the error if possible and 14860 * // resolve promiseB with newPromiseOrValue, 14861 * // otherwise forward the rejection to promiseB 14862 * if (canHandle(reason)) { 14863 * // handle the error and recover 14864 * return newPromiseOrValue; 14865 * } 14866 * return $q.reject(reason); 14867 * }); 14868 * ``` 14869 * 14870 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 14871 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 14872 */ 14873 var reject = function(reason) { 14874 var result = new Deferred(); 14875 result.reject(reason); 14876 return result.promise; 14877 }; 14878 14879 var makePromise = function makePromise(value, resolved) { 14880 var result = new Deferred(); 14881 if (resolved) { 14882 result.resolve(value); 14883 } else { 14884 result.reject(value); 14885 } 14886 return result.promise; 14887 }; 14888 14889 var handleCallback = function handleCallback(value, isResolved, callback) { 14890 var callbackOutput = null; 14891 try { 14892 if (isFunction(callback)) callbackOutput = callback(); 14893 } catch (e) { 14894 return makePromise(e, false); 14895 }
14896 if (isPromiseLike(callbackOutput)) { 14897 return callbackOutput.then(function() { 14898 return makePromise(value, isResolved); 14899 }, function(error) { 14900 return makePromise(error, false); 14901 }); 14902 } else { 14903 return makePromise(value, isResolved); 14904 } 14905 }; 14906 14907 /** 14908 * @ngdoc method 14909 * @name $q#when 14910 * @kind function 14911 * 14912 * @description 14913 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 14914 * This is useful when you are dealing with an object that might or might not be a promise, or if 14915 * the promise comes from a source that can't be trusted. 14916 * 14917 * @param {*} value Value or a promise 14918 * @param {Function=} successCallback 14919 * @param {Function=} errorCallback 14920 * @param {Function=} progressCallback 14921 * @returns {Promise} Returns a promise of the passed value or promise 14922 */ 14923 14924 14925 var when = function(value, callback, errback, progressBack) { 14926 var result = new Deferred(); 14927 result.resolve(value); 14928 return result.promise.then(callback, errback, progressBack); 14929 }; 14930 14931 /** 14932 * @ngdoc method 14933 * @name $q#resolve 14934 * @kind function 14935 * 14936 * @description 14937 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. 14938 * 14939 * @param {*} value Value or a promise 14940 * @param {Function=} successCallback 14941 * @param {Function=} errorCallback 14942 * @param {Function=} progressCallback 14943 * @returns {Promise} Returns a promise of the passed value or promise 14944 */ 14945 var resolve = when; 14946 14947 /** 14948 * @ngdoc method 14949 * @name $q#all 14950 * @kind function 14951 * 14952 * @description 14953 * Combines multiple promises into a single promise that is resolved when all of the input 14954 * promises are resolved. 14955 * 14956 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 14957 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 14958 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 14959 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 14960 * with the same rejection value. 14961 */ 14962 14963 function all(promises) { 14964 var deferred = new Deferred(), 14965 counter = 0, 14966 results = isArray(promises) ? [] : {}; 14967 14968 forEach(promises, function(promise, key) { 14969 counter++; 14970 when(promise).then(function(value) { 14971 if (results.hasOwnProperty(key)) return; 14972 results[key] = value; 14973 if (!(--counter)) deferred.resolve(results); 14974 }, function(reason) { 14975 if (results.hasOwnProperty(key)) return; 14976 deferred.reject(reason); 14977 }); 14978 }); 14979 14980 if (counter === 0) { 14981 deferred.resolve(results); 14982 } 14983 14984 return deferred.promise; 14985 } 14986 14987 var $Q = function Q(resolver) { 14988 if (!isFunction(resolver)) { 14989 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); 14990 } 14991 14992 if (!(this instanceof Q)) { 14993 // More useful when $Q is the Promise itself. 14994 return new Q(resolver); 14995 } 14996 14997 var deferred = new Deferred(); 14998 14999 function resolveFn(value) { 15000 deferred.resolve(value); 15001 } 15002 15003 function rejectFn(reason) { 15004 deferred.reject(reason); 15005 } 15006 15007 resolver(resolveFn, rejectFn); 15008 15009 return deferred.promise; 15010 }; 15011 15012 $Q.defer = defer; 15013 $Q.reject = reject; 15014 $Q.when = when; 15015 $Q.resolve = resolve; 15016 $Q.all = all; 15017 15018 return $Q; 15019} 15020 15021function $$RAFProvider() { //rAF 15022 this.$get = ['$window', '$timeout', function($window, $timeout) { 15023 var requestAnimationFrame = $window.requestAnimationFrame || 15024 $window.webkitRequestAnimationFrame; 15025 15026 var cancelAnimationFrame = $window.cancelAnimationFrame || 15027 $window.webkitCancelAnimationFrame || 15028 $window.webkitCancelRequestAnimationFrame; 15029 15030 var rafSupported = !!requestAnimationFrame; 15031 var raf = rafSupported 15032 ? function(fn) { 15033 var id = requestAnimationFrame(fn); 15034 return function() { 15035 cancelAnimationFrame(id); 15036 }; 15037 } 15038 : function(fn) { 15039 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 15040 return function() { 15041 $timeout.cancel(timer); 15042 }; 15043 }; 15044 15045 raf.supported = rafSupported; 15046 15047 return raf; 15048 }]; 15049} 15050 15051/** 15052 * DESIGN NOTES 15053 * 15054 * The design decisions behind the scope are heavily favored for speed and memory consumption. 15055 * 15056 * The typical use of scope is to watch the expressions, which most of the time return the same 15057 * value as last time so we optimize the operation. 15058 * 15059 * Closures construction is expensive in terms of speed as well as memory: 15060 * - No closures, instead use prototypical inheritance for API 15061 * - Internal state needs to be stored on scope directly, which means that private state is 15062 * exposed as $$____ properties 15063 * 15064 * Loop operations are optimized by using while(count--) { ... } 15065 * - this means that in order to keep the same order of execution as addition we have to add 15066 * items to the array at the beginning (unshift) instead of at the end (push) 15067 * 15068 * Child scopes are created and removed often 15069 * - Using an array would be slow since inserts in middle are expensive so we use linked list 15070 *
15071 * There are few watches then a lot of observers. This is why you don't want the observer to be 15072 * implemented in the same way as watch. Watch requires return of initialization function which 15073 * are expensive to construct. 15074 */ 15075 15076 15077/** 15078 * @ngdoc provider 15079 * @name $rootScopeProvider 15080 * @description 15081 * 15082 * Provider for the $rootScope service. 15083 */ 15084 15085/** 15086 * @ngdoc method 15087 * @name $rootScopeProvider#digestTtl 15088 * @description 15089 * 15090 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 15091 * assuming that the model is unstable. 15092 * 15093 * The current default is 10 iterations. 15094 * 15095 * In complex applications it's possible that the dependencies between `$watch`s will result in 15096 * several digest iterations. However if an application needs more than the default 10 digest 15097 * iterations for its model to stabilize then you should investigate what is causing the model to 15098 * continuously change during the digest. 15099 * 15100 * Increasing the TTL could have performance implications, so you should not change it without 15101 * proper justification. 15102 * 15103 * @param {number} limit The number of digest iterations. 15104 */ 15105 15106 15107/** 15108 * @ngdoc service 15109 * @name $rootScope 15110 * @description 15111 * 15112 * Every application has a single root {@link ng.$rootScope.Scope scope}. 15113 * All other scopes are descendant scopes of the root scope. Scopes provide separation 15114 * between the model and the view, via a mechanism for watching the model for changes. 15115 * They also provide an event emission/broadcast and subscription facility. See the 15116 * {@link guide/scope developer guide on scopes}. 15117 */ 15118function $RootScopeProvider() { 15119 var TTL = 10; 15120 var $rootScopeMinErr = minErr('$rootScope'); 15121 var lastDirtyWatch = null; 15122 var applyAsyncId = null; 15123 15124 this.digestTtl = function(value) { 15125 if (arguments.length) { 15126 TTL = value; 15127 } 15128 return TTL; 15129 }; 15130 15131 function createChildScopeClass(parent) { 15132 function ChildScope() { 15133 this.$$watchers = this.$$nextSibling = 15134 this.$$childHead = this.$$childTail = null; 15135 this.$$listeners = {}; 15136 this.$$listenerCount = {}; 15137 this.$$watchersCount = 0; 15138 this.$id = nextUid(); 15139 this.$$ChildScope = null; 15140 } 15141 ChildScope.prototype = parent; 15142 return ChildScope; 15143 } 15144 15145 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 15146 function($injector, $exceptionHandler, $parse, $browser) { 15147 15148 function destroyChildScope($event) { 15149 $event.currentScope.$$destroyed = true; 15150 } 15151 15152 /** 15153 * @ngdoc type 15154 * @name $rootScope.Scope 15155 * 15156 * @description 15157 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 15158 * {@link auto.$injector $injector}. Child scopes are created using the 15159 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 15160 * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for 15161 * an in-depth introduction and usage examples. 15162 * 15163 * 15164 * # Inheritance 15165 * A scope can inherit from a parent scope, as in this example: 15166 * ```js 15167 var parent = $rootScope; 15168 var child = parent.$new(); 15169 15170 parent.salutation = "Hello"; 15171 expect(child.salutation).toEqual('Hello'); 15172 15173 child.salutation = "Welcome"; 15174 expect(child.salutation).toEqual('Welcome'); 15175 expect(parent.salutation).toEqual('Hello'); 15176 * ``` 15177 * 15178 * When interacting with `Scope` in tests, additional helper methods are available on the 15179 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 15180 * details. 15181 * 15182 * 15183 * @param {Object.<string, function()>=} providers Map of service factory which need to be 15184 * provided for the current scope. Defaults to {@link ng}. 15185 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 15186 * append/override services provided by `providers`. This is handy 15187 * when unit-testing and having the need to override a default 15188 * service. 15189 * @returns {Object} Newly created scope. 15190 * 15191 */ 15192 function Scope() { 15193 this.$id = nextUid();
vendor: 6,561 bytes, lines 15194-15331
15194 this.$$phase = this.$parent = this.$$watchers = 15195 this.$$nextSibling = this.$$prevSibling = 15196 this.$$childHead = this.$$childTail = null; 15197 this.$root = this; 15198 this.$$destroyed = false; 15199 this.$$listeners = {}; 15200 this.$$listenerCount = {}; 15201 this.$$watchersCount = 0; 15202 this.$$isolateBindings = null; 15203 } 15204 15205 /** 15206 * @ngdoc property 15207 * @name $rootScope.Scope#$id 15208 * 15209 * @description 15210 * Unique scope ID (monotonically increasing) useful for debugging. 15211 */ 15212 15213 /** 15214 * @ngdoc property 15215 * @name $rootScope.Scope#$parent 15216 * 15217 * @description 15218 * Reference to the parent scope. 15219 */ 15220 15221 /** 15222 * @ngdoc property 15223 * @name $rootScope.Scope#$root 15224 * 15225 * @description 15226 * Reference to the root scope. 15227 */ 15228 15229 Scope.prototype = { 15230 constructor: Scope, 15231 /** 15232 * @ngdoc method 15233 * @name $rootScope.Scope#$new 15234 * @kind function 15235 * 15236 * @description 15237 * Creates a new child {@link ng.$rootScope.Scope scope}. 15238 * 15239 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 15240 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 15241 * 15242 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 15243 * desired for the scope and its child scopes to be permanently detached from the parent and 15244 * thus stop participating in model change detection and listener notification by invoking. 15245 * 15246 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 15247 * parent scope. The scope is isolated, as it can not see parent scope properties. 15248 * When creating widgets, it is useful for the widget to not accidentally read parent 15249 * state. 15250 * 15251 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 15252 * of the newly created scope. Defaults to `this` scope if not provided. 15253 * This is used when creating a transclude scope to correctly place it 15254 * in the scope hierarchy while maintaining the correct prototypical 15255 * inheritance. 15256 * 15257 * @returns {Object} The newly created child scope. 15258 * 15259 */ 15260 $new: function(isolate, parent) { 15261 var child; 15262 15263 parent = parent || this; 15264 15265 if (isolate) { 15266 child = new Scope(); 15267 child.$root = this.$root; 15268 } else { 15269 // Only create a child scope class if somebody asks for one, 15270 // but cache it to allow the VM to optimize lookups. 15271 if (!this.$$ChildScope) { 15272 this.$$ChildScope = createChildScopeClass(this); 15273 } 15274 child = new this.$$ChildScope(); 15275 } 15276 child.$parent = parent; 15277 child.$$prevSibling = parent.$$childTail; 15278 if (parent.$$childHead) { 15279 parent.$$childTail.$$nextSibling = child; 15280 parent.$$childTail = child; 15281 } else { 15282 parent.$$childHead = parent.$$childTail = child; 15283 } 15284 15285 // When the new scope is not isolated or we inherit from `this`, and 15286 // the parent scope is destroyed, the property `$$destroyed` is inherited 15287 // prototypically. In all other cases, this property needs to be set 15288 // when the parent scope is destroyed. 15289 // The listener needs to be added after the parent is set 15290 if (isolate || parent != this) child.$on('$destroy', destroyChildScope); 15291 15292 return child; 15293 }, 15294 15295 /** 15296 * @ngdoc method 15297 * @name $rootScope.Scope#$watch 15298 * @kind function 15299 * 15300 * @description 15301 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 15302 * 15303 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 15304 * $digest()} and should return the value that will be watched. (`watchExpression` should not change 15305 * its value when executed multiple times with the same input because it may be executed multiple 15306 * times by {@link ng.$rootScope.Scope#$digest $digest()}. That is, `watchExpression` should be 15307 * [idempotent](http://en.wikipedia.org/wiki/Idempotence). 15308 * - The `listener` is called only when the value from the current `watchExpression` and the 15309 * previous call to `watchExpression` are not equal (with the exception of the initial run, 15310 * see below). Inequality is determined according to reference inequality, 15311 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 15312 * via the `!==` Javascript operator, unless `objectEquality == true` 15313 * (see next point) 15314 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 15315 * according to the {@link angular.equals} function. To save the value of the object for 15316 * later comparison, the {@link angular.copy} function is used. This therefore means that 15317 * watching complex objects will have adverse memory and performance implications. 15318 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 15319 * This is achieved by rerunning the watchers until no changes are detected. The rerun 15320 * iteration limit is 10 to prevent an infinite loop deadlock. 15321 * 15322 * 15323 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 15324 * you can register a `watchExpression` function with no `listener`. (Be prepared for 15325 * multiple calls to your `watchExpression` because it will execute multiple times in a 15326 * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.) 15327 * 15328 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 15329 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 15330 * watcher. In rare cases, this is undesirable because the listener is called when the result 15331 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
15332 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 15333 * listener was called due to initialization. 15334 * 15335 * 15336 * 15337 * # Example 15338 * ```js 15339 // let's assume that scope was dependency injected as the $rootScope 15340 var scope = $rootScope; 15341 scope.name = 'misko'; 15342 scope.counter = 0; 15343 15344 expect(scope.counter).toEqual(0); 15345 scope.$watch('name', function(newValue, oldValue) { 15346 scope.counter = scope.counter + 1; 15347 }); 15348 expect(scope.counter).toEqual(0); 15349 15350 scope.$digest(); 15351 // the listener is always called during the first $digest loop after it was registered 15352 expect(scope.counter).toEqual(1); 15353 15354 scope.$digest(); 15355 // but now it will not be called unless the value changes 15356 expect(scope.counter).toEqual(1); 15357 15358 scope.name = 'adam'; 15359 scope.$digest(); 15360 expect(scope.counter).toEqual(2); 15361 15362 15363 15364 // Using a function as a watchExpression 15365 var food; 15366 scope.foodCounter = 0; 15367 expect(scope.foodCounter).toEqual(0); 15368 scope.$watch( 15369 // This function returns the value being watched. It is called for each turn of the $digest loop 15370 function() { return food; }, 15371 // This is the change listener, called when the value returned from the above function changes 15372 function(newValue, oldValue) { 15373 if ( newValue !== oldValue ) { 15374 // Only increment the counter if the value changed 15375 scope.foodCounter = scope.foodCounter + 1; 15376 } 15377 } 15378 ); 15379 // No digest has been run so the counter will be zero 15380 expect(scope.foodCounter).toEqual(0); 15381 15382 // Run the digest but since food has not changed count will still be zero 15383 scope.$digest(); 15384 expect(scope.foodCounter).toEqual(0); 15385 15386 // Update food and run digest. Now the counter will increment 15387 food = 'cheeseburger'; 15388 scope.$digest(); 15389 expect(scope.foodCounter).toEqual(1); 15390 15391 * ``` 15392 * 15393 * 15394 * 15395 * @param {(function()|string)} watchExpression Expression that is evaluated on each 15396 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 15397 * a call to the `listener`. 15398 * 15399 * - `string`: Evaluated as {@link guide/expression expression} 15400 * - `function(scope)`: called with current `scope` as a parameter. 15401 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 15402 * of `watchExpression` changes. 15403 * 15404 * - `newVal` contains the current value of the `watchExpression` 15405 * - `oldVal` contains the previous value of the `watchExpression` 15406 * - `scope` refers to the current scope 15407 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of 15408 * comparing for reference equality. 15409 * @returns {function()} Returns a deregistration function for this listener. 15410 */ 15411 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { 15412 var get = $parse(watchExp); 15413 15414 if (get.$$watchDelegate) { 15415 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); 15416 } 15417 var scope = this, 15418 array = scope.$$watchers, 15419 watcher = { 15420 fn: listener, 15421 last: initWatchVal, 15422 get: get, 15423 exp: prettyPrintExpression || watchExp, 15424 eq: !!objectEquality 15425 }; 15426 15427 lastDirtyWatch = null; 15428 15429 if (!isFunction(listener)) { 15430 watcher.fn = noop; 15431 } 15432 15433 if (!array) { 15434 array = scope.$$watchers = []; 15435 } 15436 // we use unshift since we use a while loop in $digest for speed. 15437 // the while loop reads in reverse order. 15438 array.unshift(watcher); 15439 incrementWatchersCount(this, 1); 15440 15441 return function deregisterWatch() { 15442 if (arrayRemove(array, watcher) >= 0) { 15443 incrementWatchersCount(scope, -1); 15444 } 15445 lastDirtyWatch = null; 15446 }; 15447 }, 15448 15449 /** 15450 * @ngdoc method 15451 * @name $rootScope.Scope#$watchGroup 15452 * @kind function 15453 *
15454 * @description 15455 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 15456 * If any one expression in the collection changes the `listener` is executed. 15457 * 15458 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every 15459 * call to $digest() to see if any items changes. 15460 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 15461 * 15462 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 15463 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 15464 * 15465 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 15466 * expression in `watchExpressions` changes 15467 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 15468 * those of `watchExpression` 15469 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 15470 * those of `watchExpression` 15471 * The `scope` refers to the current scope. 15472 * @returns {function()} Returns a de-registration function for all listeners. 15473 */ 15474 $watchGroup: function(watchExpressions, listener) { 15475 var oldValues = new Array(watchExpressions.length); 15476 var newValues = new Array(watchExpressions.length); 15477 var deregisterFns = []; 15478 var self = this; 15479 var changeReactionScheduled = false; 15480 var firstRun = true; 15481 15482 if (!watchExpressions.length) { 15483 // No expressions means we call the listener ASAP 15484 var shouldCall = true; 15485 self.$evalAsync(function() { 15486 if (shouldCall) listener(newValues, newValues, self); 15487 }); 15488 return function deregisterWatchGroup() { 15489 shouldCall = false; 15490 }; 15491 } 15492 15493 if (watchExpressions.length === 1) { 15494 // Special case size of one 15495 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 15496 newValues[0] = value; 15497 oldValues[0] = oldValue; 15498 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 15499 }); 15500 } 15501 15502 forEach(watchExpressions, function(expr, i) { 15503 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { 15504 newValues[i] = value; 15505 oldValues[i] = oldValue; 15506 if (!changeReactionScheduled) { 15507 changeReactionScheduled = true; 15508 self.$evalAsync(watchGroupAction); 15509 } 15510 }); 15511 deregisterFns.push(unwatchFn); 15512 }); 15513 15514 function watchGroupAction() { 15515 changeReactionScheduled = false; 15516 15517 if (firstRun) { 15518 firstRun = false; 15519 listener(newValues, newValues, self); 15520 } else { 15521 listener(newValues, oldValues, self); 15522 } 15523 } 15524 15525 return function deregisterWatchGroup() { 15526 while (deregisterFns.length) { 15527 deregisterFns.shift()(); 15528 } 15529 }; 15530 }, 15531 15532 15533 /** 15534 * @ngdoc method 15535 * @name $rootScope.Scope#$watchCollection 15536 * @kind function 15537 * 15538 * @description 15539 * Shallow watches the properties of an object and fires whenever any of the properties change 15540 * (for arrays, this implies watching the array items; for object maps, this implies watching 15541 * the properties). If a change is detected, the `listener` callback is fired. 15542 * 15543 * - The `obj` collection is observed via standard $watch operation and is examined on every 15544 * call to $digest() to see if any items have been added, removed, or moved. 15545 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 15546 * adding, removing, and moving items belonging to an object or array. 15547 * 15548 * 15549 * # Example 15550 * ```js 15551 $scope.names = ['igor', 'matias', 'misko', 'james']; 15552 $scope.dataCount = 4; 15553 15554 $scope.$watchCollection('names', function(newNames, oldNames) { 15555 $scope.dataCount = newNames.length; 15556 }); 15557 15558 expect($scope.dataCount).toEqual(4); 15559 $scope.$digest(); 15560 15561 //still at 4 ... no changes 15562 expect($scope.dataCount).toEqual(4); 15563 15564 $scope.names.pop(); 15565 $scope.$digest(); 15566 15567 //now there's been a change 15568 expect($scope.dataCount).toEqual(3); 15569 * ``` 15570 * 15571 * 15572 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 15573 * expression value should evaluate to an object or an array which is observed on each 15574 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 15575 * collection will trigger a call to the `listener`. 15576 * 15577 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 15578 * when a change is detected. 15579 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 15580 * - The `oldCollection` object is a copy of the former collection data. 15581 * Due to performance considerations, the`oldCollection` value is computed only if the 15582 * `listener` function declares two or more arguments. 15583 * - The `scope` argument refers to the current scope. 15584 * 15585 * @returns {function()} Returns a de-registration function for this listener. When the 15586 * de-registration function is executed, the internal watch operation is terminated. 15587 */ 15588 $watchCollection: function(obj, listener) { 15589 $watchCollectionInterceptor.$stateful = true; 15590 15591 var self = this; 15592 // the current value, updated on each dirty-check run 15593 var newValue; 15594 // a shallow copy of the newValue from the last dirty-check run, 15595 // updated to match newValue during dirty-check run 15596 var oldValue; 15597 // a shallow copy of the newValue from when the last change happened 15598 var veryOldValue; 15599 // only track veryOldValue if the listener is asking for it 15600 var trackVeryOldValue = (listener.length > 1); 15601 var changeDetected = 0; 15602 var changeDetector = $parse(obj, $watchCollectionInterceptor); 15603 var internalArray = []; 15604 var internalObject = {}; 15605 var initRun = true; 15606 var oldLength = 0; 15607 15608 function $watchCollectionInterceptor(_value) { 15609 newValue = _value; 15610 var newLength, key, bothNaN, newItem, oldItem; 15611 15612 // If the new value is undefined, then return undefined as the watch may be a one-time watch 15613 if (isUndefined(newValue)) return; 15614 15615 if (!isObject(newValue)) { // if primitive 15616 if (oldValue !== newValue) { 15617 oldValue = newValue; 15618 changeDetected++; 15619 } 15620 } else if (isArrayLike(newValue)) { 15621 if (oldValue !== internalArray) { 15622 // we are transitioning from something which was not an array into array. 15623 oldValue = internalArray; 15624 oldLength = oldValue.length = 0; 15625 changeDetected++; 15626 } 15627 15628 newLength = newValue.length; 15629 15630 if (oldLength !== newLength) { 15631 // if lengths do not match we need to trigger change notification 15632 changeDetected++; 15633 oldValue.length = oldLength = newLength; 15634 } 15635 // copy the items to oldValue and look for changes. 15636 for (var i = 0; i < newLength; i++) { 15637 oldItem = oldValue[i]; 15638 newItem = newValue[i]; 15639 15640 bothNaN = (oldItem !== oldItem) && (newItem !== newItem);
vendor: 5,713 bytes, lines 15641-15792
15641 if (!bothNaN && (oldItem !== newItem)) { 15642 changeDetected++; 15643 oldValue[i] = newItem; 15644 } 15645 } 15646 } else { 15647 if (oldValue !== internalObject) { 15648 // we are transitioning from something which was not an object into object. 15649 oldValue = internalObject = {}; 15650 oldLength = 0; 15651 changeDetected++; 15652 } 15653 // copy the items to oldValue and look for changes. 15654 newLength = 0; 15655 for (key in newValue) { 15656 if (hasOwnProperty.call(newValue, key)) { 15657 newLength++; 15658 newItem = newValue[key]; 15659 oldItem = oldValue[key]; 15660 15661 if (key in oldValue) { 15662 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 15663 if (!bothNaN && (oldItem !== newItem)) { 15664 changeDetected++; 15665 oldValue[key] = newItem; 15666 } 15667 } else { 15668 oldLength++; 15669 oldValue[key] = newItem; 15670 changeDetected++; 15671 } 15672 } 15673 } 15674 if (oldLength > newLength) { 15675 // we used to have more keys, need to find them and destroy them. 15676 changeDetected++; 15677 for (key in oldValue) { 15678 if (!hasOwnProperty.call(newValue, key)) { 15679 oldLength--; 15680 delete oldValue[key]; 15681 } 15682 } 15683 } 15684 } 15685 return changeDetected; 15686 } 15687 15688 function $watchCollectionAction() { 15689 if (initRun) { 15690 initRun = false; 15691 listener(newValue, newValue, self); 15692 } else { 15693 listener(newValue, veryOldValue, self); 15694 } 15695 15696 // make a copy for the next time a collection is changed 15697 if (trackVeryOldValue) { 15698 if (!isObject(newValue)) { 15699 //primitive 15700 veryOldValue = newValue; 15701 } else if (isArrayLike(newValue)) { 15702 veryOldValue = new Array(newValue.length); 15703 for (var i = 0; i < newValue.length; i++) { 15704 veryOldValue[i] = newValue[i]; 15705 } 15706 } else { // if object 15707 veryOldValue = {}; 15708 for (var key in newValue) { 15709 if (hasOwnProperty.call(newValue, key)) { 15710 veryOldValue[key] = newValue[key]; 15711 } 15712 } 15713 } 15714 } 15715 } 15716 15717 return this.$watch(changeDetector, $watchCollectionAction); 15718 }, 15719 15720 /** 15721 * @ngdoc method 15722 * @name $rootScope.Scope#$digest 15723 * @kind function 15724 * 15725 * @description 15726 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 15727 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 15728 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 15729 * until no more listeners are firing. This means that it is possible to get into an infinite 15730 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 15731 * iterations exceeds 10. 15732 * 15733 * Usually, you don't call `$digest()` directly in 15734 * {@link ng.directive:ngController controllers} or in 15735 * {@link ng.$compileProvider#directive directives}. 15736 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 15737 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 15738 * 15739 * If you want to be notified whenever `$digest()` is called, 15740 * you can register a `watchExpression` function with 15741 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 15742 * 15743 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 15744 * 15745 * # Example 15746 * ```js 15747 var scope = ...; 15748 scope.name = 'misko'; 15749 scope.counter = 0; 15750 15751 expect(scope.counter).toEqual(0); 15752 scope.$watch('name', function(newValue, oldValue) { 15753 scope.counter = scope.counter + 1; 15754 }); 15755 expect(scope.counter).toEqual(0); 15756 15757 scope.$digest(); 15758 // the listener is always called during the first $digest loop after it was registered 15759 expect(scope.counter).toEqual(1); 15760 15761 scope.$digest(); 15762 // but now it will not be called unless the value changes 15763 expect(scope.counter).toEqual(1); 15764 15765 scope.name = 'adam'; 15766 scope.$digest(); 15767 expect(scope.counter).toEqual(2); 15768 * ``` 15769 * 15770 */ 15771 $digest: function() { 15772 var watch, value, last, 15773 watchers, 15774 length, 15775 dirty, ttl = TTL, 15776 next, current, target = this, 15777 watchLog = [], 15778 logIdx, logMsg, asyncTask; 15779 15780 beginPhase('$digest'); 15781 // Check for changes to browser url that happened in sync before the call to $digest 15782 $browser.$$checkUrlChange(); 15783 15784 if (this === $rootScope && applyAsyncId !== null) { 15785 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 15786 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 15787 $browser.defer.cancel(applyAsyncId); 15788 flushApplyAsync(); 15789 } 15790 15791 lastDirtyWatch = null; 15792
15793 do { // "while dirty" loop 15794 dirty = false; 15795 current = target; 15796 15797 while (asyncQueue.length) { 15798 try { 15799 asyncTask = asyncQueue.shift(); 15800 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); 15801 } catch (e) { 15802 $exceptionHandler(e); 15803 } 15804 lastDirtyWatch = null; 15805 } 15806 15807 traverseScopesLoop: 15808 do { // "traverse the scopes" loop 15809 if ((watchers = current.$$watchers)) { 15810 // process our watches 15811 length = watchers.length; 15812 while (length--) { 15813 try { 15814 watch = watchers[length]; 15815 // Most common watches are on primitives, in which case we can short 15816 // circuit it with === operator, only when === fails do we use .equals 15817 if (watch) { 15818 if ((value = watch.get(current)) !== (last = watch.last) && 15819 !(watch.eq 15820 ? equals(value, last) 15821 : (typeof value === 'number' && typeof last === 'number' 15822 && isNaN(value) && isNaN(last)))) { 15823 dirty = true; 15824 lastDirtyWatch = watch; 15825 watch.last = watch.eq ? copy(value, null) : value; 15826 watch.fn(value, ((last === initWatchVal) ? value : last), current); 15827 if (ttl < 5) { 15828 logIdx = 4 - ttl; 15829 if (!watchLog[logIdx]) watchLog[logIdx] = []; 15830 watchLog[logIdx].push({ 15831 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 15832 newVal: value, 15833 oldVal: last 15834 }); 15835 } 15836 } else if (watch === lastDirtyWatch) { 15837 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 15838 // have already been tested. 15839 dirty = false; 15840 break traverseScopesLoop; 15841 } 15842 } 15843 } catch (e) { 15844 $exceptionHandler(e); 15845 } 15846 } 15847 } 15848 15849 // Insanity Warning: scope depth-first traversal 15850 // yes, this code is a bit crazy, but it works and we have tests to prove it! 15851 // this piece should be kept in sync with the traversal in $broadcast 15852 if (!(next = ((current.$$watchersCount && current.$$childHead) || 15853 (current !== target && current.$$nextSibling)))) { 15854 while (current !== target && !(next = current.$$nextSibling)) { 15855 current = current.$parent; 15856 } 15857 } 15858 } while ((current = next)); 15859 15860 // `break traverseScopesLoop;` takes us to here 15861 15862 if ((dirty || asyncQueue.length) && !(ttl--)) { 15863 clearPhase(); 15864 throw $rootScopeMinErr('infdig', 15865 '{0} $digest() iterations reached. Aborting!\n' + 15866 'Watchers fired in the last 5 iterations: {1}', 15867 TTL, watchLog); 15868 } 15869 15870 } while (dirty || asyncQueue.length); 15871 15872 clearPhase(); 15873 15874 while (postDigestQueue.length) { 15875 try { 15876 postDigestQueue.shift()(); 15877 } catch (e) { 15878 $exceptionHandler(e); 15879 } 15880 } 15881 }, 15882 15883 15884 /** 15885 * @ngdoc event 15886 * @name $rootScope.Scope#$destroy 15887 * @eventType broadcast on scope being destroyed 15888 * 15889 * @description 15890 * Broadcasted when a scope and its children are being destroyed. 15891 * 15892 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 15893 * clean up DOM bindings before an element is removed from the DOM. 15894 */ 15895 15896 /** 15897 * @ngdoc method 15898 * @name $rootScope.Scope#$destroy 15899 * @kind function 15900 *
15901 * @description 15902 * Removes the current scope (and all of its children) from the parent scope. Removal implies 15903 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 15904 * propagate to the current scope and its children. Removal also implies that the current 15905 * scope is eligible for garbage collection. 15906 * 15907 * The `$destroy()` is usually used by directives such as 15908 * {@link ng.directive:ngRepeat ngRepeat} for managing the 15909 * unrolling of the loop. 15910 * 15911 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 15912 * Application code can register a `$destroy` event handler that will give it a chance to 15913 * perform any necessary cleanup. 15914 * 15915 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 15916 * clean up DOM bindings before an element is removed from the DOM. 15917 */ 15918 $destroy: function() { 15919 // We can't destroy a scope that has been already destroyed. 15920 if (this.$$destroyed) return; 15921 var parent = this.$parent; 15922 15923 this.$broadcast('$destroy'); 15924 this.$$destroyed = true; 15925 15926 if (this === $rootScope) { 15927 //Remove handlers attached to window when $rootScope is removed 15928 $browser.$$applicationDestroyed(); 15929 } 15930 15931 incrementWatchersCount(this, -this.$$watchersCount); 15932 for (var eventName in this.$$listenerCount) { 15933 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 15934 } 15935 15936 // sever all the references to parent scopes (after this cleanup, the current scope should 15937 // not be retained by any of our references and should be eligible for garbage collection) 15938 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 15939 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 15940 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 15941 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 15942 15943 // Disable listeners, watchers and apply/digest methods 15944 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 15945 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 15946 this.$$listeners = {}; 15947 15948 // All of the code below is bogus code that works around V8's memory leak via optimized code 15949 // and inline caches. 15950 // 15951 // see: 15952 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 15953 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 15954 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 15955 15956 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 15957 this.$$childTail = this.$root = this.$$watchers = null; 15958 }, 15959 15960 /** 15961 * @ngdoc method 15962 * @name $rootScope.Scope#$eval 15963 * @kind function 15964 * 15965 * @description 15966 * Executes the `expression` on the current scope and returns the result. Any exceptions in 15967 * the expression are propagated (uncaught). This is useful when evaluating Angular 15968 * expressions. 15969 * 15970 * # Example 15971 * ```js 15972 var scope = ng.$rootScope.Scope(); 15973 scope.a = 1; 15974 scope.b = 2; 15975 15976 expect(scope.$eval('a+b')).toEqual(3); 15977 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 15978 * ``` 15979 * 15980 * @param {(string|function())=} expression An angular expression to be executed. 15981 * 15982 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 15983 * - `function(scope)`: execute the function with the current `scope` parameter. 15984 * 15985 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 15986 * @returns {*} The result of evaluating the expression. 15987 */ 15988 $eval: function(expr, locals) { 15989 return $parse(expr)(this, locals); 15990 }, 15991 15992 /** 15993 * @ngdoc method 15994 * @name $rootScope.Scope#$evalAsync 15995 * @kind function 15996 *
15997 * @description 15998 * Executes the expression on the current scope at a later point in time. 15999 * 16000 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 16001 * that: 16002 * 16003 * - it will execute after the function that scheduled the evaluation (preferably before DOM 16004 * rendering). 16005 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 16006 * `expression` execution. 16007 * 16008 * Any exceptions from the execution of the expression are forwarded to the 16009 * {@link ng.$exceptionHandler $exceptionHandler} service. 16010 * 16011 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 16012 * will be scheduled. However, it is encouraged to always call code that changes the model 16013 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 16014 * 16015 * @param {(string|function())=} expression An angular expression to be executed. 16016 * 16017 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16018 * - `function(scope)`: execute the function with the current `scope` parameter. 16019 * 16020 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 16021 */ 16022 $evalAsync: function(expr, locals) { 16023 // if we are outside of an $digest loop and this is the first time we are scheduling async 16024 // task also schedule async auto-flush 16025 if (!$rootScope.$$phase && !asyncQueue.length) { 16026 $browser.defer(function() { 16027 if (asyncQueue.length) { 16028 $rootScope.$digest(); 16029 } 16030 }); 16031 } 16032 16033 asyncQueue.push({scope: this, expression: expr, locals: locals}); 16034 }, 16035 16036 $$postDigest: function(fn) { 16037 postDigestQueue.push(fn); 16038 }, 16039 16040 /** 16041 * @ngdoc method 16042 * @name $rootScope.Scope#$apply 16043 * @kind function 16044 * 16045 * @description 16046 * `$apply()` is used to execute an expression in angular from outside of the angular 16047 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 16048 * Because we are calling into the angular framework we need to perform proper scope life 16049 * cycle of {@link ng.$exceptionHandler exception handling}, 16050 * {@link ng.$rootScope.Scope#$digest executing watches}. 16051 * 16052 * ## Life cycle 16053 * 16054 * # Pseudo-Code of `$apply()` 16055 * ```js 16056 function $apply(expr) { 16057 try { 16058 return $eval(expr); 16059 } catch (e) { 16060 $exceptionHandler(e); 16061 } finally { 16062 $root.$digest(); 16063 } 16064 } 16065 * ``` 16066 * 16067 * 16068 * Scope's `$apply()` method transitions through the following stages: 16069 * 16070 * 1. The {@link guide/expression expression} is executed using the 16071 * {@link ng.$rootScope.Scope#$eval $eval()} method. 16072 * 2. Any exceptions from the execution of the expression are forwarded to the 16073 * {@link ng.$exceptionHandler $exceptionHandler} service. 16074 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 16075 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 16076 * 16077 * 16078 * @param {(string|function())=} exp An angular expression to be executed. 16079 * 16080 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16081 * - `function(scope)`: execute the function with current `scope` parameter. 16082 * 16083 * @returns {*} The result of evaluating the expression. 16084 */ 16085 $apply: function(expr) { 16086 try { 16087 beginPhase('$apply'); 16088 try { 16089 return this.$eval(expr); 16090 } finally { 16091 clearPhase(); 16092 } 16093 } catch (e) { 16094 $exceptionHandler(e); 16095 } finally { 16096 try { 16097 $rootScope.$digest(); 16098 } catch (e) { 16099 $exceptionHandler(e); 16100 throw e; 16101 } 16102 } 16103 }, 16104 16105 /** 16106 * @ngdoc method 16107 * @name $rootScope.Scope#$applyAsync 16108 * @kind function 16109 *
16110 * @description 16111 * Schedule the invocation of $apply to occur at a later time. The actual time difference 16112 * varies across browsers, but is typically around ~10 milliseconds. 16113 * 16114 * This can be used to queue up multiple expressions which need to be evaluated in the same 16115 * digest. 16116 * 16117 * @param {(string|function())=} exp An angular expression to be executed. 16118 * 16119 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 16120 * - `function(scope)`: execute the function with current `scope` parameter. 16121 */ 16122 $applyAsync: function(expr) { 16123 var scope = this; 16124 expr && applyAsyncQueue.push($applyAsyncExpression); 16125 scheduleApplyAsync(); 16126 16127 function $applyAsyncExpression() { 16128 scope.$eval(expr); 16129 } 16130 }, 16131 16132 /** 16133 * @ngdoc method 16134 * @name $rootScope.Scope#$on 16135 * @kind function 16136 * 16137 * @description 16138 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 16139 * discussion of event life cycle. 16140 * 16141 * The event listener function format is: `function(event, args...)`. The `event` object 16142 * passed into the listener has the following attributes: 16143 * 16144 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 16145 * `$broadcast`-ed. 16146 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 16147 * event propagates through the scope hierarchy, this property is set to null. 16148 * - `name` - `{string}`: name of the event. 16149 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 16150 * further event propagation (available only for events that were `$emit`-ed). 16151 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 16152 * to true. 16153 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 16154 * 16155 * @param {string} name Event name to listen on. 16156 * @param {function(event, ...args)} listener Function to call when the event is emitted. 16157 * @returns {function()} Returns a deregistration function for this listener. 16158 */ 16159 $on: function(name, listener) { 16160 var namedListeners = this.$$listeners[name]; 16161 if (!namedListeners) { 16162 this.$$listeners[name] = namedListeners = []; 16163 } 16164 namedListeners.push(listener); 16165 16166 var current = this; 16167 do { 16168 if (!current.$$listenerCount[name]) { 16169 current.$$listenerCount[name] = 0; 16170 } 16171 current.$$listenerCount[name]++; 16172 } while ((current = current.$parent)); 16173 16174 var self = this; 16175 return function() { 16176 var indexOfListener = namedListeners.indexOf(listener); 16177 if (indexOfListener !== -1) { 16178 namedListeners[indexOfListener] = null; 16179 decrementListenerCount(self, 1, name); 16180 } 16181 }; 16182 }, 16183 16184 16185 /** 16186 * @ngdoc method 16187 * @name $rootScope.Scope#$emit 16188 * @kind function 16189 * 16190 * @description 16191 * Dispatches an event `name` upwards through the scope hierarchy notifying the 16192 * registered {@link ng.$rootScope.Scope#$on} listeners. 16193 * 16194 * The event life cycle starts at the scope on which `$emit` was called. All 16195 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 16196 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 16197 * registered listeners along the way. The event will stop propagating if one of the listeners 16198 * cancels it. 16199 * 16200 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 16201 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 16202 * 16203 * @param {string} name Event name to emit. 16204 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 16205 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 16206 */ 16207 $emit: function(name, args) { 16208 var empty = [], 16209 namedListeners, 16210 scope = this, 16211 stopPropagation = false, 16212 event = { 16213 name: name, 16214 targetScope: scope, 16215 stopPropagation: function() {stopPropagation = true;}, 16216 preventDefault: function() { 16217 event.defaultPrevented = true; 16218 }, 16219 defaultPrevented: false 16220 }, 16221 listenerArgs = concat([event], arguments, 1), 16222 i, length; 16223 16224 do { 16225 namedListeners = scope.$$listeners[name] || empty; 16226 event.currentScope = scope; 16227 for (i = 0, length = namedListeners.length; i < length; i++) { 16228 16229 // if listeners were deregistered, defragment the array 16230 if (!namedListeners[i]) { 16231 namedListeners.splice(i, 1); 16232 i--; 16233 length--; 16234 continue; 16235 } 16236 try { 16237 //allow all listeners attached to the current scope to run 16238 namedListeners[i].apply(null, listenerArgs); 16239 } catch (e) { 16240 $exceptionHandler(e); 16241 } 16242 } 16243 //if any listener on the current scope stops propagation, prevent bubbling 16244 if (stopPropagation) { 16245 event.currentScope = null; 16246 return event; 16247 } 16248 //traverse upwards 16249 scope = scope.$parent; 16250 } while (scope); 16251 16252 event.currentScope = null; 16253 16254 return event; 16255 }, 16256 16257 16258 /** 16259 * @ngdoc method 16260 * @name $rootScope.Scope#$broadcast 16261 * @kind function 16262 *
16263 * @description 16264 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 16265 * registered {@link ng.$rootScope.Scope#$on} listeners. 16266 * 16267 * The event life cycle starts at the scope on which `$broadcast` was called. All 16268 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 16269 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 16270 * scope and calls all registered listeners along the way. The event cannot be canceled. 16271 * 16272 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 16273 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 16274 * 16275 * @param {string} name Event name to broadcast. 16276 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 16277 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 16278 */ 16279 $broadcast: function(name, args) { 16280 var target = this, 16281 current = target, 16282 next = target, 16283 event = { 16284 name: name, 16285 targetScope: target, 16286 preventDefault: function() { 16287 event.defaultPrevented = true; 16288 }, 16289 defaultPrevented: false 16290 }; 16291 16292 if (!target.$$listenerCount[name]) return event; 16293 16294 var listenerArgs = concat([event], arguments, 1), 16295 listeners, i, length; 16296 16297 //down while you can, then up and next sibling or up and next sibling until back at root 16298 while ((current = next)) { 16299 event.currentScope = current; 16300 listeners = current.$$listeners[name] || []; 16301 for (i = 0, length = listeners.length; i < length; i++) { 16302 // if listeners were deregistered, defragment the array 16303 if (!listeners[i]) { 16304 listeners.splice(i, 1); 16305 i--; 16306 length--; 16307 continue; 16308 } 16309 16310 try { 16311 listeners[i].apply(null, listenerArgs); 16312 } catch (e) { 16313 $exceptionHandler(e); 16314 } 16315 } 16316 16317 // Insanity Warning: scope depth-first traversal 16318 // yes, this code is a bit crazy, but it works and we have tests to prove it! 16319 // this piece should be kept in sync with the traversal in $digest 16320 // (though it differs due to having the extra check for $$listenerCount) 16321 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 16322 (current !== target && current.$$nextSibling)))) { 16323 while (current !== target && !(next = current.$$nextSibling)) { 16324 current = current.$parent; 16325 } 16326 } 16327 } 16328 16329 event.currentScope = null; 16330 return event; 16331 } 16332 }; 16333 16334 var $rootScope = new Scope(); 16335 16336 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 16337 var asyncQueue = $rootScope.$$asyncQueue = []; 16338 var postDigestQueue = $rootScope.$$postDigestQueue = []; 16339 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 16340 16341 return $rootScope; 16342 16343 16344 function beginPhase(phase) { 16345 if ($rootScope.$$phase) { 16346 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 16347 } 16348 16349 $rootScope.$$phase = phase; 16350 } 16351 16352 function clearPhase() { 16353 $rootScope.$$phase = null; 16354 } 16355 16356 function incrementWatchersCount(current, count) { 16357 do { 16358 current.$$watchersCount += count; 16359 } while ((current = current.$parent)); 16360 } 16361 16362 function decrementListenerCount(current, count, name) { 16363 do { 16364 current.$$listenerCount[name] -= count; 16365 16366 if (current.$$listenerCount[name] === 0) { 16367 delete current.$$listenerCount[name]; 16368 } 16369 } while ((current = current.$parent)); 16370 } 16371 16372 /** 16373 * function used as an initial value for watchers. 16374 * because it's unique we can easily tell it apart from other values 16375 */ 16376 function initWatchVal() {} 16377 16378 function flushApplyAsync() {
vendor: 5,601 bytes, lines 16379-16524
16379 while (applyAsyncQueue.length) { 16380 try { 16381 applyAsyncQueue.shift()(); 16382 } catch (e) { 16383 $exceptionHandler(e); 16384 } 16385 } 16386 applyAsyncId = null; 16387 } 16388 16389 function scheduleApplyAsync() { 16390 if (applyAsyncId === null) { 16391 applyAsyncId = $browser.defer(function() { 16392 $rootScope.$apply(flushApplyAsync); 16393 }); 16394 } 16395 } 16396 }]; 16397} 16398 16399/** 16400 * @description 16401 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 16402 */ 16403function $$SanitizeUriProvider() { 16404 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 16405 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; 16406 16407 /** 16408 * @description 16409 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 16410 * urls during a[href] sanitization. 16411 * 16412 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 16413 * 16414 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 16415 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 16416 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 16417 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 16418 * 16419 * @param {RegExp=} regexp New regexp to whitelist urls with. 16420 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 16421 * chaining otherwise. 16422 */ 16423 this.aHrefSanitizationWhitelist = function(regexp) { 16424 if (isDefined(regexp)) { 16425 aHrefSanitizationWhitelist = regexp; 16426 return this; 16427 } 16428 return aHrefSanitizationWhitelist; 16429 }; 16430 16431 16432 /** 16433 * @description 16434 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 16435 * urls during img[src] sanitization. 16436 * 16437 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 16438 * 16439 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 16440 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 16441 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 16442 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 16443 * 16444 * @param {RegExp=} regexp New regexp to whitelist urls with. 16445 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 16446 * chaining otherwise. 16447 */ 16448 this.imgSrcSanitizationWhitelist = function(regexp) { 16449 if (isDefined(regexp)) { 16450 imgSrcSanitizationWhitelist = regexp; 16451 return this; 16452 } 16453 return imgSrcSanitizationWhitelist; 16454 }; 16455 16456 this.$get = function() { 16457 return function sanitizeUri(uri, isImage) { 16458 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 16459 var normalizedVal; 16460 normalizedVal = urlResolve(uri).href; 16461 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 16462 return 'unsafe:' + normalizedVal; 16463 } 16464 return uri; 16465 }; 16466 }; 16467} 16468 16469/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 16470 * Any commits to this file should be reviewed with security in mind. * 16471 * Changes to this file can potentially create security vulnerabilities. * 16472 * An approval from 2 Core members with history of modifying * 16473 * this file is required. * 16474 * * 16475 * Does the change somehow allow for arbitrary javascript to be executed? * 16476 * Or allows for someone to change the prototype of built-in objects? * 16477 * Or gives undesired access to variables likes document or window? * 16478 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 16479 16480var $sceMinErr = minErr('$sce'); 16481 16482var SCE_CONTEXTS = { 16483 HTML: 'html', 16484 CSS: 'css', 16485 URL: 'url', 16486 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 16487 // url. (e.g. ng-include, script src, templateUrl) 16488 RESOURCE_URL: 'resourceUrl', 16489 JS: 'js' 16490}; 16491 16492// Helper functions follow. 16493 16494function adjustMatcher(matcher) { 16495 if (matcher === 'self') { 16496 return matcher; 16497 } else if (isString(matcher)) { 16498 // Strings match exactly except for 2 wildcards - '*' and '**'. 16499 // '*' matches any character except those from the set ':/.?&'. 16500 // '**' matches any character (like .* in a RegExp). 16501 // More than 2 *'s raises an error as it's ill defined. 16502 if (matcher.indexOf('***') > -1) { 16503 throw $sceMinErr('iwcard', 16504 'Illegal sequence *** in string matcher. String: {0}', matcher); 16505 } 16506 matcher = escapeForRegexp(matcher). 16507 replace('\\*\\*', '.*'). 16508 replace('\\*', '[^:/.?&;]*'); 16509 return new RegExp('^' + matcher + '$'); 16510 } else if (isRegExp(matcher)) { 16511 // The only other type of matcher allowed is a Regexp. 16512 // Match entire URL / disallow partial matches. 16513 // Flags are reset (i.e. no global, ignoreCase or multiline) 16514 return new RegExp('^' + matcher.source + '$'); 16515 } else { 16516 throw $sceMinErr('imatcher', 16517 'Matchers may only be "self", string patterns or RegExp objects'); 16518 } 16519} 16520 16521 16522function adjustMatchers(matchers) { 16523 var adjustedMatchers = []; 16524 if (isDefined(matchers)) {
16525 forEach(matchers, function(matcher) { 16526 adjustedMatchers.push(adjustMatcher(matcher)); 16527 }); 16528 } 16529 return adjustedMatchers; 16530} 16531 16532 16533/** 16534 * @ngdoc service 16535 * @name $sceDelegate 16536 * @kind function 16537 * 16538 * @description 16539 * 16540 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 16541 * Contextual Escaping (SCE)} services to AngularJS. 16542 * 16543 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 16544 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 16545 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 16546 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 16547 * work because `$sce` delegates to `$sceDelegate` for these operations. 16548 * 16549 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 16550 * 16551 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 16552 * can override it completely to change the behavior of `$sce`, the common case would 16553 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 16554 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 16555 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 16556 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 16557 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 16558 */ 16559 16560/** 16561 * @ngdoc provider 16562 * @name $sceDelegateProvider 16563 * @description 16564 * 16565 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 16566 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 16567 * that the URLs used for sourcing Angular templates are safe. Refer {@link 16568 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 16569 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 16570 * 16571 * For the general details about this service in Angular, read the main page for {@link ng.$sce 16572 * Strict Contextual Escaping (SCE)}. 16573 * 16574 * **Example**: Consider the following case. <a name="example"></a> 16575 * 16576 * - your app is hosted at url `http://myapp.example.com/` 16577 * - but some of your templates are hosted on other domains you control such as 16578 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 16579 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 16580 * 16581 * Here is what a secure configuration for this scenario might look like: 16582 * 16583 * ``` 16584 * angular.module('myApp', []).config(function($sceDelegateProvider) { 16585 * $sceDelegateProvider.resourceUrlWhitelist([ 16586 * // Allow same origin resource loads. 16587 * 'self', 16588 * // Allow loading from our assets domain. Notice the difference between * and **. 16589 * 'http://srv*.assets.example.com/**' 16590 * ]); 16591 * 16592 * // The blacklist overrides the whitelist so the open redirect here is blocked. 16593 * $sceDelegateProvider.resourceUrlBlacklist([ 16594 * 'http://myapp.example.com/clickThru**' 16595 * ]); 16596 * }); 16597 * ``` 16598 */ 16599 16600function $SceDelegateProvider() { 16601 this.SCE_CONTEXTS = SCE_CONTEXTS; 16602 16603 // Resource URLs can also be trusted by policy. 16604 var resourceUrlWhitelist = ['self'], 16605 resourceUrlBlacklist = []; 16606 16607 /** 16608 * @ngdoc method 16609 * @name $sceDelegateProvider#resourceUrlWhitelist 16610 * @kind function 16611 * 16612 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 16613 * provided. This must be an array or null. A snapshot of this array is used so further 16614 * changes to the array are ignored. 16615 * 16616 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 16617 * allowed in this array. 16618 * 16619 * Note: **an empty whitelist array will block all URLs**! 16620 * 16621 * @return {Array} the currently set whitelist array. 16622 * 16623 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 16624 * same origin resource requests. 16625 * 16626 * @description 16627 * Sets/Gets the whitelist of trusted resource URLs. 16628 */ 16629 this.resourceUrlWhitelist = function(value) { 16630 if (arguments.length) { 16631 resourceUrlWhitelist = adjustMatchers(value); 16632 } 16633 return resourceUrlWhitelist; 16634 }; 16635 16636 /** 16637 * @ngdoc method 16638 * @name $sceDelegateProvider#resourceUrlBlacklist 16639 * @kind function 16640 * 16641 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 16642 * provided. This must be an array or null. A snapshot of this array is used so further 16643 * changes to the array are ignored. 16644 * 16645 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 16646 * allowed in this array. 16647 *
16648 * The typical usage for the blacklist is to **block 16649 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 16650 * these would otherwise be trusted but actually return content from the redirected domain. 16651 * 16652 * Finally, **the blacklist overrides the whitelist** and has the final say. 16653 * 16654 * @return {Array} the currently set blacklist array. 16655 * 16656 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 16657 * is no blacklist.) 16658 * 16659 * @description 16660 * Sets/Gets the blacklist of trusted resource URLs. 16661 */ 16662 16663 this.resourceUrlBlacklist = function(value) { 16664 if (arguments.length) { 16665 resourceUrlBlacklist = adjustMatchers(value); 16666 } 16667 return resourceUrlBlacklist; 16668 }; 16669 16670 this.$get = ['$injector', function($injector) { 16671 16672 var htmlSanitizer = function htmlSanitizer(html) { 16673 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 16674 }; 16675 16676 if ($injector.has('$sanitize')) { 16677 htmlSanitizer = $injector.get('$sanitize'); 16678 } 16679 16680 16681 function matchUrl(matcher, parsedUrl) { 16682 if (matcher === 'self') { 16683 return urlIsSameOrigin(parsedUrl); 16684 } else { 16685 // definitely a regex. See adjustMatchers() 16686 return !!matcher.exec(parsedUrl.href); 16687 } 16688 } 16689 16690 function isResourceUrlAllowedByPolicy(url) { 16691 var parsedUrl = urlResolve(url.toString()); 16692 var i, n, allowed = false; 16693 // Ensure that at least one item from the whitelist allows this url. 16694 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 16695 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 16696 allowed = true; 16697 break; 16698 } 16699 } 16700 if (allowed) { 16701 // Ensure that no item from the blacklist blocked this url. 16702 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 16703 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 16704 allowed = false; 16705 break; 16706 } 16707 } 16708 } 16709 return allowed; 16710 } 16711 16712 function generateHolderType(Base) { 16713 var holderType = function TrustedValueHolderType(trustedValue) { 16714 this.$$unwrapTrustedValue = function() { 16715 return trustedValue; 16716 }; 16717 }; 16718 if (Base) { 16719 holderType.prototype = new Base(); 16720 } 16721 holderType.prototype.valueOf = function sceValueOf() { 16722 return this.$$unwrapTrustedValue(); 16723 }; 16724 holderType.prototype.toString = function sceToString() { 16725 return this.$$unwrapTrustedValue().toString(); 16726 }; 16727 return holderType; 16728 } 16729 16730 var trustedValueHolderBase = generateHolderType(), 16731 byType = {}; 16732 16733 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 16734 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 16735 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 16736 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 16737 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 16738 16739 /** 16740 * @ngdoc method 16741 * @name $sceDelegate#trustAs 16742 * 16743 * @description 16744 * Returns an object that is trusted by angular for use in specified strict 16745 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 16746 * attribute interpolation, any dom event binding attribute interpolation 16747 * such as for onclick, etc.) that uses the provided value. 16748 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 16749 * 16750 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 16751 * resourceUrl, html, js and css. 16752 * @param {*} value The value that that should be considered trusted/safe. 16753 * @returns {*} A value that can be used to stand in for the provided `value` in places 16754 * where Angular expects a $sce.trustAs() return value. 16755 */ 16756 function trustAs(type, trustedValue) { 16757 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 16758 if (!Constructor) { 16759 throw $sceMinErr('icontext',
vendor: 5,127 bytes, lines 16760-16883
16760 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 16761 type, trustedValue); 16762 } 16763 if (trustedValue === null || isUndefined(trustedValue) || trustedValue === '') { 16764 return trustedValue; 16765 } 16766 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 16767 // mutable objects, we ensure here that the value passed in is actually a string. 16768 if (typeof trustedValue !== 'string') { 16769 throw $sceMinErr('itype', 16770 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 16771 type); 16772 } 16773 return new Constructor(trustedValue); 16774 } 16775 16776 /** 16777 * @ngdoc method 16778 * @name $sceDelegate#valueOf 16779 * 16780 * @description 16781 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 16782 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 16783 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 16784 * 16785 * If the passed parameter is not a value that had been returned by {@link 16786 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 16787 * 16788 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 16789 * call or anything else. 16790 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 16791 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 16792 * `value` unchanged. 16793 */ 16794 function valueOf(maybeTrusted) { 16795 if (maybeTrusted instanceof trustedValueHolderBase) { 16796 return maybeTrusted.$$unwrapTrustedValue(); 16797 } else { 16798 return maybeTrusted; 16799 } 16800 } 16801 16802 /** 16803 * @ngdoc method 16804 * @name $sceDelegate#getTrusted 16805 * 16806 * @description 16807 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 16808 * returns the originally supplied value if the queried context type is a supertype of the 16809 * created type. If this condition isn't satisfied, throws an exception. 16810 * 16811 * @param {string} type The kind of context in which this value is to be used. 16812 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 16813 * `$sceDelegate.trustAs`} call. 16814 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 16815 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 16816 */ 16817 function getTrusted(type, maybeTrusted) { 16818 if (maybeTrusted === null || isUndefined(maybeTrusted) || maybeTrusted === '') { 16819 return maybeTrusted; 16820 } 16821 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 16822 if (constructor && maybeTrusted instanceof constructor) { 16823 return maybeTrusted.$$unwrapTrustedValue(); 16824 } 16825 // If we get here, then we may only take one of two actions. 16826 // 1. sanitize the value for the requested type, or 16827 // 2. throw an exception. 16828 if (type === SCE_CONTEXTS.RESOURCE_URL) { 16829 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 16830 return maybeTrusted; 16831 } else { 16832 throw $sceMinErr('insecurl', 16833 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 16834 maybeTrusted.toString()); 16835 } 16836 } else if (type === SCE_CONTEXTS.HTML) { 16837 return htmlSanitizer(maybeTrusted); 16838 } 16839 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 16840 } 16841 16842 return { trustAs: trustAs, 16843 getTrusted: getTrusted, 16844 valueOf: valueOf }; 16845 }]; 16846} 16847 16848 16849/** 16850 * @ngdoc provider 16851 * @name $sceProvider 16852 * @description 16853 * 16854 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 16855 * - enable/disable Strict Contextual Escaping (SCE) in a module 16856 * - override the default implementation with a custom delegate 16857 * 16858 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 16859 */ 16860 16861/* jshint maxlen: false*/ 16862 16863/** 16864 * @ngdoc service 16865 * @name $sce 16866 * @kind function 16867 * 16868 * @description 16869 * 16870 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 16871 * 16872 * # Strict Contextual Escaping 16873 * 16874 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 16875 * contexts to result in a value that is marked as safe to use for that context. One example of 16876 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 16877 * to these contexts as privileged or SCE contexts. 16878 * 16879 * As of version 1.2, Angular ships with SCE enabled by default. 16880 * 16881 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow 16882 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 16883 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
16884 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 16885 * to the top of your HTML document. 16886 * 16887 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 16888 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 16889 * 16890 * Here's an example of a binding in a privileged context: 16891 * 16892 * ``` 16893 * <input ng-model="userHtml" aria-label="User input"> 16894 * <div ng-bind-html="userHtml"></div> 16895 * ``` 16896 * 16897 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 16898 * disabled, this application allows the user to render arbitrary HTML into the DIV. 16899 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 16900 * bindings. (HTML is just one example of a context where rendering user controlled input creates 16901 * security vulnerabilities.) 16902 * 16903 * For the case of HTML, you might use a library, either on the client side, or on the server side, 16904 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 16905 * 16906 * How would you ensure that every place that used these types of bindings was bound to a value that 16907 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 16908 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 16909 * properties/fields and forgot to update the binding to the sanitized value? 16910 * 16911 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 16912 * determine that something explicitly says it's safe to use a value for binding in that
16913 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 16914 * for those values that you can easily tell are safe - because they were received from your server, 16915 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 16916 * allowing only the files in a specific directory to do this. Ensuring that the internal API 16917 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 16918 * 16919 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 16920 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 16921 * obtain values that will be accepted by SCE / privileged contexts. 16922 * 16923 * 16924 * ## How does it work? 16925 * 16926 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 16927 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 16928 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 16929 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 16930 * 16931 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 16932 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 16933 * simplified): 16934 * 16935 * ``` 16936 * var ngBindHtmlDirective = ['$sce', function($sce) { 16937 * return function(scope, element, attr) { 16938 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 16939 * element.html(value || ''); 16940 * }); 16941 * }; 16942 * }]; 16943 * ``` 16944 * 16945 * ## Impact on loading templates 16946 * 16947 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 16948 * `templateUrl`'s specified by {@link guide/directive directives}. 16949 * 16950 * By default, Angular only loads templates from the same domain and protocol as the application 16951 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 16952 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 16953 * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 16954 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 16955 * 16956 * *Please note*: 16957 * The browser's 16958 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 16959 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 16960 * policy apply in addition to this and may further restrict whether the template is successfully 16961 * loaded. This means that without the right CORS policy, loading templates from a different domain 16962 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 16963 * browsers. 16964 * 16965 * ## This feels like too much overhead 16966 * 16967 * It's important to remember that SCE only applies to interpolation expressions. 16968 * 16969 * If your expressions are constant literals, they're automatically trusted and you don't need to 16970 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 16971 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 16972 * 16973 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 16974 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 16975 * 16976 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 16977 * templates in `ng-include` from your application's domain without having to even know about SCE. 16978 * It blocks loading templates from other domains or loading templates over http from an https 16979 * served document. You can change these by setting your own custom {@link 16980 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 16981 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 16982 * 16983 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 16984 * application that's secure and can be audited to verify that with much more ease than bolting 16985 * security onto an application later. 16986 * 16987 * <a name="contexts"></a> 16988 * ## What trusted context types are supported? 16989 * 16990 * | Context | Notes | 16991 * |---------------------|----------------| 16992 * | `$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. | 16993 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 16994 * | `$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. | 16995 * | `$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. | 16996 * | `$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. | 16997 * 16998 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 16999 * 17000 * Each element in these arrays must be one of the following: 17001 * 17002 * - **'self'** 17003 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 17004 * domain** as the application document using the **same protocol**. 17005 * - **String** (except the special value `'self'`) 17006 * - The string is matched against the full *normalized / absolute URL* of the resource 17007 * being tested (substring matches are not good enough.) 17008 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 17009 * match themselves. 17010 * - `*`: matches zero or more occurrences of any character other than one of the following 6 17011 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
17012 * in a whitelist. 17013 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 17014 * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g. 17015 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 17016 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 17017 * http://foo.example.com/templates/**). 17018 * - **RegExp** (*see caveat below*) 17019 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 17020 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 17021 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 17022 * have good test coverage). For instance, the use of `.` in the regex is correct only in a 17023 * small number of cases. A `.` character in the regex used when matching the scheme or a 17024 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 17025 * is highly recommended to use the string patterns and only fall back to regular expressions 17026 * as a last resort. 17027 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 17028 * matched against the **entire** *normalized / absolute URL* of the resource being tested 17029 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 17030 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 17031 * - If you are generating your JavaScript from some other templating engine (not 17032 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 17033 * remember to escape your regular expression (and be aware that you might need more than 17034 * one level of escaping depending on your templating engine and the way you interpolated 17035 * the value.) Do make use of your platform's escaping mechanism as it might be good 17036 * enough before coding your own. E.g. Ruby has 17037 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 17038 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 17039 * Javascript lacks a similar built in function for escaping. Take a look at Google 17040 * Closure library's [goog.string.regExpEscape(s)]( 17041 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 17042 * 17043 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 17044 * 17045 * ## Show me an example using SCE. 17046 * 17047 * <example module="mySceApp" deps="angular-sanitize.js"> 17048 * <file name="index.html"> 17049 * <div ng-controller="AppController as myCtrl"> 17050 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 17051 * <b>User comments</b><br> 17052 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 17053 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 17054 * exploit. 17055 * <div class="well"> 17056 * <div ng-repeat="userComment in myCtrl.userComments"> 17057 * <b>{{userComment.name}}</b>: 17058 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 17059 * <br> 17060 * </div> 17061 * </div> 17062 * </div> 17063 * </file> 17064 * 17065 * <file name="script.js"> 17066 * angular.module('mySceApp', ['ngSanitize']) 17067 * .controller('AppController', ['$http', '$templateCache', '$sce', 17068 * function($http, $templateCache, $sce) { 17069 * var self = this; 17070 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 17071 * self.userComments = userComments; 17072 * }); 17073 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 17074 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 17075 * 'sanitization."">Hover over this text.</span>'); 17076 * }]); 17077 * </file> 17078 * 17079 * <file name="test_data.json"> 17080 * [ 17081 * { "name": "Alice", 17082 * "htmlComment": 17083 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 17084 * }, 17085 * { "name": "Bob",
17086 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 17087 * } 17088 * ] 17089 * </file> 17090 * 17091 * <file name="protractor.js" type="protractor"> 17092 * describe('SCE doc demo', function() { 17093 * it('should sanitize untrusted values', function() { 17094 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) 17095 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 17096 * }); 17097 * 17098 * it('should NOT sanitize explicitly trusted values', function() { 17099 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 17100 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 17101 * 'sanitization."">Hover over this text.</span>'); 17102 * }); 17103 * }); 17104 * </file> 17105 * </example> 17106 * 17107 * 17108 * 17109 * ## Can I disable SCE completely? 17110 * 17111 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 17112 * for little coding overhead. It will be much harder to take an SCE disabled application and 17113 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 17114 * for cases where you have a lot of existing code that was written before SCE was introduced and 17115 * you're migrating them a module at a time. 17116 * 17117 * That said, here's how you can completely disable SCE: 17118 * 17119 * ``` 17120 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 17121 * // Completely disable SCE. For demonstration purposes only! 17122 * // Do not use in new projects. 17123 * $sceProvider.enabled(false); 17124 * }); 17125 * ``` 17126 * 17127 */ 17128/* jshint maxlen: 100 */ 17129 17130function $SceProvider() { 17131 var enabled = true; 17132 17133 /** 17134 * @ngdoc method 17135 * @name $sceProvider#enabled 17136 * @kind function 17137 * 17138 * @param {boolean=} value If provided, then enables/disables SCE. 17139 * @return {boolean} true if SCE is enabled, false otherwise. 17140 * 17141 * @description 17142 * Enables/disables SCE and returns the current value. 17143 */ 17144 this.enabled = function(value) { 17145 if (arguments.length) { 17146 enabled = !!value; 17147 } 17148 return enabled; 17149 }; 17150 17151 17152 /* Design notes on the default implementation for SCE. 17153 * 17154 * The API contract for the SCE delegate 17155 * ------------------------------------- 17156 * The SCE delegate object must provide the following 3 methods: 17157 * 17158 * - trustAs(contextEnum, value) 17159 * This method is used to tell the SCE service that the provided value is OK to use in the 17160 * contexts specified by contextEnum. It must return an object that will be accepted by 17161 * getTrusted() for a compatible contextEnum and return this value. 17162 * 17163 * - valueOf(value) 17164 * For values that were not produced by trustAs(), return them as is. For values that were 17165 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 17166 * trustAs is wrapping the given values into some type, this operation unwraps it when given 17167 * such a value. 17168 * 17169 * - getTrusted(contextEnum, value) 17170 * This function should return the a value that is safe to use in the context specified by 17171 * contextEnum or throw and exception otherwise. 17172 * 17173 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 17174 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 17175 * instance, an implementation could maintain a registry of all trusted objects by context. In 17176 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 17177 * return the same object passed in if it was found in the registry under a compatible context or 17178 * throw an exception otherwise. An implementation might only wrap values some of the time based 17179 * on some criteria. getTrusted() might return a value and not throw an exception for special 17180 * constants or objects even if not wrapped. All such implementations fulfill this contract. 17181 * 17182 * 17183 * A note on the inheritance model for SCE contexts 17184 * ------------------------------------------------ 17185 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 17186 * is purely an implementation details. 17187 * 17188 * The contract is simply this: 17189 * 17190 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 17191 * will also succeed. 17192 * 17193 * Inheritance happens to capture this in a natural way. In some future, we
17194 * may not use inheritance anymore. That is OK because no code outside of 17195 * sce.js and sceSpecs.js would need to be aware of this detail. 17196 */ 17197 17198 this.$get = ['$parse', '$sceDelegate', function( 17199 $parse, $sceDelegate) { 17200 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 17201 // the "expression(javascript expression)" syntax which is insecure. 17202 if (enabled && msie < 8) { 17203 throw $sceMinErr('iequirks', 17204 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 17205 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 17206 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 17207 } 17208 17209 var sce = shallowCopy(SCE_CONTEXTS); 17210 17211 /** 17212 * @ngdoc method 17213 * @name $sce#isEnabled 17214 * @kind function 17215 * 17216 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 17217 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 17218 * 17219 * @description 17220 * Returns a boolean indicating if SCE is enabled. 17221 */ 17222 sce.isEnabled = function() { 17223 return enabled; 17224 }; 17225 sce.trustAs = $sceDelegate.trustAs; 17226 sce.getTrusted = $sceDelegate.getTrusted; 17227 sce.valueOf = $sceDelegate.valueOf; 17228 17229 if (!enabled) { 17230 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 17231 sce.valueOf = identity; 17232 } 17233 17234 /** 17235 * @ngdoc method 17236 * @name $sce#parseAs 17237 * 17238 * @description 17239 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 17240 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 17241 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 17242 * *result*)} 17243 * 17244 * @param {string} type The kind of SCE context in which this result will be used. 17245 * @param {string} expression String expression to compile. 17246 * @returns {function(context, locals)} a function which represents the compiled expression: 17247 * 17248 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17249 * are evaluated against (typically a scope object). 17250 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17251 * `context`. 17252 */ 17253 sce.parseAs = function sceParseAs(type, expr) { 17254 var parsed = $parse(expr); 17255 if (parsed.literal && parsed.constant) { 17256 return parsed; 17257 } else { 17258 return $parse(expr, function(value) { 17259 return sce.getTrusted(type, value); 17260 }); 17261 } 17262 }; 17263 17264 /** 17265 * @ngdoc method 17266 * @name $sce#trustAs 17267 * 17268 * @description 17269 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 17270 * returns an object that is trusted by angular for use in specified strict contextual 17271 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 17272 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 17273 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 17274 * escaping. 17275 * 17276 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 17277 * resourceUrl, html, js and css. 17278 * @param {*} value The value that that should be considered trusted/safe. 17279 * @returns {*} A value that can be used to stand in for the provided `value` in places 17280 * where Angular expects a $sce.trustAs() return value. 17281 */ 17282 17283 /** 17284 * @ngdoc method 17285 * @name $sce#trustAsHtml 17286 * 17287 * @description 17288 * Shorthand method. `$sce.trustAsHtml(value)` â 17289 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 17290 * 17291 * @param {*} value The value to trustAs. 17292 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 17293 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 17294 * only accept expressions that are either literal constants or are the 17295 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17296 */ 17297 17298 /** 17299 * @ngdoc method 17300 * @name $sce#trustAsUrl 17301 *
17302 * @description 17303 * Shorthand method. `$sce.trustAsUrl(value)` â 17304 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 17305 * 17306 * @param {*} value The value to trustAs. 17307 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 17308 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 17309 * only accept expressions that are either literal constants or are the 17310 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17311 */ 17312 17313 /** 17314 * @ngdoc method 17315 * @name $sce#trustAsResourceUrl 17316 * 17317 * @description 17318 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 17319 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 17320 * 17321 * @param {*} value The value to trustAs. 17322 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 17323 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 17324 * only accept expressions that are either literal constants or are the return 17325 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 17326 */ 17327 17328 /** 17329 * @ngdoc method 17330 * @name $sce#trustAsJs 17331 * 17332 * @description 17333 * Shorthand method. `$sce.trustAsJs(value)` â 17334 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 17335 * 17336 * @param {*} value The value to trustAs. 17337 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 17338 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 17339 * only accept expressions that are either literal constants or are the 17340 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17341 */ 17342 17343 /** 17344 * @ngdoc method 17345 * @name $sce#getTrusted 17346 * 17347 * @description 17348 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 17349 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 17350 * originally supplied value if the queried context type is a supertype of the created type. 17351 * If this condition isn't satisfied, throws an exception. 17352 * 17353 * @param {string} type The kind of context in which this value is to be used. 17354 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 17355 * call. 17356 * @returns {*} The value the was originally provided to 17357 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 17358 * Otherwise, throws an exception. 17359 */ 17360 17361 /** 17362 * @ngdoc method 17363 * @name $sce#getTrustedHtml 17364 * 17365 * @description 17366 * Shorthand method. `$sce.getTrustedHtml(value)` â 17367 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 17368 * 17369 * @param {*} value The value to pass to `$sce.getTrusted`. 17370 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 17371 */ 17372 17373 /** 17374 * @ngdoc method 17375 * @name $sce#getTrustedCss 17376 * 17377 * @description 17378 * Shorthand method. `$sce.getTrustedCss(value)` â 17379 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 17380 * 17381 * @param {*} value The value to pass to `$sce.getTrusted`. 17382 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 17383 */ 17384 17385 /** 17386 * @ngdoc method 17387 * @name $sce#getTrustedUrl 17388 * 17389 * @description 17390 * Shorthand method. `$sce.getTrustedUrl(value)` â 17391 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 17392 * 17393 * @param {*} value The value to pass to `$sce.getTrusted`. 17394 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 17395 */ 17396 17397 /** 17398 * @ngdoc method 17399 * @name $sce#getTrustedResourceUrl 17400 * 17401 * @description 17402 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 17403 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 17404 * 17405 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 17406 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 17407 */ 17408 17409 /** 17410 * @ngdoc method 17411 * @name $sce#getTrustedJs 17412 *
17413 * @description 17414 * Shorthand method. `$sce.getTrustedJs(value)` â 17415 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 17416 * 17417 * @param {*} value The value to pass to `$sce.getTrusted`. 17418 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 17419 */ 17420 17421 /** 17422 * @ngdoc method 17423 * @name $sce#parseAsHtml 17424 * 17425 * @description 17426 * Shorthand method. `$sce.parseAsHtml(expression string)` â 17427 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 17428 * 17429 * @param {string} expression String expression to compile. 17430 * @returns {function(context, locals)} a function which represents the compiled expression: 17431 * 17432 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17433 * are evaluated against (typically a scope object). 17434 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17435 * `context`. 17436 */ 17437 17438 /** 17439 * @ngdoc method 17440 * @name $sce#parseAsCss 17441 * 17442 * @description 17443 * Shorthand method. `$sce.parseAsCss(value)` â 17444 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 17445 * 17446 * @param {string} expression String expression to compile. 17447 * @returns {function(context, locals)} a function which represents the compiled expression: 17448 * 17449 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17450 * are evaluated against (typically a scope object). 17451 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17452 * `context`. 17453 */ 17454 17455 /** 17456 * @ngdoc method 17457 * @name $sce#parseAsUrl 17458 * 17459 * @description 17460 * Shorthand method. `$sce.parseAsUrl(value)` â 17461 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 17462 * 17463 * @param {string} expression String expression to compile. 17464 * @returns {function(context, locals)} a function which represents the compiled expression: 17465 * 17466 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17467 * are evaluated against (typically a scope object). 17468 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17469 * `context`. 17470 */ 17471 17472 /** 17473 * @ngdoc method 17474 * @name $sce#parseAsResourceUrl 17475 * 17476 * @description 17477 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 17478 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 17479 * 17480 * @param {string} expression String expression to compile. 17481 * @returns {function(context, locals)} a function which represents the compiled expression: 17482 * 17483 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17484 * are evaluated against (typically a scope object). 17485 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17486 * `context`. 17487 */ 17488 17489 /** 17490 * @ngdoc method 17491 * @name $sce#parseAsJs 17492 * 17493 * @description 17494 * Shorthand method. `$sce.parseAsJs(value)` â 17495 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 17496 * 17497 * @param {string} expression String expression to compile. 17498 * @returns {function(context, locals)} a function which represents the compiled expression: 17499 * 17500 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17501 * are evaluated against (typically a scope object). 17502 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17503 * `context`. 17504 */ 17505 17506 // Shorthand delegations. 17507 var parse = sce.parseAs, 17508 getTrusted = sce.getTrusted, 17509 trustAs = sce.trustAs; 17510 17511 forEach(SCE_CONTEXTS, function(enumValue, name) { 17512 var lName = lowercase(name); 17513 sce[camelCase("parse_as_" + lName)] = function(expr) { 17514 return parse(enumValue, expr); 17515 }; 17516 sce[camelCase("get_trusted_" + lName)] = function(value) { 17517 return getTrusted(enumValue, value); 17518 }; 17519 sce[camelCase("trust_as_" + lName)] = function(value) { 17520 return trustAs(enumValue, value); 17521 }; 17522 });
17523 17524 return sce; 17525 }]; 17526} 17527 17528/** 17529 * !!! This is an undocumented "private" service !!! 17530 * 17531 * @name $sniffer 17532 * @requires $window 17533 * @requires $document 17534 * 17535 * @property {boolean} history Does the browser support html5 history api ? 17536 * @property {boolean} transitions Does the browser support CSS transition events ? 17537 * @property {boolean} animations Does the browser support CSS animation events ? 17538 * 17539 * @description 17540 * This is very simple implementation of testing browser's features. 17541 */ 17542function $SnifferProvider() { 17543 this.$get = ['$window', '$document', function($window, $document) { 17544 var eventSupport = {}, 17545 android = 17546 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 17547 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 17548 document = $document[0] || {}, 17549 vendorPrefix, 17550 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, 17551 bodyStyle = document.body && document.body.style, 17552 transitions = false, 17553 animations = false, 17554 match; 17555 17556 if (bodyStyle) { 17557 for (var prop in bodyStyle) { 17558 if (match = vendorRegex.exec(prop)) { 17559 vendorPrefix = match[0]; 17560 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 17561 break; 17562 } 17563 } 17564 17565 if (!vendorPrefix) { 17566 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 17567 } 17568 17569 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 17570 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 17571 17572 if (android && (!transitions || !animations)) { 17573 transitions = isString(bodyStyle.webkitTransition); 17574 animations = isString(bodyStyle.webkitAnimation); 17575 } 17576 } 17577 17578 17579 return { 17580 // Android has history.pushState, but it does not update location correctly 17581 // so let's not use the history API at all. 17582 // http://code.google.com/p/android/issues/detail?id=17471 17583 // https://github.com/angular/angular.js/issues/904 17584 17585 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 17586 // so let's not use the history API also 17587 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 17588 // jshint -W018 17589 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 17590 // jshint +W018 17591 hasEvent: function(event) { 17592 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 17593 // it. In particular the event is not fired when backspace or delete key are pressed or 17594 // when cut operation is performed. 17595 // IE10+ implements 'input' event but it erroneously fires under various situations, 17596 // e.g. when placeholder changes, or a form is focused. 17597 if (event === 'input' && msie <= 11) return false; 17598 17599 if (isUndefined(eventSupport[event])) { 17600 var divElm = document.createElement('div'); 17601 eventSupport[event] = 'on' + event in divElm; 17602 } 17603 17604 return eventSupport[event]; 17605 }, 17606 csp: csp(), 17607 vendorPrefix: vendorPrefix, 17608 transitions: transitions, 17609 animations: animations, 17610 android: android 17611 }; 17612 }]; 17613} 17614 17615var $compileMinErr = minErr('$compile'); 17616 17617/** 17618 * @ngdoc service 17619 * @name $templateRequest 17620 * 17621 * @description 17622 * The `$templateRequest` service runs security checks then downloads the provided template using 17623 * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request 17624 * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the 17625 * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the 17626 * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted 17627 * when `tpl` is of type string and `$templateCache` has the matching entry. 17628 * 17629 * @param {string|TrustedResourceUrl} tpl The HTTP request template URL 17630 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 17631 * 17632 * @return {Promise} a promise for the HTTP response data of the given URL. 17633 * 17634 * @property {number} totalPendingRequests total amount of pending template requests being downloaded. 17635 */ 17636function $TemplateRequestProvider() { 17637 this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) { 17638 function handleRequestFn(tpl, ignoreRequestError) { 17639 handleRequestFn.totalPendingRequests++; 17640 17641 // We consider the template cache holds only trusted templates, so 17642 // there's no need to go through whitelisting again for keys that already 17643 // are included in there. This also makes Angular accept any script 17644 // directive, no matter its name. However, we still need to unwrap trusted 17645 // types. 17646 if (!isString(tpl) || !$templateCache.get(tpl)) { 17647 tpl = $sce.getTrustedResourceUrl(tpl); 17648 } 17649 17650 var transformResponse = $http.defaults && $http.defaults.transformResponse; 17651 17652 if (isArray(transformResponse)) { 17653 transformResponse = transformResponse.filter(function(transformer) { 17654 return transformer !== defaultHttpResponseTransform; 17655 }); 17656 } else if (transformResponse === defaultHttpResponseTransform) { 17657 transformResponse = null; 17658 } 17659 17660 var httpOptions = { 17661 cache: $templateCache, 17662 transformResponse: transformResponse 17663 }; 17664 17665 return $http.get(tpl, httpOptions) 17666 ['finally'](function() { 17667 handleRequestFn.totalPendingRequests--; 17668 }) 17669 .then(function(response) { 17670 $templateCache.put(tpl, response.data); 17671 return response.data; 17672 }, handleError); 17673 17674 function handleError(resp) { 17675 if (!ignoreRequestError) { 17676 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', 17677 tpl, resp.status, resp.statusText); 17678 } 17679 return $q.reject(resp); 17680 } 17681 } 17682 17683 handleRequestFn.totalPendingRequests = 0; 17684 17685 return handleRequestFn; 17686 }]; 17687} 17688
vendor: 5,594 bytes, lines 17689-17842
17689function $$TestabilityProvider() { 17690 this.$get = ['$rootScope', '$browser', '$location', 17691 function($rootScope, $browser, $location) { 17692 17693 /** 17694 * @name $testability 17695 * 17696 * @description 17697 * The private $$testability service provides a collection of methods for use when debugging 17698 * or by automated test and debugging tools. 17699 */ 17700 var testability = {}; 17701 17702 /** 17703 * @name $$testability#findBindings 17704 * 17705 * @description 17706 * Returns an array of elements that are bound (via ng-bind or {{}}) 17707 * to expressions matching the input. 17708 * 17709 * @param {Element} element The element root to search from. 17710 * @param {string} expression The binding expression to match. 17711 * @param {boolean} opt_exactMatch If true, only returns exact matches 17712 * for the expression. Filters and whitespace are ignored. 17713 */ 17714 testability.findBindings = function(element, expression, opt_exactMatch) { 17715 var bindings = element.getElementsByClassName('ng-binding'); 17716 var matches = []; 17717 forEach(bindings, function(binding) { 17718 var dataBinding = angular.element(binding).data('$binding'); 17719 if (dataBinding) { 17720 forEach(dataBinding, function(bindingName) { 17721 if (opt_exactMatch) { 17722 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 17723 if (matcher.test(bindingName)) { 17724 matches.push(binding); 17725 } 17726 } else { 17727 if (bindingName.indexOf(expression) != -1) { 17728 matches.push(binding); 17729 } 17730 } 17731 }); 17732 } 17733 }); 17734 return matches; 17735 }; 17736 17737 /** 17738 * @name $$testability#findModels 17739 * 17740 * @description 17741 * Returns an array of elements that are two-way found via ng-model to 17742 * expressions matching the input. 17743 * 17744 * @param {Element} element The element root to search from. 17745 * @param {string} expression The model expression to match. 17746 * @param {boolean} opt_exactMatch If true, only returns exact matches 17747 * for the expression. 17748 */ 17749 testability.findModels = function(element, expression, opt_exactMatch) { 17750 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 17751 for (var p = 0; p < prefixes.length; ++p) { 17752 var attributeEquals = opt_exactMatch ? '=' : '*='; 17753 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 17754 var elements = element.querySelectorAll(selector); 17755 if (elements.length) { 17756 return elements; 17757 } 17758 } 17759 }; 17760 17761 /** 17762 * @name $$testability#getLocation 17763 * 17764 * @description 17765 * Shortcut for getting the location in a browser agnostic way. Returns 17766 * the path, search, and hash. (e.g. /path?a=b#hash) 17767 */ 17768 testability.getLocation = function() { 17769 return $location.url(); 17770 }; 17771 17772 /** 17773 * @name $$testability#setLocation 17774 * 17775 * @description 17776 * Shortcut for navigating to a location without doing a full page reload. 17777 * 17778 * @param {string} url The location url (path, search and hash, 17779 * e.g. /path?a=b#hash) to go to. 17780 */ 17781 testability.setLocation = function(url) { 17782 if (url !== $location.url()) { 17783 $location.url(url); 17784 $rootScope.$digest(); 17785 } 17786 }; 17787 17788 /** 17789 * @name $$testability#whenStable 17790 * 17791 * @description 17792 * Calls the callback when $timeout and $http requests are completed. 17793 * 17794 * @param {function} callback 17795 */ 17796 testability.whenStable = function(callback) { 17797 $browser.notifyWhenNoOutstandingRequests(callback); 17798 }; 17799 17800 return testability; 17801 }]; 17802} 17803 17804function $TimeoutProvider() { 17805 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 17806 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 17807 17808 var deferreds = {}; 17809 17810 17811 /** 17812 * @ngdoc service 17813 * @name $timeout 17814 * 17815 * @description 17816 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 17817 * block and delegates any exceptions to 17818 * {@link ng.$exceptionHandler $exceptionHandler} service. 17819 * 17820 * The return value of calling `$timeout` is a promise, which will be resolved when 17821 * the delay has passed and the timeout function, if provided, is executed. 17822 * 17823 * To cancel a timeout request, call `$timeout.cancel(promise)`. 17824 * 17825 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 17826 * synchronously flush the queue of deferred functions. 17827 * 17828 * If you only want a promise that will be resolved after some specified delay 17829 * then you can call `$timeout` without the `fn` function. 17830 * 17831 * @param {function()=} fn A function, whose execution should be delayed. 17832 * @param {number=} [delay=0] Delay in milliseconds. 17833 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 17834 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 17835 * @param {...*=} Pass additional parameters to the executed function. 17836 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 17837 * promise will be resolved with is the return value of the `fn` function. 17838 * 17839 */ 17840 function timeout(fn, delay, invokeApply) { 17841 if (!isFunction(fn)) { 17842 invokeApply = delay;
17843 delay = fn; 17844 fn = noop; 17845 } 17846 17847 var args = sliceArgs(arguments, 3), 17848 skipApply = (isDefined(invokeApply) && !invokeApply), 17849 deferred = (skipApply ? $$q : $q).defer(), 17850 promise = deferred.promise, 17851 timeoutId; 17852 17853 timeoutId = $browser.defer(function() { 17854 try { 17855 deferred.resolve(fn.apply(null, args)); 17856 } catch (e) { 17857 deferred.reject(e); 17858 $exceptionHandler(e); 17859 } 17860 finally { 17861 delete deferreds[promise.$$timeoutId]; 17862 } 17863 17864 if (!skipApply) $rootScope.$apply(); 17865 }, delay); 17866 17867 promise.$$timeoutId = timeoutId; 17868 deferreds[timeoutId] = deferred; 17869 17870 return promise; 17871 } 17872 17873 17874 /** 17875 * @ngdoc method 17876 * @name $timeout#cancel 17877 * 17878 * @description 17879 * Cancels a task associated with the `promise`. As a result of this, the promise will be 17880 * resolved with a rejection. 17881 * 17882 * @param {Promise=} promise Promise returned by the `$timeout` function. 17883 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 17884 * canceled. 17885 */ 17886 timeout.cancel = function(promise) { 17887 if (promise && promise.$$timeoutId in deferreds) { 17888 deferreds[promise.$$timeoutId].reject('canceled'); 17889 delete deferreds[promise.$$timeoutId]; 17890 return $browser.defer.cancel(promise.$$timeoutId); 17891 } 17892 return false; 17893 }; 17894 17895 return timeout; 17896 }]; 17897} 17898 17899// NOTE: The usage of window and document instead of $window and $document here is 17900// deliberate. This service depends on the specific behavior of anchor nodes created by the 17901// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 17902// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 17903// doesn't know about mocked locations and resolves URLs to the real document - which is 17904// exactly the behavior needed here. There is little value is mocking these out for this 17905// service. 17906var urlParsingNode = document.createElement("a"); 17907var originUrl = urlResolve(window.location.href); 17908 17909 17910/** 17911 * 17912 * Implementation Notes for non-IE browsers 17913 * ---------------------------------------- 17914 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 17915 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 17916 * URL will be resolved into an absolute URL in the context of the application document. 17917 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 17918 * properties are all populated to reflect the normalized URL. This approach has wide 17919 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 17920 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 17921 * 17922 * Implementation Notes for IE 17923 * --------------------------- 17924 * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other 17925 * browsers. However, the parsed components will not be set if the URL assigned did not specify 17926 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 17927 * work around that by performing the parsing in a 2nd step by taking a previously normalized 17928 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 17929 * properties such as protocol, hostname, port, etc. 17930 * 17931 * References: 17932 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 17933 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 17934 * http://url.spec.whatwg.org/#urlutils 17935 * https://github.com/angular/angular.js/pull/2902 17936 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 17937 * 17938 * @kind function 17939 * @param {string} url The URL to be parsed. 17940 * @description Normalizes and parses a URL. 17941 * @returns {object} Returns the normalized URL as a dictionary. 17942 * 17943 * | member name | Description | 17944 * |---------------|----------------| 17945 * | href | A normalized version of the provided URL if it was not an absolute URL | 17946 * | protocol | The protocol including the trailing colon | 17947 * | host | The host and port (if the port is non-default) of the normalizedUrl | 17948 * | search | The search params, minus the question mark | 17949 * | hash | The hash string, minus the hash symbol 17950 * | hostname | The hostname 17951 * | port | The port, without ":" 17952 * | pathname | The pathname, beginning with "/" 17953 * 17954 */ 17955function urlResolve(url) { 17956 var href = url; 17957 17958 if (msie) { 17959 // Normalize before parse. Refer Implementation Notes on why this is 17960 // done in two steps on IE. 17961 urlParsingNode.setAttribute("href", href); 17962 href = urlParsingNode.href; 17963 } 17964 17965 urlParsingNode.setAttribute('href', href); 17966 17967 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 17968 return { 17969 href: urlParsingNode.href, 17970 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 17971 host: urlParsingNode.host, 17972 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 17973 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 17974 hostname: urlParsingNode.hostname, 17975 port: urlParsingNode.port, 17976 pathname: (urlParsingNode.pathname.charAt(0) === '/') 17977 ? urlParsingNode.pathname 17978 : '/' + urlParsingNode.pathname 17979 }; 17980} 17981 17982/** 17983 * Parse a request URL and determine whether this is a same-origin request as the application document. 17984 * 17985 * @param {string|object} requestUrl The url of the request as a string that will be resolved 17986 * or a parsed URL object. 17987 * @returns {boolean} Whether the request is for the same origin as the application document. 17988 */ 17989function urlIsSameOrigin(requestUrl) { 17990 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 17991 return (parsed.protocol === originUrl.protocol && 17992 parsed.host === originUrl.host); 17993} 17994 17995/** 17996 * @ngdoc service 17997 * @name $window 17998 * 17999 * @description 18000 * A reference to the browser's `window` object. While `window` 18001 * is globally available in JavaScript, it causes testability problems, because 18002 * it is a global variable. In angular we always refer to it through the 18003 * `$window` service, so it may be overridden, removed or mocked for testing. 18004 * 18005 * Expressions, like the one defined for the `ngClick` directive in the example 18006 * below, are evaluated with respect to the current scope. Therefore, there is 18007 * no risk of inadvertently coding in a dependency on a global value in such an 18008 * expression. 18009 * 18010 * @example 18011 <example module="windowExample"> 18012 <file name="index.html"> 18013 <script> 18014 angular.module('windowExample', [])
18015 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 18016 $scope.greeting = 'Hello, World!'; 18017 $scope.doGreeting = function(greeting) { 18018 $window.alert(greeting); 18019 }; 18020 }]); 18021 </script> 18022 <div ng-controller="ExampleController"> 18023 <input type="text" ng-model="greeting" aria-label="greeting" /> 18024 <button ng-click="doGreeting(greeting)">ALERT</button> 18025 </div> 18026 </file> 18027 <file name="protractor.js" type="protractor"> 18028 it('should display the greeting in the input box', function() { 18029 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 18030 // If we click the button it will block the test runner 18031 // element(':button').click(); 18032 }); 18033 </file> 18034 </example> 18035 */ 18036function $WindowProvider() { 18037 this.$get = valueFn(window); 18038} 18039 18040/** 18041 * @name $$cookieReader 18042 * @requires $document 18043 * 18044 * @description 18045 * This is a private service for reading cookies used by $http and ngCookies 18046 * 18047 * @return {Object} a key/value map of the current cookies 18048 */ 18049function $$CookieReader($document) { 18050 var rawDocument = $document[0] || {}; 18051 var lastCookies = {}; 18052 var lastCookieString = ''; 18053 18054 function safeDecodeURIComponent(str) { 18055 try { 18056 return decodeURIComponent(str); 18057 } catch (e) { 18058 return str; 18059 } 18060 } 18061 18062 return function() { 18063 var cookieArray, cookie, i, index, name; 18064 var currentCookieString = rawDocument.cookie || ''; 18065 18066 if (currentCookieString !== lastCookieString) { 18067 lastCookieString = currentCookieString; 18068 cookieArray = lastCookieString.split('; '); 18069 lastCookies = {}; 18070 18071 for (i = 0; i < cookieArray.length; i++) { 18072 cookie = cookieArray[i]; 18073 index = cookie.indexOf('='); 18074 if (index > 0) { //ignore nameless cookies 18075 name = safeDecodeURIComponent(cookie.substring(0, index)); 18076 // the first value that is seen for a cookie is the most 18077 // specific one. values for the same cookie name that 18078 // follow are for less specific paths. 18079 if (isUndefined(lastCookies[name])) { 18080 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 18081 } 18082 } 18083 } 18084 } 18085 return lastCookies; 18086 }; 18087} 18088 18089$$CookieReader.$inject = ['$document']; 18090 18091function $$CookieReaderProvider() { 18092 this.$get = $$CookieReader; 18093} 18094 18095/* global currencyFilter: true, 18096 dateFilter: true, 18097 filterFilter: true, 18098 jsonFilter: true, 18099 limitToFilter: true, 18100 lowercaseFilter: true, 18101 numberFilter: true, 18102 orderByFilter: true, 18103 uppercaseFilter: true, 18104 */ 18105 18106/** 18107 * @ngdoc provider 18108 * @name $filterProvider 18109 * @description 18110 * 18111 * Filters are just functions which transform input to an output. However filters need to be 18112 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 18113 * annotated with dependencies and is responsible for creating a filter function. 18114 * 18115 * <div class="alert alert-warning"> 18116 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 18117 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 18118 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 18119 * (`myapp_subsection_filterx`). 18120 * </div> 18121 * 18122 * ```js 18123 * // Filter registration 18124 * function MyModule($provide, $filterProvider) { 18125 * // create a service to demonstrate injection (not always needed) 18126 * $provide.value('greet', function(name){ 18127 * return 'Hello ' + name + '!'; 18128 * }); 18129 * 18130 * // register a filter factory which uses the 18131 * // greet service to demonstrate DI. 18132 * $filterProvider.register('greet', function(greet){ 18133 * // return the filter function which uses the greet service 18134 * // to generate salutation 18135 * return function(text) { 18136 * // filters need to be forgiving so check input validity 18137 * return text && greet(text) || text; 18138 * }; 18139 * }); 18140 * } 18141 * ``` 18142 * 18143 * The filter function is registered with the `$injector` under the filter name suffix with 18144 * `Filter`. 18145 * 18146 * ```js 18147 * it('should be the same instance', inject( 18148 * function($filterProvider) { 18149 * $filterProvider.register('reverse', function(){ 18150 * return ...; 18151 * }); 18152 * }, 18153 * function($filter, reverseFilter) { 18154 * expect($filter('reverse')).toBe(reverseFilter); 18155 * }); 18156 * ``` 18157 * 18158 * 18159 * For more information about how angular filters work, and how to create your own filters, see 18160 * {@link guide/filter Filters} in the Angular Developer Guide. 18161 */ 18162 18163/** 18164 * @ngdoc service 18165 * @name $filter 18166 * @kind function 18167 * @description 18168 * Filters are used for formatting data displayed to the user. 18169 * 18170 * The general syntax in templates is as follows: 18171 * 18172 * {{ expression [| filter_name[:parameter_value] ... ] }} 18173 * 18174 * @param {String} name Name of the filter function to retrieve 18175 * @return {Function} the filter function 18176 * @example 18177 <example name="$filter" module="filterExample"> 18178 <file name="index.html"> 18179 <div ng-controller="MainCtrl"> 18180 <h3>{{ originalText }}</h3> 18181 <h3>{{ filteredText }}</h3> 18182 </div> 18183 </file> 18184 18185 <file name="script.js"> 18186 angular.module('filterExample', []) 18187 .controller('MainCtrl', function($scope, $filter) { 18188 $scope.originalText = 'hello'; 18189 $scope.filteredText = $filter('uppercase')($scope.originalText); 18190 }); 18191 </file> 18192 </example> 18193 */ 18194$FilterProvider.$inject = ['$provide']; 18195function $FilterProvider($provide) { 18196 var suffix = 'Filter'; 18197 18198 /** 18199 * @ngdoc method 18200 * @name $filterProvider#register 18201 * @param {string|Object} name Name of the filter function, or an object map of filters where 18202 * the keys are the filter names and the values are the filter factories. 18203 * 18204 * <div class="alert alert-warning"> 18205 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 18206 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 18207 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 18208 * (`myapp_subsection_filterx`). 18209 * </div> 18210 * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered. 18211 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 18212 * of the registered filter instances. 18213 */ 18214 function register(name, factory) { 18215 if (isObject(name)) { 18216 var filters = {};
18217 forEach(name, function(filter, key) { 18218 filters[key] = register(key, filter); 18219 }); 18220 return filters; 18221 } else { 18222 return $provide.factory(name + suffix, factory); 18223 } 18224 } 18225 this.register = register; 18226 18227 this.$get = ['$injector', function($injector) { 18228 return function(name) { 18229 return $injector.get(name + suffix); 18230 }; 18231 }]; 18232 18233 //////////////////////////////////////// 18234 18235 /* global 18236 currencyFilter: false, 18237 dateFilter: false, 18238 filterFilter: false, 18239 jsonFilter: false, 18240 limitToFilter: false, 18241 lowercaseFilter: false, 18242 numberFilter: false, 18243 orderByFilter: false, 18244 uppercaseFilter: false, 18245 */ 18246 18247 register('currency', currencyFilter); 18248 register('date', dateFilter); 18249 register('filter', filterFilter); 18250 register('json', jsonFilter); 18251 register('limitTo', limitToFilter); 18252 register('lowercase', lowercaseFilter); 18253 register('number', numberFilter); 18254 register('orderBy', orderByFilter); 18255 register('uppercase', uppercaseFilter); 18256} 18257 18258/** 18259 * @ngdoc filter 18260 * @name filter 18261 * @kind function 18262 * 18263 * @description 18264 * Selects a subset of items from `array` and returns it as a new array. 18265 * 18266 * @param {Array} array The source array. 18267 * @param {string|Object|function()} expression The predicate to be used for selecting items from 18268 * `array`. 18269 * 18270 * Can be one of: 18271 * 18272 * - `string`: The string is used for matching against the contents of the `array`. All strings or 18273 * objects with string properties in `array` that match this string will be returned. This also 18274 * applies to nested object properties. 18275 * The predicate can be negated by prefixing the string with `!`. 18276 * 18277 * - `Object`: A pattern object can be used to filter specific properties on objects contained 18278 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 18279 * which have property `name` containing "M" and property `phone` containing "1". A special 18280 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 18281 * property of the object or its nested object properties. That's equivalent to the simple 18282 * substring match with a `string` as described above. The predicate can be negated by prefixing 18283 * the string with `!`. 18284 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 18285 * not containing "M". 18286 * 18287 * Note that a named property will match properties on the same level only, while the special 18288 * `$` property will match properties on the same level or deeper. E.g. an array item like 18289 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 18290 * **will** be matched by `{$: 'John'}`. 18291 * 18292 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. 18293 * The function is called for each element of the array, with the element, its index, and 18294 * the entire array itself as arguments. 18295 * 18296 * The final result is an array of those elements that the predicate returned true for. 18297 * 18298 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 18299 * determining if the expected value (from the filter expression) and actual value (from 18300 * the object in the array) should be considered a match. 18301 * 18302 * Can be one of: 18303 * 18304 * - `function(actual, expected)`: 18305 * The function will be given the object value and the predicate value to compare and 18306 * should return true if both values should be considered equal. 18307 * 18308 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 18309 * This is essentially strict comparison of expected and actual. 18310 * 18311 * - `false|undefined`: A short hand for a function which will look for a substring match in case 18312 * insensitive way. 18313 * 18314 * Primitive values are converted to strings. Objects are not compared against primitives, 18315 * unless they have a custom `toString` method (e.g. `Date` objects). 18316 * 18317 * @example 18318 <example> 18319 <file name="index.html"> 18320 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 18321 {name:'Mary', phone:'800-BIG-MARY'}, 18322 {name:'Mike', phone:'555-4321'}, 18323 {name:'Adam', phone:'555-5678'}, 18324 {name:'Julie', phone:'555-8765'}, 18325 {name:'Juliette', phone:'555-5678'}]"></div> 18326 18327 <label>Search: <input ng-model="searchText"></label> 18328 <table id="searchTextResults"> 18329 <tr><th>
18329Name</th><th>Phone</th></tr> 18330 <tr ng-repeat="friend in friends | filter:searchText"> 18331 <td>{{friend.name}}</td> 18332 <td>{{friend.phone}}</td> 18333 </tr> 18334 </table> 18335 <hr> 18336 <label>Any: <input ng-model="search.$"></label> <br> 18337 <label>Name only <input ng-model="search.name"></label><br> 18338 <label>Phone only <input ng-model="search.phone"></label><br> 18339 <label>Equality <input type="checkbox" ng-model="strict"></label><br> 18340 <table id="searchObjResults"> 18341 <tr><th>Name</th><th>Phone</th></tr> 18342 <tr ng-repeat="friendObj in friends | filter:search:strict"> 18343 <td>{{friendObj.name}}</td> 18344 <td>{{friendObj.phone}}</td> 18345 </tr> 18346 </table> 18347 </file> 18348 <file name="protractor.js" type="protractor"> 18349 var expectFriendNames = function(expectedNames, key) { 18350 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 18351 arr.forEach(function(wd, i) { 18352 expect(wd.getText()).toMatch(expectedNames[i]); 18353 }); 18354 }); 18355 }; 18356 18357 it('should search across all fields when filtering with a string', function() { 18358 var searchText = element(by.model('searchText')); 18359 searchText.clear(); 18360 searchText.sendKeys('m'); 18361 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 18362 18363 searchText.clear(); 18364 searchText.sendKeys('76'); 18365 expectFriendNames(['John', 'Julie'], 'friend'); 18366 }); 18367 18368 it('should search in specific fields when filtering with a predicate object', function() { 18369 var searchAny = element(by.model('search.$')); 18370 searchAny.clear(); 18371 searchAny.sendKeys('i'); 18372 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 18373 }); 18374 it('should use a equal comparison when comparator is true', function() { 18375 var searchName = element(by.model('search.name')); 18376 var strict = element(by.model('strict')); 18377 searchName.clear(); 18378 searchName.sendKeys('Julie'); 18379 strict.click(); 18380 expectFriendNames(['Julie'], 'friendObj'); 18381 }); 18382 </file> 18383 </example> 18384 */ 18385function filterFilter() { 18386 return function(array, expression, comparator) { 18387 if (!isArrayLike(array)) { 18388 if (array == null) { 18389 return array; 18390 } else { 18391 throw minErr('filter')('notarray', 'Expected array but received: {0}', array); 18392 } 18393 } 18394 18395 var expressionType = getTypeForFilter(expression); 18396 var predicateFn; 18397 var matchAgainstAnyProp; 18398 18399 switch (expressionType) { 18400 case 'function': 18401 predicateFn = expression; 18402 break; 18403 case 'boolean': 18404 case 'null': 18405 case 'number': 18406 case 'string': 18407 matchAgainstAnyProp = true; 18408 //jshint -W086 18409 case 'object': 18410 //jshint +W086 18411 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); 18412 break; 18413 default: 18414 return array; 18415 } 18416 18417 return Array.prototype.filter.call(array, predicateFn); 18418 }; 18419} 18420 18421// Helper functions for `filterFilter` 18422function createPredicateFn(expression, comparator, matchAgainstAnyProp) { 18423 var shouldMatchPrimitives = isObject(expression) && ('$' in expression); 18424 var predicateFn; 18425 18426 if (comparator === true) { 18427 comparator = equals; 18428 } else if (!isFunction(comparator)) { 18429 comparator = function(actual, expected) { 18430 if (isUndefined(actual)) { 18431 // No substring matching against `undefined` 18432 return false; 18433 } 18434 if ((actual === null) || (expected === null)) { 18435 // No substring matching against `null`; only match against `null` 18436 return actual === expected; 18437 } 18438 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { 18439 // Should not compare primitives against objects, unless they have custom `toString` method 18440 return false; 18441 } 18442 18443 actual = lowercase('' + actual); 18444 expected = lowercase('' + expected); 18445 return actual.indexOf(expected) !== -1; 18446 }; 18447 } 18448 18449 predicateFn = function(item) { 18450 if (shouldMatchPrimitives && !isObject(item)) { 18451 return deepCompare(item, expression.$, comparator, false); 18452 } 18453 return deepCompare(item, expression, comparator, matchAgainstAnyProp); 18454 }; 18455 18456 return predicateFn; 18457} 18458 18459function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { 18460 var actualType = getTypeForFilter(actual); 18461 var expectedType = getTypeForFilter(expected); 18462 18463 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 18464 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); 18465 } else if (isArray(actual)) { 18466 // In case `actual` is an array, consider it a match 18467 // if ANY of it's items matches `expected` 18468 return actual.some(function(item) { 18469 return deepCompare(item, expected, comparator, matchAgainstAnyProp); 18470 });
18471 } 18472 18473 switch (actualType) { 18474 case 'object': 18475 var key; 18476 if (matchAgainstAnyProp) { 18477 for (key in actual) { 18478 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) { 18479 return true; 18480 } 18481 } 18482 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); 18483 } else if (expectedType === 'object') { 18484 for (key in expected) { 18485 var expectedVal = expected[key]; 18486 if (isFunction(expectedVal) || isUndefined(expectedVal)) { 18487 continue; 18488 } 18489 18490 var matchAnyProperty = key === '$'; 18491 var actualVal = matchAnyProperty ? actual : actual[key]; 18492 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { 18493 return false; 18494 } 18495 } 18496 return true; 18497 } else { 18498 return comparator(actual, expected); 18499 } 18500 break; 18501 case 'function': 18502 return false; 18503 default: 18504 return comparator(actual, expected); 18505 } 18506} 18507 18508// Used for easily differentiating between `null` and actual `object` 18509function getTypeForFilter(val) { 18510 return (val === null) ? 'null' : typeof val; 18511} 18512 18513/** 18514 * @ngdoc filter 18515 * @name currency 18516 * @kind function 18517 * 18518 * @description 18519 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 18520 * symbol for current locale is used. 18521 * 18522 * @param {number} amount Input to filter. 18523 * @param {string=} symbol Currency symbol or identifier to be displayed. 18524 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 18525 * @returns {string} Formatted number. 18526 * 18527 * 18528 * @example 18529 <example module="currencyExample"> 18530 <file name="index.html"> 18531 <script> 18532 angular.module('currencyExample', []) 18533 .controller('ExampleController', ['$scope', function($scope) { 18534 $scope.amount = 1234.56; 18535 }]); 18536 </script>
18537 <div ng-controller="ExampleController"> 18538 <input type="number" ng-model="amount" aria-label="amount"> <br> 18539 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 18540 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span> 18541 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 18542 </div> 18543 </file> 18544 <file name="protractor.js" type="protractor"> 18545 it('should init with 1234.56', function() { 18546 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 18547 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 18548 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 18549 }); 18550 it('should update', function() { 18551 if (browser.params.browser == 'safari') { 18552 // Safari does not understand the minus key. See 18553 // https://github.com/angular/protractor/issues/481 18554 return; 18555 } 18556 element(by.model('amount')).clear(); 18557 element(by.model('amount')).sendKeys('-1234'); 18558 expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00'); 18559 expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00'); 18560 expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234'); 18561 }); 18562 </file> 18563 </example> 18564 */ 18565currencyFilter.$inject = ['$locale']; 18566function currencyFilter($locale) { 18567 var formats = $locale.NUMBER_FORMATS; 18568 return function(amount, currencySymbol, fractionSize) { 18569 if (isUndefined(currencySymbol)) { 18570 currencySymbol = formats.CURRENCY_SYM; 18571 } 18572 18573 if (isUndefined(fractionSize)) { 18574 fractionSize = formats.PATTERNS[1].maxFrac; 18575 } 18576 18577 // if null or undefined pass it through 18578 return (amount == null) 18579 ? amount 18580 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 18581 replace(/\u00A4/g, currencySymbol); 18582 }; 18583} 18584 18585/** 18586 * @ngdoc filter 18587 * @name number 18588 * @kind function 18589 * 18590 * @description 18591 * Formats a number as text. 18592 * 18593 * If the input is null or undefined, it will just be returned. 18594 * If the input is infinite (Infinity/-Infinity) the Infinity symbol 'â' is returned. 18595 * If the input is not a number an empty string is returned. 18596 * 18597 * 18598 * @param {number|string} number Number to format. 18599 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 18600 * If this is not provided then the fraction size is computed from the current locale's number 18601 * formatting pattern. In the case of the default locale, it will be 3. 18602 * @returns {string} Number rounded to decimalPlaces and places a â,â after each third digit. 18603 * 18604 * @example 18605 <example module="numberFilterExample"> 18606 <file name="index.html"> 18607 <script> 18608 angular.module('numberFilterExample', []) 18609 .controller('ExampleController', ['$scope', function($scope) { 18610 $scope.val = 1234.56789; 18611 }]); 18612 </script>
18613 <div ng-controller="ExampleController"> 18614 <label>Enter number: <input ng-model='val'></label><br> 18615 Default formatting: <span id='number-default'>{{val | number}}</span><br> 18616 No fractions: <span>{{val | number:0}}</span><br> 18617 Negative number: <span>{{-val | number:4}}</span> 18618 </div> 18619 </file> 18620 <file name="protractor.js" type="protractor"> 18621 it('should format numbers', function() { 18622 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 18623 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 18624 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 18625 }); 18626 18627 it('should update', function() { 18628 element(by.model('val')).clear(); 18629 element(by.model('val')).sendKeys('3374.333'); 18630 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 18631 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 18632 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 18633 }); 18634 </file> 18635 </example> 18636 */ 18637 18638 18639numberFilter.$inject = ['$locale']; 18640function numberFilter($locale) { 18641 var formats = $locale.NUMBER_FORMATS; 18642 return function(number, fractionSize) { 18643 18644 // if null or undefined pass it through 18645 return (number == null) 18646 ? number 18647 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 18648 fractionSize); 18649 }; 18650} 18651 18652var DECIMAL_SEP = '.'; 18653function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 18654 if (isObject(number)) return ''; 18655 18656 var isNegative = number < 0; 18657 number = Math.abs(number); 18658 18659 var isInfinity = number === Infinity; 18660 if (!isInfinity && !isFinite(number)) return ''; 18661 18662 var numStr = number + '', 18663 formatedText = '', 18664 hasExponent = false, 18665 parts = []; 18666 18667 if (isInfinity) formatedText = '\u221e'; 18668 18669 if (!isInfinity && numStr.indexOf('e') !== -1) { 18670 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 18671 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 18672 number = 0; 18673 } else { 18674 formatedText = numStr; 18675 hasExponent = true; 18676 } 18677 } 18678 18679 if (!isInfinity && !hasExponent) { 18680 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 18681 18682 // determine fractionSize if it is not specified 18683 if (isUndefined(fractionSize)) { 18684 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 18685 } 18686 18687 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics 18688 // inspired by: 18689 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round 18690 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); 18691 18692 var fraction = ('' + number).split(DECIMAL_SEP); 18693 var whole = fraction[0]; 18694 fraction = fraction[1] || ''; 18695 18696 var i, pos = 0, 18697 lgroup = pattern.lgSize, 18698 group = pattern.gSize; 18699 18700 if (whole.length >= (lgroup + group)) { 18701 pos = whole.length - lgroup; 18702 for (i = 0; i < pos; i++) { 18703 if ((pos - i) % group === 0 && i !== 0) { 18704 formatedText += groupSep; 18705 } 18706 formatedText += whole.charAt(i); 18707 } 18708 } 18709 18710 for (i = pos; i < whole.length; i++) { 18711 if ((whole.length - i) % lgroup === 0 && i !== 0) { 18712 formatedText += groupSep; 18713 } 18714 formatedText += whole.charAt(i); 18715 } 18716 18717 // format fraction part. 18718 while (fraction.length < fractionSize) { 18719 fraction += '0'; 18720 } 18721 18722 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 18723 } else { 18724 if (fractionSize > 0 && number < 1) { 18725 formatedText = number.toFixed(fractionSize); 18726 number = parseFloat(formatedText); 18727 formatedText = formatedText.replace(DECIMAL_SEP, decimalSep); 18728 } 18729 } 18730 18731 if (number === 0) { 18732 isNegative = false; 18733 } 18734 18735 parts.push(isNegative ? pattern.negPre : pattern.posPre, 18736 formatedText, 18737 isNegative ? pattern.negSuf : pattern.posSuf); 18738 return parts.join(''); 18739} 18740 18741function padNumber(num, digits, trim) { 18742 var neg = ''; 18743 if (num < 0) { 18744 neg = '-'; 18745 num = -num; 18746 } 18747 num = '' + num; 18748 while (num.length < digits) num = '0' + num; 18749 if (trim) { 18750 num = num.substr(num.length - digits); 18751 } 18752 return neg + num; 18753} 18754 18755 18756function dateGetter(name, size, offset, trim) { 18757 offset = offset || 0; 18758 return function(date) { 18759 var value = date['get' + name](); 18760 if (offset > 0 || value > -offset) { 18761 value += offset; 18762 } 18763 if (value === 0 && offset == -12) value = 12; 18764 return padNumber(value, size, trim); 18765 }; 18766} 18767 18768function dateStrGetter(name, shortForm) { 18769 return function(date, formats) { 18770 var value = date['get' + name](); 18771 var get = uppercase(shortForm ? ('SHORT' + name) : name); 18772 18773 return formats[get][value]; 18774 }; 18775} 18776 18777function timeZoneGetter(date, formats, offset) { 18778 var zone = -1 * offset; 18779 var paddedZone = (zone >= 0) ? "+" : ""; 18780 18781 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 18782 padNumber(Math.abs(zone % 60), 2); 18783 18784 return paddedZone; 18785} 18786 18787function getFirstThursdayOfYear(year) { 18788 // 0 = index of January 18789 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 18790 // 4 = index of Thursday (+1 to account for 1st = 5) 18791 // 11 = index of *next* Thursday (+1 account for 1st = 12) 18792 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 18793} 18794 18795function getThursdayThisWeek(datetime) { 18796 return new Date(datetime.getFullYear(), datetime.getMonth(), 18797 // 4 = index of Thursday 18798 datetime.getDate() + (4 - datetime.getDay())); 18799} 18800 18801function weekGetter(size) { 18802 return function(date) { 18803 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 18804 thisThurs = getThursdayThisWeek(date); 18805 18806 var diff = +thisThurs - +firstThurs, 18807 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 18808 18809 return padNumber(result, size); 18810 }; 18811} 18812 18813function ampmGetter(date, formats) { 18814 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 18815} 18816 18817function eraGetter(date, formats) {
vendor: 5,270 bytes, lines 18818-18930
18818 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; 18819} 18820 18821function longEraGetter(date, formats) { 18822 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; 18823} 18824 18825var DATE_FORMATS = { 18826 yyyy: dateGetter('FullYear', 4), 18827 yy: dateGetter('FullYear', 2, 0, true), 18828 y: dateGetter('FullYear', 1), 18829 MMMM: dateStrGetter('Month'), 18830 MMM: dateStrGetter('Month', true), 18831 MM: dateGetter('Month', 2, 1), 18832 M: dateGetter('Month', 1, 1), 18833 dd: dateGetter('Date', 2), 18834 d: dateGetter('Date', 1), 18835 HH: dateGetter('Hours', 2), 18836 H: dateGetter('Hours', 1), 18837 hh: dateGetter('Hours', 2, -12), 18838 h: dateGetter('Hours', 1, -12), 18839 mm: dateGetter('Minutes', 2), 18840 m: dateGetter('Minutes', 1), 18841 ss: dateGetter('Seconds', 2), 18842 s: dateGetter('Seconds', 1), 18843 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 18844 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 18845 sss: dateGetter('Milliseconds', 3), 18846 EEEE: dateStrGetter('Day'), 18847 EEE: dateStrGetter('Day', true), 18848 a: ampmGetter, 18849 Z: timeZoneGetter, 18850 ww: weekGetter(2), 18851 w: weekGetter(1), 18852 G: eraGetter, 18853 GG: eraGetter, 18854 GGG: eraGetter, 18855 GGGG: longEraGetter 18856}; 18857 18858var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, 18859 NUMBER_STRING = /^\-?\d+$/; 18860 18861/** 18862 * @ngdoc filter 18863 * @name date 18864 * @kind function 18865 * 18866 * @description 18867 * Formats `date` to a string based on the requested `format`. 18868 * 18869 * `format` string can be composed of the following elements: 18870 * 18871 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 18872 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 18873 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 18874 * * `'MMMM'`: Month in year (January-December) 18875 * * `'MMM'`: Month in year (Jan-Dec) 18876 * * `'MM'`: Month in year, padded (01-12) 18877 * * `'M'`: Month in year (1-12) 18878 * * `'dd'`: Day in month, padded (01-31) 18879 * * `'d'`: Day in month (1-31) 18880 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 18881 * * `'EEE'`: Day in Week, (Sun-Sat) 18882 * * `'HH'`: Hour in day, padded (00-23) 18883 * * `'H'`: Hour in day (0-23) 18884 * * `'hh'`: Hour in AM/PM, padded (01-12) 18885 * * `'h'`: Hour in AM/PM, (1-12) 18886 * * `'mm'`: Minute in hour, padded (00-59) 18887 * * `'m'`: Minute in hour (0-59) 18888 * * `'ss'`: Second in minute, padded (00-59) 18889 * * `'s'`: Second in minute (0-59) 18890 * * `'sss'`: Millisecond in second, padded (000-999) 18891 * * `'a'`: AM/PM marker 18892 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 18893 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 18894 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year 18895 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') 18896 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') 18897 * 18898 * `format` string can also be one of the following predefined 18899 * {@link guide/i18n localizable formats}: 18900 * 18901 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 18902 * (e.g. Sep 3, 2010 12:05:08 PM) 18903 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 18904 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 18905 * (e.g. Friday, September 3, 2010) 18906 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 18907 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 18908 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 18909 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 18910 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 18911 * 18912 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 18913 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 18914 * (e.g. `"h 'o''clock'"`). 18915 * 18916 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 18917 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 18918 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 18919 * specified in the string input, the time is considered to be in the local timezone. 18920 * @param {string=} format Formatting rules (see Description). If not specified, 18921 * `mediumDate` is used. 18922 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the 18923 * continental US time zone abbreviations, but for general use, use a time zone offset, for 18924 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 18925 * If not specified, the timezone of the browser will be used. 18926 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 18927 * 18928 * @example 18929 <example> 18930 <file name="index.html">
vendor: 4,190 bytes, lines 18931-19043
18931 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 18932 <span>{{1288323623006 | date:'medium'}}</span><br> 18933 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 18934 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 18935 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 18936 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 18937 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 18938 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 18939 </file> 18940 <file name="protractor.js" type="protractor"> 18941 it('should format date', function() { 18942 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 18943 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 18944 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 18945 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 18946 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 18947 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 18948 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 18949 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 18950 }); 18951 </file> 18952 </example> 18953 */ 18954dateFilter.$inject = ['$locale']; 18955function dateFilter($locale) { 18956 18957 18958 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 18959 // 1 2 3 4 5 6 7 8 9 10 11 18960 function jsonStringToDate(string) { 18961 var match; 18962 if (match = string.match(R_ISO8601_STR)) { 18963 var date = new Date(0), 18964 tzHour = 0, 18965 tzMin = 0, 18966 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 18967 timeSetter = match[8] ? date.setUTCHours : date.setHours; 18968 18969 if (match[9]) { 18970 tzHour = toInt(match[9] + match[10]); 18971 tzMin = toInt(match[9] + match[11]); 18972 } 18973 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 18974 var h = toInt(match[4] || 0) - tzHour; 18975 var m = toInt(match[5] || 0) - tzMin; 18976 var s = toInt(match[6] || 0); 18977 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 18978 timeSetter.call(date, h, m, s, ms); 18979 return date; 18980 } 18981 return string; 18982 } 18983 18984 18985 return function(date, format, timezone) { 18986 var text = '', 18987 parts = [], 18988 fn, match; 18989 18990 format = format || 'mediumDate'; 18991 format = $locale.DATETIME_FORMATS[format] || format; 18992 if (isString(date)) { 18993 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); 18994 } 18995 18996 if (isNumber(date)) { 18997 date = new Date(date); 18998 } 18999 19000 if (!isDate(date) || !isFinite(date.getTime())) { 19001 return date; 19002 } 19003 19004 while (format) { 19005 match = DATE_FORMATS_SPLIT.exec(format); 19006 if (match) { 19007 parts = concat(parts, match, 1); 19008 format = parts.pop(); 19009 } else { 19010 parts.push(format); 19011 format = null; 19012 } 19013 } 19014 19015 var dateTimezoneOffset = date.getTimezoneOffset(); 19016 if (timezone) { 19017 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); 19018 date = convertTimezoneToLocal(date, timezone, true); 19019 } 19020 forEach(parts, function(value) { 19021 fn = DATE_FORMATS[value]; 19022 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) 19023 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 19024 }); 19025 19026 return text; 19027 }; 19028} 19029 19030 19031/** 19032 * @ngdoc filter 19033 * @name json 19034 * @kind function 19035 * 19036 * @description 19037 * Allows you to convert a JavaScript object into JSON string. 19038 * 19039 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 19040 * the binding is automatically converted to JSON. 19041 * 19042 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 19043 * @param {number=}
19043 spacing The number of spaces to use per indentation, defaults to 2. 19044 * @returns {string} JSON string. 19045 * 19046 * 19047 * @example 19048 <example> 19049 <file name="index.html"> 19050 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 19051 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 19052 </file> 19053 <file name="protractor.js" type="protractor"> 19054 it('should jsonify filtered objects', function() { 19055 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 19056 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 19057 }); 19058 </file> 19059 </example> 19060 * 19061 */ 19062function jsonFilter() { 19063 return function(object, spacing) { 19064 if (isUndefined(spacing)) { 19065 spacing = 2; 19066 } 19067 return toJson(object, spacing); 19068 }; 19069} 19070 19071 19072/** 19073 * @ngdoc filter 19074 * @name lowercase 19075 * @kind function 19076 * @description 19077 * Converts string to lowercase. 19078 * @see angular.lowercase 19079 */ 19080var lowercaseFilter = valueFn(lowercase); 19081 19082 19083/** 19084 * @ngdoc filter 19085 * @name uppercase 19086 * @kind function 19087 * @description 19088 * Converts string to uppercase. 19089 * @see angular.uppercase 19090 */ 19091var uppercaseFilter = valueFn(uppercase); 19092 19093/** 19094 * @ngdoc filter 19095 * @name limitTo 19096 * @kind function 19097 * 19098 * @description 19099 * Creates a new array or string containing only a specified number of elements. The elements 19100 * are taken from either the beginning or the end of the source array, string or number, as specified by 19101 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is 19102 * converted to a string. 19103 * 19104 * @param {Array|string|number} input Source array, string or number to be limited. 19105 * @param {string|number} limit The length of the returned array or string. If the `limit` number 19106 * is positive, `limit` number of items from the beginning of the source array/string are copied. 19107 * If the number is negative, `limit` number of items from the end of the source array/string 19108 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, 19109 * the input will be returned unchanged. 19110 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin` 19111 * indicates an offset from the end of `input`. Defaults to `0`. 19112 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 19113 * had less than `limit` elements. 19114 * 19115 * @example 19116 <example module="limitToExample"> 19117 <file name="index.html"> 19118 <script> 19119 angular.module('limitToExample', []) 19120 .controller('ExampleController', ['$scope', function($scope) { 19121 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 19122 $scope.letters = "abcdefghi"; 19123 $scope.longNumber = 2345432342; 19124 $scope.numLimit = 3; 19125 $scope.letterLimit = 3; 19126 $scope.longNumberLimit = 3; 19127 }]); 19128 </script>
19129 <div ng-controller="ExampleController"> 19130 <label> 19131 Limit {{numbers}} to: 19132 <input type="number" step="1" ng-model="numLimit"> 19133 </label> 19134 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 19135 <label> 19136 Limit {{letters}} to: 19137 <input type="number" step="1" ng-model="letterLimit"> 19138 </label> 19139 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 19140 <label> 19141 Limit {{longNumber}} to: 19142 <input type="number" step="1" ng-model="longNumberLimit"> 19143 </label> 19144 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 19145 </div> 19146 </file> 19147 <file name="protractor.js" type="protractor"> 19148 var numLimitInput = element(by.model('numLimit')); 19149 var letterLimitInput = element(by.model('letterLimit')); 19150 var longNumberLimitInput = element(by.model('longNumberLimit')); 19151 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 19152 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 19153 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 19154 19155 it('should limit the number array to first three items', function() { 19156 expect(numLimitInput.getAttribute('value')).toBe('3'); 19157 expect(letterLimitInput.getAttribute('value')).toBe('3'); 19158 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 19159 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 19160 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 19161 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 19162 }); 19163 19164 // There is a bug in safari and protractor that doesn't like the minus key 19165 // it('should update the output when -3 is entered', function() { 19166 // numLimitInput.clear(); 19167 // numLimitInput.sendKeys('-3'); 19168 // letterLimitInput.clear(); 19169 // letterLimitInput.sendKeys('-3'); 19170 // longNumberLimitInput.clear(); 19171 // longNumberLimitInput.sendKeys('-3'); 19172 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 19173 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 19174 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 19175 // }); 19176 19177 it('should not exceed the maximum size of input array', function() { 19178 numLimitInput.clear(); 19179 numLimitInput.sendKeys('100'); 19180 letterLimitInput.clear(); 19181 letterLimitInput.sendKeys('100'); 19182 longNumberLimitInput.clear(); 19183 longNumberLimitInput.sendKeys('100'); 19184 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 19185 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 19186 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 19187 }); 19188 </file> 19189 </example> 19190*/ 19191function limitToFilter() { 19192 return function(input, limit, begin) { 19193 if (Math.abs(Number(limit)) === Infinity) { 19194 limit = Number(limit); 19195 } else { 19196 limit = toInt(limit); 19197 } 19198 if (isNaN(limit)) return input; 19199 19200 if (isNumber(input)) input = input.toString(); 19201 if (!isArray(input) && !isString(input)) return input; 19202 19203 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); 19204 begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin; 19205 19206 if (limit >= 0) { 19207 return input.slice(begin, begin + limit); 19208 } else { 19209 if (begin === 0) { 19210 return input.slice(limit, input.length); 19211 } else { 19212 return input.slice(Math.max(0, begin + limit), begin); 19213 } 19214 } 19215 }; 19216} 19217 19218/** 19219 * @ngdoc filter 19220 * @name orderBy 19221 * @kind function 19222 * 19223 * @description 19224 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 19225 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 19226 * as expected, make sure they are actually being saved as numbers and not strings. 19227 * 19228 * @param {Array} array The array to sort. 19229 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be 19230 * used by the comparator to determine the order of elements. 19231 * 19232 * Can be one of: 19233 * 19234 * - `function`: Getter function. The result of this function will be sorted using the 19235 * `<`, `===`, `>` operator. 19236 * - `string`: An Angular expression. The result of this expression is used to compare elements 19237 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by 19238 * 3 first characters of a property called `name`). The result of a constant expression 19239 * is interpreted as a property name to be used in comparisons (for example `"special name"` 19240 * to sort object by the value of their `special name` property). An expression can be 19241 * optionally prefixed with `+` or `-` to control ascending or descending sort order 19242 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array 19243 * element itself is used to compare where sorting. 19244 * - `Array`: An array of function or string predicates. The first predicate in the array 19245 * is used for sorting, but when two items are equivalent, the next predicate is used. 19246 * 19247 * If the predicate is missing or empty then it defaults to `'+'`. 19248 * 19249 * @param {boolean=} reverse Reverse the order of the array. 19250 * @returns {Array} Sorted copy of the source array. 19251 * 19252 * 19253 * @example 19254 * The example below demonstrates a simple ngRepeat, where the data is sorted 19255 * by age in descending order (predicate is set to `'-age'`). 19256 * `reverse` is not set, which means it defaults to `false`. 19257 <example module="orderByExample"> 19258 <file name="index.html"> 19259 <script> 19260 angular.module('orderByExample', [])
19261 .controller('ExampleController', ['$scope', function($scope) { 19262 $scope.friends = 19263 [{name:'John', phone:'555-1212', age:10}, 19264 {name:'Mary', phone:'555-9876', age:19}, 19265 {name:'Mike', phone:'555-4321', age:21}, 19266 {name:'Adam', phone:'555-5678', age:35}, 19267 {name:'Julie', phone:'555-8765', age:29}]; 19268 }]); 19269 </script> 19270 <div ng-controller="ExampleController"> 19271 <table class="friend"> 19272 <tr> 19273 <th>Name</th> 19274 <th>Phone Number</th> 19275 <th>Age</th> 19276 </tr> 19277 <tr ng-repeat="friend in friends | orderBy:'-age'"> 19278 <td>{{friend.name}}</td> 19279 <td>{{friend.phone}}</td> 19280 <td>{{friend.age}}</td> 19281 </tr> 19282 </table> 19283 </div> 19284 </file> 19285 </example> 19286 * 19287 * The predicate and reverse parameters can be controlled dynamically through scope properties, 19288 * as shown in the next example. 19289 * @example 19290 <example module="orderByExample"> 19291 <file name="index.html"> 19292 <script> 19293 angular.module('orderByExample', []) 19294 .controller('ExampleController', ['$scope', function($scope) { 19295 $scope.friends = 19296 [{name:'John', phone:'555-1212', age:10}, 19297 {name:'Mary', phone:'555-9876', age:19}, 19298 {name:'Mike', phone:'555-4321', age:21}, 19299 {name:'Adam', phone:'555-5678', age:35}, 19300 {name:'Julie', phone:'555-8765', age:29}]; 19301 $scope.predicate = 'age'; 19302 $scope.reverse = true; 19303 $scope.order = function(predicate) { 19304 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 19305 $scope.predicate = predicate; 19306 }; 19307 }]); 19308 </script> 19309 <style type="text/css"> 19310 .sortorder:after { 19311 content: '\25b2'; 19312 } 19313 .sortorder.reverse:after { 19314 content: '\25bc'; 19315 } 19316 </style>
19317 <div ng-controller="ExampleController"> 19318 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 19319 <hr/> 19320 [ <a href="" ng-click="predicate=''">unsorted</a> ] 19321 <table class="friend"> 19322 <tr> 19323 <th> 19324 <a href="" ng-click="order('name')">Name</a> 19325 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 19326 </th> 19327 <th> 19328 <a href="" ng-click="order('phone')">Phone Number</a> 19329 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 19330 </th> 19331 <th> 19332 <a href="" ng-click="order('age')">Age</a> 19333 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 19334 </th> 19335 </tr> 19336 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 19337 <td>{{friend.name}}</td> 19338 <td>{{friend.phone}}</td> 19339 <td>{{friend.age}}</td> 19340 </tr> 19341 </table> 19342 </div> 19343 </file> 19344 </example> 19345 * 19346 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the 19347 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 19348 * desired parameters. 19349 * 19350 * Example: 19351 * 19352 * @example 19353 <example module="orderByExample"> 19354 <file name="index.html">
19355 <div ng-controller="ExampleController"> 19356 <table class="friend"> 19357 <tr> 19358 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a> 19359 (<a href="" ng-click="order('-name',false)">^</a>)</th> 19360 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th> 19361 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th> 19362 </tr> 19363 <tr ng-repeat="friend in friends"> 19364 <td>{{friend.name}}</td> 19365 <td>{{friend.phone}}</td> 19366 <td>{{friend.age}}</td> 19367 </tr> 19368 </table> 19369 </div> 19370 </file> 19371 19372 <file name="script.js"> 19373 angular.module('orderByExample', []) 19374 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { 19375 var orderBy = $filter('orderBy'); 19376 $scope.friends = [ 19377 { name: 'John', phone: '555-1212', age: 10 }, 19378 { name: 'Mary', phone: '555-9876', age: 19 }, 19379 { name: 'Mike', phone: '555-4321', age: 21 }, 19380 { name: 'Adam', phone: '555-5678', age: 35 }, 19381 { name: 'Julie', phone: '555-8765', age: 29 } 19382 ]; 19383 $scope.order = function(predicate, reverse) { 19384 $scope.friends = orderBy($scope.friends, predicate, reverse); 19385 }; 19386 $scope.order('-age',false); 19387 }]); 19388 </file> 19389</example> 19390 */ 19391orderByFilter.$inject = ['$parse']; 19392function orderByFilter($parse) { 19393 return function(array, sortPredicate, reverseOrder) { 19394 19395 if (!(isArrayLike(array))) return array; 19396 19397 if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; } 19398 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 19399 19400 var predicates = processPredicates(sortPredicate, reverseOrder); 19401 // Add a predicate at the end that evaluates to the element index. This makes the 19402 // sort stable as it works as a tie-breaker when all the input predicates cannot 19403 // distinguish between two elements. 19404 predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1}); 19405 19406 // The next three lines are a version of a Swartzian Transform idiom from Perl 19407 // (sometimes called the Decorate-Sort-Undecorate idiom) 19408 // See https://en.wikipedia.org/wiki/Schwartzian_transform 19409 var compareValues = Array.prototype.map.call(array, getComparisonObject); 19410 compareValues.sort(doComparison); 19411 array = compareValues.map(function(item) { return item.value; }); 19412 19413 return array; 19414 19415 function getComparisonObject(value, index) { 19416 return { 19417 value: value, 19418 predicateValues: predicates.map(function(predicate) { 19419 return getPredicateValue(predicate.get(value), index); 19420 }) 19421 }; 19422 } 19423 19424 function doComparison(v1, v2) { 19425 var result = 0; 19426 for (var index=0, length = predicates.length; index < length; ++index) { 19427 result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending; 19428 if (result) break; 19429 } 19430 return result; 19431 } 19432 }; 19433 19434 function processPredicates(sortPredicate, reverseOrder) { 19435 reverseOrder = reverseOrder ? -1 : 1; 19436 return sortPredicate.map(function(predicate) { 19437 var descending = 1, get = identity; 19438 19439 if (isFunction(predicate)) { 19440 get = predicate; 19441 } else if (isString(predicate)) { 19442 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 19443 descending = predicate.charAt(0) == '-' ? -1 : 1; 19444 predicate = predicate.substring(1); 19445 } 19446 if (predicate !== '') { 19447 get = $parse(predicate); 19448 if (get.constant) { 19449 var key = get(); 19450 get = function(value) { return value[key]; }; 19451 } 19452 } 19453 } 19454 return { get: get, descending: descending * reverseOrder }; 19455 }); 19456 } 19457 19458 function isPrimitive(value) { 19459 switch (typeof value) { 19460 case 'number': /* falls through */ 19461 case 'boolean': /* falls through */ 19462 case 'string': 19463 return true; 19464 default: 19465 return false; 19466 } 19467 } 19468 19469 function objectValue(value, index) { 19470 // If `valueOf` is a valid function use that 19471 if (typeof value.valueOf === 'function') { 19472 value = value.valueOf(); 19473 if (isPrimitive(value)) return value; 19474 } 19475 // If `toString` is a valid function and not the one from `Object.prototype` use that 19476 if (hasCustomToString(value)) { 19477 value = value.toString(); 19478 if (isPrimitive(value)) return value; 19479 } 19480 // We have a basic object so we use the position of the object in the collection 19481 return index; 19482 } 19483 19484 function getPredicateValue(value, index) { 19485 var type = typeof value; 19486 if (value === null) { 19487 type = 'string'; 19488 value = 'null'; 19489 } else if (type === 'string') { 19490 value = value.toLowerCase(); 19491 } else if (type === 'object') { 19492 value = objectValue(value, index); 19493 } 19494 return { value: value, type: type }; 19495 } 19496 19497 function compare(v1, v2) { 19498 var result = 0; 19499 if (v1.type === v2.type) { 19500 if (v1.value !== v2.value) { 19501 result = v1.value < v2.value ? -1 : 1; 19502 } 19503 } else { 19504 result = v1.type < v2.type ? -1 : 1; 19505 } 19506 return result; 19507 } 19508} 19509 19510function ngDirective(directive) { 19511 if (isFunction(directive)) { 19512 directive = { 19513 link: directive 19514 }; 19515 } 19516 directive.restrict = directive.restrict || 'AC'; 19517 return valueFn(directive); 19518} 19519 19520/** 19521 * @ngdoc directive 19522 * @name a 19523 * @restrict E 19524 * 19525 * @description 19526 * Modifies the default behavior of the html A tag so that the default action is prevented when 19527 * the href attribute is empty. 19528 * 19529 * This change permits the easy creation of action links with the `ngClick` directive 19530 * without changing the location or causing page reloads, e.g.: 19531 * `<a href="" ng-click="list.addItem()">Add Item</a>` 19532 */ 19533var htmlAnchorDirective = valueFn({ 19534 restrict: 'E',
19535 compile: function(element, attr) { 19536 if (!attr.href && !attr.xlinkHref) { 19537 return function(scope, element) { 19538 // If the linked element is not an anchor tag anymore, do nothing 19539 if (element[0].nodeName.toLowerCase() !== 'a') return; 19540 19541 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 19542 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 19543 'xlink:href' : 'href'; 19544 element.on('click', function(event) { 19545 // if we have no href url, then don't navigate anywhere. 19546 if (!element.attr(href)) { 19547 event.preventDefault(); 19548 } 19549 }); 19550 }; 19551 } 19552 } 19553}); 19554 19555/** 19556 * @ngdoc directive 19557 * @name ngHref 19558 * @restrict A 19559 * @priority 99 19560 * 19561 * @description 19562 * Using Angular markup like `{{hash}}` in an href attribute will 19563 * make the link go to the wrong URL if the user clicks it before 19564 * Angular has a chance to replace the `{{hash}}` markup with its 19565 * value. Until Angular replaces the markup the link will be broken 19566 * and will most likely return a 404 error. The `ngHref` directive 19567 * solves this problem. 19568 * 19569 * The wrong way to write it: 19570 * ```html 19571 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 19572 * ``` 19573 * 19574 * The correct way to write it: 19575 * ```html 19576 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 19577 * ``` 19578 * 19579 * @element A 19580 * @param {template} ngHref any string which can contain `{{}}` markup. 19581 * 19582 * @example 19583 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 19584 * in links and their different behaviors: 19585 <example> 19586 <file name="index.html"> 19587 <input ng-model="value" /><br /> 19588 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 19589 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 19590 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 19591 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 19592 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 19593 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 19594 </file> 19595 <file name="protractor.js" type="protractor"> 19596 it('should execute ng-click but not reload when href without value', function() { 19597 element(by.id('link-1')).click(); 19598 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 19599 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 19600 }); 19601 19602 it('should execute ng-click but not reload when href empty string', function() { 19603 element(by.id('link-2')).click(); 19604 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 19605 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 19606 }); 19607 19608 it('should execute ng-click and change url when ng-href specified', function() { 19609 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 19610 19611 element(by.id('link-3')).click(); 19612 19613 // At this point, we navigate away from an Angular page, so we need 19614 // to use browser.driver to get the base webdriver. 19615 19616 browser.wait(function() { 19617 return browser.driver.getCurrentUrl().then(function(url) { 19618 return url.match(/\/123$/); 19619 }); 19620 }, 5000, 'page should navigate to /123'); 19621 }); 19622 19623 it('should execute ng-click but not reload when href empty string and name specified', function() { 19624 element(by.id('link-4')).click(); 19625 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 19626 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 19627 }); 19628 19629 it('should execute ng-click but not reload when no href but name specified', function() { 19630 element(by.id('link-5')).click(); 19631 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 19632 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 19633 }); 19634 19635 it('should only change url when only ng-href', function() {
19636 element(by.model('value')).clear(); 19637 element(by.model('value')).sendKeys('6'); 19638 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 19639 19640 element(by.id('link-6')).click(); 19641 19642 // At this point, we navigate away from an Angular page, so we need 19643 // to use browser.driver to get the base webdriver. 19644 browser.wait(function() { 19645 return browser.driver.getCurrentUrl().then(function(url) { 19646 return url.match(/\/6$/); 19647 }); 19648 }, 5000, 'page should navigate to /6'); 19649 }); 19650 </file> 19651 </example> 19652 */ 19653 19654/** 19655 * @ngdoc directive 19656 * @name ngSrc 19657 * @restrict A 19658 * @priority 99 19659 * 19660 * @description 19661 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 19662 * work right: The browser will fetch from the URL with the literal 19663 * text `{{hash}}` until Angular replaces the expression inside 19664 * `{{hash}}`. The `ngSrc` directive solves this problem. 19665 * 19666 * The buggy way to write it: 19667 * ```html 19668 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> 19669 * ``` 19670 * 19671 * The correct way to write it: 19672 * ```html 19673 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> 19674 * ``` 19675 * 19676 * @element IMG 19677 * @param {template} ngSrc any string which can contain `{{}}` markup. 19678 */ 19679 19680/** 19681 * @ngdoc directive 19682 * @name ngSrcset 19683 * @restrict A 19684 * @priority 99 19685 * 19686 * @description 19687 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 19688 * work right: The browser will fetch from the URL with the literal 19689 * text `{{hash}}` until Angular replaces the expression inside 19690 * `{{hash}}`. The `ngSrcset` directive solves this problem. 19691 * 19692 * The buggy way to write it: 19693 * ```html 19694 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> 19695 * ``` 19696 * 19697 * The correct way to write it: 19698 * ```html 19699 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> 19700 * ``` 19701 * 19702 * @element IMG 19703 * @param {template} ngSrcset any string which can contain `{{}}` markup. 19704 */ 19705 19706/** 19707 * @ngdoc directive 19708 * @name ngDisabled 19709 * @restrict A 19710 * @priority 100 19711 * 19712 * @description 19713 * 19714 * This directive sets the `disabled` attribute on the element if the 19715 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. 19716 * 19717 * A special directive is necessary because we cannot use interpolation inside the `disabled` 19718 * attribute. The following example would make the button enabled on Chrome/Firefox 19719 * but not on older IEs: 19720 * 19721 * ```html 19722 * <!-- See below for an example of ng-disabled being used correctly --> 19723 * <div ng-init="isDisabled = false"> 19724 * <button disabled="{{isDisabled}}">Disabled</button> 19725 * </div> 19726 * ``` 19727 * 19728 * This is because the HTML specification does not require browsers to preserve the values of 19729 * boolean attributes such as `disabled` (Their presence means true and their absence means false.) 19730 * If we put an Angular interpolation expression into such an attribute then the 19731 * binding information would be lost when the browser removes the attribute. 19732 * 19733 * @example 19734 <example> 19735 <file name="index.html"> 19736 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> 19737 <button ng-model="button" ng-disabled="checked">Button</button> 19738 </file> 19739 <file name="protractor.js" type="protractor"> 19740 it('should toggle button', function() { 19741 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); 19742 element(by.model('checked')).click(); 19743 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 19744 }); 19745 </file> 19746 </example> 19747 * 19748 * @element INPUT 19749 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 19750 * then the `disabled` attribute will be set on the element 19751 */ 19752 19753 19754/** 19755 * @ngdoc directive 19756 * @name ngChecked 19757 * @restrict A 19758 * @priority 100 19759 * 19760 * @description 19761 * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy. 19762 * 19763 * Note that this directive should not be used together with {@link ngModel `ngModel`}, 19764 * as this can lead to unexpected behavior. 19765 * 19766 * ### Why do we need `ngChecked`? 19767 * 19768 * The HTML specification does not require browsers to preserve the values of boolean attributes 19769 * such as checked. (Their presence means true and their absence means false.) 19770 * If we put an Angular interpolation expression into such an attribute then the 19771 * binding information would be lost when the browser removes the attribute. 19772 * The `ngChecked` directive solves this problem for the `checked` attribute. 19773 * This complementary directive is not removed by the browser and so provides 19774 * a permanent reliable place to store the binding information. 19775 * @example 19776 <example> 19777 <file name="index.html"> 19778 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/> 19779 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input"> 19780 </file> 19781 <file name="protractor.js" type="protractor"> 19782 it('should check both checkBoxes', function() { 19783 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
19784 element(by.model('master')).click(); 19785 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 19786 }); 19787 </file> 19788 </example> 19789 * 19790 * @element INPUT 19791 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 19792 * then the `checked` attribute will be set on the element 19793 */ 19794 19795 19796/** 19797 * @ngdoc directive 19798 * @name ngReadonly 19799 * @restrict A 19800 * @priority 100 19801 * 19802 * @description 19803 * The HTML specification does not require browsers to preserve the values of boolean attributes 19804 * such as readonly. (Their presence means true and their absence means false.) 19805 * If we put an Angular interpolation expression into such an attribute then the 19806 * binding information would be lost when the browser removes the attribute. 19807 * The `ngReadonly` directive solves this problem for the `readonly` attribute. 19808 * This complementary directive is not removed by the browser and so provides 19809 * a permanent reliable place to store the binding information. 19810 * @example 19811 <example> 19812 <file name="index.html"> 19813 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> 19814 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" /> 19815 </file> 19816 <file name="protractor.js" type="protractor"> 19817 it('should toggle readonly attr', function() { 19818 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 19819 element(by.model('checked')).click(); 19820 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 19821 }); 19822 </file> 19823 </example> 19824 * 19825 * @element INPUT 19826 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 19827 * then special attribute "readonly" will be set on the element 19828 */ 19829 19830 19831/** 19832 * @ngdoc directive 19833 * @name ngSelected 19834 * @restrict A 19835 * @priority 100 19836 * 19837 * @description 19838 * The HTML specification does not require browsers to preserve the values of boolean attributes 19839 * such as selected. (Their presence means true and their absence means false.) 19840 * If we put an Angular interpolation expression into such an attribute then the 19841 * binding information would be lost when the browser removes the attribute. 19842 * The `ngSelected` directive solves this problem for the `selected` attribute. 19843 * This complementary directive is not removed by the browser and so provides 19844 * a permanent reliable place to store the binding information. 19845 * 19846 * @example 19847 <example> 19848 <file name="index.html"> 19849 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> 19850 <select aria-label="ngSelected demo"> 19851 <option>Hello!</option> 19852 <option id="greet" ng-selected="selected">Greetings!</option> 19853 </select> 19854 </file> 19855 <file name="protractor.js" type="protractor"> 19856 it('should select Greetings!', function() { 19857 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); 19858 element(by.model('selected')).click(); 19859 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 19860 }); 19861 </file> 19862 </example> 19863 * 19864 * @element OPTION 19865 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 19866 * then special attribute "selected" will be set on the element 19867 */ 19868 19869/** 19870 * @ngdoc directive 19871 * @name ngOpen 19872 * @restrict A 19873 * @priority 100 19874 * 19875 * @description 19876 * The HTML specification does not require browsers to preserve the values of boolean attributes 19877 * such as open. (Their presence means true and their absence means false.) 19878 * If we put an Angular interpolation expression into such an attribute then the 19879 * binding information would be lost when the browser removes the attribute. 19880 * The `ngOpen` directive solves this problem for the `open` attribute. 19881 * This complementary directive is not removed by the browser and so provides 19882 * a permanent reliable place to store the binding information. 19883 * @example 19884 <example> 19885 <file name="index.html"> 19886 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/> 19887 <details id="details" ng-open="open"> 19888 <summary>Show/Hide me</summary> 19889 </details> 19890 </file> 19891 <file name="protractor.js" type="protractor"> 19892 it('should toggle open', function() { 19893 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
19894 element(by.model('open')).click(); 19895 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 19896 }); 19897 </file> 19898 </example> 19899 * 19900 * @element DETAILS 19901 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 19902 * then special attribute "open" will be set on the element 19903 */ 19904 19905var ngAttributeAliasDirectives = {}; 19906 19907// boolean attrs are evaluated 19908forEach(BOOLEAN_ATTR, function(propName, attrName) { 19909 // binding to multiple is not supported 19910 if (propName == "multiple") return; 19911 19912 function defaultLinkFn(scope, element, attr) { 19913 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 19914 attr.$set(attrName, !!value); 19915 }); 19916 } 19917 19918 var normalized = directiveNormalize('ng-' + attrName); 19919 var linkFn = defaultLinkFn; 19920 19921 if (propName === 'checked') { 19922 linkFn = function(scope, element, attr) { 19923 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input 19924 if (attr.ngModel !== attr[normalized]) { 19925 defaultLinkFn(scope, element, attr); 19926 } 19927 }; 19928 } 19929 19930 ngAttributeAliasDirectives[normalized] = function() { 19931 return { 19932 restrict: 'A', 19933 priority: 100, 19934 link: linkFn 19935 }; 19936 }; 19937}); 19938 19939// aliased input attrs are evaluated 19940forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 19941 ngAttributeAliasDirectives[ngAttr] = function() { 19942 return { 19943 priority: 100, 19944 link: function(scope, element, attr) { 19945 //special case ngPattern when a literal regular expression value 19946 //is used as the expression (this way we don't have to watch anything). 19947 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { 19948 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 19949 if (match) { 19950 attr.$set("ngPattern", new RegExp(match[1], match[2])); 19951 return; 19952 } 19953 } 19954 19955 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 19956 attr.$set(ngAttr, value); 19957 }); 19958 } 19959 }; 19960 }; 19961}); 19962 19963// ng-src, ng-srcset, ng-href are interpolated 19964forEach(['src', 'srcset', 'href'], function(attrName) { 19965 var normalized = directiveNormalize('ng-' + attrName); 19966 ngAttributeAliasDirectives[normalized] = function() { 19967 return { 19968 priority: 99, // it needs to run after the attributes are interpolated 19969 link: function(scope, element, attr) { 19970 var propName = attrName, 19971 name = attrName; 19972 19973 if (attrName === 'href' && 19974 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 19975 name = 'xlinkHref'; 19976 attr.$attr[name] = 'xlink:href'; 19977 propName = null; 19978 } 19979 19980 attr.$observe(normalized, function(value) { 19981 if (!value) { 19982 if (attrName === 'href') { 19983 attr.$set(name, null); 19984 } 19985 return; 19986 } 19987 19988 attr.$set(name, value); 19989 19990 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 19991 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 19992 // to set the property as well to achieve the desired effect. 19993 // we use attr[attrName] value since $set can sanitize the url. 19994 if (msie && propName) element.prop(propName, attr[name]); 19995 }); 19996 } 19997 }; 19998 }; 19999}); 20000 20001/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true 20002 */ 20003var nullFormCtrl = { 20004 $addControl: noop, 20005 $$renameControl: nullFormRenameControl, 20006 $removeControl: noop, 20007 $setValidity: noop, 20008 $setDirty: noop, 20009 $setPristine: noop, 20010 $setSubmitted: noop 20011}, 20012SUBMITTED_CLASS = 'ng-submitted'; 20013 20014function nullFormRenameControl(control, name) { 20015 control.$name = name; 20016} 20017 20018/** 20019 * @ngdoc type 20020 * @name form.FormController 20021 * 20022 * @property {boolean} $pristine True if user has not interacted with the form yet. 20023 * @property {boolean} $dirty True if user has already interacted with the form. 20024 * @property {boolean} $valid True if all of the containing forms and controls are valid. 20025 * @property {boolean}
20025 $invalid True if at least one containing control or form is invalid. 20026 * @property {boolean} $pending True if at least one containing control or form is pending. 20027 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 20028 * 20029 * @property {Object} $error Is an object hash, containing references to controls or 20030 * forms with failing validators, where: 20031 * 20032 * - keys are validation tokens (error names), 20033 * - values are arrays of controls or forms that have a failing validator for given error name. 20034 * 20035 * Built-in validation tokens: 20036 * 20037 * - `email` 20038 * - `max` 20039 * - `maxlength` 20040 * - `min` 20041 * - `minlength` 20042 * - `number` 20043 * - `pattern` 20044 * - `required` 20045 * - `url` 20046 * - `date` 20047 * - `datetimelocal` 20048 * - `time` 20049 * - `week` 20050 * - `month` 20051 * 20052 * @description 20053 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 20054 * such as being valid/invalid or dirty/pristine. 20055 * 20056 * Each {@link ng.directive:form form} directive creates an instance 20057 * of `FormController`. 20058 * 20059 */ 20060//asks for $scope to fool the BC controller module 20061FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 20062function FormController(element, attrs, $scope, $animate, $interpolate) { 20063 var form = this, 20064 controls = []; 20065 20066 // init state 20067 form.$error = {}; 20068 form.$$success = {}; 20069 form.$pending = undefined; 20070 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); 20071 form.$dirty = false; 20072 form.$pristine = true; 20073 form.$valid = true; 20074 form.$invalid = false; 20075 form.$submitted = false; 20076 form.$$parentForm = nullFormCtrl; 20077 20078 /** 20079 * @ngdoc method 20080 * @name form.FormController#$rollbackViewValue 20081 * 20082 * @description 20083 * Rollback all form controls pending updates to the `$modelValue`. 20084 * 20085 * Updates may be pending by a debounced event or because the input is waiting for a some future 20086 * event defined in `ng-model-options`. This method is typically needed by the reset button of 20087 * a form that uses `ng-model-options` to pend updates. 20088 */ 20089 form.$rollbackViewValue = function() { 20090 forEach(controls, function(control) { 20091 control.$rollbackViewValue(); 20092 }); 20093 }; 20094 20095 /** 20096 * @ngdoc method 20097 * @name form.FormController#$commitViewValue 20098 * 20099 * @description 20100 * Commit all form controls pending updates to the `$modelValue`. 20101 * 20102 * Updates may be pending by a debounced event or because the input is waiting for a some future 20103 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 20104 * usually handles calling this in response to input events. 20105 */ 20106 form.$commitViewValue = function() { 20107 forEach(controls, function(control) { 20108 control.$commitViewValue(); 20109 }); 20110 }; 20111 20112 /** 20113 * @ngdoc method 20114 * @name form.FormController#$addControl 20115 * @param {object} control control object, either a {@link form.FormController} or an 20116 * {@link ngModel.NgModelController} 20117 * 20118 * @description 20119 * Register a control with the form. Input elements using ngModelController do this automatically 20120 * when they are linked. 20121 * 20122 * Note that the current state of the control will not be reflected on the new parent form. This 20123 * is not an issue with normal use, as freshly compiled and linked controls are in a `$pristine` 20124 * state. 20125 * 20126 * However, if the method is used programmatically, for example by adding dynamically created controls, 20127 * or controls that have been previously removed without destroying their corresponding DOM element, 20128 * it's the developers responsiblity to make sure the current state propagates to the parent form. 20129 * 20130 * For example, if an input control is added that is already `$dirty` and has `$error` properties, 20131 * calling `$setDirty()` and `$validate()` afterwards will propagate the state to the parent form. 20132 */ 20133 form.$addControl = function(control) { 20134 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 20135 // and not added to the scope. Now we throw an error. 20136 assertNotHasOwnProperty(control.$name, 'input'); 20137 controls.push(control); 20138 20139 if (control.$name) { 20140 form[control.$name] = control; 20141 } 20142 20143 control.$$parentForm = form; 20144 }; 20145 20146 // Private API: rename a form control 20147 form.$$renameControl = function(control, newName) { 20148 var oldName = control.$name; 20149 20150 if (form[oldName] === control) { 20151 delete form[oldName]; 20152 } 20153 form[newName] = control; 20154 control.$name = newName; 20155 }; 20156 20157 /** 20158 * @ngdoc method
20159 * @name form.FormController#$removeControl 20160 * @param {object} control control object, either a {@link form.FormController} or an 20161 * {@link ngModel.NgModelController} 20162 * 20163 * @description 20164 * Deregister a control from the form. 20165 * 20166 * Input elements using ngModelController do this automatically when they are destroyed. 20167 * 20168 * Note that only the removed control's validation state (`$errors`etc.) will be removed from the 20169 * form. `$dirty`, `$submitted` states will not be changed, because the expected behavior can be 20170 * different from case to case. For example, removing the only `$dirty` control from a form may or 20171 * may not mean that the form is still `$dirty`. 20172 */ 20173 form.$removeControl = function(control) { 20174 if (control.$name && form[control.$name] === control) { 20175 delete form[control.$name]; 20176 } 20177 forEach(form.$pending, function(value, name) { 20178 form.$setValidity(name, null, control); 20179 }); 20180 forEach(form.$error, function(value, name) { 20181 form.$setValidity(name, null, control); 20182 }); 20183 forEach(form.$$success, function(value, name) { 20184 form.$setValidity(name, null, control); 20185 }); 20186 20187 arrayRemove(controls, control); 20188 control.$$parentForm = nullFormCtrl; 20189 }; 20190 20191 20192 /** 20193 * @ngdoc method 20194 * @name form.FormController#$setValidity 20195 * 20196 * @description 20197 * Sets the validity of a form control. 20198 * 20199 * This method will also propagate to parent forms. 20200 */ 20201 addSetValidityMethod({ 20202 ctrl: this, 20203 $element: element, 20204 set: function(object, property, controller) { 20205 var list = object[property]; 20206 if (!list) { 20207 object[property] = [controller]; 20208 } else { 20209 var index = list.indexOf(controller); 20210 if (index === -1) { 20211 list.push(controller); 20212 } 20213 } 20214 }, 20215 unset: function(object, property, controller) { 20216 var list = object[property]; 20217 if (!list) { 20218 return; 20219 } 20220 arrayRemove(list, controller); 20221 if (list.length === 0) { 20222 delete object[property]; 20223 } 20224 }, 20225 $animate: $animate 20226 }); 20227 20228 /** 20229 * @ngdoc method 20230 * @name form.FormController#$setDirty 20231 * 20232 * @description 20233 * Sets the form to a dirty state. 20234 * 20235 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 20236 * state (ng-dirty class). This method will also propagate to parent forms. 20237 */ 20238 form.$setDirty = function() { 20239 $animate.removeClass(element, PRISTINE_CLASS); 20240 $animate.addClass(element, DIRTY_CLASS); 20241 form.$dirty = true; 20242 form.$pristine = false; 20243 form.$$parentForm.$setDirty(); 20244 }; 20245 20246 /** 20247 * @ngdoc method 20248 * @name form.FormController#$setPristine 20249 * 20250 * @description 20251 * Sets the form to its pristine state. 20252 * 20253 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 20254 * state (ng-pristine class). This method will also propagate to all the controls contained 20255 * in this form. 20256 * 20257 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 20258 * saving or resetting it. 20259 */ 20260 form.$setPristine = function() { 20261 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 20262 form.$dirty = false; 20263 form.$pristine = true; 20264 form.$submitted = false; 20265 forEach(controls, function(control) { 20266 control.$setPristine(); 20267 }); 20268 }; 20269 20270 /** 20271 * @ngdoc method 20272 * @name form.FormController#$setUntouched 20273 * 20274 * @description 20275 * Sets the form to its untouched state. 20276 * 20277 * This method can be called to remove the 'ng-touched' class and set the form controls to their 20278 * untouched state (ng-untouched class). 20279 * 20280 * Setting a form controls back to their untouched state is often useful when setting the form 20281 * back to its pristine state. 20282 */ 20283 form.$setUntouched = function() { 20284 forEach(controls, function(control) { 20285 control.$setUntouched(); 20286 }); 20287 }; 20288 20289 /** 20290 * @ngdoc method 20291 * @name form.FormController#$setSubmitted 20292 * 20293 * @description 20294 * Sets the form to its submitted state. 20295 */ 20296 form.$setSubmitted = function() {
20297 $animate.addClass(element, SUBMITTED_CLASS); 20298 form.$submitted = true; 20299 form.$$parentForm.$setSubmitted(); 20300 }; 20301} 20302 20303/** 20304 * @ngdoc directive 20305 * @name ngForm 20306 * @restrict EAC 20307 * 20308 * @description 20309 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 20310 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 20311 * sub-group of controls needs to be determined. 20312 * 20313 * Note: the purpose of `ngForm` is to group controls, 20314 * but not to be a replacement for the `<form>` tag with all of its capabilities 20315 * (e.g. posting to the server, ...). 20316 * 20317 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 20318 * related scope, under this name. 20319 * 20320 */ 20321 20322 /** 20323 * @ngdoc directive 20324 * @name form 20325 * @restrict E 20326 * 20327 * @description 20328 * Directive that instantiates 20329 * {@link form.FormController FormController}. 20330 * 20331 * If the `name` attribute is specified, the form controller is published onto the current scope under 20332 * this name. 20333 * 20334 * # Alias: {@link ng.directive:ngForm `ngForm`} 20335 * 20336 * In Angular, forms can be nested. This means that the outer form is valid when all of the child 20337 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 20338 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to 20339 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when 20340 * using Angular validation directives in forms that are dynamically generated using the 20341 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` 20342 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an 20343 * `ngForm` directive and nest these in an outer `form` element. 20344 * 20345 * 20346 * # CSS classes 20347 * - `ng-valid` is set if the form is valid. 20348 * - `ng-invalid` is set if the form is invalid. 20349 * - `ng-pending` is set if the form is pending. 20350 * - `ng-pristine` is set if the form is pristine. 20351 * - `ng-dirty` is set if the form is dirty. 20352 * - `ng-submitted` is set if the form was submitted. 20353 * 20354 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 20355 * 20356 * 20357 * # Submitting a form and preventing the default action 20358 * 20359 * Since the role of forms in client-side Angular applications is different than in classical 20360 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 20361 * page reload that sends the data to the server. Instead some javascript logic should be triggered 20362 * to handle the form submission in an application-specific way. 20363 * 20364 * For this reason, Angular prevents the default action (form submission to the server) unless the 20365 * `<form>` element has an `action` attribute specified. 20366 * 20367 * You can use one of the following two ways to specify what javascript method should be called when 20368 * a form is submitted: 20369 * 20370 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 20371 * - {@link ng.directive:ngClick ngClick} directive on the first 20372 * button or input field of type submit (input[type=submit]) 20373 * 20374 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 20375 * or {@link ng.directive:ngClick ngClick} directives. 20376 * This is because of the following form submission rules in the HTML specification: 20377 * 20378 * - If a form has only one input field then hitting enter in this field triggers form submit 20379 * (`ngSubmit`) 20380 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 20381 * doesn't trigger submit 20382 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 20383 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 20384 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 20385 * 20386 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 20387 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 20388 * to have access to the updated model. 20389 * 20390 * ## Animation Hooks 20391 * 20392 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 20393 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 20394 * other validations that are performed within the form. Animations in ngForm are similar to how 20395 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 20396 * as JS animations. 20397 * 20398 * The following example shows a simple way to utilize CSS transitions to style a form element
20399 * that has been rendered as invalid after it has been validated: 20400 * 20401 * <pre> 20402 * //be sure to include ngAnimate as a module to hook into more 20403 * //advanced animations 20404 * .my-form { 20405 * transition:0.5s linear all; 20406 * background: white; 20407 * } 20408 * .my-form.ng-invalid { 20409 * background: red; 20410 * color:white; 20411 * } 20412 * </pre> 20413 * 20414 * @example 20415 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 20416 <file name="index.html"> 20417 <script> 20418 angular.module('formExample', []) 20419 .controller('FormController', ['$scope', function($scope) { 20420 $scope.userType = 'guest'; 20421 }]); 20422 </script> 20423 <style> 20424 .my-form { 20425 transition:all linear 0.5s; 20426 background: transparent; 20427 } 20428 .my-form.ng-invalid { 20429 background: red; 20430 } 20431 </style> 20432 <form name="myForm" ng-controller="FormController" class="my-form"> 20433 userType: <input name="input" ng-model="userType" required> 20434 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 20435 <code>userType = {{userType}}</code><br> 20436 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> 20437 <code>myForm.input.$error = {{myForm.input.$error}}</code><br> 20438 <code>myForm.$valid = {{myForm.$valid}}</code><br> 20439 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> 20440 </form> 20441 </file> 20442 <file name="protractor.js" type="protractor"> 20443 it('should initialize to model', function() { 20444 var userType = element(by.binding('userType')); 20445 var valid = element(by.binding('myForm.input.$valid')); 20446 20447 expect(userType.getText()).toContain('guest'); 20448 expect(valid.getText()).toContain('true'); 20449 }); 20450 20451 it('should be invalid if empty', function() { 20452 var userType = element(by.binding('userType')); 20453 var valid = element(by.binding('myForm.input.$valid')); 20454 var userInput = element(by.model('userType')); 20455 20456 userInput.clear(); 20457 userInput.sendKeys(''); 20458 20459 expect(userType.getText()).toEqual('userType ='); 20460 expect(valid.getText()).toContain('false'); 20461 }); 20462 </file> 20463 </example> 20464 * 20465 * @param {string=} name Name of the form. If specified, the form controller will be published into 20466 * related scope, under this name. 20467 */ 20468var formDirectiveFactory = function(isNgForm) { 20469 return ['$timeout', '$parse', function($timeout, $parse) { 20470 var formDirective = { 20471 name: 'form', 20472 restrict: isNgForm ? 'EAC' : 'E', 20473 require: ['form', '^^?form'], //first is the form's own ctrl, second is an optional parent form 20474 controller: FormController, 20475 compile: function ngFormCompile(formElement, attr) { 20476 // Setup initial state of the control 20477 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 20478 20479 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); 20480 20481 return { 20482 pre: function ngFormPreLink(scope, formElement, attr, ctrls) { 20483 var controller = ctrls[0]; 20484 20485 // if `action` attr is not present on the form, prevent the default action (submission) 20486 if (!('action' in attr)) { 20487 // we can't use jq events because if a form is destroyed during submission the default 20488 // action is not prevented. see #1238 20489 // 20490 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 20491 // page reload if the form was destroyed by submission of the form via a click handler 20492 // on a button in the form. Looks like an IE9 specific bug. 20493 var handleFormSubmission = function(event) { 20494 scope.$apply(function() { 20495 controller.$commitViewValue(); 20496 controller.$setSubmitted(); 20497 }); 20498 20499 event.preventDefault(); 20500 }; 20501 20502 addEventListenerFn(formElement[0], 'submit', handleFormSubmission); 20503 20504 // unregister the preventDefault listener so that we don't not leak memory but in a 20505 // way that will achieve the prevention of the default action. 20506 formElement.on('$destroy', function() { 20507 $timeout(function() { 20508 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); 20509 }, 0, false); 20510 }); 20511 } 20512 20513 var parentFormCtrl = ctrls[1] || controller.$$parentForm; 20514 parentFormCtrl.$addControl(controller); 20515 20516 var setter = nameAttr ? getSetter(controller.$name) : noop; 20517 20518 if (nameAttr) { 20519 setter(scope, controller); 20520 attr.$observe(nameAttr, function(newValue) { 20521 if (controller.$name === newValue) return; 20522 setter(scope, undefined); 20523 controller.$$parentForm.$$renameControl(controller, newValue); 20524 setter = getSetter(controller.$name); 20525 setter(scope, controller); 20526 }); 20527 } 20528 formElement.on('$destroy', function() { 20529 controller.$$parentForm.$removeControl(controller); 20530 setter(scope, undefined); 20531 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 20532 }); 20533 } 20534 }; 20535 } 20536 }; 20537 20538 return formDirective; 20539 20540 function getSetter(expression) { 20541 if (expression === '') { 20542 //create an assignable expression, so forms with an empty name can be renamed later 20543 return $parse('this[""]').assign; 20544 } 20545 return $parse(expression).assign || noop; 20546 } 20547 }]; 20548}; 20549 20550var formDirective = formDirectiveFactory(); 20551var ngFormDirective = formDirectiveFactory(true); 20552 20553/* global VALID_CLASS: false, 20554 INVALID_CLASS: false, 20555 PRISTINE_CLASS: false, 20556 DIRTY_CLASS: false, 20557 UNTOUCHED_CLASS: false, 20558 TOUCHED_CLASS: false, 20559 ngModelMinErr: false, 20560*/ 20561 20562// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 20563var 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)/; 20564var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 20565var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
20566var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; 20567var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; 20568var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 20569var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; 20570var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; 20571var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 20572 20573var inputType = { 20574 20575 /** 20576 * @ngdoc input 20577 * @name input[text] 20578 * 20579 * @description 20580 * Standard HTML text input with angular data binding, inherited by most of the `input` elements. 20581 * 20582 * 20583 * @param {string} ngModel Assignable angular expression to data-bind to. 20584 * @param {string=} name Property name of the form under which the control is published. 20585 * @param {string=} required Adds `required` validation error key if the value is not entered. 20586 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20587 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20588 * `required` when you want to data-bind to the `required` attribute. 20589 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 20590 * minlength. 20591 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 20592 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 20593 * any length. 20594 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 20595 * that contains the regular expression body that will be converted to a regular expression 20596 * as in the ngPattern directive. 20597 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 20598 * a RegExp found by evaluating the Angular expression given in the attribute value. 20599 * If the expression evaluates to a RegExp object, then this is used directly. 20600 * If the expression evaluates to a string, then it will be converted to a RegExp 20601 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 20602 * `new RegExp('^abc$')`.<br /> 20603 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 20604 * start at the index of the last search's match, thus not taking the whole input value into 20605 * account. 20606 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20607 * interaction with the input element. 20608 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 20609 * This parameter is ignored for input[type=password] controls, which will never trim the 20610 * input. 20611 * 20612 * @example 20613 <example name="text-input-directive" module="textInputExample"> 20614 <file name="index.html"> 20615 <script> 20616 angular.module('textInputExample', []) 20617 .controller('ExampleController', ['$scope', function($scope) { 20618 $scope.example = { 20619 text: 'guest', 20620 word: /^\s*\w*\s*$/ 20621 }; 20622 }]); 20623 </script>
20624 <form name="myForm" ng-controller="ExampleController"> 20625 <label>Single word: 20626 <input type="text" name="input" ng-model="example.text" 20627 ng-pattern="example.word" required ng-trim="false"> 20628 </label> 20629 <div role="alert"> 20630 <span class="error" ng-show="myForm.input.$error.required"> 20631 Required!</span> 20632 <span class="error" ng-show="myForm.input.$error.pattern"> 20633 Single word only!</span> 20634 </div> 20635 <tt>text = {{example.text}}</tt><br/> 20636 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20637 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20638 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20639 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20640 </form> 20641 </file> 20642 <file name="protractor.js" type="protractor"> 20643 var text = element(by.binding('example.text')); 20644 var valid = element(by.binding('myForm.input.$valid')); 20645 var input = element(by.model('example.text')); 20646 20647 it('should initialize to model', function() { 20648 expect(text.getText()).toContain('guest'); 20649 expect(valid.getText()).toContain('true'); 20650 }); 20651 20652 it('should be invalid if empty', function() { 20653 input.clear(); 20654 input.sendKeys(''); 20655 20656 expect(text.getText()).toEqual('text ='); 20657 expect(valid.getText()).toContain('false'); 20658 }); 20659 20660 it('should be invalid if multi word', function() { 20661 input.clear(); 20662 input.sendKeys('hello world'); 20663 20664 expect(valid.getText()).toContain('false'); 20665 }); 20666 </file> 20667 </example> 20668 */ 20669 'text': textInputType, 20670 20671 /** 20672 * @ngdoc input 20673 * @name input[date] 20674 * 20675 * @description 20676 * Input with date validation and transformation. In browsers that do not yet support 20677 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 20678 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 20679 * modern browsers do not yet support this input type, it is important to provide cues to users on the 20680 * expected input format via a placeholder or label. 20681 * 20682 * The model must always be a Date object, otherwise Angular will throw an error. 20683 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20684 * 20685 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20686 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20687 * 20688 * @param {string} ngModel Assignable angular expression to data-bind to. 20689 * @param {string=} name Property name of the form under which the control is published. 20690 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 20691 * valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 20692 * (e.g. `min="{{minDate | date:'yyyy-MM-dd'}}"`). Note that `min` will also add native HTML5 20693 * constraint validation. 20694 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 20695 * a valid ISO date string (yyyy-MM-dd). You can also use interpolation inside this attribute 20696 * (e.g. `max="{{maxDate | date:'yyyy-MM-dd'}}"`). Note that `max` will also add native HTML5 20697 * constraint validation. 20698 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO date string 20699 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 20700 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO date string 20701 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 20702 * @param {string=} required Sets `required` validation error key if the value is not entered. 20703 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20704 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20705 * `required` when you want to data-bind to the `required` attribute. 20706 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20707 * interaction with the input element. 20708 * 20709 * @example 20710 <example name="date-input-directive" module="dateInputExample"> 20711 <file name="index.html"> 20712 <script> 20713 angular.module('dateInputExample', []) 20714 .controller('DateController', ['$scope', function($scope) { 20715 $scope.example = { 20716 value: new Date(2013, 9, 22) 20717 }; 20718 }]); 20719 </script> 20720 <form name="myForm" ng-controller="DateController as dateCtrl"> 20721 <label for="exampleInput">Pick a date in 2013:</label> 20722 <input type="date" id="exampleInput" name="input" ng-model="example.value" 20723 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> 20724 <div role="alert">
20725 <span class="error" ng-show="myForm.input.$error.required"> 20726 Required!</span> 20727 <span class="error" ng-show="myForm.input.$error.date"> 20728 Not a valid date!</span> 20729 </div> 20730 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 20731 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20732 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20733 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20734 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20735 </form> 20736 </file> 20737 <file name="protractor.js" type="protractor"> 20738 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 20739 var valid = element(by.binding('myForm.input.$valid')); 20740 var input = element(by.model('example.value')); 20741 20742 // currently protractor/webdriver does not support 20743 // sending keys to all known HTML5 input controls 20744 // for various browsers (see https://github.com/angular/protractor/issues/562). 20745 function setInput(val) { 20746 // set the value of the element and force validation. 20747 var scr = "var ipt = document.getElementById('exampleInput'); " + 20748 "ipt.value = '" + val + "';" + 20749 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20750 browser.executeScript(scr); 20751 } 20752 20753 it('should initialize to model', function() { 20754 expect(value.getText()).toContain('2013-10-22'); 20755 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20756 }); 20757 20758 it('should be invalid if empty', function() { 20759 setInput(''); 20760 expect(value.getText()).toEqual('value ='); 20761 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20762 }); 20763 20764 it('should be invalid if over max', function() { 20765 setInput('2015-01-01'); 20766 expect(value.getText()).toContain(''); 20767 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20768 }); 20769 </file> 20770 </example> 20771 */ 20772 'date': createDateInputType('date', DATE_REGEXP, 20773 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 20774 'yyyy-MM-dd'), 20775 20776 /** 20777 * @ngdoc input 20778 * @name input[datetime-local] 20779 * 20780 * @description 20781 * Input with datetime validation and transformation. In browsers that do not yet support 20782 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 20783 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 20784 * 20785 * The model must always be a Date object, otherwise Angular will throw an error. 20786 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20787 * 20788 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20789 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20790 * 20791 * @param {string} ngModel Assignable angular expression to data-bind to. 20792 * @param {string=} name Property name of the form under which the control is published. 20793 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 20794 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 20795 * inside this attribute (e.g. `min="{{minDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 20796 * Note that `min` will also add native HTML5 constraint validation. 20797 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 20798 * This must be a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). You can also use interpolation 20799 * inside this attribute (e.g. `max="{{maxDatetimeLocal | date:'yyyy-MM-ddTHH:mm:ss'}}"`). 20800 * Note that `max` will also add native HTML5 constraint validation. 20801 * @param {(date|string)=} ngMin Sets the `min` validation error key to the Date / ISO datetime string 20802 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 20803 * @param {(date|string)=} ngMax Sets the `max` validation error key to the Date / ISO datetime string 20804 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 20805 * @param {string=} required Sets `required` validation error key if the value is not entered. 20806 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20807 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20808 * `required` when you want to data-bind to the `required` attribute. 20809 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20810 * interaction with the input element. 20811 * 20812 * @example 20813 <example name="datetimelocal-input-directive" module="dateExample"> 20814 <file name="index.html"> 20815 <script> 20816 angular.module('dateExample', []) 20817 .controller('DateController', ['$scope', function($scope) { 20818 $scope.example = { 20819 value: new Date(2010, 11, 28, 14, 57) 20820 }; 20821 }]); 20822 </script> 20823 <form name="myForm" ng-controller="DateController as dateCtrl"> 20824 <label for="exampleInput">Pick a date between in 2013:</label> 20825 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 20826 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> 20827 <div role="alert">
20828 <span class="error" ng-show="myForm.input.$error.required"> 20829 Required!</span> 20830 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 20831 Not a valid date!</span> 20832 </div> 20833 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 20834 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20835 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20836 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20837 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20838 </form> 20839 </file> 20840 <file name="protractor.js" type="protractor"> 20841 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 20842 var valid = element(by.binding('myForm.input.$valid')); 20843 var input = element(by.model('example.value')); 20844 20845 // currently protractor/webdriver does not support 20846 // sending keys to all known HTML5 input controls 20847 // for various browsers (https://github.com/angular/protractor/issues/562). 20848 function setInput(val) { 20849 // set the value of the element and force validation. 20850 var scr = "var ipt = document.getElementById('exampleInput'); " + 20851 "ipt.value = '" + val + "';" + 20852 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20853 browser.executeScript(scr); 20854 } 20855 20856 it('should initialize to model', function() { 20857 expect(value.getText()).toContain('2010-12-28T14:57:00'); 20858 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20859 }); 20860 20861 it('should be invalid if empty', function() { 20862 setInput(''); 20863 expect(value.getText()).toEqual('value ='); 20864 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20865 }); 20866 20867 it('should be invalid if over max', function() { 20868 setInput('2015-01-01T23:59:00'); 20869 expect(value.getText()).toContain(''); 20870 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20871 }); 20872 </file> 20873 </example> 20874 */ 20875 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 20876 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 20877 'yyyy-MM-ddTHH:mm:ss.sss'), 20878 20879 /** 20880 * @ngdoc input 20881 * @name input[time] 20882 * 20883 * @description 20884 * Input with time validation and transformation. In browsers that do not yet support 20885 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 20886 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 20887 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 20888 * 20889 * The model must always be a Date object, otherwise Angular will throw an error. 20890 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20891 * 20892 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20893 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20894 * 20895 * @param {string} ngModel Assignable angular expression to data-bind to. 20896 * @param {string=} name Property name of the form under which the control is published. 20897 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 20898 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 20899 * attribute (e.g. `min="{{minTime | date:'HH:mm:ss'}}"`). Note that `min` will also add 20900 * native HTML5 constraint validation. 20901 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 20902 * This must be a valid ISO time format (HH:mm:ss). You can also use interpolation inside this 20903 * attribute (e.g. `max="{{maxTime | date:'HH:mm:ss'}}"`). Note that `max` will also add 20904 * native HTML5 constraint validation. 20905 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO time string the 20906 * `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 20907 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO time string the 20908 * `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 20909 * @param {string=} required Sets `required` validation error key if the value is not entered. 20910 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20911 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20912 * `required` when you want to data-bind to the `required` attribute. 20913 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20914 * interaction with the input element. 20915 * 20916 * @example 20917 <example name="time-input-directive" module="timeExample"> 20918 <file name="index.html"> 20919 <script> 20920 angular.module('timeExample', []) 20921 .controller('DateController', ['$scope', function($scope) { 20922 $scope.example = { 20923 value: new Date(1970, 0, 1, 14, 57, 0) 20924 }; 20925 }]); 20926 </script> 20927 <form name="myForm" ng-controller="DateController as dateCtrl"> 20928 <label for="exampleInput">Pick a between 8am and 5pm:</label> 20929 <input type="time" id="exampleInput" name="input" ng-model="example.value" 20930 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> 20931 <div role="alert">
20932 <span class="error" ng-show="myForm.input.$error.required"> 20933 Required!</span> 20934 <span class="error" ng-show="myForm.input.$error.time"> 20935 Not a valid date!</span> 20936 </div> 20937 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 20938 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20939 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20940 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20941 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20942 </form> 20943 </file> 20944 <file name="protractor.js" type="protractor"> 20945 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 20946 var valid = element(by.binding('myForm.input.$valid')); 20947 var input = element(by.model('example.value')); 20948 20949 // currently protractor/webdriver does not support 20950 // sending keys to all known HTML5 input controls 20951 // for various browsers (https://github.com/angular/protractor/issues/562). 20952 function setInput(val) { 20953 // set the value of the element and force validation. 20954 var scr = "var ipt = document.getElementById('exampleInput'); " + 20955 "ipt.value = '" + val + "';" + 20956 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20957 browser.executeScript(scr); 20958 } 20959 20960 it('should initialize to model', function() { 20961 expect(value.getText()).toContain('14:57:00'); 20962 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20963 }); 20964 20965 it('should be invalid if empty', function() { 20966 setInput(''); 20967 expect(value.getText()).toEqual('value ='); 20968 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20969 }); 20970 20971 it('should be invalid if over max', function() { 20972 setInput('23:59:00'); 20973 expect(value.getText()).toContain(''); 20974 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20975 }); 20976 </file> 20977 </example> 20978 */ 20979 'time': createDateInputType('time', TIME_REGEXP, 20980 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 20981 'HH:mm:ss.sss'), 20982 20983 /** 20984 * @ngdoc input 20985 * @name input[week] 20986 * 20987 * @description 20988 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 20989 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 20990 * week format (yyyy-W##), for example: `2013-W02`. 20991 * 20992 * The model must always be a Date object, otherwise Angular will throw an error. 20993 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20994 * 20995 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20996 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20997 * 20998 * @param {string} ngModel Assignable angular expression to data-bind to. 20999 * @param {string=} name Property name of the form under which the control is published. 21000 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21001 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 21002 * attribute (e.g. `min="{{minWeek | date:'yyyy-Www'}}"`). Note that `min` will also add 21003 * native HTML5 constraint validation. 21004 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21005 * This must be a valid ISO week format (yyyy-W##). You can also use interpolation inside this 21006 * attribute (e.g. `max="{{maxWeek | date:'yyyy-Www'}}"`). Note that `max` will also add 21007 * native HTML5 constraint validation. 21008 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 21009 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21010 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 21011 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21012 * @param {string=} required Sets `required` validation error key if the value is not entered. 21013 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21014 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21015 * `required` when you want to data-bind to the `required` attribute. 21016 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21017 * interaction with the input element. 21018 * 21019 * @example 21020 <example name="week-input-directive" module="weekExample"> 21021 <file name="index.html"> 21022 <script> 21023 angular.module('weekExample', []) 21024 .controller('DateController', ['$scope', function($scope) { 21025 $scope.example = { 21026 value: new Date(2013, 0, 3) 21027 }; 21028 }]); 21029 </script> 21030 <form name="myForm" ng-controller="DateController as dateCtrl"> 21031 <label>Pick a date between in 2013: 21032 <input id="exampleInput" type="week" name="input" ng-model="example.value" 21033 placeholder="YYYY-W##" min="2012-W32" 21034 max="2013-W52" required /> 21035 </label> 21036 <div role="alert">
21037 <span class="error" ng-show="myForm.input.$error.required"> 21038 Required!</span> 21039 <span class="error" ng-show="myForm.input.$error.week"> 21040 Not a valid date!</span> 21041 </div> 21042 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 21043 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21044 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21045 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21046 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21047 </form> 21048 </file> 21049 <file name="protractor.js" type="protractor"> 21050 var value = element(by.binding('example.value | date: "yyyy-Www"')); 21051 var valid = element(by.binding('myForm.input.$valid')); 21052 var input = element(by.model('example.value')); 21053 21054 // currently protractor/webdriver does not support 21055 // sending keys to all known HTML5 input controls 21056 // for various browsers (https://github.com/angular/protractor/issues/562). 21057 function setInput(val) { 21058 // set the value of the element and force validation. 21059 var scr = "var ipt = document.getElementById('exampleInput'); " + 21060 "ipt.value = '" + val + "';" + 21061 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21062 browser.executeScript(scr); 21063 } 21064 21065 it('should initialize to model', function() { 21066 expect(value.getText()).toContain('2013-W01'); 21067 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21068 }); 21069 21070 it('should be invalid if empty', function() { 21071 setInput(''); 21072 expect(value.getText()).toEqual('value ='); 21073 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21074 }); 21075 21076 it('should be invalid if over max', function() { 21077 setInput('2015-W01'); 21078 expect(value.getText()).toContain(''); 21079 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21080 }); 21081 </file> 21082 </example> 21083 */ 21084 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 21085 21086 /** 21087 * @ngdoc input 21088 * @name input[month] 21089 * 21090 * @description 21091 * Input with month validation and transformation. In browsers that do not yet support 21092 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 21093 * month format (yyyy-MM), for example: `2009-01`. 21094 * 21095 * The model must always be a Date object, otherwise Angular will throw an error. 21096 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 21097 * If the model is not set to the first of the month, the next view to model update will set it 21098 * to the first of the month. 21099 * 21100 * The timezone to be used to read/write the `Date` instance in the model can be defined using 21101 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 21102 * 21103 * @param {string} ngModel Assignable angular expression to data-bind to. 21104 * @param {string=} name Property name of the form under which the control is published. 21105 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21106 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 21107 * attribute (e.g. `min="{{minMonth | date:'yyyy-MM'}}"`). Note that `min` will also add 21108 * native HTML5 constraint validation. 21109 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21110 * This must be a valid ISO month format (yyyy-MM). You can also use interpolation inside this 21111 * attribute (e.g. `max="{{maxMonth | date:'yyyy-MM'}}"`). Note that `max` will also add 21112 * native HTML5 constraint validation. 21113 * @param {(date|string)=} ngMin Sets the `min` validation constraint to the Date / ISO week string 21114 * the `ngMin` expression evaluates to. Note that it does not set the `min` attribute. 21115 * @param {(date|string)=} ngMax Sets the `max` validation constraint to the Date / ISO week string 21116 * the `ngMax` expression evaluates to. Note that it does not set the `max` attribute. 21117 21118 * @param {string=} required Sets `required` validation error key if the value is not entered. 21119 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21120 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21121 * `required` when you want to data-bind to the `required` attribute. 21122 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21123 * interaction with the input element. 21124 * 21125 * @example 21126 <example name="month-input-directive" module="monthExample"> 21127 <file name="index.html"> 21128 <script> 21129 angular.module('monthExample', []) 21130 .controller('DateController', ['$scope', function($scope) { 21131 $scope.example = { 21132 value: new Date(2013, 9, 1) 21133 }; 21134 }]); 21135 </script> 21136 <form name="myForm" ng-controller="DateController as dateCtrl"> 21137 <label for="exampleInput">Pick a month in 2013:</label> 21138 <input id="exampleInput" type="month" name="input" ng-model="example.value" 21139 placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> 21140 <div role="alert">
21141 <span class="error" ng-show="myForm.input.$error.required"> 21142 Required!</span> 21143 <span class="error" ng-show="myForm.input.$error.month"> 21144 Not a valid month!</span> 21145 </div> 21146 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 21147 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21148 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21149 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21150 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21151 </form> 21152 </file> 21153 <file name="protractor.js" type="protractor"> 21154 var value = element(by.binding('example.value | date: "yyyy-MM"')); 21155 var valid = element(by.binding('myForm.input.$valid')); 21156 var input = element(by.model('example.value')); 21157 21158 // currently protractor/webdriver does not support 21159 // sending keys to all known HTML5 input controls 21160 // for various browsers (https://github.com/angular/protractor/issues/562). 21161 function setInput(val) { 21162 // set the value of the element and force validation. 21163 var scr = "var ipt = document.getElementById('exampleInput'); " + 21164 "ipt.value = '" + val + "';" + 21165 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 21166 browser.executeScript(scr); 21167 } 21168 21169 it('should initialize to model', function() { 21170 expect(value.getText()).toContain('2013-10'); 21171 expect(valid.getText()).toContain('myForm.input.$valid = true'); 21172 }); 21173 21174 it('should be invalid if empty', function() { 21175 setInput(''); 21176 expect(value.getText()).toEqual('value ='); 21177 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21178 }); 21179 21180 it('should be invalid if over max', function() { 21181 setInput('2015-01'); 21182 expect(value.getText()).toContain(''); 21183 expect(valid.getText()).toContain('myForm.input.$valid = false'); 21184 }); 21185 </file> 21186 </example> 21187 */ 21188 'month': createDateInputType('month', MONTH_REGEXP, 21189 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 21190 'yyyy-MM'), 21191 21192 /** 21193 * @ngdoc input 21194 * @name input[number] 21195 * 21196 * @description 21197 * Text input with number validation and transformation. Sets the `number` validation 21198 * error if not a valid number. 21199 * 21200 * <div class="alert alert-warning"> 21201 * The model must always be of type `number` otherwise Angular will throw an error. 21202 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} 21203 * error docs for more information and an example of how to convert your model if necessary. 21204 * </div> 21205 * 21206 * ## Issues with HTML5 constraint validation 21207 * 21208 * In browsers that follow the 21209 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), 21210 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. 21211 * If a non-number is entered in the input, the browser will report the value as an empty string, 21212 * which means the view / model values in `ngModel` and subsequently the scope value 21213 * will also be an empty string. 21214 * 21215 * 21216 * @param {string} ngModel Assignable angular expression to data-bind to. 21217 * @param {string=} name Property name of the form under which the control is published. 21218 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 21219 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 21220 * @param {string=} required Sets `required` validation error key if the value is not entered. 21221 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21222 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21223 * `required` when you want to data-bind to the `required` attribute. 21224 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21225 * minlength. 21226 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21227 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21228 * any length. 21229 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21230 * that contains the regular expression body that will be converted to a regular expression 21231 * as in the ngPattern directive. 21232 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21233 * a RegExp found by evaluating the Angular expression given in the attribute value. 21234 * If the expression evaluates to a RegExp object, then this is used directly. 21235 * If the expression evaluates to a string, then it will be converted to a RegExp 21236 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21237 * `new RegExp('^abc$')`.<br /> 21238 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21239 * start at the index of the last search's match, thus not taking the whole input value into 21240 * account. 21241 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21242 * interaction with the input element. 21243 * 21244 * @example 21245 <example name="number-input-directive" module="numberExample"> 21246 <file name="index.html"> 21247 <script> 21248 angular.module('numberExample', [])
21249 .controller('ExampleController', ['$scope', function($scope) { 21250 $scope.example = { 21251 value: 12 21252 }; 21253 }]); 21254 </script> 21255 <form name="myForm" ng-controller="ExampleController"> 21256 <label>Number: 21257 <input type="number" name="input" ng-model="example.value" 21258 min="0" max="99" required> 21259 </label> 21260 <div role="alert"> 21261 <span class="error" ng-show="myForm.input.$error.required"> 21262 Required!</span> 21263 <span class="error" ng-show="myForm.input.$error.number"> 21264 Not valid number!</span> 21265 </div> 21266 <tt>value = {{example.value}}</tt><br/> 21267 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21268 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21269 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21270 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21271 </form> 21272 </file> 21273 <file name="protractor.js" type="protractor"> 21274 var value = element(by.binding('example.value')); 21275 var valid = element(by.binding('myForm.input.$valid')); 21276 var input = element(by.model('example.value')); 21277 21278 it('should initialize to model', function() { 21279 expect(value.getText()).toContain('12'); 21280 expect(valid.getText()).toContain('true'); 21281 }); 21282 21283 it('should be invalid if empty', function() { 21284 input.clear(); 21285 input.sendKeys(''); 21286 expect(value.getText()).toEqual('value ='); 21287 expect(valid.getText()).toContain('false'); 21288 }); 21289 21290 it('should be invalid if over max', function() { 21291 input.clear(); 21292 input.sendKeys('123'); 21293 expect(value.getText()).toEqual('value ='); 21294 expect(valid.getText()).toContain('false'); 21295 }); 21296 </file> 21297 </example> 21298 */ 21299 'number': numberInputType, 21300 21301 21302 /** 21303 * @ngdoc input 21304 * @name input[url] 21305 * 21306 * @description 21307 * Text input with URL validation. Sets the `url` validation error key if the content is not a 21308 * valid URL. 21309 * 21310 * <div class="alert alert-warning"> 21311 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 21312 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 21313 * the built-in validators (see the {@link guide/forms Forms guide}) 21314 * </div> 21315 * 21316 * @param {string} ngModel Assignable angular expression to data-bind to. 21317 * @param {string=} name Property name of the form under which the control is published. 21318 * @param {string=} required Sets `required` validation error key if the value is not entered. 21319 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21320 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21321 * `required` when you want to data-bind to the `required` attribute. 21322 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21323 * minlength. 21324 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21325 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21326 * any length. 21327 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21328 * that contains the regular expression body that will be converted to a regular expression 21329 * as in the ngPattern directive. 21330 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21331 * a RegExp found by evaluating the Angular expression given in the attribute value. 21332 * If the expression evaluates to a RegExp object, then this is used directly. 21333 * If the expression evaluates to a string, then it will be converted to a RegExp 21334 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21335 * `new RegExp('^abc$')`.<br /> 21336 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21337 * start at the index of the last search's match, thus not taking the whole input value into 21338 * account. 21339 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21340 * interaction with the input element. 21341 * 21342 * @example 21343 <example name="url-input-directive" module="urlExample"> 21344 <file name="index.html"> 21345 <script> 21346 angular.module('urlExample', [])
21347 .controller('ExampleController', ['$scope', function($scope) { 21348 $scope.url = { 21349 text: 'http://google.com' 21350 }; 21351 }]); 21352 </script> 21353 <form name="myForm" ng-controller="ExampleController"> 21354 <label>URL: 21355 <input type="url" name="input" ng-model="url.text" required> 21356 <label> 21357 <div role="alert"> 21358 <span class="error" ng-show="myForm.input.$error.required"> 21359 Required!</span> 21360 <span class="error" ng-show="myForm.input.$error.url"> 21361 Not valid url!</span> 21362 </div> 21363 <tt>text = {{url.text}}</tt><br/> 21364 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21365 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21366 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21367 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21368 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 21369 </form> 21370 </file> 21371 <file name="protractor.js" type="protractor"> 21372 var text = element(by.binding('url.text')); 21373 var valid = element(by.binding('myForm.input.$valid')); 21374 var input = element(by.model('url.text')); 21375 21376 it('should initialize to model', function() { 21377 expect(text.getText()).toContain('http://google.com'); 21378 expect(valid.getText()).toContain('true'); 21379 }); 21380 21381 it('should be invalid if empty', function() { 21382 input.clear(); 21383 input.sendKeys(''); 21384 21385 expect(text.getText()).toEqual('text ='); 21386 expect(valid.getText()).toContain('false'); 21387 }); 21388 21389 it('should be invalid if not url', function() { 21390 input.clear(); 21391 input.sendKeys('box'); 21392 21393 expect(valid.getText()).toContain('false'); 21394 }); 21395 </file> 21396 </example> 21397 */ 21398 'url': urlInputType, 21399 21400 21401 /** 21402 * @ngdoc input 21403 * @name input[email] 21404 * 21405 * @description 21406 * Text input with email validation. Sets the `email` validation error key if not a valid email 21407 * address. 21408 * 21409 * <div class="alert alert-warning"> 21410 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 21411 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can 21412 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) 21413 * </div> 21414 * 21415 * @param {string} ngModel Assignable angular expression to data-bind to. 21416 * @param {string=} name Property name of the form under which the control is published. 21417 * @param {string=} required Sets `required` validation error key if the value is not entered. 21418 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21419 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21420 * `required` when you want to data-bind to the `required` attribute. 21421 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21422 * minlength. 21423 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21424 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21425 * any length. 21426 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21427 * that contains the regular expression body that will be converted to a regular expression 21428 * as in the ngPattern directive. 21429 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21430 * a RegExp found by evaluating the Angular expression given in the attribute value. 21431 * If the expression evaluates to a RegExp object, then this is used directly. 21432 * If the expression evaluates to a string, then it will be converted to a RegExp 21433 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21434 * `new RegExp('^abc$')`.<br /> 21435 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21436 * start at the index of the last search's match, thus not taking the whole input value into 21437 * account. 21438 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21439 * interaction with the input element. 21440 * 21441 * @example 21442 <example name="email-input-directive" module="emailExample"> 21443 <file name="index.html"> 21444 <script> 21445 angular.module('emailExample', [])
21446 .controller('ExampleController', ['$scope', function($scope) { 21447 $scope.email = { 21448 text: '[email protected]' 21449 }; 21450 }]); 21451 </script> 21452 <form name="myForm" ng-controller="ExampleController"> 21453 <label>Email: 21454 <input type="email" name="input" ng-model="email.text" required> 21455 </label> 21456 <div role="alert"> 21457 <span class="error" ng-show="myForm.input.$error.required"> 21458 Required!</span> 21459 <span class="error" ng-show="myForm.input.$error.email"> 21460 Not valid email!</span> 21461 </div> 21462 <tt>text = {{email.text}}</tt><br/> 21463 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21464 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21465 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21466 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21467 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 21468 </form> 21469 </file> 21470 <file name="protractor.js" type="protractor"> 21471 var text = element(by.binding('email.text')); 21472 var valid = element(by.binding('myForm.input.$valid')); 21473 var input = element(by.model('email.text')); 21474 21475 it('should initialize to model', function() { 21476 expect(text.getText()).toContain('[email protected]'); 21477 expect(valid.getText()).toContain('true'); 21478 }); 21479 21480 it('should be invalid if empty', function() { 21481 input.clear(); 21482 input.sendKeys(''); 21483 expect(text.getText()).toEqual('text ='); 21484 expect(valid.getText()).toContain('false'); 21485 }); 21486 21487 it('should be invalid if not email', function() { 21488 input.clear(); 21489 input.sendKeys('xxx'); 21490 21491 expect(valid.getText()).toContain('false'); 21492 }); 21493 </file> 21494 </example> 21495 */ 21496 'email': emailInputType, 21497 21498 21499 /** 21500 * @ngdoc input 21501 * @name input[radio] 21502 * 21503 * @description 21504 * HTML radio button. 21505 * 21506 * @param {string} ngModel Assignable angular expression to data-bind to. 21507 * @param {string} value The value to which the `ngModel` expression should be set when selected. 21508 * Note that `value` only supports `string` values, i.e. the scope model needs to be a string, 21509 * too. Use `ngValue` if you need complex models (`number`, `object`, ...). 21510 * @param {string=} name Property name of the form under which the control is published. 21511 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21512 * interaction with the input element. 21513 * @param {string} ngValue Angular expression to which `ngModel` will be be set when the radio 21514 * is selected. Should be used instead of the `value` attribute if you need 21515 * a non-string `ngModel` (`boolean`, `array`, ...). 21516 * 21517 * @example 21518 <example name="radio-input-directive" module="radioExample"> 21519 <file name="index.html"> 21520 <script> 21521 angular.module('radioExample', []) 21522 .controller('ExampleController', ['$scope', function($scope) { 21523 $scope.color = { 21524 name: 'blue' 21525 }; 21526 $scope.specialValue = { 21527 "id": "12345", 21528 "value": "green" 21529 }; 21530 }]); 21531 </script>
21532 <form name="myForm" ng-controller="ExampleController"> 21533 <label> 21534 <input type="radio" ng-model="color.name" value="red"> 21535 Red 21536 </label><br/> 21537 <label> 21538 <input type="radio" ng-model="color.name" ng-value="specialValue"> 21539 Green 21540 </label><br/> 21541 <label> 21542 <input type="radio" ng-model="color.name" value="blue"> 21543 Blue 21544 </label><br/> 21545 <tt>color = {{color.name | json}}</tt><br/> 21546 </form> 21547 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 21548 </file> 21549 <file name="protractor.js" type="protractor"> 21550 it('should change state', function() { 21551 var color = element(by.binding('color.name')); 21552 21553 expect(color.getText()).toContain('blue'); 21554 21555 element.all(by.model('color.name')).get(0).click(); 21556 21557 expect(color.getText()).toContain('red'); 21558 }); 21559 </file> 21560 </example> 21561 */ 21562 'radio': radioInputType, 21563 21564 21565 /** 21566 * @ngdoc input 21567 * @name input[checkbox] 21568 * 21569 * @description 21570 * HTML checkbox. 21571 * 21572 * @param {string} ngModel Assignable angular expression to data-bind to. 21573 * @param {string=} name Property name of the form under which the control is published. 21574 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 21575 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 21576 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21577 * interaction with the input element. 21578 * 21579 * @example 21580 <example name="checkbox-input-directive" module="checkboxExample"> 21581 <file name="index.html"> 21582 <script> 21583 angular.module('checkboxExample', []) 21584 .controller('ExampleController', ['$scope', function($scope) {
21585 $scope.checkboxModel = { 21586 value1 : true, 21587 value2 : 'YES' 21588 }; 21589 }]); 21590 </script> 21591 <form name="myForm" ng-controller="ExampleController"> 21592 <label>Value1: 21593 <input type="checkbox" ng-model="checkboxModel.value1"> 21594 </label><br/> 21595 <label>Value2: 21596 <input type="checkbox" ng-model="checkboxModel.value2" 21597 ng-true-value="'YES'" ng-false-value="'NO'"> 21598 </label><br/> 21599 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 21600 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 21601 </form> 21602 </file> 21603 <file name="protractor.js" type="protractor"> 21604 it('should change state', function() { 21605 var value1 = element(by.binding('checkboxModel.value1')); 21606 var value2 = element(by.binding('checkboxModel.value2')); 21607 21608 expect(value1.getText()).toContain('true'); 21609 expect(value2.getText()).toContain('YES'); 21610 21611 element(by.model('checkboxModel.value1')).click(); 21612 element(by.model('checkboxModel.value2')).click(); 21613 21614 expect(value1.getText()).toContain('false'); 21615 expect(value2.getText()).toContain('NO'); 21616 }); 21617 </file> 21618 </example> 21619 */ 21620 'checkbox': checkboxInputType, 21621 21622 'hidden': noop, 21623 'button': noop, 21624 'submit': noop, 21625 'reset': noop, 21626 'file': noop 21627}; 21628 21629function stringBasedInputType(ctrl) { 21630 ctrl.$formatters.push(function(value) { 21631 return ctrl.$isEmpty(value) ? value : value.toString(); 21632 }); 21633} 21634 21635function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21636 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21637 stringBasedInputType(ctrl); 21638} 21639 21640function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21641 var type = lowercase(element[0].type); 21642 21643 // In composition mode, users are still inputing intermediate text buffer, 21644 // hold the listener until composition is done. 21645 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 21646 if (!$sniffer.android) { 21647 var composing = false; 21648 21649 element.on('compositionstart', function(data) { 21650 composing = true; 21651 }); 21652 21653 element.on('compositionend', function() { 21654 composing = false; 21655 listener(); 21656 }); 21657 } 21658 21659 var listener = function(ev) { 21660 if (timeout) { 21661 $browser.defer.cancel(timeout); 21662 timeout = null; 21663 } 21664 if (composing) return; 21665 var value = element.val(), 21666 event = ev && ev.type; 21667 21668 // By default we will trim the value 21669 // If the attribute ng-trim exists we will avoid trimming 21670 // If input type is 'password', the value is never trimmed 21671 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 21672 value = trim(value); 21673 } 21674 21675 // If a control is suffering from bad input (due to native validators), browsers discard its 21676 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 21677 // control's value is the same empty value twice in a row. 21678 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 21679 ctrl.$setViewValue(value, event); 21680 } 21681 }; 21682 21683 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 21684 // input event on backspace, delete or cut 21685 if ($sniffer.hasEvent('input')) { 21686 element.on('input', listener); 21687 } else { 21688 var timeout; 21689 21690 var deferListener = function(ev, input, origValue) { 21691 if (!timeout) { 21692 timeout = $browser.defer(function() { 21693 timeout = null; 21694 if (!input || input.value !== origValue) { 21695 listener(ev); 21696 } 21697 }); 21698 } 21699 }; 21700 21701 element.on('keydown', function(event) { 21702 var key = event.keyCode; 21703 21704 // ignore 21705 // command modifiers arrows 21706 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 21707 21708 deferListener(event, this, this.value); 21709 });
21710 21711 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 21712 if ($sniffer.hasEvent('paste')) { 21713 element.on('paste cut', deferListener); 21714 } 21715 } 21716 21717 // if user paste into input using mouse on older browser 21718 // or form autocomplete on newer browser, we need "change" event to catch it 21719 element.on('change', listener); 21720 21721 ctrl.$render = function() { 21722 // Workaround for Firefox validation #12102. 21723 var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue; 21724 if (element.val() !== value) { 21725 element.val(value); 21726 } 21727 }; 21728} 21729 21730function weekParser(isoWeek, existingDate) { 21731 if (isDate(isoWeek)) { 21732 return isoWeek; 21733 } 21734 21735 if (isString(isoWeek)) { 21736 WEEK_REGEXP.lastIndex = 0; 21737 var parts = WEEK_REGEXP.exec(isoWeek); 21738 if (parts) { 21739 var year = +parts[1], 21740 week = +parts[2], 21741 hours = 0, 21742 minutes = 0, 21743 seconds = 0, 21744 milliseconds = 0, 21745 firstThurs = getFirstThursdayOfYear(year), 21746 addDays = (week - 1) * 7; 21747 21748 if (existingDate) { 21749 hours = existingDate.getHours(); 21750 minutes = existingDate.getMinutes(); 21751 seconds = existingDate.getSeconds(); 21752 milliseconds = existingDate.getMilliseconds(); 21753 } 21754 21755 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 21756 } 21757 } 21758 21759 return NaN; 21760} 21761 21762function createDateParser(regexp, mapping) { 21763 return function(iso, date) { 21764 var parts, map; 21765 21766 if (isDate(iso)) { 21767 return iso; 21768 } 21769 21770 if (isString(iso)) { 21771 // When a date is JSON'ified to wraps itself inside of an extra 21772 // set of double quotes. This makes the date parsing code unable 21773 // to match the date string and parse it as a date. 21774 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { 21775 iso = iso.substring(1, iso.length - 1); 21776 } 21777 if (ISO_DATE_REGEXP.test(iso)) { 21778 return new Date(iso); 21779 } 21780 regexp.lastIndex = 0; 21781 parts = regexp.exec(iso); 21782 21783 if (parts) { 21784 parts.shift(); 21785 if (date) { 21786 map = { 21787 yyyy: date.getFullYear(), 21788 MM: date.getMonth() + 1, 21789 dd: date.getDate(), 21790 HH: date.getHours(), 21791 mm: date.getMinutes(), 21792 ss: date.getSeconds(), 21793 sss: date.getMilliseconds() / 1000 21794 }; 21795 } else { 21796 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 21797 } 21798 21799 forEach(parts, function(part, index) { 21800 if (index < mapping.length) { 21801 map[mapping[index]] = +part; 21802 } 21803 }); 21804 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 21805 } 21806 } 21807 21808 return NaN; 21809 }; 21810} 21811 21812function createDateInputType(type, regexp, parseDate, format) { 21813 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { 21814 badInputChecker(scope, element, attr, ctrl); 21815 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21816 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; 21817 var previousDate; 21818 21819 ctrl.$$parserName = type; 21820 ctrl.$parsers.push(function(value) { 21821 if (ctrl.$isEmpty(value)) return null; 21822 if (regexp.test(value)) { 21823 // Note: We cannot read ctrl.$modelValue, as there might be a different 21824 // parser/formatter in the processing chain so that the model 21825 // contains some different data format! 21826 var parsedDate = parseDate(value, previousDate); 21827 if (timezone) { 21828 parsedDate = convertTimezoneToLocal(parsedDate, timezone); 21829 } 21830 return parsedDate; 21831 } 21832 return undefined; 21833 }); 21834 21835 ctrl.$formatters.push(function(value) { 21836 if (value && !isDate(value)) { 21837 throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 21838 } 21839 if (isValidDate(value)) { 21840 previousDate = value; 21841 if (previousDate && timezone) { 21842 previousDate = convertTimezoneToLocal(previousDate, timezone, true); 21843 } 21844 return $filter('date')(value, format, timezone); 21845 } else { 21846 previousDate = null; 21847 return ''; 21848 } 21849 });
21850 21851 if (isDefined(attr.min) || attr.ngMin) { 21852 var minVal; 21853 ctrl.$validators.min = function(value) { 21854 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; 21855 }; 21856 attr.$observe('min', function(val) { 21857 minVal = parseObservedDateValue(val); 21858 ctrl.$validate(); 21859 }); 21860 } 21861 21862 if (isDefined(attr.max) || attr.ngMax) { 21863 var maxVal; 21864 ctrl.$validators.max = function(value) { 21865 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; 21866 }; 21867 attr.$observe('max', function(val) { 21868 maxVal = parseObservedDateValue(val); 21869 ctrl.$validate(); 21870 }); 21871 } 21872 21873 function isValidDate(value) { 21874 // Invalid Date: getTime() returns NaN 21875 return value && !(value.getTime && value.getTime() !== value.getTime()); 21876 } 21877 21878 function parseObservedDateValue(val) { 21879 return isDefined(val) && !isDate(val) ? parseDate(val) || undefined : val; 21880 } 21881 }; 21882} 21883 21884function badInputChecker(scope, element, attr, ctrl) { 21885 var node = element[0]; 21886 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 21887 if (nativeValidation) { 21888 ctrl.$parsers.push(function(value) { 21889 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 21890 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430): 21891 // - also sets validity.badInput (should only be validity.typeMismatch). 21892 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email) 21893 // - can ignore this case as we can still read out the erroneous email... 21894 return validity.badInput && !validity.typeMismatch ? undefined : value; 21895 }); 21896 } 21897} 21898 21899function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21900 badInputChecker(scope, element, attr, ctrl); 21901 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21902 21903 ctrl.$$parserName = 'number'; 21904 ctrl.$parsers.push(function(value) { 21905 if (ctrl.$isEmpty(value)) return null; 21906 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 21907 return undefined; 21908 }); 21909 21910 ctrl.$formatters.push(function(value) { 21911 if (!ctrl.$isEmpty(value)) { 21912 if (!isNumber(value)) { 21913 throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 21914 } 21915 value = value.toString(); 21916 } 21917 return value; 21918 }); 21919 21920 if (isDefined(attr.min) || attr.ngMin) { 21921 var minVal; 21922 ctrl.$validators.min = function(value) { 21923 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; 21924 }; 21925 21926 attr.$observe('min', function(val) { 21927 if (isDefined(val) && !isNumber(val)) { 21928 val = parseFloat(val, 10); 21929 } 21930 minVal = isNumber(val) && !isNaN(val) ? val : undefined; 21931 // TODO(matsko): implement validateLater to reduce number of validations 21932 ctrl.$validate(); 21933 }); 21934 } 21935 21936 if (isDefined(attr.max) || attr.ngMax) { 21937 var maxVal; 21938 ctrl.$validators.max = function(value) { 21939 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; 21940 }; 21941 21942 attr.$observe('max', function(val) { 21943 if (isDefined(val) && !isNumber(val)) { 21944 val = parseFloat(val, 10); 21945 } 21946 maxVal = isNumber(val) && !isNaN(val) ? val : undefined; 21947 // TODO(matsko): implement validateLater to reduce number of validations 21948 ctrl.$validate(); 21949 }); 21950 } 21951} 21952 21953function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21954 // Note: no badInputChecker here by purpose as `url` is only a validation 21955 // in browsers, i.e. we can always read out input.value even if it is not valid! 21956 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21957 stringBasedInputType(ctrl); 21958 21959 ctrl.$$parserName = 'url'; 21960 ctrl.$validators.url = function(modelValue, viewValue) { 21961 var value = modelValue || viewValue; 21962 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 21963 }; 21964} 21965 21966function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21967 // Note: no badInputChecker here by purpose as `url` is only a validation 21968 // in browsers, i.e. we can always read out input.value even if it is not val
vendor: 6,806 bytes, lines 21968-22124
21968id! 21969 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21970 stringBasedInputType(ctrl); 21971 21972 ctrl.$$parserName = 'email'; 21973 ctrl.$validators.email = function(modelValue, viewValue) { 21974 var value = modelValue || viewValue; 21975 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 21976 }; 21977} 21978 21979function radioInputType(scope, element, attr, ctrl) { 21980 // make the name unique, if not defined 21981 if (isUndefined(attr.name)) { 21982 element.attr('name', nextUid()); 21983 } 21984 21985 var listener = function(ev) { 21986 if (element[0].checked) { 21987 ctrl.$setViewValue(attr.value, ev && ev.type); 21988 } 21989 }; 21990 21991 element.on('click', listener); 21992 21993 ctrl.$render = function() { 21994 var value = attr.value; 21995 element[0].checked = (value == ctrl.$viewValue); 21996 }; 21997 21998 attr.$observe('value', ctrl.$render); 21999} 22000 22001function parseConstantExpr($parse, context, name, expression, fallback) { 22002 var parseFn; 22003 if (isDefined(expression)) { 22004 parseFn = $parse(expression); 22005 if (!parseFn.constant) { 22006 throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' + 22007 '`{1}`.', name, expression); 22008 } 22009 return parseFn(context); 22010 } 22011 return fallback; 22012} 22013 22014function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 22015 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 22016 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 22017 22018 var listener = function(ev) { 22019 ctrl.$setViewValue(element[0].checked, ev && ev.type); 22020 }; 22021 22022 element.on('click', listener); 22023 22024 ctrl.$render = function() { 22025 element[0].checked = ctrl.$viewValue; 22026 }; 22027 22028 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 22029 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 22030 // it to a boolean. 22031 ctrl.$isEmpty = function(value) { 22032 return value === false; 22033 }; 22034 22035 ctrl.$formatters.push(function(value) { 22036 return equals(value, trueValue); 22037 }); 22038 22039 ctrl.$parsers.push(function(value) { 22040 return value ? trueValue : falseValue; 22041 }); 22042} 22043 22044 22045/** 22046 * @ngdoc directive 22047 * @name textarea 22048 * @restrict E 22049 * 22050 * @description 22051 * HTML textarea element control with angular data-binding. The data-binding and validation 22052 * properties of this element are exactly the same as those of the 22053 * {@link ng.directive:input input element}. 22054 * 22055 * @param {string} ngModel Assignable angular expression to data-bind to. 22056 * @param {string=} name Property name of the form under which the control is published. 22057 * @param {string=} required Sets `required` validation error key if the value is not entered. 22058 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 22059 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 22060 * `required` when you want to data-bind to the `required` attribute. 22061 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22062 * minlength. 22063 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22064 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 22065 * length. 22066 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 22067 * a RegExp found by evaluating the Angular expression given in the attribute value. 22068 * If the expression evaluates to a RegExp object, then this is used directly. 22069 * If the expression evaluates to a string, then it will be converted to a RegExp 22070 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22071 * `new RegExp('^abc$')`.<br /> 22072 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22073 * start at the index of the last search's match, thus not taking the whole input value into 22074 * account. 22075 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22076 * interaction with the input element. 22077 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 22078 */ 22079 22080 22081/** 22082 * @ngdoc directive 22083 * @name input 22084 * @restrict E 22085 * 22086 * @description 22087 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 22088 * input state control, and validation. 22089 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 22090 * 22091 * <div class="alert alert-warning"> 22092 * **Note:** Not every feature offered is available for all input types. 22093 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 22094 * </div> 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=} required Sets `required` validation error key if the value is not entered. 22099 * @param {boolean=} ngRequired Sets `required` attribute if set to true 22100 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 22101 * minlength. 22102 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 22103 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 22104 * length. 22105 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 22106 * a RegExp found by evaluating the Angular expression given in the attribute value. 22107 * If the expression evaluates to a RegExp object, then this is used directly. 22108 * If the expression evaluates to a string, then it will be converted to a RegExp 22109 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 22110 * `new RegExp('^abc$')`.<br /> 22111 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 22112 * start at the index of the last search's match, thus not taking the whole input value into 22113 * account. 22114 * @param {string=} ngChange Angular expression to be executed when input changes due to user 22115 * interaction with the input element. 22116 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 22117 * This parameter is ignored for input[type=password] controls, which will never trim the 22118 * input. 22119 * 22120 * @example 22121 <example name="input-directive" module="inputExample"> 22122 <file name="index.html"> 22123 <script> 22124 angular.module('inputExample', [])
22125 .controller('ExampleController', ['$scope', function($scope) { 22126 $scope.user = {name: 'guest', last: 'visitor'}; 22127 }]); 22128 </script> 22129 <div ng-controller="ExampleController"> 22130 <form name="myForm"> 22131 <label> 22132 User name: 22133 <input type="text" name="userName" ng-model="user.name" required> 22134 </label> 22135 <div role="alert"> 22136 <span class="error" ng-show="myForm.userName.$error.required"> 22137 Required!</span> 22138 </div> 22139 <label> 22140 Last name: 22141 <input type="text" name="lastName" ng-model="user.last" 22142 ng-minlength="3" ng-maxlength="10"> 22143 </label> 22144 <div role="alert"> 22145 <span class="error" ng-show="myForm.lastName.$error.minlength"> 22146 Too short!</span> 22147 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 22148 Too long!</span> 22149 </div> 22150 </form> 22151 <hr> 22152 <tt>user = {{user}}</tt><br/> 22153 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> 22154 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> 22155 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> 22156 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> 22157 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22158 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22159 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> 22160 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> 22161 </div> 22162 </file> 22163 <file name="protractor.js" type="protractor"> 22164 var user = element(by.exactBinding('user')); 22165 var userNameValid = element(by.binding('myForm.userName.$valid')); 22166 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 22167 var lastNameError = element(by.binding('myForm.lastName.$error')); 22168 var formValid = element(by.binding('myForm.$valid')); 22169 var userNameInput = element(by.model('user.name')); 22170 var userLastInput = element(by.model('user.last')); 22171 22172 it('should initialize to model', function() {
22173 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 22174 expect(userNameValid.getText()).toContain('true'); 22175 expect(formValid.getText()).toContain('true'); 22176 }); 22177 22178 it('should be invalid if empty when required', function() { 22179 userNameInput.clear(); 22180 userNameInput.sendKeys(''); 22181 22182 expect(user.getText()).toContain('{"last":"visitor"}'); 22183 expect(userNameValid.getText()).toContain('false'); 22184 expect(formValid.getText()).toContain('false'); 22185 }); 22186 22187 it('should be valid if empty when min length is set', function() { 22188 userLastInput.clear(); 22189 userLastInput.sendKeys(''); 22190 22191 expect(user.getText()).toContain('{"name":"guest","last":""}'); 22192 expect(lastNameValid.getText()).toContain('true'); 22193 expect(formValid.getText()).toContain('true'); 22194 }); 22195 22196 it('should be invalid if less than required min length', function() { 22197 userLastInput.clear(); 22198 userLastInput.sendKeys('xx'); 22199
22200 expect(user.getText()).toContain('{"name":"guest"}'); 22201 expect(lastNameValid.getText()).toContain('false'); 22202 expect(lastNameError.getText()).toContain('minlength'); 22203 expect(formValid.getText()).toContain('false'); 22204 }); 22205 22206 it('should be invalid if longer than max length', function() { 22207 userLastInput.clear(); 22208 userLastInput.sendKeys('some ridiculously long name'); 22209 22210 expect(user.getText()).toContain('{"name":"guest"}'); 22211 expect(lastNameValid.getText()).toContain('false'); 22212 expect(lastNameError.getText()).toContain('maxlength'); 22213 expect(formValid.getText()).toContain('false'); 22214 }); 22215 </file> 22216 </example> 22217 */ 22218var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 22219 function($browser, $sniffer, $filter, $parse) { 22220 return { 22221 restrict: 'E', 22222 require: ['?ngModel'], 22223 link: { 22224 pre: function(scope, element, attr, ctrls) { 22225 if (ctrls[0]) { 22226 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 22227 $browser, $filter, $parse); 22228 } 22229 } 22230 } 22231 }; 22232}]; 22233 22234 22235 22236var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 22237/** 22238 * @ngdoc directive 22239 * @name ngValue 22240 * 22241 * @description 22242 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, 22243 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to 22244 * the bound value. 22245 * 22246 * `ngValue` is useful when dynamically generating lists of radio buttons using 22247 * {@link ngRepeat `ngRepeat`}, as shown below. 22248 * 22249 * Likewise, `ngValue` can be used to generate `<option>` elements for 22250 * the {@link select `select`} element. In that case however, only strings are supported 22251 * for the `value `attribute, so the resulting `ngModel` will always be a string. 22252 * Support for `select` models with non-string values is available via `ngOptions`. 22253 * 22254 * @element input 22255 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 22256 * of the `input` element 22257 * 22258 * @example 22259 <example name="ngValue-directive" module="valueExample"> 22260 <file name="index.html"> 22261 <script> 22262 angular.module('valueExample', []) 22263 .controller('ExampleController', ['$scope', function($scope) { 22264 $scope.names = ['pizza', 'unicorns', 'robots']; 22265 $scope.my = { favorite: 'unicorns' }; 22266 }]); 22267 </script>
22268 <form ng-controller="ExampleController"> 22269 <h2>Which is your favorite?</h2> 22270 <label ng-repeat="name in names" for="{{name}}"> 22271 {{name}} 22272 <input type="radio" 22273 ng-model="my.favorite" 22274 ng-value="name" 22275 id="{{name}}" 22276 name="favorite"> 22277 </label> 22278 <div>You chose {{my.favorite}}</div> 22279 </form> 22280 </file> 22281 <file name="protractor.js" type="protractor"> 22282 var favorite = element(by.binding('my.favorite')); 22283 22284 it('should initialize to model', function() { 22285 expect(favorite.getText()).toContain('unicorns'); 22286 }); 22287 it('should bind the values to the inputs', function() { 22288 element.all(by.model('my.favorite')).get(0).click(); 22289 expect(favorite.getText()).toContain('pizza'); 22290 }); 22291 </file> 22292 </example> 22293 */ 22294var ngValueDirective = function() { 22295 return { 22296 restrict: 'A', 22297 priority: 100, 22298 compile: function(tpl, tplAttr) { 22299 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 22300 return function ngValueConstantLink(scope, elm, attr) { 22301 attr.$set('value', scope.$eval(attr.ngValue)); 22302 }; 22303 } else { 22304 return function ngValueLink(scope, elm, attr) { 22305 scope.$watch(attr.ngValue, function valueWatchAction(value) { 22306 attr.$set('value', value); 22307 }); 22308 }; 22309 } 22310 } 22311 }; 22312}; 22313 22314/** 22315 * @ngdoc directive 22316 * @name ngBind 22317 * @restrict AC 22318 * 22319 * @description 22320 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 22321 * with the value of a given expression, and to update the text content when the value of that 22322 * expression changes. 22323 * 22324 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 22325 * `{{ expression }}` which is similar but less verbose. 22326 * 22327 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 22328 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 22329 * element attribute, it makes the bindings invisible to the user while the page is loading. 22330 * 22331 * An alternative solution to this problem would be using the 22332 * {@link ng.directive:ngCloak ngCloak} directive. 22333 * 22334 * 22335 * @element ANY 22336 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 22337 * 22338 * @example 22339 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 22340 <example module="bindExample"> 22341 <file name="index.html"> 22342 <script> 22343 angular.module('bindExample', []) 22344 .controller('ExampleController', ['$scope', function($scope) { 22345 $scope.name = 'Whirled'; 22346 }]); 22347 </script>
22348 <div ng-controller="ExampleController"> 22349 <label>Enter name: <input type="text" ng-model="name"></label><br> 22350 Hello <span ng-bind="name"></span>! 22351 </div> 22352 </file> 22353 <file name="protractor.js" type="protractor"> 22354 it('should check ng-bind', function() { 22355 var nameInput = element(by.model('name')); 22356 22357 expect(element(by.binding('name')).getText()).toBe('Whirled'); 22358 nameInput.clear(); 22359 nameInput.sendKeys('world'); 22360 expect(element(by.binding('name')).getText()).toBe('world'); 22361 }); 22362 </file> 22363 </example> 22364 */ 22365var ngBindDirective = ['$compile', function($compile) { 22366 return { 22367 restrict: 'AC', 22368 compile: function ngBindCompile(templateElement) { 22369 $compile.$$addBindingClass(templateElement); 22370 return function ngBindLink(scope, element, attr) { 22371 $compile.$$addBindingInfo(element, attr.ngBind); 22372 element = element[0]; 22373 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 22374 element.textContent = isUndefined(value) ? '' : value; 22375 }); 22376 }; 22377 } 22378 }; 22379}]; 22380 22381 22382/** 22383 * @ngdoc directive 22384 * @name ngBindTemplate 22385 * 22386 * @description 22387 * The `ngBindTemplate` directive specifies that the element 22388 * text content should be replaced with the interpolation of the template 22389 * in the `ngBindTemplate` attribute. 22390 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 22391 * expressions. This directive is needed since some HTML elements 22392 * (such as TITLE and OPTION) cannot contain SPAN elements. 22393 * 22394 * @element ANY 22395 * @param {string} ngBindTemplate template of form 22396 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 22397 * 22398 * @example 22399 * Try it here: enter text in text box and watch the greeting change. 22400 <example module="bindExample"> 22401 <file name="index.html"> 22402 <script> 22403 angular.module('bindExample', []) 22404 .controller('ExampleController', ['$scope', function($scope) { 22405 $scope.salutation = 'Hello'; 22406 $scope.name = 'World'; 22407 }]); 22408 </script>
22409 <div ng-controller="ExampleController"> 22410 <label>Salutation: <input type="text" ng-model="salutation"></label><br> 22411 <label>Name: <input type="text" ng-model="name"></label><br> 22412 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 22413 </div> 22414 </file> 22415 <file name="protractor.js" type="protractor"> 22416 it('should check ng-bind', function() { 22417 var salutationElem = element(by.binding('salutation')); 22418 var salutationInput = element(by.model('salutation')); 22419 var nameInput = element(by.model('name')); 22420 22421 expect(salutationElem.getText()).toBe('Hello World!'); 22422 22423 salutationInput.clear(); 22424 salutationInput.sendKeys('Greetings'); 22425 nameInput.clear(); 22426 nameInput.sendKeys('user'); 22427 22428 expect(salutationElem.getText()).toBe('Greetings user!'); 22429 }); 22430 </file> 22431 </example> 22432 */ 22433var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 22434 return { 22435 compile: function ngBindTemplateCompile(templateElement) { 22436 $compile.$$addBindingClass(templateElement); 22437 return function ngBindTemplateLink(scope, element, attr) { 22438 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 22439 $compile.$$addBindingInfo(element, interpolateFn.expressions); 22440 element = element[0]; 22441 attr.$observe('ngBindTemplate', function(value) { 22442 element.textContent = isUndefined(value) ? '' : value; 22443 }); 22444 }; 22445 } 22446 }; 22447}]; 22448 22449 22450/** 22451 * @ngdoc directive 22452 * @name ngBindHtml 22453 * 22454 * @description 22455 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 22456 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 22457 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 22458 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} 22459 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 22460 * 22461 * You may also bypass sanitization for values you know are safe. To do so, bind to 22462 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 22463 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 22464 * 22465 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 22466 * will have an exception (instead of an exploit.) 22467 * 22468 * @element ANY 22469 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 22470 * 22471 * @example 22472 22473 <example module="bindHtmlExample" deps="angular-sanitize.js"> 22474 <file name="index.html">
22475 <div ng-controller="ExampleController"> 22476 <p ng-bind-html="myHTML"></p> 22477 </div> 22478 </file> 22479 22480 <file name="script.js"> 22481 angular.module('bindHtmlExample', ['ngSanitize']) 22482 .controller('ExampleController', ['$scope', function($scope) { 22483 $scope.myHTML = 22484 'I am an <code>HTML</code>string with ' + 22485 '<a href="#">links!</a> and other <em>stuff</em>'; 22486 }]); 22487 </file> 22488 22489 <file name="protractor.js" type="protractor"> 22490 it('should check ng-bind-html', function() { 22491 expect(element(by.binding('myHTML')).getText()).toBe( 22492 'I am an HTMLstring with links! and other stuff'); 22493 }); 22494 </file> 22495 </example> 22496 */ 22497var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 22498 return { 22499 restrict: 'A', 22500 compile: function ngBindHtmlCompile(tElement, tAttrs) { 22501 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 22502 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) { 22503 return (value || '').toString(); 22504 }); 22505 $compile.$$addBindingClass(tElement); 22506 22507 return function ngBindHtmlLink(scope, element, attr) { 22508 $compile.$$addBindingInfo(element, attr.ngBindHtml); 22509 22510 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 22511 // we re-evaluate the expr because we want a TrustedValueHolderType 22512 // for $sce, not a string 22513 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || ''); 22514 }); 22515 }; 22516 } 22517 }; 22518}]; 22519 22520/** 22521 * @ngdoc directive 22522 * @name ngChange 22523 * 22524 * @description 22525 * Evaluate the given expression when the user changes the input. 22526 * The expression is evaluated immediately, unlike the JavaScript onchange event 22527 * which only triggers at the end of a change (usually, when the user leaves the 22528 * form element or presses the return key). 22529 * 22530 * The `ngChange` expression is only evaluated when a change in the input value causes 22531 * a new value to be committed to the model. 22532 * 22533 * It will not be evaluated: 22534 * * if the value returned from the `$parsers` transformation pipeline has not changed 22535 * * if the input has continued to be invalid since the model will stay `null` 22536 * * if the model is changed programmatically and not by a change to the input value 22537 * 22538 * 22539 * Note, this directive requires `ngModel` to be present. 22540 * 22541 * @element input 22542 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 22543 * in input value. 22544 * 22545 * @example 22546 * <example name="ngChange-directive" module="changeExample"> 22547 * <file name="index.html"> 22548 * <script> 22549 * angular.module('changeExample', []) 22550 * .controller('ExampleController', ['$scope', function($scope) { 22551 * $scope.counter = 0; 22552 * $scope.change = function() { 22553 * $scope.counter++; 22554 * }; 22555 * }]); 22556 * </script>
22557 * <div ng-controller="ExampleController"> 22558 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 22559 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 22560 * <label for="ng-change-example2">Confirmed</label><br /> 22561 * <tt>debug = {{confirmed}}</tt><br/> 22562 * <tt>counter = {{counter}}</tt><br/> 22563 * </div> 22564 * </file> 22565 * <file name="protractor.js" type="protractor"> 22566 * var counter = element(by.binding('counter')); 22567 * var debug = element(by.binding('confirmed')); 22568 * 22569 * it('should evaluate the expression if changing from view', function() { 22570 * expect(counter.getText()).toContain('0'); 22571 * 22572 * element(by.id('ng-change-example1')).click(); 22573 * 22574 * expect(counter.getText()).toContain('1'); 22575 * expect(debug.getText()).toContain('true'); 22576 * }); 22577 * 22578 * it('should not evaluate the expression if changing from model', function() { 22579 * element(by.id('ng-change-example2')).click(); 22580 22581 * expect(counter.getText()).toContain('0'); 22582 * expect(debug.getText()).toContain('true'); 22583 * }); 22584 * </file> 22585 * </example> 22586 */ 22587var ngChangeDirective = valueFn({ 22588 restrict: 'A', 22589 require: 'ngModel', 22590 link: function(scope, element, attr, ctrl) { 22591 ctrl.$viewChangeListeners.push(function() { 22592 scope.$eval(attr.ngChange); 22593 }); 22594 } 22595}); 22596 22597function classDirective(name, selector) { 22598 name = 'ngClass' + name; 22599 return ['$animate', function($animate) { 22600 return { 22601 restrict: 'AC', 22602 link: function(scope, element, attr) { 22603 var oldVal; 22604 22605 scope.$watch(attr[name], ngClassWatchAction, true); 22606 22607 attr.$observe('class', function(value) { 22608 ngClassWatchAction(scope.$eval(attr[name])); 22609 }); 22610 22611 22612 if (name !== 'ngClass') { 22613 scope.$watch('$index', function($index, old$index) { 22614 // jshint bitwise: false 22615 var mod = $index & 1; 22616 if (mod !== (old$index & 1)) { 22617 var classes = arrayClasses(scope.$eval(attr[name])); 22618 mod === selector ? 22619 addClasses(classes) : 22620 removeClasses(classes); 22621 } 22622 }); 22623 } 22624 22625 function addClasses(classes) { 22626 var newClasses = digestClassCounts(classes, 1); 22627 attr.$addClass(newClasses); 22628 } 22629 22630 function removeClasses(classes) { 22631 var newClasses = digestClassCounts(classes, -1); 22632 attr.$removeClass(newClasses); 22633 } 22634 22635 function digestClassCounts(classes, count) { 22636 // Use createMap() to prevent class assumptions involving property 22637 // names in Object.prototype 22638 var classCounts = element.data('$classCounts') || createMap(); 22639 var classesToUpdate = []; 22640 forEach(classes, function(className) { 22641 if (count > 0 || classCounts[className]) { 22642 classCounts[className] = (classCounts[className] || 0) + count; 22643 if (classCounts[className] === +(count > 0)) { 22644 classesToUpdate.push(className); 22645 } 22646 } 22647 }); 22648 element.data('$classCounts', classCounts); 22649 return classesToUpdate.join(' '); 22650 } 22651 22652 function updateClasses(oldClasses, newClasses) { 22653 var toAdd = arrayDifference(newClasses, oldClasses); 22654 var toRemove = arrayDifference(oldClasses, newClasses); 22655 toAdd = digestClassCounts(toAdd, 1); 22656 toRemove = digestClassCounts(toRemove, -1); 22657 if (toAdd && toAdd.length) { 22658 $animate.addClass(element, toAdd); 22659 } 22660 if (toRemove && toRemove.length) { 22661 $animate.removeClass(element, toRemove); 22662 } 22663 } 22664 22665 function ngClassWatchAction(newVal) { 22666 if (selector === true || scope.$index % 2 === selector) { 22667 var newClasses = arrayClasses(newVal || []); 22668 if (!oldVal) { 22669 addClasses(newClasses); 22670 } else if (!equals(newVal,oldVal)) {
22671 var oldClasses = arrayClasses(oldVal); 22672 updateClasses(oldClasses, newClasses); 22673 } 22674 } 22675 oldVal = shallowCopy(newVal); 22676 } 22677 } 22678 }; 22679 22680 function arrayDifference(tokens1, tokens2) { 22681 var values = []; 22682 22683 outer: 22684 for (var i = 0; i < tokens1.length; i++) { 22685 var token = tokens1[i]; 22686 for (var j = 0; j < tokens2.length; j++) { 22687 if (token == tokens2[j]) continue outer; 22688 } 22689 values.push(token); 22690 } 22691 return values; 22692 } 22693 22694 function arrayClasses(classVal) { 22695 var classes = []; 22696 if (isArray(classVal)) { 22697 forEach(classVal, function(v) { 22698 classes = classes.concat(arrayClasses(v)); 22699 }); 22700 return classes; 22701 } else if (isString(classVal)) { 22702 return classVal.split(' '); 22703 } else if (isObject(classVal)) { 22704 forEach(classVal, function(v, k) { 22705 if (v) { 22706 classes = classes.concat(k.split(' ')); 22707 } 22708 }); 22709 return classes; 22710 } 22711 return classVal; 22712 } 22713 }]; 22714} 22715 22716/** 22717 * @ngdoc directive 22718 * @name ngClass 22719 * @restrict AC 22720 * 22721 * @description 22722 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 22723 * an expression that represents all classes to be added. 22724 * 22725 * The directive operates in three different ways, depending on which of three types the expression 22726 * evaluates to: 22727 * 22728 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 22729 * names. 22730 * 22731 * 2. If the expression evaluates to an object, then for each key-value pair of the 22732 * object with a truthy value the corresponding key is used as a class name. 22733 * 22734 * 3. If the expression evaluates to an array, each element of the array should either be a string as in 22735 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array 22736 * to give you more control over what CSS classes appear. See the code below for an example of this. 22737 * 22738 * 22739 * The directive won't add duplicate classes if a particular class was already set. 22740 * 22741 * When the expression changes, the previously added classes are removed and only then are the 22742 * new classes added. 22743 * 22744 * @animations 22745 * **add** - happens just before the class is applied to the elements 22746 * 22747 * **remove** - happens just before the class is removed from the element 22748 * 22749 * @element ANY 22750 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 22751 * of the evaluation can be a string representing space delimited class 22752 * names, an array, or a map of class names to boolean values. In the case of a map, the 22753 * names of the properties whose values are truthy will be added as css classes to the 22754 * element. 22755 * 22756 * @example Example that demonstrates basic bindings via ngClass directive. 22757 <example> 22758 <file name="index.html"> 22759 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> 22760 <label> 22761 <input type="checkbox" ng-model="deleted"> 22762 deleted (apply "strike" class) 22763 </label><br> 22764 <label> 22765 <input type="checkbox" ng-model="important"> 22766 important (apply "bold" class) 22767 </label><br> 22768 <label> 22769 <input type="checkbox" ng-model="error"> 22770 error (apply "has-error" class) 22771 </label> 22772 <hr> 22773 <p ng-class="style">Using String Syntax</p> 22774 <input type="text" ng-model="style" 22775 placeholder="Type: bold strike red" aria-label="Type: bold strike red"> 22776 <hr> 22777 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 22778 <input ng-model="style1" 22779 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> 22780 <input ng-model="style2" 22781 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> 22782 <input ng-model="style3" 22783 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> 22784 <hr> 22785 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> 22786 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> 22787 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> 22788 </file> 22789 <file name="style.css"> 22790 .strike { 22791 text-decoration: line-through; 22792 } 22793 .bold { 22794 font-weight: bold; 22795 } 22796 .red { 22797 color: red; 22798 } 22799 .has-error { 22800 color: red; 22801 background-color: yellow; 22802 } 22803 .orange { 22804 color: orange; 22805 } 22806 </file> 22807 <file name="protractor.js" type="protractor"> 22808 var ps = element.all(by.css('p')); 22809 22810 it('should let you toggle the class', function() { 22811 22812 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 22813 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); 22814
22815 element(by.model('important')).click(); 22816 expect(ps.first().getAttribute('class')).toMatch(/bold/); 22817 22818 element(by.model('error')).click(); 22819 expect(ps.first().getAttribute('class')).toMatch(/has-error/); 22820 }); 22821 22822 it('should let you toggle string example', function() { 22823 expect(ps.get(1).getAttribute('class')).toBe(''); 22824 element(by.model('style')).clear(); 22825 element(by.model('style')).sendKeys('red'); 22826 expect(ps.get(1).getAttribute('class')).toBe('red'); 22827 }); 22828 22829 it('array example should have 3 classes', function() { 22830 expect(ps.get(2).getAttribute('class')).toBe(''); 22831 element(by.model('style1')).sendKeys('bold'); 22832 element(by.model('style2')).sendKeys('strike'); 22833 element(by.model('style3')).sendKeys('red'); 22834 expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); 22835 }); 22836 22837 it('array with map example should have 2 classes', function() { 22838 expect(ps.last().getAttribute('class')).toBe(''); 22839 element(by.model('style4')).sendKeys('bold'); 22840 element(by.model('warning')).click(); 22841 expect(ps.last().getAttribute('class')).toBe('bold orange'); 22842 }); 22843 </file> 22844 </example> 22845 22846 ## Animations 22847 22848 The example below demonstrates how to perform animations using ngClass. 22849 22850 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 22851 <file name="index.html"> 22852 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 22853 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 22854 <br> 22855 <span class="base-class" ng-class="myVar">Sample Text</span> 22856 </file> 22857 <file name="style.css"> 22858 .base-class { 22859 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 22860 } 22861 22862 .base-class.my-class { 22863 color: red; 22864 font-size:3em; 22865 } 22866 </file> 22867 <file name="protractor.js" type="protractor"> 22868 it('should check ng-class', function() { 22869 expect(element(by.css('.base-class')).getAttribute('class')).not. 22870 toMatch(/my-class/); 22871 22872 element(by.id('setbtn')).click(); 22873 22874 expect(element(by.css('.base-class')).getAttribute('class')). 22875 toMatch(/my-class/); 22876 22877 element(by.id('clearbtn')).click(); 22878 22879 expect(element(by.css('.base-class')).getAttribute('class')).not. 22880 toMatch(/my-class/); 22881 }); 22882 </file> 22883 </example> 22884 22885 22886 ## ngClass and pre-existing CSS3 Transitions/Animations 22887 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 22888 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 22889 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 22890 to view the step by step details of {@link $animate#addClass $animate.addClass} and 22891 {@link $animate#removeClass $animate.removeClass}. 22892 */ 22893var ngClassDirective = classDirective('', true); 22894 22895/** 22896 * @ngdoc directive 22897 * @name ngClassOdd 22898 * @restrict AC 22899 * 22900 * @description 22901 * The `ngClassOdd` and `ngClassEven` directives work exactly as 22902 * {@link ng.directive:ngClass ngClass}, except they work in 22903 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 22904 * 22905 * This directive can be applied only within the scope of an 22906 * {@link ng.directive:ngRepeat ngRepeat}. 22907 * 22908 * @element ANY 22909 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 22910 * of the evaluation can be a string representing space delimited class names or an array. 22911 * 22912 * @example 22913 <example> 22914 <file name="index.html"> 22915 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 22916 <li ng-repeat="name in names"> 22917 <span ng-class-odd="'odd'" ng-class-even="'even'"> 22918 {{name}} 22919 </span> 22920 </li> 22921 </ol> 22922 </file> 22923 <file name="style.css"> 22924 .odd { 22925 color: red; 22926 } 22927 .even { 22928 color: blue; 22929 } 22930 </file> 22931 <file name="protractor.js" type="protractor"> 22932 it('should check ng-class-odd and ng-class-even', function() { 22933 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 22934 toMatch(/odd/); 22935 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 22936 toMatch(/even/); 22937 }); 22938 </file> 22939 </example> 22940 */ 22941var ngClassOddDirective = classDirective('Odd', 0); 22942 22943/** 22944 * @ngdoc directive 22945 * @name ngClassEven 22946 * @restrict AC 22947 * 22948 * @description 22949 * The `ngClassOdd` and `ngClassEven` directives work exactly as 22950 * {@link ng.directive:ngClass ngClass}, except they work in 22951 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 22952 * 22953 * This directive can be applied only within the scope of an 22954 * {@link ng.directive:ngRepeat ngRepeat}. 22955 * 22956 * @element ANY 22957 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 22958 * result of the evaluation can be a string representing space delimited class names or an array. 22959 * 22960 * @example 22961 <example> 22962 <file name="index.html"> 22963 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 22964 <li ng-repeat="name in names">
22965 <span ng-class-odd="'odd'" ng-class-even="'even'"> 22966 {{name}} 22967 </span> 22968 </li> 22969 </ol> 22970 </file> 22971 <file name="style.css"> 22972 .odd { 22973 color: red; 22974 } 22975 .even { 22976 color: blue; 22977 } 22978 </file> 22979 <file name="protractor.js" type="protractor"> 22980 it('should check ng-class-odd and ng-class-even', function() { 22981 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 22982 toMatch(/odd/); 22983 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 22984 toMatch(/even/); 22985 }); 22986 </file> 22987 </example> 22988 */ 22989var ngClassEvenDirective = classDirective('Even', 1); 22990 22991/** 22992 * @ngdoc directive 22993 * @name ngCloak 22994 * @restrict AC 22995 * 22996 * @description 22997 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 22998 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 22999 * directive to avoid the undesirable flicker effect caused by the html template display. 23000 * 23001 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 23002 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 23003 * of the browser view. 23004 * 23005 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 23006 * `angular.min.js`. 23007 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 23008 * 23009 * ```css 23010 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 23011 * display: none !important; 23012 * } 23013 * ``` 23014 * 23015 * When this css rule is loaded by the browser, all html elements (including their children) that 23016 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 23017 * during the compilation of the template it deletes the `ngCloak` element attribute, making 23018 * the compiled element visible. 23019 * 23020 * For the best result, the `angular.js` script must be loaded in the head section of the html 23021 * document; alternatively, the css rule above must be included in the external stylesheet of the 23022 * application. 23023 * 23024 * @element ANY 23025 * 23026 * @example 23027 <example> 23028 <file name="index.html"> 23029 <div id="template1" ng-cloak>{{ 'hello' }}</div> 23030 <div id="template2" class="ng-cloak">{{ 'world' }}</div> 23031 </file> 23032 <file name="protractor.js" type="protractor"> 23033 it('should remove the template directive and css class', function() { 23034 expect($('#template1').getAttribute('ng-cloak')). 23035 toBeNull(); 23036 expect($('#template2').getAttribute('ng-cloak')). 23037 toBeNull(); 23038 }); 23039 </file> 23040 </example> 23041 * 23042 */ 23043var ngCloakDirective = ngDirective({ 23044 compile: function(element, attr) { 23045 attr.$set('ngCloak', undefined); 23046 element.removeClass('ng-cloak'); 23047 } 23048}); 23049 23050/** 23051 * @ngdoc directive 23052 * @name ngController 23053 * 23054 * @description 23055 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 23056 * supports the principles behind the Model-View-Controller design pattern. 23057 * 23058 * MVC components in angular: 23059 * 23060 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 23061 * are accessed through bindings. 23062 * * View â The template (HTML with data bindings) that is rendered into the View. 23063 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 23064 * logic behind the application to decorate the scope with functions and values 23065 * 23066 * Note that you can also attach controllers to the DOM by declaring it in a route definition 23067 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 23068 * again using `ng-controller` in the template itself. This will cause the controller to be attached 23069 * and executed twice. 23070 * 23071 * @element ANY 23072 * @scope 23073 * @priority 500 23074 * @param {expression} ngController Name of a constructor function registered with the current 23075 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 23076 * that on the current scope evaluates to a constructor function. 23077 * 23078 * The controller instance can be published into a scope property by specifying 23079 * `ng-controller="as propertyName"`. 23080 * 23081 * If the current `$controllerProvider` is configured to use globals (via 23082 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may 23083 * also be the name of a globally accessible constructor function (not recommended). 23084 * 23085 * @example 23086 * Here is a simple form for editing user contact information. Adding, remov
23086ing, clearing, and 23087 * greeting are methods declared on the controller (see source tab). These methods can 23088 * easily be called from the angular markup. Any changes to the data are automatically reflected 23089 * in the View without the need for a manual update. 23090 * 23091 * Two different declaration styles are included below: 23092 * 23093 * * one binds methods and properties directly onto the controller using `this`: 23094 * `ng-controller="SettingsController1 as settings"` 23095 * * one injects `$scope` into the controller: 23096 * `ng-controller="SettingsController2"` 23097 * 23098 * The second option is more common in the Angular community, and is generally used in boilerplates 23099 * and in this guide. However, there are advantages to binding properties directly to the controller 23100 * and avoiding scope. 23101 * 23102 * * Using `controller as` makes it obvious which controller you are accessing in the template when 23103 * multiple controllers apply to an element. 23104 * * If you are writing your controllers as classes you have easier access to the properties and 23105 * methods, which will appear on the scope, from inside the controller code. 23106 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 23107 * inheritance masking primitives. 23108 * 23109 * This example demonstrates the `controller as` syntax. 23110 * 23111 * <example name="ngControllerAs" module="controllerAsExample"> 23112 * <file name="index.html"> 23113 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 23114 * <label>Name: <input type="text" ng-model="settings.name"/></label> 23115 * <button ng-click="settings.greet()">greet</button><br/> 23116 * Contact: 23117 * <ul> 23118 * <li ng-repeat="contact in settings.contacts"> 23119 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> 23120 * <option>phone</option> 23121 * <option>email</option> 23122 * </select> 23123 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 23124 * <button ng-click="settings.clearContact(contact)">clear</button> 23125 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> 23126 * </li> 23127 * <li><button ng-click="settings.addContact()">add</button></li> 23128 * </ul> 23129 * </div> 23130 * </file> 23131 * <file name="app.js"> 23132 * angular.module('controllerAsExample', []) 23133 * .controller('SettingsController1', SettingsController1); 23134 * 23135 * function SettingsController1() { 23136 * this.name = "John Smith"; 23137 * this.contacts = [ 23138 * {type: 'phone', value: '408 555 1212'}, 23139 * {type: 'email', value: '[email protected]'} ]; 23140 * } 23141 * 23142 * SettingsController1.prototype.greet = function() { 23143 * alert(this.name); 23144 * }; 23145 * 23146 * SettingsController1.prototype.addContact = function() { 23147 * this.contacts.push({type: 'email', value: '[email protected]'}); 23148 * }; 23149 * 23150 * SettingsController1.prototype.removeContact = function(contactToRemove) { 23151 * var index = this.contacts.indexOf(contactToRemove); 23152 * this.contacts.splice(index, 1); 23153 * }; 23154 * 23155 * SettingsController1.prototype.clearContact = function(contact) { 23156 * contact.type = 'phone'; 23157 * contact.value = ''; 23158 * }; 23159 * </file> 23160 * <file name="protractor.js" type="protractor"> 23161 * it('should check controller as', function() { 23162 * var container = element(by.id('ctrl-as-exmpl')); 23163 * expect(container.element(by.model('settings.name')) 23164 * .getAttribute('value')).toBe('John Smith'); 23165 * 23166 * var firstRepeat = 23167 * container.element(by.repeater('contact in settings.contacts').row(0)); 23168 * var secondRepeat = 23169 * container.element(by.repeater('contact in settings.contacts').row(1)); 23170 * 23171 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 23172 * .toBe('408 555 1212'); 23173 * 23174 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 23175 * .toBe('[email protected]'); 23176 * 23177 * firstRepeat.element(by.buttonText('clear')).click(); 23178 * 23179 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 23180 * .toBe(''); 23181 * 23182 * container.element(by.buttonText('add')).click(); 23183 * 23184 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 23185 * .element(by.model('contact.value')) 23186 * .getAttribute('value')) 23187 * .toBe('[email protected]'); 23188 * }); 23189 * </file> 23190 * </example> 23191 * 23192 * This example demonstrates the "attach to `$scope`" style of controller. 23193 * 23194 * <example name="ngController" module="controllerExample"> 23195 * <file name="index.html"> 23196 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 23197 * <label>Name: <input type="text" ng-model="name"/></label> 23198 * <button ng-click="greet()">greet</button><br/> 23199 * Contact: 23200 * <ul> 23201 * <li ng-repeat="contact in contacts"> 23202 * <select ng-model="contact.type" id="select_{{$index}}"> 23203 * <option>phone</option> 23204 * <option>email</option> 23205 * </select> 23206 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 23207 * <button ng-click="clearContact(contact)">clear</button> 23208 * <button ng-click="removeContact(contact)">X</button> 23209 * </li> 23210 * <li>[ <button ng-click="addContact()">add</button> ]</li> 23211 * </ul> 23212 * </div> 23213 * </file> 23214 * <file name="app.js">
23215 * angular.module('controllerExample', []) 23216 * .controller('SettingsController2', ['$scope', SettingsController2]); 23217 * 23218 * function SettingsController2($scope) { 23219 * $scope.name = "John Smith"; 23220 * $scope.contacts = [ 23221 * {type:'phone', value:'408 555 1212'}, 23222 * {type:'email', value:'[email protected]'} ]; 23223 * 23224 * $scope.greet = function() { 23225 * alert($scope.name); 23226 * }; 23227 * 23228 * $scope.addContact = function() { 23229 * $scope.contacts.push({type:'email', value:'[email protected]'}); 23230 * }; 23231 * 23232 * $scope.removeContact = function(contactToRemove) { 23233 * var index = $scope.contacts.indexOf(contactToRemove); 23234 * $scope.contacts.splice(index, 1); 23235 * }; 23236 * 23237 * $scope.clearContact = function(contact) { 23238 * contact.type = 'phone'; 23239 * contact.value = ''; 23240 * }; 23241 * } 23242 * </file> 23243 * <file name="protractor.js" type="protractor"> 23244 * it('should check controller', function() { 23245 * var container = element(by.id('ctrl-exmpl')); 23246 * 23247 * expect(container.element(by.model('name')) 23248 * .getAttribute('value')).toBe('John Smith'); 23249 * 23250 * var firstRepeat = 23251 * container.element(by.repeater('contact in contacts').row(0)); 23252 * var secondRepeat = 23253 * container.element(by.repeater('contact in contacts').row(1)); 23254 * 23255 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 23256 * .toBe('408 555 1212'); 23257 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 23258 * .toBe('[email protected]'); 23259 * 23260 * firstRepeat.element(by.buttonText('clear')).click(); 23261 * 23262 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 23263 * .toBe(''); 23264 * 23265 * container.element(by.buttonText('add')).click(); 23266 * 23267 * expect(container.element(by.repeater('contact in contacts').row(2)) 23268 * .element(by.model('contact.value')) 23269 * .getAttribute('value')) 23270 * .toBe('[email protected]'); 23271 * }); 23272 * </file> 23273 *</example> 23274 23275 */ 23276var ngControllerDirective = [function() { 23277 return { 23278 restrict: 'A', 23279 scope: true, 23280 controller: '@', 23281 priority: 500 23282 }; 23283}]; 23284 23285/** 23286 * @ngdoc directive 23287 * @name ngCsp 23288 * 23289 * @element html 23290 * @description 23291 * 23292 * Angular has some features that can break certain 23293 * [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) rules. 23294 * 23295 * If you intend to implement these rules then you must tell Angular not to use these features. 23296 * 23297 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 23298 * 23299 * 23300 * The following rules affect Angular: 23301 * 23302 * * `unsafe-eval`: this rule forbids apps to use `eval` or `Function(string)` generated functions 23303 * (among other things). Angular makes use of this in the {@link $parse} service to provide a 30% 23304 * increase in the speed of evaluating Angular expressions. 23305 * 23306 * * `unsafe-inline`: this rule forbids apps from inject custom styles into the document. Angular 23307 * makes use of this to include some CSS rules (e.g. {@link ngCloak} and {@link ngHide}). 23308 * To make these directives work when a CSP rule is blocking inline styles, you must link to the 23309 * `angular-csp.css` in your HTML manually. 23310 * 23311 * If you do not provide `ngCsp` then Angular tries to autodetect if CSP is blocking unsafe-eval 23312 * and automatically deactivates this feature in the {@link $parse} service. This autodetection, 23313 * however, triggers a CSP error to be logged in the console: 23314 * 23315 * ``` 23316 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 23317 * script in the following Content Security Policy directive: "default-src 'self'". Note that 23318 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 23319 * ``` 23320 * 23321 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 23322 * directive on an element of the HTML document that appears before the `<script>` tag that loads 23323 * the `angular.js` file. 23324 * 23325 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 23326 * 23327 * You can specify which of the CSP related Angular features should be deactivated by providing 23328 * a value for the `ng-csp` attribute. The options are as follows: 23329 * 23330 * * no-inline-style: this stops Angular from injecting CSS styles into the DOM 23331 *
23332 * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings 23333 * 23334 * You can use these values in the following combinations: 23335 * 23336 * 23337 * * No declaration means that Angular will assume that you can do inline styles, but it will do 23338 * a runtime check for unsafe-eval. E.g. `<body>`. This is backwardly compatible with previous versions 23339 * of Angular. 23340 * 23341 * * A simple `ng-csp` (or `data-ng-csp`) attribute will tell Angular to deactivate both inline 23342 * styles and unsafe eval. E.g. `<body ng-csp>`. This is backwardly compatible with previous versions 23343 * of Angular. 23344 * 23345 * * Specifying only `no-unsafe-eval` tells Angular that we must not use eval, but that we can inject 23346 * inline styles. E.g. `<body ng-csp="no-unsafe-eval">`. 23347 * 23348 * * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can 23349 * run eval - no automcatic check for unsafe eval will occur. E.g. `<body ng-csp="no-inline-style">` 23350 * 23351 * * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject 23352 * styles nor use eval, which is the same as an empty: ng-csp. 23353 * E.g.`<body ng-csp="no-inline-style;no-unsafe-eval">` 23354 * 23355 * @example 23356 * This example shows how to apply the `ngCsp` directive to the `html` tag. 23357 ```html 23358 <!doctype html> 23359 <html ng-app ng-csp> 23360 ... 23361 ... 23362 </html> 23363 ``` 23364 * @example 23365 // Note: the suffix `.csp` in the example name triggers 23366 // csp mode in our http server! 23367 <example name="example.csp" module="cspExample" ng-csp="true"> 23368 <file name="index.html"> 23369 <div ng-controller="MainController as ctrl"> 23370 <div> 23371 <button ng-click="ctrl.inc()" id="inc">Increment</button> 23372 <span id="counter"> 23373 {{ctrl.counter}} 23374 </span> 23375 </div> 23376 23377 <div> 23378 <button ng-click="ctrl.evil()" id="evil">Evil</button> 23379 <span id="evilError"> 23380 {{ctrl.evilError}} 23381 </span> 23382 </div> 23383 </div> 23384 </file> 23385 <file name="script.js"> 23386 angular.module('cspExample', []) 23387 .controller('MainController', function() { 23388 this.counter = 0; 23389 this.inc = function() { 23390 this.counter++; 23391 }; 23392 this.evil = function() { 23393 // jshint evil:true 23394 try { 23395 eval('1+2'); 23396 } catch (e) { 23397 this.evilError = e.message; 23398 } 23399 }; 23400 }); 23401 </file> 23402 <file name="protractor.js" type="protractor"> 23403 var util, webdriver; 23404 23405 var incBtn = element(by.id('inc')); 23406 var counter = element(by.id('counter')); 23407 var evilBtn = element(by.id('evil')); 23408 var evilError = element(by.id('evilError')); 23409 23410 function getAndClearSevereErrors() { 23411 return browser.manage().logs().get('browser').then(function(browserLog) { 23412 return browserLog.filter(function(logEntry) { 23413 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 23414 }); 23415 }); 23416 } 23417 23418 function clearErrors() { 23419 getAndClearSevereErrors(); 23420 } 23421 23422 function expectNoErrors() { 23423 getAndClearSevereErrors().then(function(filteredLog) { 23424 expect(filteredLog.length).toEqual(0); 23425 if (filteredLog.length) { 23426 console.log('browser console errors: ' + util.inspect(filteredLog)); 23427 } 23428 }); 23429 } 23430 23431 function expectError(regex) { 23432 getAndClearSevereErrors().then(function(filteredLog) { 23433 var found = false; 23434 filteredLog.forEach(function(log) { 23435 if (log.message.match(regex)) { 23436 found = true; 23437 } 23438 }); 23439 if (!found) { 23440 throw new Error('expected an error that matches ' + regex); 23441 } 23442 }); 23443 } 23444 23445 beforeEach(function() { 23446 util = require('util'); 23447 webdriver = require('protractor/node_modules/selenium-webdriver'); 23448 }); 23449 23450 // For now, we only test on Chrome, 23451 // as Safari does not load the page with Protractor's injected scripts, 23452 // and Firefox webdriver always disables content security policy (#6358) 23453 if (browser.params.browser !== 'chrome') { 23454 return; 23455 } 23456 23457 it('should not report errors when the page is loaded', function() { 23458 // clear errors so we are not dependent on previous tests 23459 clearErrors(); 23460 // Need to reload the page as the page is already loaded when 23461 // we come here 23462 browser.driver.getCurrentUrl().then(function(url) { 23463 browser.get(url); 23464 }); 23465 expectNoErrors(); 23466 }); 23467 23468 it('should evaluate expressions', function() { 23469 expect(counter.getText()).toEqual('0'); 23470 incBtn.click(); 23471 expect(counter.getText()).toEqual('1'); 23472 expectNoErrors(); 23473 }); 23474 23475 it('should throw and report an error when using "eval"', function() { 23476 evilBtn.click(); 23477 expect(evilError.getText()).toMatch(/Content Security Policy/); 23478 expectError(/Content Security Policy/); 23479 }); 23480 </file> 23481 </example> 23482 */ 23483 23484// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
23485// bootstrap the system (before $parse is instantiated), for this reason we just have 23486// the csp() fn that looks for the `ng-csp` attribute anywhere in the current doc 23487 23488/** 23489 * @ngdoc directive 23490 * @name ngClick 23491 * 23492 * @description 23493 * The ngClick directive allows you to specify custom behavior when 23494 * an element is clicked. 23495 * 23496 * @element ANY 23497 * @priority 0 23498 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 23499 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 23500 * 23501 * @example 23502 <example> 23503 <file name="index.html"> 23504 <button ng-click="count = count + 1" ng-init="count=0"> 23505 Increment 23506 </button> 23507 <span> 23508 count: {{count}} 23509 </span> 23510 </file> 23511 <file name="protractor.js" type="protractor"> 23512 it('should check ng-click', function() { 23513 expect(element(by.binding('count')).getText()).toMatch('0'); 23514 element(by.css('button')).click(); 23515 expect(element(by.binding('count')).getText()).toMatch('1'); 23516 }); 23517 </file> 23518 </example> 23519 */ 23520/* 23521 * A collection of directives that allows creation of custom event handlers that are defined as 23522 * angular expressions and are compiled and executed within the current scope. 23523 */ 23524var ngEventDirectives = {}; 23525 23526// For events that might fire synchronously during DOM manipulation 23527// we need to execute their event handlers asynchronously using $evalAsync, 23528// so that they are not executed in an inconsistent state. 23529var forceAsyncEvents = { 23530 'blur': true, 23531 'focus': true 23532}; 23533forEach( 23534 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 23535 function(eventName) { 23536 var directiveName = directiveNormalize('ng-' + eventName); 23537 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 23538 return { 23539 restrict: 'A', 23540 compile: function($element, attr) { 23541 // We expose the powerful $event object on the scope that provides access to the Window, 23542 // etc. that isn't protected by the fast paths in $parse. We explicitly request better 23543 // checks at the cost of speed since event handler expressions are not executed as 23544 // frequently as regular change detection. 23545 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); 23546 return function ngEventHandler(scope, element) { 23547 element.on(eventName, function(event) { 23548 var callback = function() { 23549 fn(scope, {$event:event}); 23550 }; 23551 if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 23552 scope.$evalAsync(callback); 23553 } else { 23554 scope.$apply(callback); 23555 } 23556 }); 23557 }; 23558 } 23559 }; 23560 }]; 23561 } 23562); 23563 23564/** 23565 * @ngdoc directive 23566 * @name ngDblclick 23567 * 23568 * @description 23569 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 23570 * 23571 * @element ANY 23572 * @priority 0 23573 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 23574 * a dblclick. (The Event object is available as `$event`) 23575 * 23576 * @example 23577 <example> 23578 <file name="index.html"> 23579 <button ng-dblclick="count = count + 1" ng-init="count=0"> 23580 Increment (on double click) 23581 </button> 23582 count: {{count}} 23583 </file> 23584 </example> 23585 */ 23586 23587 23588/** 23589 * @ngdoc directive 23590 * @name ngMousedown 23591 * 23592 * @description 23593 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 23594 * 23595 * @element ANY 23596 * @priority 0 23597 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 23598 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 23599 * 23600 * @example 23601 <example> 23602 <file name="index.html"> 23603 <button ng-mousedown="count = count + 1" ng-init="count=0"> 23604 Increment (on mouse down) 23605 </button> 23606 count: {{count}} 23607 </file> 23608 </example> 23609 */ 23610 23611 23612/** 23613 * @ngdoc directive 23614 * @name ngMouseup 23615 * 23616 * @description 23617 * Specify custom behavior on mouseup event. 23618 * 23619 * @element ANY 23620 * @priority 0 23621 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 23622 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 23623 * 23624 * @example 23625 <example> 23626 <file name="index.html"> 23627 <button ng-mouseup="count = count + 1" ng-init="count=0"> 23628 Increment (on mouse up) 23629 </button> 23630 count: {{count}} 23631 </file> 23632 </example> 23633 */ 23634 23635/** 23636 * @ngdoc directive 23637 * @name ngMouseover 23638 * 23639 * @description 23640 * Specify custom behavior on mouseover event. 23641 * 23642 * @element ANY 23643 * @priority 0 23644 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 23645 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 23646 * 23647 * @example 23648 <example> 23649 <file name="index.html"> 23650 <button ng-mouseover="count = count + 1" ng-init="count=0"> 23651 Increment (when mouse is over) 23652 </button> 23653 count: {{count}} 23654 </file> 23655 </example> 23656 */ 23657 23658 23659/** 23660 * @ngdoc directive 23661 * @name ngMouseenter 23662 * 23663 * @description 23664 * Specify custom behavior on mouseenter event. 23665 * 23666 * @element ANY 23667 * @priority 0 23668 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 23669 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 23670 * 23671 * @example 23672 <example> 23673 <file name="index.html"> 23674 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 23675 Increment (when mouse enters) 23676 </button> 23677 count: {{count}} 23678 </file> 23679 </example> 23680 */ 23681 23682 23683/** 23684 * @ngdoc directive 23685 * @name ngMouseleave 23686 * 23687 * @description 23688 * Specify custom behavior on mouseleave event. 23689 * 23690 * @element ANY 23691 * @priority 0 23692 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 23693 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 23694 * 23695 * @example 23696 <example> 23697 <file name="index.html"> 23698 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 23699 Increment (when mouse leaves) 23700 </button> 23701 count: {{count}} 23702 </file> 23703 </example> 23704 */ 23705 23706 23707/** 23708 * @ngdoc directive 23709 * @name ngMousemove 23710 * 23711 * @description 23712 * Specify custom behavior on mousemove event. 23713 * 23714 * @element ANY 23715 * @priority 0 23716 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 23717 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 23718 * 23719 * @example 23720 <example> 23721 <file name="index.html"> 23722 <button ng-mousemove="count = count + 1" ng-init="count=0"> 23723 Increment (when mouse moves) 23724 </button> 23725 count: {{count}} 23726 </file> 23727 </example> 23728 */ 23729 23730 23731/** 23732 * @ngdoc directive 23733 * @name ngKeydown 23734 * 23735 * @description 23736 * Specify custom behavior on keydown event. 23737 * 23738 * @element ANY 23739 * @priority 0 23740 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 23741 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 23742 * 23743 * @example 23744 <example> 23745 <file name="index.html"> 23746 <input ng-keydown="count = count + 1" ng-init="count=0"> 23747 key down count: {{count}} 23748 </file> 23749 </example> 23750 */ 23751 23752 23753/** 23754 * @ngdoc directive 23755 * @name ngKeyup 23756 * 23757 * @description 23758 * Specify custom behavior on keyup event. 23759 * 23760 * @element ANY 23761 * @priority 0 23762 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 23763 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 23764 * 23765 * @example 23766 <example> 23767 <file name="index.html"> 23768 <p>Typing in the input box below updates the key count</p> 23769 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 23770 23771 <p>Typing in the input box below updates the keycode</p> 23772 <input ng-keyup="event=$event"> 23773 <p>event keyCode: {{ event.keyCode }}</p> 23774 <p>event altKey: {{ event.altKey }}</p> 23775 </file> 23776 </example> 23777 */ 23778 23779 23780/** 23781 * @ngdoc directive 23782 * @name ngKeypress 23783 * 23784 * @description 23785 * Specify custom behavior on keypress event. 23786 * 23787 * @element ANY 23788 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 23789 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 23790 * and can be interrogated for keyCode, altKey, etc.) 23791 * 23792 * @example 23793 <example> 23794 <file name="index.html"> 23795 <input ng-keypress="count = count + 1" ng-init="count=0"> 23796 key press count: {{count}} 23797 </file> 23798 </example> 23799 */ 23800 23801 23802/** 23803 * @ngdoc directive 23804 * @name ngSubmit 23805 * 23806 * @description 23807 * Enables binding angular expressions to onsubmit events. 23808 * 23809 * Additionally it prevents the default action (which for form means
23809sending the request to the 23810 * server and reloading the current page), but only if the form does not contain `action`, 23811 * `data-action`, or `x-action` attributes. 23812 * 23813 * <div class="alert alert-warning"> 23814 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 23815 * `ngSubmit` handlers together. See the 23816 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 23817 * for a detailed discussion of when `ngSubmit` may be triggered. 23818 * </div> 23819 * 23820 * @element form 23821 * @priority 0 23822 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 23823 * ({@link guide/expression#-event- Event object is available as `$event`}) 23824 * 23825 * @example 23826 <example module="submitExample"> 23827 <file name="index.html"> 23828 <script> 23829 angular.module('submitExample', []) 23830 .controller('ExampleController', ['$scope', function($scope) { 23831 $scope.list = []; 23832 $scope.text = 'hello'; 23833 $scope.submit = function() { 23834 if ($scope.text) { 23835 $scope.list.push(this.text); 23836 $scope.text = ''; 23837 } 23838 }; 23839 }]); 23840 </script>
23841 <form ng-submit="submit()" ng-controller="ExampleController"> 23842 Enter text and hit enter: 23843 <input type="text" ng-model="text" name="text" /> 23844 <input type="submit" id="submit" value="Submit" /> 23845 <pre>list={{list}}</pre> 23846 </form> 23847 </file> 23848 <file name="protractor.js" type="protractor"> 23849 it('should check ng-submit', function() { 23850 expect(element(by.binding('list')).getText()).toBe('list=[]'); 23851 element(by.css('#submit')).click(); 23852 expect(element(by.binding('list')).getText()).toContain('hello'); 23853 expect(element(by.model('text')).getAttribute('value')).toBe(''); 23854 }); 23855 it('should ignore empty strings', function() { 23856 expect(element(by.binding('list')).getText()).toBe('list=[]'); 23857 element(by.css('#submit')).click(); 23858 element(by.css('#submit')).click(); 23859 expect(element(by.binding('list')).getText()).toContain('hello'); 23860 }); 23861 </file> 23862 </example> 23863 */ 23864 23865/** 23866 * @ngdoc directive 23867 * @name ngFocus 23868 * 23869 * @description 23870 * Specify custom behavior on focus event. 23871 * 23872 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 23873 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 23874 * during an `$apply` to ensure a consistent state. 23875 * 23876 * @element window, input, select, textarea, a 23877 * @priority 0 23878 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 23879 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 23880 * 23881 * @example 23882 * See {@link ng.directive:ngClick ngClick} 23883 */ 23884 23885/** 23886 * @ngdoc directive 23887 * @name ngBlur 23888 * 23889 * @description 23890 * Specify custom behavior on blur event. 23891 * 23892 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 23893 * an element has lost focus. 23894 * 23895 * Note: As the `blur` event is executed synchronously also during DOM manipulations 23896 * (e.g. removing a focussed input), 23897 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 23898 * during an `$apply` to ensure a consistent state. 23899 * 23900 * @element window, input, select, textarea, a 23901 * @priority 0 23902 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 23903 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 23904 * 23905 * @example 23906 * See {@link ng.directive:ngClick ngClick} 23907 */ 23908 23909/** 23910 * @ngdoc directive 23911 * @name ngCopy 23912 * 23913 * @description 23914 * Specify custom behavior on copy event. 23915 * 23916 * @element window, input, select, textarea, a 23917 * @priority 0 23918 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 23919 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 23920 * 23921 * @example 23922 <example> 23923 <file name="index.html"> 23924 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 23925 copied: {{copied}} 23926 </file> 23927 </example> 23928 */ 23929 23930/** 23931 * @ngdoc directive 23932 * @name ngCut 23933 * 23934 * @description 23935 * Specify custom behavior on cut event. 23936 * 23937 * @element window, input, select, textarea, a 23938 * @priority 0 23939 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 23940 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 23941 * 23942 * @example 23943 <example> 23944 <file name="index.html"> 23945 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 23946 cut: {{cut}} 23947 </file> 23948 </example> 23949 */ 23950 23951/** 23952 * @ngdoc directive 23953 * @name ngPaste 23954 * 23955 * @description 23956 * Specify custom behavior on paste event. 23957 * 23958 * @element window, input, select, textarea, a 23959 * @priority 0 23960 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 23961 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 23962 * 23963 * @example 23964 <example> 23965 <file name="index.html"> 23966 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 23967 pasted: {{paste}} 23968 </file> 23969 </example> 23970 */ 23971 23972/** 23973 * @ngdoc directive 23974 * @name ngIf 23975 * @restrict A 23976 * @multiElement 23977 * 23978 * @description 23979 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 23980 * {expression}. If the expression assigned to `ngIf` evaluates to a false 23981 * value then the element is removed from the DOM, otherwise a clone of the 23982 * element is reinserted into the DOM. 23983 * 23984 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 23985 * element in the DOM rather than changing its visibility via the `display` css property. A common 23986 * case when this difference is significant is when using css selectors that rely on an element's 23987 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
23987s. 23988 * 23989 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 23990 * is created when the element is restored. The scope created within `ngIf` inherits from 23991 * its parent scope using 23992 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 23993 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 23994 * a javascript primitive defined in the parent scope. In this case any modifications made to the 23995 * variable within the child scope will override (hide) the value in the parent scope. 23996 * 23997 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 23998 * is if an element's class attribute is directly modified after it's compiled, using something like 23999 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 24000 * the added class will be lost because the original compiled state is used to regenerate the element. 24001 * 24002 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 24003 * and `leave` effects. 24004 * 24005 * @animations 24006 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container 24007 * leave - happens just before the `ngIf` contents are removed from the DOM 24008 * 24009 * @element ANY 24010 * @scope 24011 * @priority 600 24012 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 24013 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 24014 * element is added to the DOM tree. 24015 * 24016 * @example 24017 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 24018 <file name="index.html"> 24019 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> 24020 Show when checked: 24021 <span ng-if="checked" class="animate-if"> 24022 This is removed when the checkbox is unchecked. 24023 </span> 24024 </file> 24025 <file name="animations.css"> 24026 .animate-if { 24027 background:white; 24028 border:1px solid black; 24029 padding:10px; 24030 } 24031 24032 .animate-if.ng-enter, .animate-if.ng-leave { 24033 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 24034 } 24035 24036 .animate-if.ng-enter, 24037 .animate-if.ng-leave.ng-leave-active { 24038 opacity:0; 24039 } 24040 24041 .animate-if.ng-leave, 24042 .animate-if.ng-enter.ng-enter-active { 24043 opacity:1; 24044 } 24045 </file> 24046 </example> 24047 */ 24048var ngIfDirective = ['$animate', function($animate) { 24049 return { 24050 multiElement: true, 24051 transclude: 'element', 24052 priority: 600, 24053 terminal: true, 24054 restrict: 'A', 24055 $$tlb: true, 24056 link: function($scope, $element, $attr, ctrl, $transclude) { 24057 var block, childScope, previousElements; 24058 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 24059 24060 if (value) { 24061 if (!childScope) { 24062 $transclude(function(clone, newScope) { 24063 childScope = newScope; 24064 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 24065 // Note: We only need the first/last node of the cloned nodes. 24066 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 24067 // by a directive with templateUrl when its template arrives. 24068 block = { 24069 clone: clone 24070 }; 24071 $animate.enter(clone, $element.parent(), $element); 24072 }); 24073 } 24074 } else { 24075 if (previousElements) { 24076 previousElements.remove(); 24077 previousElements = null; 24078 } 24079 if (childScope) { 24080 childScope.$destroy(); 24081 childScope = null; 24082 } 24083 if (block) { 24084 previousElements = getBlockNodes(block.clone); 24085 $animate.leave(previousElements).then(function() { 24086 previousElements = null; 24087 }); 24088 block = null; 24089 } 24090 } 24091 }); 24092 } 24093 }; 24094}]; 24095 24096/** 24097 * @ngdoc directive 24098 * @name ngInclude 24099 * @restrict ECA 24100 * 24101 * @description 24102 * Fetches, compiles and includes an external HTML fragment. 24103 * 24104 * By default, the template URL is restricted to the same domain and protocol as the 24105 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 24106 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 24107 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 24108 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 24109 * ng.$sce Strict Contextual Escaping}. 24110 * 24111 * In addition, the browser's 24112 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 24113 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 24114 * policy may further restrict whether the template is successfully loaded. 24115 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 24116 * access on some browsers. 24117 * 24118 * @animations 24119 * enter - animation is used to bring new content into the browser. 24120 * leave - animation is used to animate existing content away. 24121 * 24122 * The enter and leave animation occur concurrently. 24123 * 24124 * @scope 24125 * @priority 400 24126 * 24127 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 24128 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 24129 * @param {string=} onload Expression to evaluate when a new partial is loaded. 24130 * 24131 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 24132 * $anchorScroll} to scroll the viewport after the content is loaded. 24133 * 24134 * - If the attribute is not set, disable scrolling. 24135 * - If the attribute is set without value, enable scrolling. 24136 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 24137 * 24138 * @example 24139 <example module="includeExample" deps="angular-animate.js" animations="true"> 24140 <file name="index.html">
24141 <div ng-controller="ExampleController"> 24142 <select ng-model="template" ng-options="t.name for t in templates"> 24143 <option value="">(blank)</option> 24144 </select> 24145 url of the template: <code>{{template.url}}</code> 24146 <hr/> 24147 <div class="slide-animate-container"> 24148 <div class="slide-animate" ng-include="template.url"></div> 24149 </div> 24150 </div> 24151 </file> 24152 <file name="script.js"> 24153 angular.module('includeExample', ['ngAnimate']) 24154 .controller('ExampleController', ['$scope', function($scope) { 24155 $scope.templates = 24156 [ { name: 'template1.html', url: 'template1.html'}, 24157 { name: 'template2.html', url: 'template2.html'} ]; 24158 $scope.template = $scope.templates[0]; 24159 }]); 24160 </file> 24161 <file name="template1.html"> 24162 Content of template1.html 24163 </file> 24164 <file name="template2.html"> 24165 Content of template2.html 24166 </file> 24167 <file name="animations.css"> 24168 .slide-animate-container { 24169 position:relative; 24170 background:white; 24171 border:1px solid black; 24172 height:40px; 24173 overflow:hidden; 24174 } 24175 24176 .slide-animate { 24177 padding:10px; 24178 } 24179 24180 .slide-animate.ng-enter, .slide-animate.ng-leave { 24181 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 24182 24183 position:absolute; 24184 top:0; 24185 left:0; 24186 right:0; 24187 bottom:0; 24188 display:block; 24189 padding:10px; 24190 } 24191 24192 .slide-animate.ng-enter { 24193 top:-50px; 24194 } 24195 .slide-animate.ng-enter.ng-enter-active { 24196 top:0; 24197 } 24198 24199 .slide-animate.ng-leave { 24200 top:0; 24201 } 24202 .slide-animate.ng-leave.ng-leave-active { 24203 top:50px; 24204 } 24205 </file> 24206 <file name="protractor.js" type="protractor"> 24207 var templateSelect = element(by.model('template')); 24208 var includeElem = element(by.css('[ng-include]')); 24209 24210 it('should load template1.html', function() { 24211 expect(includeElem.getText()).toMatch(/Content of template1.html/); 24212 }); 24213 24214 it('should load template2.html', function() { 24215 if (browser.params.browser == 'firefox') { 24216 // Firefox can't handle using selects 24217 // See https://github.com/angular/protractor/issues/480 24218 return; 24219 } 24220 templateSelect.click(); 24221 templateSelect.all(by.css('option')).get(2).click(); 24222 expect(includeElem.getText()).toMatch(/Content of template2.html/); 24223 }); 24224 24225 it('should change to blank', function() { 24226 if (browser.params.browser == 'firefox') { 24227 // Firefox can't handle using selects 24228 return; 24229 } 24230 templateSelect.click(); 24231 templateSelect.all(by.css('option')).get(0).click(); 24232 expect(includeElem.isPresent()).toBe(false); 24233 }); 24234 </file> 24235 </example> 24236 */ 24237 24238 24239/** 24240 * @ngdoc event 24241 * @name ngInclude#$includeContentRequested 24242 * @eventType emit on the scope ngInclude was declared in 24243 * @description 24244 * Emitted every time the ngInclude content is requested. 24245 * 24246 * @param {Object} angularEvent Synthetic event object. 24247 * @param {String} src URL of content to load. 24248 */ 24249 24250 24251/** 24252 * @ngdoc event 24253 * @name ngInclude#$includeContentLoaded 24254 * @eventType emit on the current ngInclude scope 24255 * @description 24256 * Emitted every time the ngInclude content is reloaded. 24257 * 24258 * @param {Object} angularEvent Synthetic event object. 24259 * @param {String} src URL of content to load. 24260 */ 24261 24262 24263/** 24264 * @ngdoc event 24265 * @name ngInclude#$includeContentError 24266 * @eventType emit on the scope ngInclude was declared in 24267 * @description 24268 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 24269 * 24270 * @param {Object} angularEvent Synthetic event object. 24271 * @param {String} src URL of content to load. 24272 */ 24273var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', 24274 function($templateRequest, $anchorScroll, $animate) { 24275 return { 24276 restrict: 'ECA', 24277 priority: 400, 24278 terminal: true, 24279 transclude: 'element', 24280 controller: angular.noop, 24281 compile: function(element, attr) { 24282 var srcExp = attr.ngInclude || attr.src, 24283 onloadExp = attr.onload || '', 24284 autoScrollExp = attr.autoscroll; 24285 24286 return function(scope, $element, $attr, ctrl, $transclude) { 24287 var changeCounter = 0, 24288 currentScope, 24289 previousElement, 24290 currentElement; 24291 24292 var cleanupLastIncludeContent = function() { 24293 if (previousElement) { 24294 previousElement.remove(); 24295 previousElement = null; 24296 } 24297 if (currentScope) { 24298 currentScope.$destroy(); 24299 currentScope = null; 24300 } 24301 if (currentElement) { 24302 $animate.leave(currentElement).then(function() { 24303 previousElement = null; 24304 }); 24305 previousElement = currentElement; 24306 currentElement = null; 24307 } 24308 }; 24309 24310 scope.$watch(srcExp, function ngIncludeWatchAction(src) { 24311 var afterAnimation = function() { 24312 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 24313 $anchorScroll(); 24314 } 24315 }; 24316 var thisChangeId = ++changeCounter; 24317 24318 if (src) { 24319 //set the 2nd param to true to ignore the template request error so that the inner 24320 //contents and scope can be cleaned up. 24321 $templateRequest(src, true).then(function(response) { 24322 if (thisChangeId !== changeCounter) return; 24323 var newScope = scope.$new(); 24324 ctrl.template = response; 24325 24326 // Note: This will also link all children of ng-include that were contained in the original 24327 // html. If that content contains controllers, ... they could pollute/change the scope. 24328 // However, using ng-include on an element with additional content does not make sense... 24329 // Note: We can't remove them in the cloneAttchFn of $transclude as that 24330 // function is called before linking the content, which would apply child 24331 // directives to non existing elements. 24332 var clone = $transclude(newScope, function(clone) { 24333 cleanupLastIncludeContent(); 24334 $animate.enter(clone, null, $element).then(afterAnimation); 24335 }); 24336 24337 currentScope = newScope; 24338 currentElement = clone; 24339 24340 currentScope.$emit('$includeContentLoaded', src); 24341 scope.$eval(onloadExp); 24342 }, function() { 24343 if (thisChangeId === changeCounter) { 24344 cleanupLastIncludeContent();
vendor: 2,913 bytes, lines 24345-24427
24345 scope.$emit('$includeContentError', src); 24346 } 24347 }); 24348 scope.$emit('$includeContentRequested', src); 24349 } else { 24350 cleanupLastIncludeContent(); 24351 ctrl.template = null; 24352 } 24353 }); 24354 }; 24355 } 24356 }; 24357}]; 24358 24359// This directive is called during the $transclude call of the first `ngInclude` directive. 24360// It will replace and compile the content of the element with the loaded template. 24361// We need this directive so that the element content is already filled when 24362// the link function of another directive on the same element as ngInclude 24363// is called. 24364var ngIncludeFillContentDirective = ['$compile', 24365 function($compile) { 24366 return { 24367 restrict: 'ECA', 24368 priority: -400, 24369 require: 'ngInclude', 24370 link: function(scope, $element, $attr, ctrl) { 24371 if (/SVG/.test($element[0].toString())) { 24372 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 24373 // support innerHTML, so detect this here and try to generate the contents 24374 // specially. 24375 $element.empty(); 24376 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope, 24377 function namespaceAdaptedClone(clone) { 24378 $element.append(clone); 24379 }, {futureParentElement: $element}); 24380 return; 24381 } 24382 24383 $element.html(ctrl.template); 24384 $compile($element.contents())(scope); 24385 } 24386 }; 24387 }]; 24388 24389/** 24390 * @ngdoc directive 24391 * @name ngInit 24392 * @restrict AC 24393 * 24394 * @description 24395 * The `ngInit` directive allows you to evaluate an expression in the 24396 * current scope. 24397 * 24398 * <div class="alert alert-danger"> 24399 * This directive can be abused to add unnecessary amounts of logic into your templates. 24400 * There are only a few appropriate uses of `ngInit`, such as for aliasing special properties of 24401 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below; and for injecting data via 24402 * server side scripting. Besides these few cases, you should use {@link guide/controller controllers} 24403 * rather than `ngInit` to initialize values on a scope. 24404 * </div> 24405 * 24406 * <div class="alert alert-warning"> 24407 * **Note**: If you have assignment in `ngInit` along with a {@link ng.$filter `filter`}, make 24408 * sure you have parentheses to ensure correct operator precedence: 24409 * <pre class="prettyprint"> 24410 * `<div ng-init="test1 = ($index | toString)"></div>` 24411 * </pre> 24412 * </div> 24413 * 24414 * @priority 450 24415 * 24416 * @element ANY 24417 * @param {expression} ngInit {@link guide/expression Expression} to eval. 24418 * 24419 * @example 24420 <example module="initExample"> 24421 <file name="index.html"> 24422 <script> 24423 angular.module('initExample', []) 24424 .controller('ExampleController', ['$scope', function($scope) { 24425 $scope.list = [['a', 'b'], ['c', 'd']]; 24426 }]); 24427 </script>
24428 <div ng-controller="ExampleController"> 24429 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 24430 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 24431 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 24432 </div> 24433 </div> 24434 </div> 24435 </file> 24436 <file name="protractor.js" type="protractor"> 24437 it('should alias index positions', function() { 24438 var elements = element.all(by.css('.example-init')); 24439 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 24440 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 24441 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 24442 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 24443 }); 24444 </file> 24445 </example> 24446 */ 24447var ngInitDirective = ngDirective({ 24448 priority: 450, 24449 compile: function() { 24450 return { 24451 pre: function(scope, element, attrs) { 24452 scope.$eval(attrs.ngInit); 24453 } 24454 }; 24455 } 24456}); 24457 24458/** 24459 * @ngdoc directive 24460 * @name ngList 24461 * 24462 * @description 24463 * Text input that converts between a delimited string and an array of strings. The default 24464 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 24465 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 24466 * 24467 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 24468 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 24469 * list item is respected. This implies that the user of the directive is responsible for 24470 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 24471 * tab or newline character. 24472 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 24473 * when joining the list items back together) and whitespace around each list item is stripped 24474 * before it is added to the model. 24475 * 24476 * ### Example with Validation 24477 * 24478 * <example name="ngList-directive" module="listExample"> 24479 * <file name="app.js"> 24480 * angular.module('listExample', []) 24481 * .controller('ExampleController', ['$scope', function($scope) { 24482 * $scope.names = ['morpheus', 'neo', 'trinity']; 24483 * }]); 24484 * </file> 24485 * <file name="index.html">
24486 * <form name="myForm" ng-controller="ExampleController"> 24487 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> 24488 * <span role="alert"> 24489 * <span class="error" ng-show="myForm.namesInput.$error.required"> 24490 * Required!</span> 24491 * </span> 24492 * <br> 24493 * <tt>names = {{names}}</tt><br/> 24494 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 24495 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 24496 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 24497 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 24498 * </form> 24499 * </file> 24500 * <file name="protractor.js" type="protractor"> 24501 * var listInput = element(by.model('names')); 24502 * var names = element(by.exactBinding('names')); 24503 * var valid = element(by.binding('myForm.namesInput.$valid')); 24504 * var error = element(by.css('span.error')); 24505 * 24506 * it('should initialize to model', function() { 24507 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 24508 * expect(valid.getText()).toContain('true'); 24509 * expect(error.getCssValue('display')).toBe('none'); 24510 * }); 24511 * 24512 * it('should be invalid if empty', function() { 24513 * listInput.clear(); 24514 * listInput.sendKeys(''); 24515 * 24516 * expect(names.getText()).toContain(''); 24517 * expect(valid.getText()).toContain('false'); 24518 * expect(error.getCssValue('display')).not.toBe('none'); 24519 * }); 24520 * </file> 24521 * </example> 24522 * 24523 * ### Example - splitting on newline 24524 * <example name="ngList-directive-newlines"> 24525 * <file name="index.html"> 24526 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 24527 * <pre>{{ list | json }}</pre> 24528 * </file> 24529 * <file name="protractor.js" type="protractor"> 24530 * it("should split the text by newlines", function() { 24531 * var listInput = element(by.model('list')); 24532 * var output = element(by.binding('list | json')); 24533 * listInput.sendKeys('abc\ndef\nghi'); 24534 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 24535 * }); 24536 * </file> 24537 * </example> 24538 * 24539 * @element input 24540 * @param {string=} ngList optional delimiter that should be used to split the value. 24541 */ 24542var ngListDirective = function() { 24543 return { 24544 restrict: 'A', 24545 priority: 100, 24546 require: 'ngModel', 24547 link: function(scope, element, attr, ctrl) { 24548 // We want to control whitespace trimming so we use this convoluted approach 24549 // to access the ngList attribute, which doesn't pre-trim the attribute 24550 var ngList = element.attr(attr.$attr.ngList) || ', '; 24551 var trimValues = attr.ngTrim !== 'false'; 24552 var separator = trimValues ? trim(ngList) : ngList; 24553 24554 var parse = function(viewValue) { 24555 // If the viewValue is invalid (say required but empty) it will be `undefined` 24556 if (isUndefined(viewValue)) return; 24557 24558 var list = []; 24559 24560 if (viewValue) { 24561 forEach(viewValue.split(separator), function(value) { 24562 if (value) list.push(trimValues ? trim(value) : value); 24563 }); 24564 } 24565 24566 return list; 24567 }; 24568 24569 ctrl.$parsers.push(parse); 24570 ctrl.$formatters.push(function(value) { 24571 if (isArray(value)) { 24572 return value.join(ngList); 24573 } 24574 24575 return undefined; 24576 }); 24577 24578 // Override the standard $isEmpty because an empty array means the input is empty. 24579 ctrl.$isEmpty = function(value) { 24580 return !value || !value.length; 24581 }; 24582 } 24583 }; 24584}; 24585 24586/* global VALID_CLASS: true, 24587 INVALID_CLASS: true, 24588 PRISTINE_CLASS: true, 24589 DIRTY_CLASS: true, 24590 UNTOUCHED_CLASS: true, 24591 TOUCHED_CLASS: true, 24592*/ 24593 24594var VALID_CLASS = 'ng-valid', 24595 INVALID_CLASS = 'ng-invalid', 24596 PRISTINE_CLASS = 'ng-pristine', 24597 DIRTY_CLASS = 'ng-dirty', 24598 UNTOUCHED_CLASS = 'ng-untouched', 24599 TOUCHED_CLASS = 'ng-touched', 24600 PENDING_CLASS = 'ng-pending'; 24601 24602var ngModelMinErr = minErr('ngModel'); 24603 24604/** 24605 * @ngdoc type 24606 * @name ngModel.NgModelController 24607 * 24608 * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a 24609 * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue 24610 * is set. 24611 * @property {*} $modelValue The value in the model that the control is bound to. 24612 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 24613 the control reads value from the DOM. The functions are called in array order, each passing 24614 its return value through to the next. The last return value is forwarded to the 24615 {@link ngModel.NgModelController#$validators `$validators`} collection. 24616 24617Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 24618`$viewValue`}. 24619 24620Returning `undefined` from a parser means a parse error occurred. In that case, 24621no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 24622will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 24623is set to `true`. The parse error is stored in `ngModel.$error.parse`. 24624 24625 * 24626 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 24627 the model value changes. The functions are called in reverse array order, each passing the value through to the 24628 next. The last return value is used as the actual DOM value. 24629 Used to format / convert values for display in the control. 24630 * ```js 24631 * function formatter(value) { 24632 * if (value) { 24633 * return value.toUpperCase(); 24634 * } 24635 * } 24636 * ngModel.$formatters.push(formatter); 24637 * ``` 24638 * 24639 * @property {Object.<string, function>} $validators A collection of validators that are applied 24640 * whenever the model value changes. The key value within the object refers to the name of the 24641 * validator while the function refers to the validation operation. The validation operation is 24642 * provided with the model value as an argument and must return a true or false value depending 24643 * on the response of that validation. 24644 * 24645 * ```js 24646 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 24647 * var value = modelValue || viewValue; 24648 * return /[0-9]+/.test(value) && 24649 * /[a-z]+/.test(value) && 24650 * /[A-Z]+/.test(value) && 24651 * /\W+/.test(value); 24652 * }; 24653 * ``` 24654 * 24655 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 24656 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 24657 * is expected to return a promise when it is run during the model validation process. Once the promise 24658 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 24659 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 24660 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 24661 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 24662 * will only run once all synchronous validators have passed. 24663 * 24664 * Please note that if $http is used then it is important that the server returns a success HTTP response code 24665 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 24666 * 24667 * ```js 24668 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 24669 * var value = modelValue || viewValue; 24670 * 24671 * // Lookup user by username 24672 * return $http.get('/api/users/' + value). 24673 * then(function resolved() { 24674 * //username exists, this means validation fails 24675 * return $q.reject('exists'); 24676 * }, function rejected() { 24677 * //username does not exist, therefore this validation passes 24678 * return true; 24679 * }); 24680 * }; 24681 * ``` 24682 * 24683 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 24684 * view value has changed. It is called with no arguments, and its return value is ignored. 24685 * This can be used in place of additional $watches against the model value. 24686 * 24687 * @property {Object} $error An object hash with all failing validator ids as keys. 24688 * @property {Object} $pending An object hash with all pending validator ids as keys. 24689 * 24690 * @property {boolean} $untouched True if control has not lost focus yet. 24691 * @property {boolean} $touched True if control has lost focus. 24692 * @property {boolean} $pristine True if user has not interacted with the control yet. 24693 * @property {boolean} $dirty True if user has already interacted with the control. 24694 * @property {boolean} $valid True if there is no error. 24695 * @property {boolean}
24695 $invalid True if at least one error on the control. 24696 * @property {string} $name The name attribute of the control. 24697 * 24698 * @description 24699 * 24700 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 24701 * The controller contains services for data-binding, validation, CSS updates, and value formatting 24702 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 24703 * listening to DOM events. 24704 * Such DOM related logic should be provided by other directives which make use of 24705 * `NgModelController` for data-binding to control elements. 24706 * Angular provides this DOM logic for most {@link input `input`} elements. 24707 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 24708 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 24709 * 24710 * @example 24711 * ### Custom Control Example 24712 * This example shows how to use `NgModelController` with a custom control to achieve 24713 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 24714 * collaborate together to achieve the desired result. 24715 * 24716 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 24717 * contents be edited in place by the user. 24718 * 24719 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 24720 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 24721 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 24722 * that content using the `$sce` service. 24723 * 24724 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 24725 <file name="style.css"> 24726 [contenteditable] { 24727 border: 1px solid black; 24728 background-color: white; 24729 min-height: 20px; 24730 } 24731 24732 .ng-invalid { 24733 border: 1px solid red; 24734 } 24735 24736 </file> 24737 <file name="script.js"> 24738 angular.module('customControl', ['ngSanitize']). 24739 directive('contenteditable', ['$sce', function($sce) { 24740 return { 24741 restrict: 'A', // only activate on element attribute 24742 require: '?ngModel', // get a hold of NgModelController 24743 link: function(scope, element, attrs, ngModel) { 24744 if (!ngModel) return; // do nothing if no ng-model 24745 24746 // Specify how UI should be updated 24747 ngModel.$render = function() { 24748 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 24749 }; 24750 24751 // Listen for change events to enable binding 24752 element.on('blur keyup change', function() { 24753 scope.$evalAsync(read); 24754 }); 24755 read(); // initialize 24756 24757 // Write data to the model 24758 function read() { 24759 var html = element.html(); 24760 // When we clear the content editable the browser leaves a <br> behind 24761 // If strip-br attribute is provided then we strip this out 24762 if ( attrs.stripBr && html == '<br>' ) { 24763 html = ''; 24764 } 24765 ngModel.$setViewValue(html); 24766 } 24767 } 24768 }; 24769 }]); 24770 </file> 24771 <file name="index.html"> 24772 <form name="myForm"> 24773 <div contenteditable 24774 name="myWidget" ng-model="userContent" 24775 strip-br="true" 24776 required>Change me!</div> 24777 <span ng-show="myForm.myWidget.$error.required">Required!</span> 24778 <hr> 24779 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> 24780 </form> 24781 </file> 24782 <file name="protractor.js" type="protractor"> 24783 it('should data-bind and become invalid', function() { 24784 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 24785 // SafariDriver can't handle contenteditable 24786 // and Firefox driver can't clear contenteditables very well 24787 return; 24788 } 24789 var contentEditable = element(by.css('[contenteditable]')); 24790 var content = 'Change me!'; 24791 24792 expect(contentEditable.getText()).toEqual(content); 24793 24794 contentEditable.clear(); 24795 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 24796 expect(contentEditable.getText()).toEqual(''); 24797 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 24798 });
24799 </file> 24800 * </example> 24801 * 24802 * 24803 */ 24804var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', 24805 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { 24806 this.$viewValue = Number.NaN; 24807 this.$modelValue = Number.NaN; 24808 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 24809 this.$validators = {}; 24810 this.$asyncValidators = {}; 24811 this.$parsers = []; 24812 this.$formatters = []; 24813 this.$viewChangeListeners = []; 24814 this.$untouched = true; 24815 this.$touched = false; 24816 this.$pristine = true; 24817 this.$dirty = false; 24818 this.$valid = true; 24819 this.$invalid = false; 24820 this.$error = {}; // keep invalid keys here 24821 this.$$success = {}; // keep valid keys here 24822 this.$pending = undefined; // keep pending keys here 24823 this.$name = $interpolate($attr.name || '', false)($scope); 24824 this.$$parentForm = nullFormCtrl; 24825 24826 var parsedNgModel = $parse($attr.ngModel), 24827 parsedNgModelAssign = parsedNgModel.assign, 24828 ngModelGet = parsedNgModel, 24829 ngModelSet = parsedNgModelAssign, 24830 pendingDebounce = null, 24831 parserValid, 24832 ctrl = this; 24833 24834 this.$$setOptions = function(options) { 24835 ctrl.$options = options; 24836 if (options && options.getterSetter) { 24837 var invokeModelGetter = $parse($attr.ngModel + '()'), 24838 invokeModelSetter = $parse($attr.ngModel + '($$$p)'); 24839 24840 ngModelGet = function($scope) { 24841 var modelValue = parsedNgModel($scope); 24842 if (isFunction(modelValue)) { 24843 modelValue = invokeModelGetter($scope); 24844 } 24845 return modelValue; 24846 }; 24847 ngModelSet = function($scope, newValue) { 24848 if (isFunction(parsedNgModel($scope))) { 24849 invokeModelSetter($scope, {$$$p: ctrl.$modelValue}); 24850 } else { 24851 parsedNgModelAssign($scope, ctrl.$modelValue); 24852 } 24853 }; 24854 } else if (!parsedNgModel.assign) { 24855 throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 24856 $attr.ngModel, startingTag($element)); 24857 } 24858 }; 24859 24860 /** 24861 * @ngdoc method 24862 * @name ngModel.NgModelController#$render 24863 * 24864 * @description 24865 * Called when the view needs to be updated. It is expected that the user of the ng-model 24866 * directive will implement this method. 24867 * 24868 * The `$render()` method is invoked in the following situations: 24869 * 24870 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 24871 * committed value then `$render()` is called to update the input control. 24872 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 24873 * the `$viewValue` are different from last time. 24874 * 24875 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 24876 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue` 24877 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 24878 * invoked if you only change a property on the objects. 24879 */ 24880 this.$render = noop; 24881 24882 /** 24883 * @ngdoc method 24884 * @name ngModel.NgModelController#$isEmpty 24885 * 24886 * @description 24887 * This is called when we need to determine if the value of an input is empty. 24888 * 24889 * For instance, the required directive does this to work out if the input has data or not. 24890 * 24891 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 24892 * 24893 * You can override this for input directives whose concept of being empty is different from the 24894 * default. The `checkboxInputType` directive does this because in its case a value of `false` 24895 * implies empty. 24896 * 24897 * @param {*} value The value of the input to check for emptiness. 24898 * @returns {boolean} True if `value` is "empty". 24899 */ 24900 this.$isEmpty = function(value) { 24901 return isUndefined(value) || value === '' || value === null || value !== value; 24902 }; 24903 24904 var currentValidationRunId = 0; 24905 24906 /** 24907 * @ngdoc method 24908 * @name ngModel.NgModelController#$setValidity 24909 * 24910 * @description 24911 * Change the validity state, and notify the form. 24912 * 24913 * This method can be called within $parsers/$formatters or a custom validation implementation. 24914 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 24915 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 24916 * 24917 * @param {string}
24917 validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 24918 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 24919 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding. 24920 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 24921 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 24922 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 24923 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 24924 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 24925 * Skipped is used by Angular when validators do not run because of parse errors and 24926 * when `$asyncValidators` do not run because any of the `$validators` failed. 24927 */ 24928 addSetValidityMethod({ 24929 ctrl: this, 24930 $element: $element, 24931 set: function(object, property) { 24932 object[property] = true; 24933 }, 24934 unset: function(object, property) { 24935 delete object[property]; 24936 }, 24937 $animate: $animate 24938 }); 24939 24940 /** 24941 * @ngdoc method 24942 * @name ngModel.NgModelController#$setPristine 24943 * 24944 * @description 24945 * Sets the control to its pristine state. 24946 * 24947 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 24948 * state (`ng-pristine` class). A model is considered to be pristine when the control 24949 * has not been changed from when first compiled. 24950 */ 24951 this.$setPristine = function() { 24952 ctrl.$dirty = false; 24953 ctrl.$pristine = true; 24954 $animate.removeClass($element, DIRTY_CLASS); 24955 $animate.addClass($element, PRISTINE_CLASS); 24956 }; 24957 24958 /** 24959 * @ngdoc method 24960 * @name ngModel.NgModelController#$setDirty 24961 * 24962 * @description 24963 * Sets the control to its dirty state. 24964 * 24965 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 24966 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 24967 * from when first compiled. 24968 */ 24969 this.$setDirty = function() { 24970 ctrl.$dirty = true; 24971 ctrl.$pristine = false; 24972 $animate.removeClass($element, PRISTINE_CLASS); 24973 $animate.addClass($element, DIRTY_CLASS); 24974 ctrl.$$parentForm.$setDirty(); 24975 }; 24976 24977 /** 24978 * @ngdoc method 24979 * @name ngModel.NgModelController#$setUntouched 24980 * 24981 * @description 24982 * Sets the control to its untouched state. 24983 * 24984 * This method can be called to remove the `ng-touched` class and set the control to its 24985 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 24986 * by default, however this function can be used to restore that state if the model has 24987 * already been touched by the user. 24988 */ 24989 this.$setUntouched = function() { 24990 ctrl.$touched = false; 24991 ctrl.$untouched = true; 24992 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); 24993 }; 24994 24995 /** 24996 * @ngdoc method 24997 * @name ngModel.NgModelController#$setTouched 24998 * 24999 * @description 25000 * Sets the control to its touched state. 25001 * 25002 * This method can be called to remove the `ng-untouched` class and set the control to its 25003 * touched state (`ng-touched` class). A model is considered to be touched when the user has 25004 * first focused the control element and then shifted focus away from the control (blur event). 25005 */ 25006 this.$setTouched = function() { 25007 ctrl.$touched = true; 25008 ctrl.$untouched = false; 25009 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); 25010 }; 25011 25012 /** 25013 * @ngdoc method 25014 * @name ngModel.NgModelController#$rollbackViewValue 25015 * 25016 * @description 25017 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 25018 * which may be caused by a pending debounced event or because the input is waiting for a some 25019 * future event. 25020 * 25021 * If you have an input that uses `ng-model-options` to set up debounced events or events such 25022 * as blur you can have a situation where there is a period when the `$viewValue` 25023 * is out of synch with the ngModel's `$modelValue`. 25024 * 25025 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue` 25026 * programmatically before these debounced/future events have resolved/occurred, because Angular's 25027 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 25028 * 25029 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 25030 * input which may have such events pending. This is important in order to make sure that the 25031 * input field will be updated with the new model value and any pending operations are cancelled. 25032 * 25033 * <example name="ng-model-cancel-update" module="cancel-update-example"> 25034 * <file name="app.js">
25035 * angular.module('cancel-update-example', []) 25036 * 25037 * .controller('CancelUpdateController', ['$scope', function($scope) { 25038 * $scope.resetWithCancel = function(e) { 25039 * if (e.keyCode == 27) { 25040 * $scope.myForm.myInput1.$rollbackViewValue(); 25041 * $scope.myValue = ''; 25042 * } 25043 * }; 25044 * $scope.resetWithoutCancel = function(e) { 25045 * if (e.keyCode == 27) { 25046 * $scope.myValue = ''; 25047 * } 25048 * }; 25049 * }]); 25050 * </file> 25051 * <file name="index.html"> 25052 * <div ng-controller="CancelUpdateController"> 25053 * <p>Try typing something in each input. See that the model only updates when you 25054 * blur off the input. 25055 * </p> 25056 * <p>Now see what happens if you start typing then press the Escape key</p> 25057 * 25058 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 25059 * <p id="inputDescription1">With $rollbackViewValue()</p> 25060 * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue" 25061 * ng-keydown="resetWithCancel($event)"><br/> 25062 * myValue: "{{ myValue }}" 25063 * 25064 * <p id="inputDescription2">Without $rollbackViewValue()</p> 25065 * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue" 25066 * ng-keydown="resetWithoutCancel($event)"><br/> 25067 * myValue: "{{ myValue }}" 25068 * </form> 25069 * </div> 25070 * </file> 25071 * </example> 25072 */ 25073 this.$rollbackViewValue = function() { 25074 $timeout.cancel(pendingDebounce); 25075 ctrl.$viewValue = ctrl.$$lastCommittedViewValue; 25076 ctrl.$render(); 25077 }; 25078 25079 /** 25080 * @ngdoc method 25081 * @name ngModel.NgModelController#$validate 25082 * 25083 * @description 25084 * Runs each of the registered validators (first synchronous validators and then 25085 * asynchronous validators). 25086 * If the validity changes to invalid, the model will be set to `undefined`, 25087 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 25088 * If the validity changes to valid, it will set the model to the last available valid 25089 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. 25090 */ 25091 this.$validate = function() { 25092 // ignore $validate before model is initialized 25093 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 25094 return; 25095 } 25096 25097 var viewValue = ctrl.$$lastCommittedViewValue; 25098 // Note: we use the $$rawModelValue as $modelValue might have been 25099 // set to undefined during a view -> model update that found validation 25100 // errors. We can't parse the view here, since that could change 25101 // the model although neither viewValue nor the model on the scope changed 25102 var modelValue = ctrl.$$rawModelValue; 25103 25104 var prevValid = ctrl.$valid; 25105 var prevModelValue = ctrl.$modelValue; 25106 25107 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 25108 25109 ctrl.$$runValidators(modelValue, viewValue, function(allValid) { 25110 // If there was no change in validity, don't update the model 25111 // This prevents changing an invalid modelValue to undefined 25112 if (!allowInvalid && prevValid !== allValid) { 25113 // Note: Don't check ctrl.$valid here, as we could have 25114 // external validators (e.g. calculated on the server), 25115 // that just call $setValidity and need the model value 25116 // to calculate their validity. 25117 ctrl.$modelValue = allValid ? modelValue : undefined; 25118 25119 if (ctrl.$modelValue !== prevModelValue) { 25120 ctrl.$$writeModelToScope(); 25121 } 25122 } 25123 }); 25124 25125 }; 25126 25127 this.$$runValidators = function(modelValue, viewValue, doneCallback) { 25128 currentValidationRunId++; 25129 var localValidationRunId = currentValidationRunId; 25130 25131 // check parser error 25132 if (!processParseErrors()) { 25133 validationDone(false); 25134 return; 25135 } 25136 if (!processSyncValidators()) { 25137 validationDone(false); 25138 return; 25139 } 25140 processAsyncValidators(); 25141 25142 function processParseErrors() { 25143 var errorKey = ctrl.$$parserName || 'parse'; 25144 if (isUndefined(parserValid)) {
25145 setValidity(errorKey, null); 25146 } else { 25147 if (!parserValid) { 25148 forEach(ctrl.$validators, function(v, name) { 25149 setValidity(name, null); 25150 }); 25151 forEach(ctrl.$asyncValidators, function(v, name) { 25152 setValidity(name, null); 25153 }); 25154 } 25155 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName 25156 setValidity(errorKey, parserValid); 25157 return parserValid; 25158 } 25159 return true; 25160 } 25161 25162 function processSyncValidators() { 25163 var syncValidatorsValid = true; 25164 forEach(ctrl.$validators, function(validator, name) { 25165 var result = validator(modelValue, viewValue); 25166 syncValidatorsValid = syncValidatorsValid && result; 25167 setValidity(name, result); 25168 }); 25169 if (!syncValidatorsValid) { 25170 forEach(ctrl.$asyncValidators, function(v, name) { 25171 setValidity(name, null); 25172 }); 25173 return false; 25174 } 25175 return true; 25176 } 25177 25178 function processAsyncValidators() { 25179 var validatorPromises = []; 25180 var allValid = true; 25181 forEach(ctrl.$asyncValidators, function(validator, name) { 25182 var promise = validator(modelValue, viewValue); 25183 if (!isPromiseLike(promise)) { 25184 throw ngModelMinErr("$asyncValidators", 25185 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise); 25186 } 25187 setValidity(name, undefined); 25188 validatorPromises.push(promise.then(function() { 25189 setValidity(name, true); 25190 }, function(error) { 25191 allValid = false; 25192 setValidity(name, false); 25193 })); 25194 }); 25195 if (!validatorPromises.length) { 25196 validationDone(true); 25197 } else { 25198 $q.all(validatorPromises).then(function() { 25199 validationDone(allValid); 25200 }, noop); 25201 } 25202 } 25203 25204 function setValidity(name, isValid) { 25205 if (localValidationRunId === currentValidationRunId) { 25206 ctrl.$setValidity(name, isValid); 25207 } 25208 } 25209 25210 function validationDone(allValid) { 25211 if (localValidationRunId === currentValidationRunId) { 25212 25213 doneCallback(allValid); 25214 } 25215 } 25216 }; 25217 25218 /** 25219 * @ngdoc method 25220 * @name ngModel.NgModelController#$commitViewValue 25221 * 25222 * @description 25223 * Commit a pending update to the `$modelValue`. 25224 * 25225 * Updates may be pending by a debounced event or because the input is waiting for a some future 25226 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 25227 * usually handles calling this in response to input events. 25228 */ 25229 this.$commitViewValue = function() { 25230 var viewValue = ctrl.$viewValue; 25231 25232 $timeout.cancel(pendingDebounce); 25233 25234 // If the view value has not changed then we should just exit, except in the case where there is 25235 // a native validator on the element. In this case the validation state may have changed even though 25236 // the viewValue has stayed empty. 25237 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { 25238 return; 25239 } 25240 ctrl.$$lastCommittedViewValue = viewValue; 25241 25242 // change to dirty 25243 if (ctrl.$pristine) { 25244 this.$setDirty(); 25245 } 25246 this.$$parseAndValidate(); 25247 }; 25248 25249 this.$$parseAndValidate = function() { 25250 var viewValue = ctrl.$$lastCommittedViewValue; 25251 var modelValue = viewValue; 25252 parserValid = isUndefined(modelValue) ? undefined : true; 25253 25254 if (parserValid) { 25255 for (var i = 0; i < ctrl.$parsers.length; i++) { 25256 modelValue = ctrl.$parsers[i](modelValue); 25257 if (isUndefined(modelValue)) { 25258 parserValid = false; 25259 break; 25260 } 25261 } 25262 } 25263 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 25264 // ctrl.$modelValue has not been touched yet... 25265 ctrl.$modelValue = ngModelGet($scope); 25266 } 25267 var prevModelValue = ctrl.$modelValue; 25268 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 25269 ctrl.$$rawModelValue = modelValue; 25270 25271 if (allowInvalid) { 25272 ctrl.$modelValue = modelValue; 25273 writeToModelIfNeeded(); 25274 } 25275 25276 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 25277 // This can happen if e.g. $setViewValue is called from inside a parser 25278 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { 25279 if (!allowInvalid) { 25280 // Note: Don't check ctrl.$valid here, as we could have 25281 // external validators (e.g. calculated on the server), 25282 // that just call $setValidity and need the model value 25283 // to calculate their validity. 25284 ctrl.$modelValue = allValid ? modelValue : undefined; 25285 writeToModelIfNeeded(); 25286 } 25287 });
25288 25289 function writeToModelIfNeeded() { 25290 if (ctrl.$modelValue !== prevModelValue) { 25291 ctrl.$$writeModelToScope(); 25292 } 25293 } 25294 }; 25295 25296 this.$$writeModelToScope = function() { 25297 ngModelSet($scope, ctrl.$modelValue); 25298 forEach(ctrl.$viewChangeListeners, function(listener) { 25299 try { 25300 listener(); 25301 } catch (e) { 25302 $exceptionHandler(e); 25303 } 25304 }); 25305 }; 25306 25307 /** 25308 * @ngdoc method 25309 * @name ngModel.NgModelController#$setViewValue 25310 * 25311 * @description 25312 * Update the view value. 25313 * 25314 * This method should be called when a control wants to change the view value; typically, 25315 * this is done from within a DOM event handler. For example, the {@link ng.directive:input input} 25316 * directive calls it when the value of the input changes and {@link ng.directive:select select} 25317 * calls it when an option is selected. 25318 * 25319 * When `$setViewValue` is called, the new `value` will be staged for committing through the `$parsers` 25320 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 25321 * value sent directly for processing, finally to be applied to `$modelValue` and then the 25322 * **expression** specified in the `ng-model` attribute. Lastly, all the registered change listeners, 25323 * in the `$viewChangeListeners` list, are called. 25324 * 25325 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 25326 * and the `default` trigger is not listed, all those actions will remain pending until one of the 25327 * `updateOn` events is triggered on the DOM element. 25328 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 25329 * directive is used with a custom debounce for this particular event. 25330 * Note that a `$digest` is only triggered once the `updateOn` events are fired, or if `debounce` 25331 * is specified, once the timer runs out. 25332 * 25333 * When used with standard inputs, the view value will always be a string (which is in some cases 25334 * parsed into another type, such as a `Date` object for `input[date]`.) 25335 * However, custom controls might also pass objects to this method. In this case, we should make 25336 * a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not 25337 * perform a deep watch of objects, it only looks for a change of identity. If you only change 25338 * the property of the object then ngModel will not realise that the object has changed and 25339 * will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should 25340 * not change properties of the copy once it has been passed to `$setViewValue`. 25341 * Otherwise you may cause the model value on the scope to change incorrectly. 25342 * 25343 * <div class="alert alert-info"> 25344 * In any case, the value passed to the method should always reflect the current value 25345 * of the control. For example, if you are calling `$setViewValue` for an input element, 25346 * you should pass the input DOM value. Otherwise, the control and the scope model become 25347 * out of sync. It's also important to note that `$setViewValue` does not call `$render` or change 25348 * the control's DOM value in any way. If we want to change the control's DOM value 25349 * programmatically, we should update the `ngModel` scope expression. Its new value will be 25350 * picked up by the model controller, which will run it through the `$formatters`, `$render` it 25351 * to update the DOM, and finally call `$validate` on it. 25352 * </div> 25353 * 25354 * @param {*} value value from the view. 25355 * @param {string} trigger Event that triggered the update. 25356 */ 25357 this.$setViewValue = function(value, trigger) { 25358 ctrl.$viewValue = value; 25359 if (!ctrl.$options || ctrl.$options.updateOnDefault) { 25360 ctrl.$$debounceViewValueCommit(trigger); 25361 } 25362 }; 25363 25364 this.$$debounceViewValueCommit = function(trigger) { 25365 var debounceDelay = 0, 25366 options = ctrl.$options, 25367 debounce; 25368 25369 if (options && isDefined(options.debounce)) { 25370 debounce = options.debounce; 25371 if (isNumber(debounce)) { 25372 debounceDelay = debounce; 25373 } else if (isNumber(debounce[trigger])) { 25374 debounceDelay = debounce[trigger]; 25375 } else if (isNumber(debounce['default'])) { 25376 debounceDelay = debounce['default']; 25377 } 25378 } 25379 25380 $timeout.cancel(pendingDebounce); 25381 if (debounceDelay) { 25382 pendingDebounce = $timeout(function() { 25383 ctrl.$commitViewValue(); 25384 }, debounceDelay); 25385 } else if ($rootScope.$$phase) { 25386 ctrl.$commitViewValue(); 25387 } else { 25388 $scope.$apply(function() { 25389 ctrl.$commitViewValue(); 25390 }); 25391 } 25392 }; 25393 25394 // model -> value 25395 // Note: we cannot use a normal scope.$watch as we want to detect the following: 25396 // 1. scope value is 'a' 25397 // 2. user enters 'b' 25398 // 3. ng-change kicks in and reverts scope value to 'a' 25399 // ->
25399 scope value did not change since the last digest as 25400 // ng-change executes in apply phase 25401 // 4. view should be changed back to 'a' 25402 $scope.$watch(function ngModelWatch() { 25403 var modelValue = ngModelGet($scope); 25404 25405 // if scope model value and ngModel value are out of sync 25406 // TODO(perf): why not move this to the action fn? 25407 if (modelValue !== ctrl.$modelValue && 25408 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator 25409 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) 25410 ) { 25411 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; 25412 parserValid = undefined; 25413 25414 var formatters = ctrl.$formatters, 25415 idx = formatters.length; 25416 25417 var viewValue = modelValue; 25418 while (idx--) { 25419 viewValue = formatters[idx](viewValue); 25420 } 25421 if (ctrl.$viewValue !== viewValue) { 25422 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; 25423 ctrl.$render(); 25424 25425 ctrl.$$runValidators(modelValue, viewValue, noop); 25426 } 25427 } 25428 25429 return modelValue; 25430 }); 25431}]; 25432 25433 25434/** 25435 * @ngdoc directive 25436 * @name ngModel 25437 * 25438 * @element input 25439 * @priority 1 25440 * 25441 * @description 25442 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 25443 * property on the scope using {@link ngModel.NgModelController NgModelController}, 25444 * which is created and exposed by this directive. 25445 * 25446 * `ngModel` is responsible for: 25447 * 25448 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 25449 * require. 25450 * - Providing validation behavior (i.e. required, number, email, url). 25451 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 25452 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations. 25453 * - Registering the control with its parent {@link ng.directive:form form}. 25454 * 25455 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 25456 * current scope. If the property doesn't already exist on this scope, it will be created 25457 * implicitly and added to the scope. 25458 * 25459 * For best practices on using `ngModel`, see: 25460 * 25461 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 25462 * 25463 * For basic examples, how to use `ngModel`, see: 25464 * 25465 * - {@link ng.directive:input input} 25466 * - {@link input[text] text} 25467 * - {@link input[checkbox] checkbox} 25468 * - {@link input[radio] radio} 25469 * - {@link input[number] number} 25470 * - {@link input[email] email} 25471 * - {@link input[url] url} 25472 * - {@link input[date] date} 25473 * - {@link input[datetime-local] datetime-local} 25474 * - {@link input[time] time} 25475 * - {@link input[month] month} 25476 * - {@link input[week] week} 25477 * - {@link ng.directive:select select} 25478 * - {@link ng.directive:textarea textarea} 25479 * 25480 * # CSS classes 25481 * The following CSS classes are added and removed on the associated input/select/textarea element 25482 * depending on the validity of the model. 25483 * 25484 * - `ng-valid`: the model is valid 25485 * - `ng-invalid`: the model is invalid 25486 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 25487 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 25488 * - `ng-pristine`: the control hasn't been interacted with yet 25489 * - `ng-dirty`: the control has been interacted with 25490 * - `ng-touched`: the control has been blurred 25491 * - `ng-untouched`: the control hasn't been blurred 25492 * - `ng-pending`: any `$asyncValidators` are unfulfilled 25493 * 25494 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 25495 * 25496 * ## Animation Hooks 25497 * 25498 * Animations within models are triggered when any of the associated CSS classes are added and removed 25499 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, 25500 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 25501 * The animations that are triggered within ngModel are similar to how they work in ngClass and 25502 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 25503 * 25504 * The following example shows a simple way to utilize CSS transitions to style an input element
25505 * that has been rendered as invalid after it has been validated: 25506 * 25507 * <pre> 25508 * //be sure to include ngAnimate as a module to hook into more 25509 * //advanced animations 25510 * .my-input { 25511 * transition:0.5s linear all; 25512 * background: white; 25513 * } 25514 * .my-input.ng-invalid { 25515 * background: red; 25516 * color:white; 25517 * } 25518 * </pre> 25519 * 25520 * @example 25521 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> 25522 <file name="index.html"> 25523 <script> 25524 angular.module('inputExample', []) 25525 .controller('ExampleController', ['$scope', function($scope) { 25526 $scope.val = '1'; 25527 }]); 25528 </script> 25529 <style> 25530 .my-input { 25531 transition:all linear 0.5s; 25532 background: transparent; 25533 } 25534 .my-input.ng-invalid { 25535 color:white; 25536 background: red; 25537 } 25538 </style> 25539 <p id="inputDescription"> 25540 Update input to see transitions when valid/invalid. 25541 Integer is a valid value. 25542 </p>
25543 <form name="testForm" ng-controller="ExampleController"> 25544 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" 25545 aria-describedby="inputDescription" /> 25546 </form> 25547 </file> 25548 * </example> 25549 * 25550 * ## Binding to a getter/setter 25551 * 25552 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 25553 * function that returns a representation of the model when called with zero arguments, and sets 25554 * the internal state of a model when called with an argument. It's sometimes useful to use this 25555 * for models that have an internal representation that's different from what the model exposes 25556 * to the view. 25557 * 25558 * <div class="alert alert-success"> 25559 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more 25560 * frequently than other parts of your code. 25561 * </div> 25562 * 25563 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 25564 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 25565 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 25566 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 25567 * 25568 * The following example shows how to use `ngModel` with a getter/setter: 25569 * 25570 * @example 25571 * <example name="ngModel-getter-setter" module="getterSetterExample"> 25572 <file name="index.html">
25573 <div ng-controller="ExampleController"> 25574 <form name="userForm"> 25575 <label>Name: 25576 <input type="text" name="userName" 25577 ng-model="user.name" 25578 ng-model-options="{ getterSetter: true }" /> 25579 </label> 25580 </form> 25581 <pre>user.name = <span ng-bind="user.name()"></span></pre> 25582 </div> 25583 </file> 25584 <file name="app.js"> 25585 angular.module('getterSetterExample', []) 25586 .controller('ExampleController', ['$scope', function($scope) { 25587 var _name = 'Brian'; 25588 $scope.user = { 25589 name: function(newName) { 25590 // Note that newName can be undefined for two reasons: 25591 // 1. Because it is called as a getter and thus called with no arguments 25592 // 2. Because the property should actually be set to undefined. This happens e.g. if the 25593 // input is invalid 25594 return arguments.length ? (_name = newName) : _name; 25595 } 25596 }; 25597 }]); 25598 </file> 25599 * </example> 25600 */ 25601var ngModelDirective = ['$rootScope', function($rootScope) { 25602 return { 25603 restrict: 'A', 25604 require: ['ngModel', '^?form', '^?ngModelOptions'], 25605 controller: NgModelController, 25606 // Prelink needs to run before any input directive 25607 // so that we can set the NgModelOptions in NgModelController 25608 // before anyone else uses it. 25609 priority: 1, 25610 compile: function ngModelCompile(element) { 25611 // Setup initial state of the control 25612 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 25613 25614 return { 25615 pre: function ngModelPreLink(scope, element, attr, ctrls) { 25616 var modelCtrl = ctrls[0], 25617 formCtrl = ctrls[1] || modelCtrl.$$parentForm; 25618 25619 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); 25620 25621 // notify others, especially parent forms 25622 formCtrl.$addControl(modelCtrl); 25623 25624 attr.$observe('name', function(newValue) { 25625 if (modelCtrl.$name !== newValue) { 25626 modelCtrl.$$parentForm.$$renameControl(modelCtrl, newValue); 25627 } 25628 }); 25629 25630 scope.$on('$destroy', function() { 25631 modelCtrl.$$parentForm.$removeControl(modelCtrl); 25632 }); 25633 }, 25634 post: function ngModelPostLink(scope, element, attr, ctrls) { 25635 var modelCtrl = ctrls[0]; 25636 if (modelCtrl.$options && modelCtrl.$options.updateOn) { 25637 element.on(modelCtrl.$options.updateOn, function(ev) { 25638 modelCtrl.$$debounceViewValueCommit(ev && ev.type); 25639 }); 25640 } 25641 25642 element.on('blur', function(ev) { 25643 if (modelCtrl.$touched) return; 25644 25645 if ($rootScope.$$phase) { 25646 scope.$evalAsync(modelCtrl.$setTouched); 25647 } else { 25648 scope.$apply(modelCtrl.$setTouched); 25649 } 25650 }); 25651 } 25652 }; 25653 } 25654 }; 25655}]; 25656 25657var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 25658 25659/** 25660 * @ngdoc directive 25661 * @name ngModelOptions 25662 * 25663 * @description 25664 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of 25665 * events that will trigger a model update and/or a debouncing delay so that the actual update only 25666 * takes place when a timer expires; this timer will be reset after another change takes place. 25667 * 25668 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 25669 * be different from the value in the actual model. This means that if you update the model you 25670 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in 25671 * order to make sure it is synchronized with the model and that any debounced action is canceled. 25672 * 25673 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} 25674 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 25675 * important because `form` controllers are published to the related scope under the name in their 25676 * `name` attribute. 25677 * 25678 * Any pending changes will take place immediately when an enclosing form is submitted via the 25679 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 25680 * to have access to the updated model. 25681 * 25682 * `ngModelOptions` has an effect on the element it's declared on and its desce
25682ndants. 25683 * 25684 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: 25685 * - `updateOn`: string specifying which event should the input be bound to. You can set several 25686 * events using an space delimited list. There is a special event called `default` that 25687 * matches the default events belonging of the control. 25688 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 25689 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 25690 * custom value for each event. For example: 25691 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"` 25692 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 25693 * not validate correctly instead of the default behavior of setting the model to undefined. 25694 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to 25695 `ngModel` as getters/setters. 25696 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 25697 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the 25698 * continental US time zone abbreviations, but for general use, use a time zone offset, for 25699 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 25700 * If not specified, the timezone of the browser will be used. 25701 * 25702 * @example 25703 25704 The following example shows how to override immediate updates. Changes on the inputs within the 25705 form will update the model only when the control loses focus (blur event). If `escape` key is 25706 pressed while the input field is focused, the value is reset to the value in the current model. 25707 25708 <example name="ngModelOptions-directive-blur" module="optionsExample"> 25709 <file name="index.html">
25710 <div ng-controller="ExampleController"> 25711 <form name="userForm"> 25712 <label>Name: 25713 <input type="text" name="userName" 25714 ng-model="user.name" 25715 ng-model-options="{ updateOn: 'blur' }" 25716 ng-keyup="cancel($event)" /> 25717 </label><br /> 25718 <label>Other data: 25719 <input type="text" ng-model="user.data" /> 25720 </label><br /> 25721 </form> 25722 <pre>user.name = <span ng-bind="user.name"></span></pre> 25723 </div> 25724 </file> 25725 <file name="app.js"> 25726 angular.module('optionsExample', []) 25727 .controller('ExampleController', ['$scope', function($scope) { 25728 $scope.user = { name: 'say', data: '' }; 25729 25730 $scope.cancel = function(e) { 25731 if (e.keyCode == 27) { 25732 $scope.userForm.userName.$rollbackViewValue(); 25733 } 25734 }; 25735 }]); 25736 </file> 25737 <file name="protractor.js" type="protractor"> 25738 var model = element(by.binding('user.name')); 25739 var input = element(by.model('user.name')); 25740 var other = element(by.model('user.data')); 25741 25742 it('should allow custom events', function() { 25743 input.sendKeys(' hello'); 25744 input.click(); 25745 expect(model.getText()).toEqual('say'); 25746 other.click(); 25747 expect(model.getText()).toEqual('say hello'); 25748 }); 25749 25750 it('should $rollbackViewValue when model changes', function() { 25751 input.sendKeys(' hello'); 25752 expect(input.getAttribute('value')).toEqual('say hello'); 25753 input.sendKeys(protractor.Key.ESCAPE); 25754 expect(input.getAttribute('value')).toEqual('say'); 25755 other.click(); 25756 expect(model.getText()).toEqual('say'); 25757 }); 25758 </file> 25759 </example> 25760 25761 This one shows how to debounce model changes. Model will be updated only 1 sec after last change. 25762 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 25763 25764 <example name="ngModelOptions-directive-debounce" module="optionsExample"> 25765 <file name="index.html">
25766 <div ng-controller="ExampleController"> 25767 <form name="userForm"> 25768 <label>Name: 25769 <input type="text" name="userName" 25770 ng-model="user.name" 25771 ng-model-options="{ debounce: 1000 }" /> 25772 </label> 25773 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button> 25774 <br /> 25775 </form> 25776 <pre>user.name = <span ng-bind="user.name"></span></pre> 25777 </div> 25778 </file> 25779 <file name="app.js"> 25780 angular.module('optionsExample', []) 25781 .controller('ExampleController', ['$scope', function($scope) { 25782 $scope.user = { name: 'say' }; 25783 }]); 25784 </file> 25785 </example> 25786 25787 This one shows how to bind to getter/setters: 25788 25789 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 25790 <file name="index.html"> 25791 <div ng-controller="ExampleController"> 25792 <form name="userForm"> 25793 <label>Name: 25794 <input type="text" name="userName" 25795 ng-model="user.name" 25796 ng-model-options="{ getterSetter: true }" /> 25797 </label> 25798 </form> 25799 <pre>user.name = <span ng-bind="user.name()"></span></pre> 25800 </div> 25801 </file> 25802 <file name="app.js"> 25803 angular.module('getterSetterExample', []) 25804 .controller('ExampleController', ['$scope', function($scope) { 25805 var _name = 'Brian'; 25806 $scope.user = { 25807 name: function(newName) { 25808 // Note that newName can be undefined for two reasons: 25809 // 1. Because it is called as a getter and thus called with no arguments 25810 // 2. Because the property should actually be set to undefined. This happens e.g. if the 25811 // input is invalid 25812 return arguments.length ? (_name = newName) : _name; 25813 } 25814 }; 25815 }]); 25816 </file> 25817 </example> 25818 */ 25819var ngModelOptionsDirective = function() { 25820 return { 25821 restrict: 'A', 25822 controller: ['$scope', '$attrs', function($scope, $attrs) { 25823 var that = this; 25824 this.$options = copy($scope.$eval($attrs.ngModelOptions)); 25825 // Allow adding/overriding bound events 25826 if (isDefined(this.$options.updateOn)) { 25827 this.$options.updateOnDefault = false; 25828 // extract "default" pseudo-event from list of events that can trigger a model update 25829 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { 25830 that.$options.updateOnDefault = true; 25831 return ' '; 25832 })); 25833 } else { 25834 this.$options.updateOnDefault = true; 25835 } 25836 }] 25837 }; 25838}; 25839 25840 25841 25842// helper methods 25843function addSetValidityMethod(context) { 25844 var ctrl = context.ctrl, 25845 $element = context.$element, 25846 classCache = {}, 25847 set = context.set, 25848 unset = context.unset, 25849 $animate = context.$animate; 25850 25851 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); 25852 25853 ctrl.$setValidity = setValidity; 25854 25855 function setValidity(validationErrorKey, state, controller) { 25856 if (isUndefined(state)) { 25857 createAndSet('$pending', validationErrorKey, controller); 25858 } else { 25859 unsetAndCleanup('$pending', validationErrorKey, controller); 25860 } 25861 if (!isBoolean(state)) { 25862 unset(ctrl.$error, validationErrorKey, controller); 25863 unset(ctrl.$$success, validationErrorKey, controller); 25864 } else { 25865 if (state) { 25866 unset(ctrl.$error, validationErrorKey, controller); 25867 set(ctrl.$$success, validationErrorKey, controller); 25868 } else { 25869 set(ctrl.$error, validationErrorKey, controller); 25870 unset(ctrl.$$success, validationErrorKey, controller); 25871 } 25872 } 25873 if (ctrl.$pending) { 25874 cachedToggleClass(PENDING_CLASS, true); 25875 ctrl.$valid = ctrl.$invalid = undefined; 25876 toggleValidationCss('', null); 25877 } else { 25878 cachedToggleClass(PENDING_CLASS, false); 25879 ctrl.$valid = isObjectEmpty(ctrl.$error);
25880 ctrl.$invalid = !ctrl.$valid; 25881 toggleValidationCss('', ctrl.$valid); 25882 } 25883 25884 // re-read the state as the set/unset methods could have 25885 // combined state in ctrl.$error[validationError] (used for forms), 25886 // where setting/unsetting only increments/decrements the value, 25887 // and does not replace it. 25888 var combinedState; 25889 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { 25890 combinedState = undefined; 25891 } else if (ctrl.$error[validationErrorKey]) { 25892 combinedState = false; 25893 } else if (ctrl.$$success[validationErrorKey]) { 25894 combinedState = true; 25895 } else { 25896 combinedState = null; 25897 } 25898 25899 toggleValidationCss(validationErrorKey, combinedState); 25900 ctrl.$$parentForm.$setValidity(validationErrorKey, combinedState, ctrl); 25901 } 25902 25903 function createAndSet(name, value, controller) { 25904 if (!ctrl[name]) { 25905 ctrl[name] = {}; 25906 } 25907 set(ctrl[name], value, controller); 25908 } 25909 25910 function unsetAndCleanup(name, value, controller) { 25911 if (ctrl[name]) { 25912 unset(ctrl[name], value, controller); 25913 } 25914 if (isObjectEmpty(ctrl[name])) { 25915 ctrl[name] = undefined; 25916 } 25917 } 25918 25919 function cachedToggleClass(className, switchValue) { 25920 if (switchValue && !classCache[className]) { 25921 $animate.addClass($element, className); 25922 classCache[className] = true; 25923 } else if (!switchValue && classCache[className]) { 25924 $animate.removeClass($element, className); 25925 classCache[className] = false; 25926 } 25927 } 25928 25929 function toggleValidationCss(validationErrorKey, isValid) { 25930 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 25931 25932 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); 25933 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); 25934 } 25935} 25936 25937function isObjectEmpty(obj) { 25938 if (obj) { 25939 for (var prop in obj) { 25940 if (obj.hasOwnProperty(prop)) { 25941 return false; 25942 } 25943 } 25944 } 25945 return true; 25946} 25947 25948/** 25949 * @ngdoc directive 25950 * @name ngNonBindable 25951 * @restrict AC 25952 * @priority 1000 25953 * 25954 * @description 25955 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 25956 * DOM element. This is useful if the element contains what appears to be Angular directives and 25957 * bindings but which should be ignored by Angular. This could be the case if you have a site that 25958 * displays snippets of code, for instance. 25959 * 25960 * @element ANY 25961 * 25962 * @example 25963 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 25964 * but the one wrapped in `ngNonBindable` is left alone. 25965 * 25966 * @example 25967 <example> 25968 <file name="index.html"> 25969 <div>Normal: {{1 + 2}}</div> 25970 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 25971 </file> 25972 <file name="protractor.js" type="protractor"> 25973 it('should check ng-non-bindable', function() { 25974 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 25975 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 25976 }); 25977 </file> 25978 </example> 25979 */ 25980var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 25981 25982/* global jqLiteRemove */ 25983 25984var ngOptionsMinErr = minErr('ngOptions'); 25985 25986/** 25987 * @ngdoc directive 25988 * @name ngOptions 25989 * @restrict A 25990 * 25991 * @description 25992 * 25993 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 25994 * elements for the `<select>` element using the array or object obtained by evaluating the 25995 * `ngOptions` comprehension expression. 25996 * 25997 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a 25998 * similar result. However, `ngOptions` provides some benefits such as reducing memory and 25999 * increasing speed by not creating a new scope for each repeated instance, as well as providing 26000 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 26001 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound 26002 * to a non-string value. This is because an option element can only be bound to string values at 26003 * present. 26004 * 26005 * When an item in the `<select>` menu is selected, the array element or object property 26006 * represented by the selected option will be bound to the model identified by the `ngModel` 26007 * directive. 26008 * 26009 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 26010 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 26011 * option. See example below for demonstration. 26012 * 26013 * ## Complex Models (objects or collections) 26014 * 26015 * **Note:** By default, `ngModel` watches the model by reference, not value. This is important when 26016 * binding any input directive to a model that is an object or a collection. 26017 * 26018 * Since this is a common situation for `ngOptions` the directive additionally watches the model using 26019 * `$watchCollection` when the select has the `multiple` attribute or when there is a `track by` clause in 26020 * the options expression. This allows ngOptions to trigger a re-rendering of the options even if the actual 26021 * object/collection has not changed identity but only a property on the object or an item in the collection 26022 * changes. 26023 * 26024 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection 26025 * if the model is an array). This means that changing a property deeper inside the object/collection that the 26026 * first level will not trigger a re-rendering. 26027 * 26028 * 26029 * ## `select` **`as`** 26030 * 26031 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 26032 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 26033 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 26034 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 26035 * 26036 * 26037 * ### `select` **`as`** and **`track by`** 26038 * 26039 * <div class="alert alert-warning"> 26040 * Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together. 26041 * </div> 26042 * 26043 * Consider the following example: 26044 * 26045 * ```html 26046 * <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"></select> 26047 * ``` 26048 * 26049 * ```js 26050 * $scope.values = [{ 26051 * id: 1, 26052 * label: 'aLabel', 26053 * subItem: { name: 'aSubItem' } 26054 * }, { 26055 * id: 2, 26056 * label: 'bLabel',
26057 * subItem: { name: 'bSubItem' } 26058 * }]; 26059 * 26060 * $scope.selected = { name: 'aSubItem' }; 26061 * ``` 26062 * 26063 * With the purpose of preserving the selection, the **`track by`** expression is always applied to the element 26064 * of the data source (to `item` in this example). To calculate whether an element is selected, we do the 26065 * following: 26066 * 26067 * 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]` 26068 * 2. Apply **`track by`** to the already selected value in `ngModel`. 26069 * In the example: this is not possible as **`track by`** refers to `item.id`, but the selected 26070 * value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to 26071 * a wrong object, the selected element can't be found, `<select>` is always reset to the "not 26072 * selected" option. 26073 * 26074 * 26075 * @param {string} ngModel Assignable angular expression to data-bind to. 26076 * @param {string=} name Property name of the form under which the control is published. 26077 * @param {string=} required The control is considered valid only if value is entered. 26078 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 26079 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 26080 * `required` when you want to data-bind to the `required` attribute. 26081 * @param {comprehension_expression=} ngOptions in one of the following forms: 26082 * 26083 * * for array data sources: 26084 * * `label` **`for`** `value` **`in`** `array` 26085 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 26086 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 26087 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` 26088 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 26089 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 26090 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 26091 * (for including a filter with `track by`) 26092 * * for object data sources: 26093 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 26094 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 26095 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 26096 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` 26097 * * `select` **`as`** `label` **`group by`** `group` 26098 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 26099 * * `select` **`as`** `label` **`disable when`** `disable` 26100 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 26101 * 26102 * Where: 26103 * 26104 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 26105 * * `value`: local variable which will refer to each item in the `array` or each property value 26106 * of `object` during iteration. 26107 * * `key`: local variable which will refer to a property name in `object` during iteration. 26108 * * `label`: The result of this expression will be the label for `<option>` element. The 26109 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 26110 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 26111 * element. If not specified, `select` expression will default to `value`. 26112 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 26113 * DOM element. 26114 * * `disable`: The result of this expression will be used to disable the rendered `<option>` 26115 * element. Return `true` to disable. 26116 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 26117 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 26118 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 26119 * even when the options are recreated (e.g. reloaded from the server). 26120 * 26121 * @example 26122 <example module="selectExample"> 26123 <file name="index.html"> 26124 <script> 26125 angular.module('selectExample', [])
26126 .controller('ExampleController', ['$scope', function($scope) { 26127 $scope.colors = [ 26128 {name:'black', shade:'dark'}, 26129 {name:'white', shade:'light', notAnOption: true}, 26130 {name:'red', shade:'dark'}, 26131 {name:'blue', shade:'dark', notAnOption: true}, 26132 {name:'yellow', shade:'light', notAnOption: false} 26133 ]; 26134 $scope.myColor = $scope.colors[2]; // red 26135 }]); 26136 </script> 26137 <div ng-controller="ExampleController"> 26138 <ul> 26139 <li ng-repeat="color in colors"> 26140 <label>Name: <input ng-model="color.name"></label> 26141 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> 26142 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> 26143 </li> 26144 <li> 26145 <button ng-click="colors.push({})">add</button> 26146 </li> 26147 </ul> 26148 <hr/> 26149 <label>Color (null not allowed): 26150 <select ng-model="myColor" ng-options="color.name for color in colors"></select> 26151 </label><br/> 26152 <label>Color (null allowed): 26153 <span class="nullable"> 26154 <select ng-model="myColor" ng-options="color.name for color in colors"> 26155 <option value="">-- choose color --</option> 26156 </select> 26157 </span></label><br/> 26158 26159 <label>Color grouped by shade: 26160 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 26161 </select> 26162 </label><br/> 26163 26164 <label>Color grouped by shade, with some disabled: 26165 <select ng-model="myColor" 26166 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> 26167 </select> 26168 </label><br/> 26169 26170 26171 26172 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. 26173 <br/> 26174 <hr/> 26175 Currently selected: {{ {selected_color:myColor} }} 26176 <div style="border:solid 1px black; height:20px" 26177 ng-style="{'background-color':myColor.name}"> 26178 </div> 26179 </div> 26180 </file> 26181 <file name="protractor.js" type="protractor"> 26182 it('should check ng-options', function() { 26183 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 26184 element.all(by.model('myColor')).first().click(); 26185 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 26186 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 26187 element(by.css('.nullable select[ng-model="myColor"]')).click(); 26188 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 26189 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 26190 }); 26191 </file> 26192 </example> 26193 */ 26194 26195// jshint maxlen: false 26196// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999 26197var 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]+?))?$/; 26198 // 1: value expression (valueFn) 26199 // 2: label expression (displayFn) 26200 // 3: group by expression (groupByFn) 26201 // 4: disable when expression (disableWhenFn) 26202 // 5: array item variable name 26203 // 6: object item key variable name 26204 // 7: object item value variable name 26205 // 8: collection expression 26206 // 9: track by expression 26207// jshint maxlen: 100 26208 26209 26210var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { 26211 26212 function parseOptionsExpression(optionsExp, selectElement, scope) { 26213 26214 var match = optionsExp.match(NG_OPTIONS_REGEXP); 26215 if (!(match)) { 26216 throw ngOptionsMinErr('iexp', 26217 "Expected expression in form of " + 26218 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 26219 " but got '{0}'. Element: {1}", 26220 optionsExp, startingTag(selectElement)); 26221 } 26222 26223 // Extract the parts from the ngOptions expression 26224 26225 // The variable name for the value of the item in the collection 26226 var valueName = match[5] || match[7]; 26227 // The variable name for the key of the item in the collection 26228 var keyName = match[6]; 26229 26230 // An expression that generates the viewValue for an option if there is a label expression 26231 var selectAs = / as /.test(match[0]) && match[1]; 26232 // An expression that is used to track the id of each object in the options collection 26233 var trackBy = match[9]; 26234 // An expression that generates the viewValue for an option if there is no label expression 26235 var valueFn = $parse(match[2] ? match[1] : valueName); 26236 var selectAsFn = selectAs && $parse(selectAs); 26237 var viewValueFn = selectAsFn || valueFn; 26238 var trackByFn = trackBy && $parse(trackBy); 26239 26240 // Get the value by which we are going to track the option 26241 // if we have a trackFn then use that (passing scope and locals) 26242 // otherwise just hash the given viewValue 26243 var getTrackByValueFn = trackBy ? 26244 function(value, locals) { return trackByFn(scope, locals); } : 26245 function getHashOfValue(value) { return hashKey(value); }; 26246 var getTrackByValue = function(value, key) {
vendor: 5,877 bytes, lines 26247-26407
26247 return getTrackByValueFn(value, getLocals(value, key)); 26248 }; 26249 26250 var displayFn = $parse(match[2] || match[1]); 26251 var groupByFn = $parse(match[3] || ''); 26252 var disableWhenFn = $parse(match[4] || ''); 26253 var valuesFn = $parse(match[8]); 26254 26255 var locals = {}; 26256 var getLocals = keyName ? function(value, key) { 26257 locals[keyName] = key; 26258 locals[valueName] = value; 26259 return locals; 26260 } : function(value) { 26261 locals[valueName] = value; 26262 return locals; 26263 }; 26264 26265 26266 function Option(selectValue, viewValue, label, group, disabled) { 26267 this.selectValue = selectValue; 26268 this.viewValue = viewValue; 26269 this.label = label; 26270 this.group = group; 26271 this.disabled = disabled; 26272 } 26273 26274 function getOptionValuesKeys(optionValues) { 26275 var optionValuesKeys; 26276 26277 if (!keyName && isArrayLike(optionValues)) { 26278 optionValuesKeys = optionValues; 26279 } else { 26280 // if object, extract keys, in enumeration order, unsorted 26281 optionValuesKeys = []; 26282 for (var itemKey in optionValues) { 26283 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 26284 optionValuesKeys.push(itemKey); 26285 } 26286 } 26287 } 26288 return optionValuesKeys; 26289 } 26290 26291 return { 26292 trackBy: trackBy, 26293 getTrackByValue: getTrackByValue, 26294 getWatchables: $parse(valuesFn, function(optionValues) { 26295 // Create a collection of things that we would like to watch (watchedArray) 26296 // so that they can all be watched using a single $watchCollection 26297 // that only runs the handler once if anything changes 26298 var watchedArray = []; 26299 optionValues = optionValues || []; 26300 26301 var optionValuesKeys = getOptionValuesKeys(optionValues); 26302 var optionValuesLength = optionValuesKeys.length; 26303 for (var index = 0; index < optionValuesLength; index++) { 26304 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 26305 var value = optionValues[key]; 26306 26307 var locals = getLocals(optionValues[key], key); 26308 var selectValue = getTrackByValueFn(optionValues[key], locals); 26309 watchedArray.push(selectValue); 26310 26311 // Only need to watch the displayFn if there is a specific label expression 26312 if (match[2] || match[1]) { 26313 var label = displayFn(scope, locals); 26314 watchedArray.push(label); 26315 } 26316 26317 // Only need to watch the disableWhenFn if there is a specific disable expression 26318 if (match[4]) { 26319 var disableWhen = disableWhenFn(scope, locals); 26320 watchedArray.push(disableWhen); 26321 } 26322 } 26323 return watchedArray; 26324 }), 26325 26326 getOptions: function() { 26327 26328 var optionItems = []; 26329 var selectValueMap = {}; 26330 26331 // The option values were already computed in the `getWatchables` fn, 26332 // which must have been called to trigger `getOptions` 26333 var optionValues = valuesFn(scope) || []; 26334 var optionValuesKeys = getOptionValuesKeys(optionValues); 26335 var optionValuesLength = optionValuesKeys.length; 26336 26337 for (var index = 0; index < optionValuesLength; index++) { 26338 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 26339 var value = optionValues[key]; 26340 var locals = getLocals(value, key); 26341 var viewValue = viewValueFn(scope, locals); 26342 var selectValue = getTrackByValueFn(viewValue, locals); 26343 var label = displayFn(scope, locals); 26344 var group = groupByFn(scope, locals); 26345 var disabled = disableWhenFn(scope, locals); 26346 var optionItem = new Option(selectValue, viewValue, label, group, disabled); 26347 26348 optionItems.push(optionItem); 26349 selectValueMap[selectValue] = optionItem; 26350 } 26351 26352 return { 26353 items: optionItems, 26354 selectValueMap: selectValueMap, 26355 getOptionFromViewValue: function(value) { 26356 return selectValueMap[getTrackByValue(value)]; 26357 }, 26358 getViewValueFromOption: function(option) { 26359 // If the viewValue could be an object that may be mutated by the application, 26360 // we need to make a copy and not return the reference to the value on the option. 26361 return trackBy ? angular.copy(option.viewValue) : option.viewValue; 26362 } 26363 }; 26364 } 26365 }; 26366 } 26367 26368 26369 // we can't just jqLite('<option>') since jqLite is not smart enough 26370 // to create it in <select> and IE barfs otherwise. 26371 var optionTemplate = document.createElement('option'), 26372 optGroupTemplate = document.createElement('optgroup'); 26373 26374 return { 26375 restrict: 'A', 26376 terminal: true, 26377 require: ['select', '?ngModel'], 26378 link: function(scope, selectElement, attr, ctrls) { 26379 26380 // if ngModel is not defined, we don't need to do anything 26381 var ngModelCtrl = ctrls[1]; 26382 if (!ngModelCtrl) return; 26383 26384 var selectCtrl = ctrls[0]; 26385 var multiple = attr.multiple; 26386 26387 // The emptyOption allows the application developer to provide their own custom "empty" 26388 // option when the viewValue does not match any of the option values. 26389 var emptyOption; 26390 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { 26391 if (children[i].value === '') { 26392 emptyOption = children.eq(i); 26393 break; 26394 } 26395 } 26396 26397 var providedEmptyOption = !!emptyOption; 26398 26399 var unknownOption = jqLite(optionTemplate.cloneNode(false)); 26400 unknownOption.val('?'); 26401 26402 var options; 26403 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); 26404 26405 26406 var renderEmptyOption = function() { 26407 if (!providedEmptyOption) {
26408 selectElement.prepend(emptyOption); 26409 } 26410 selectElement.val(''); 26411 emptyOption.prop('selected', true); // needed for IE 26412 emptyOption.attr('selected', true); 26413 }; 26414 26415 var removeEmptyOption = function() { 26416 if (!providedEmptyOption) { 26417 emptyOption.remove(); 26418 } 26419 }; 26420 26421 26422 var renderUnknownOption = function() { 26423 selectElement.prepend(unknownOption); 26424 selectElement.val('?'); 26425 unknownOption.prop('selected', true); // needed for IE 26426 unknownOption.attr('selected', true); 26427 }; 26428 26429 var removeUnknownOption = function() { 26430 unknownOption.remove(); 26431 }; 26432 26433 26434 // Update the controller methods for multiple selectable options 26435 if (!multiple) { 26436 26437 selectCtrl.writeValue = function writeNgOptionsValue(value) { 26438 var option = options.getOptionFromViewValue(value); 26439 26440 if (option && !option.disabled) { 26441 if (selectElement[0].value !== option.selectValue) { 26442 removeUnknownOption(); 26443 removeEmptyOption(); 26444 26445 selectElement[0].value = option.selectValue; 26446 option.element.selected = true; 26447 option.element.setAttribute('selected', 'selected'); 26448 } 26449 } else { 26450 if (value === null || providedEmptyOption) { 26451 removeUnknownOption(); 26452 renderEmptyOption(); 26453 } else { 26454 removeEmptyOption(); 26455 renderUnknownOption(); 26456 } 26457 } 26458 }; 26459 26460 selectCtrl.readValue = function readNgOptionsValue() { 26461 26462 var selectedOption = options.selectValueMap[selectElement.val()]; 26463 26464 if (selectedOption && !selectedOption.disabled) { 26465 removeEmptyOption(); 26466 removeUnknownOption(); 26467 return options.getViewValueFromOption(selectedOption); 26468 } 26469 return null; 26470 }; 26471 26472 // If we are using `track by` then we must watch the tracked value on the model 26473 // since ngModel only watches for object identity change 26474 if (ngOptions.trackBy) { 26475 scope.$watch( 26476 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, 26477 function() { ngModelCtrl.$render(); } 26478 ); 26479 } 26480 26481 } else { 26482 26483 ngModelCtrl.$isEmpty = function(value) { 26484 return !value || value.length === 0; 26485 }; 26486 26487 26488 selectCtrl.writeValue = function writeNgOptionsMultiple(value) { 26489 options.items.forEach(function(option) { 26490 option.element.selected = false; 26491 }); 26492 26493 if (value) { 26494 value.forEach(function(item) { 26495 var option = options.getOptionFromViewValue(item); 26496 if (option && !option.disabled) option.element.selected = true; 26497 }); 26498 } 26499 }; 26500 26501 26502 selectCtrl.readValue = function readNgOptionsMultiple() { 26503 var selectedValues = selectElement.val() || [], 26504 selections = []; 26505 26506 forEach(selectedValues, function(value) { 26507 var option = options.selectValueMap[value]; 26508 if (option && !option.disabled) selections.push(options.getViewValueFromOption(option)); 26509 }); 26510 26511 return selections; 26512 }; 26513 26514 // If we are using `track by` then we must watch these tracked values on the model 26515 // since ngModel only watches for object identity change 26516 if (ngOptions.trackBy) { 26517 26518 scope.$watchCollection(function() { 26519 if (isArray(ngModelCtrl.$viewValue)) { 26520 return ngModelCtrl.$viewValue.map(function(value) { 26521 return ngOptions.getTrackByValue(value); 26522 }); 26523 } 26524 }, function() { 26525 ngModelCtrl.$render(); 26526 }); 26527 26528 } 26529 } 26530 26531 26532 if (providedEmptyOption) { 26533 26534 // we need to remove it before calling selectElement.empty() because otherwise IE will 26535 // remove the label from the element. wtf? 26536 emptyOption.remove(); 26537 26538 // compile the element since there might be bindings in it 26539 $compile(emptyOption)(scope); 26540 26541 // remove the class, which is added automatically because we recompile the element and it 26542 // becomes the compilation root 26543 emptyOption.removeClass('ng-scope'); 26544 } else { 26545 emptyOption = jqLite(optionTemplate.cloneNode(false)); 26546 } 26547 26548 // We need to do this here to ensure that the options object is defined 26549 // when we first hit it in writeNgOptionsValue 26550 updateOptions(); 26551 26552 // We will re-render the option elements if the option values or labels change 26553 scope.$watchCollection(ngOptions.getWatchables, updateOptions); 26554 26555 // ------------------------------------------------------------------ // 26556 26557 26558 function updateOptionElement(option, element) { 26559 option.element = element; 26560 element.disabled = option.disabled; 26561 // NOTE: The label must be set before the value, otherwise IE10/11/EDGE create unresponsive 26562 // selects in certain circumstances when multiple selects are next to each other and display 26563 // the option list in listbox style, i.e. the select is [multiple], or specifies a [size]. 26564 // See https://github.com/angular/angular.js/issues/11314 for more info. 26565 // This is unfortunately untestable with unit / e2e tests 26566 if (option.label !== element.label) { 26567 element.label = option.label; 26568 element.textContent = option.label; 26569 } 26570 if (option.value !== element.value) element.value = option.selectValue; 26571 } 26572 26573 function addOrReuseElement(parent, current, type, templateElement) { 26574 var element; 26575 // Check whether we can reuse the next element 26576 if (current && lowercase(current.nodeName) === type) { 26577 // The next element is the right type so reuse it 26578 element = current; 26579 } else { 26580 // The next element is not the right type so create a new one 26581 element = templateElement.cloneNode(false); 26582 if (!current) { 26583 // There are no more elements so just append it to the select 26584 parent.appendChild(element); 26585 } else { 26586 // The next element is not a group so insert the new one 26587 parent.insertBefore(element, current); 26588 } 26589 } 26590 return element; 26591 } 26592 26593 26594 function removeExcessElements(current) { 26595 var next; 26596 while (current) { 26597 next = current.nextSibling; 26598 jqLiteRemove(current); 26599 current = next; 26600 } 26601 } 26602 26603 26604 function skipEmptyAndUnknownOptions(current) { 26605 var emptyOption_ = emptyOption && emptyOption[0]; 26606 var unknownOption_ = unknownOption && unknownOption[0]; 26607 26608 if (emptyOption_ || unknownOption_) { 26609 while (current && 26610 (current === emptyOption_ || 26611 current === unknownOption_ || 26612 emptyOption_ && emptyOption_.nodeType === NODE_TYPE_COMMENT)) { 26613 // Empty options might have directives that transclude 26614 // and insert comments (e.g. ngIf) 26615 current = current.nextSibling; 26616 } 26617 } 26618 return current; 26619 } 26620 26621 26622 function updateOptions() { 26623 26624 var previousValue = options && selectCtrl.readValue(); 26625 26626 options = ngOptions.getOptions(); 26627 26628 var groupMap = {}; 26629 var currentElement = selectElement[0].firstChild; 26630 26631 // Ensure that the empty option is always there if it was explicitly provided 26632 if (providedEmptyOption) {
26633 selectElement.prepend(emptyOption); 26634 } 26635 26636 currentElement = skipEmptyAndUnknownOptions(currentElement); 26637 26638 options.items.forEach(function updateOption(option) { 26639 var group; 26640 var groupElement; 26641 var optionElement; 26642 26643 if (option.group) { 26644 26645 // This option is to live in a group 26646 // See if we have already created this group 26647 group = groupMap[option.group]; 26648 26649 if (!group) { 26650 26651 // We have not already created this group 26652 groupElement = addOrReuseElement(selectElement[0], 26653 currentElement, 26654 'optgroup', 26655 optGroupTemplate); 26656 // Move to the next element 26657 currentElement = groupElement.nextSibling; 26658 26659 // Update the label on the group element 26660 groupElement.label = option.group; 26661 26662 // Store it for use later 26663 group = groupMap[option.group] = { 26664 groupElement: groupElement, 26665 currentOptionElement: groupElement.firstChild 26666 }; 26667 26668 } 26669 26670 // So now we have a group for this option we add the option to the group 26671 optionElement = addOrReuseElement(group.groupElement, 26672 group.currentOptionElement, 26673 'option', 26674 optionTemplate); 26675 updateOptionElement(option, optionElement); 26676 // Move to the next element 26677 group.currentOptionElement = optionElement.nextSibling; 26678 26679 } else { 26680 26681 // This option is not in a group 26682 optionElement = addOrReuseElement(selectElement[0], 26683 currentElement, 26684 'option', 26685 optionTemplate); 26686 updateOptionElement(option, optionElement); 26687 // Move to the next element 26688 currentElement = optionElement.nextSibling; 26689 } 26690 }); 26691 26692 26693 // Now remove all excess options and group 26694 Object.keys(groupMap).forEach(function(key) { 26695 removeExcessElements(groupMap[key].currentOptionElement); 26696 }); 26697 removeExcessElements(currentElement); 26698 26699 ngModelCtrl.$render(); 26700 26701 // Check to see if the value has changed due to the update to the options 26702 if (!ngModelCtrl.$isEmpty(previousValue)) { 26703 var nextValue = selectCtrl.readValue(); 26704 if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) { 26705 ngModelCtrl.$setViewValue(nextValue); 26706 ngModelCtrl.$render(); 26707 } 26708 } 26709 26710 } 26711 26712 } 26713 }; 26714}]; 26715 26716/** 26717 * @ngdoc directive 26718 * @name ngPluralize 26719 * @restrict EA 26720 * 26721 * @description 26722 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 26723 * These rules are bundled with angular.js, but can be overridden 26724 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 26725 * by specifying the mappings between 26726 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 26727 * and the strings to be displayed. 26728 * 26729 * # Plural categories and explicit number rules 26730 * There are two 26731 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 26732 * in Angular's default en-US locale: "one" and "other". 26733 * 26734 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 26735 * any number that is not 1), an explicit number rule can only match one number. For example, the 26736 * explicit number rule for "3" matches the number 3. There are examples of plural categories 26737 * and explicit number rules throughout the rest of this documentation. 26738 * 26739 * # Configuring ngPluralize 26740 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 26741 * You can also provide an optional attribute, `offset`. 26742 * 26743 * The value of the `count` attribute can be either a string or an {@link guide/expression 26744 * Angular expression}; these are evaluated on the current scope for its bound value. 26745 * 26746 * The `when` attribute specifies the mappings between plural categories and the actual 26747 * string to be displayed. The value of the attribute should be a JSON object. 26748 * 26749 * The following example shows how to configure ngPluralize: 26750 * 26751 * ```html 26752 * <ng-pluralize count="personCount"
26753 when="{'0': 'Nobody is viewing.', 26754 * 'one': '1 person is viewing.', 26755 * 'other': '{} people are viewing.'}"> 26756 * </ng-pluralize> 26757 *``` 26758 * 26759 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 26760 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 26761 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 26762 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 26763 * show "a dozen people are viewing". 26764 * 26765 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 26766 * into pluralized strings. In the previous example, Angular will replace `{}` with 26767 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 26768 * for <span ng-non-bindable>{{numberExpression}}</span>. 26769 * 26770 * If no rule is defined for a category, then an empty string is displayed and a warning is generated. 26771 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. 26772 * 26773 * # Configuring ngPluralize with offset 26774 * The `offset` attribute allows further customization of pluralized text, which can result in 26775 * a better user experience. For example, instead of the message "4 people are viewing this document", 26776 * you might display "John, Kate and 2 others are viewing this document". 26777 * The offset attribute allows you to offset a number by any desired value. 26778 * Let's take a look at an example: 26779 * 26780 * ```html 26781 * <ng-pluralize count="personCount" offset=2 26782 * when="{'0': 'Nobody is viewing.', 26783 * '1': '{{person1}} is viewing.', 26784 * '2': '{{person1}} and {{person2}} are viewing.', 26785 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 26786 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 26787 * </ng-pluralize> 26788 * ``` 26789 * 26790 * Notice that we are still using two plural categories(one, other), but we added 26791 * three explicit number rules 0, 1 and 2. 26792 * When one person, perhaps John, views the document, "John is viewing" will be shown. 26793 * When three people view the document, no explicit number rule is found, so 26794 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 26795 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 26796 * is shown. 26797 * 26798 * Note that when you specify offsets, you must provide explicit number rules for 26799 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 26800 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 26801 * plural categories "one" and "other". 26802 * 26803 * @param {string|expression} count The variable to be bound to. 26804 * @param {string} when The mapping between plural category to its corresponding strings. 26805 * @param {number=} offset Offset to deduct from the total number. 26806 * 26807 * @example 26808 <example module="pluralizeExample"> 26809 <file name="index.html"> 26810 <script> 26811 angular.module('pluralizeExample', []) 26812 .controller('ExampleController', ['$scope', function($scope) { 26813 $scope.person1 = 'Igor'; 26814 $scope.person2 = 'Misko'; 26815 $scope.personCount = 1; 26816 }]); 26817 </script>
26818 <div ng-controller="ExampleController"> 26819 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> 26820 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> 26821 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> 26822 26823 <!--- Example with simple pluralization rules for en locale ---> 26824 Without Offset: 26825 <ng-pluralize count="personCount" 26826 when="{'0': 'Nobody is viewing.', 26827 'one': '1 person is viewing.', 26828 'other': '{} people are viewing.'}"> 26829 </ng-pluralize><br> 26830 26831 <!--- Example with offset ---> 26832 With Offset(2): 26833 <ng-pluralize count="personCount" offset=2 26834 when="{'0': 'Nobody is viewing.', 26835 '1': '{{person1}} is viewing.', 26836 '2': '{{person1}} and {{person2}} are viewing.', 26837 'one': '{{person1}}, {{person2}} and one other person are viewing.', 26838 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 26839 </ng-pluralize> 26840 </div> 26841 </file> 26842 <file name="protractor.js" type="protractor"> 26843 it('should show correct pluralized string', function() { 26844 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 26845 var withOffset = element.all(by.css('ng-pluralize')).get(1); 26846 var countInput = element(by.model('personCount')); 26847 26848 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 26849 expect(withOffset.getText()).toEqual('Igor is viewing.'); 26850 26851 countInput.clear(); 26852 countInput.sendKeys('0'); 26853 26854 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 26855 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 26856 26857 countInput.clear(); 26858 countInput.sendKeys('2'); 26859 26860 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 26861 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 26862 26863 countInput.clear(); 26864 countInput.sendKeys('3'); 26865 26866 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 26867 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 26868 26869 countInput.clear(); 26870 countInput.sendKeys('4'); 26871 26872 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 26873 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 26874 }); 26875 it('should show data-bound names', function() { 26876 var withOffset = element.all(by.css('ng-pluralize')).get(1); 26877 var personCount = element(by.model('personCount')); 26878 var person1 = element(by.model('person1')); 26879 var person2 = element(by.model('person2')); 26880 personCount.clear(); 26881 personCount.sendKeys('4'); 26882 person1.clear(); 26883 person1.sendKeys('Di'); 26884 person2.clear(); 26885 person2.sendKeys('Vojta'); 26886 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 26887 }); 26888 </file> 26889 </example> 26890 */ 26891var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { 26892 var BRACE = /{}/g, 26893 IS_WHEN = /^when(Minus)?(.+)$/; 26894 26895 return { 26896 link: function(scope, element, attr) { 26897 var numberExp = attr.count, 26898 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 26899 offset = attr.offset || 0, 26900 whens = scope.$eval(whenExp) || {}, 26901 whensExpFns = {}, 26902 startSymbol = $interpolate.startSymbol(), 26903 endSymbol = $interpolate.endSymbol(), 26904 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 26905 watchRemover = angular.noop, 26906 lastCount; 26907
vendor: 11,613 bytes, lines 26908-27161
26908 forEach(attr, function(expression, attributeName) { 26909 var tmpMatch = IS_WHEN.exec(attributeName); 26910 if (tmpMatch) { 26911 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 26912 whens[whenKey] = element.attr(attr.$attr[attributeName]); 26913 } 26914 }); 26915 forEach(whens, function(expression, key) { 26916 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 26917 26918 }); 26919 26920 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 26921 var count = parseFloat(newVal); 26922 var countIsNaN = isNaN(count); 26923 26924 if (!countIsNaN && !(count in whens)) { 26925 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 26926 // Otherwise, check it against pluralization rules in $locale service. 26927 count = $locale.pluralCat(count - offset); 26928 } 26929 26930 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 26931 // In JS `NaN !== NaN`, so we have to exlicitly check. 26932 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) { 26933 watchRemover(); 26934 var whenExpFn = whensExpFns[count]; 26935 if (isUndefined(whenExpFn)) { 26936 if (newVal != null) { 26937 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp); 26938 } 26939 watchRemover = noop; 26940 updateElementText(); 26941 } else { 26942 watchRemover = scope.$watch(whenExpFn, updateElementText); 26943 } 26944 lastCount = count; 26945 } 26946 }); 26947 26948 function updateElementText(newText) { 26949 element.text(newText || ''); 26950 } 26951 } 26952 }; 26953}]; 26954 26955/** 26956 * @ngdoc directive 26957 * @name ngRepeat 26958 * @multiElement 26959 * 26960 * @description 26961 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 26962 * instance gets its own scope, where the given loop variable is set to the current collection item, 26963 * and `$index` is set to the item index or key. 26964 * 26965 * Special properties are exposed on the local scope of each template instance, including: 26966 * 26967 * | Variable | Type | Details | 26968 * |-----------|-----------------|-----------------------------------------------------------------------------| 26969 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 26970 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 26971 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 26972 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 26973 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 26974 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 26975 * 26976 * <div class="alert alert-info"> 26977 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 26978 * This may be useful when, for instance, nesting ngRepeats. 26979 * </div> 26980 * 26981 * 26982 * # Iterating over object properties 26983 * 26984 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 26985 * syntax: 26986 * 26987 * ```js 26988 * <div ng-repeat="(key, value) in myObj"> ... </div> 26989 * ``` 26990 * 26991 * You need to be aware that the JavaScript specification does not define the order of keys 26992 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive 26993 * used to sort the keys alphabetically.) 26994 * 26995 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser 26996 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing 26997 * keys in the order in which they were defined, although there are exceptions when keys are deleted 26998 * and reinstated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues 26999 * 27000 * If this is not desired, the recommended workaround is to convert your object into an array 27001 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 27002 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 27003 * or implement a `$watch` on the object yourself. 27004 * 27005 * 27006 * # Tracking and Duplicates 27007 * 27008 * When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM: 27009 * 27010 * * When an item is added, a new instance of the template is added to the DOM. 27011 * * When an item is removed, its template instance is removed from the DOM. 27012 * * When items are reordered, their respective templates are reordered in the DOM. 27013 * 27014 * By default, `ngRepeat` does not allow duplicate items in arrays. This is because when 27015 * there are duplicates, it is not possible to maintain a one-to-one mapping between collection 27016 * items and DOM elements. 27017 * 27018 * If you do need to repeat duplicate items, you can substitute the default tracking behavior 27019 * with your own using the `track by` expression. 27020 * 27021 * For example, you may track items by the index of each item in the collection, using the 27022 * special scope property `$index`: 27023 * ```html 27024 * <div ng-repeat="n in [42, 42, 43, 43] track by $index"> 27025 * {{n}} 27026 * </div> 27027 * ``` 27028 * 27029 * You may use arbitrary expressions in `track by`, including references to custom functions 27030 * on the scope: 27031 * ```html 27032 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)"> 27033 * {{n}} 27034 * </div> 27035 * ``` 27036 * 27037 * If you are working with objects that have an identifier property, you can track 27038 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat` 27039 * will not have to rebuild the DOM elements for items it has already rendered, even if the 27040 * JavaScript objects in the collection have been substituted for new ones: 27041 * ```html 27042 * <div ng-repeat="model in collection track by model.id"> 27043 * {{model.name}} 27044 * </div> 27045 * ``` 27046 * 27047 * When no `track by` expression is provided, it is equivalent to tracking by the built-in 27048 * `$id` function, which tracks items by their identity: 27049 * ```html 27050 * <div ng-repeat="obj in collection track by $id(obj)"> 27051 * {{obj.prop}} 27052 * </div> 27053 * ``` 27054 * 27055 * <div class="alert alert-warning"> 27056 * **Note:** `track by` must always be the last expression: 27057 * </div> 27058 * ``` 27059 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id"> 27060 * {{model.name}} 27061 * </div> 27062 * ``` 27063 * 27064 * # Special repeat start and end points 27065 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 27066 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 27067 * 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) 27068 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 27069 * 27070 * The example below makes use of this feature: 27071 * ```html 27072 * <header ng-repeat-start="item in items"> 27073 * Header {{ item }} 27074 * </header> 27075 * <div class="body"> 27076 * Body {{ item }} 27077 * </div> 27078 * <footer ng-repeat-end> 27079 * Footer {{ item }} 27080 * </footer> 27081 * ``` 27082 * 27083 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 27084 * ```html 27085 * <header> 27086 * Header A 27087 * </header> 27088 * <div class="body"> 27089 * Body A 27090 * </div> 27091 * <footer> 27092 * Footer A 27093 * </footer> 27094 * <header> 27095 * Header B 27096 * </header> 27097 * <div class="body"> 27098 * Body B 27099 * </div> 27100 * <footer> 27101 * Footer B 27102 * </footer> 27103 * ``` 27104 * 27105 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 27106 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 27107 * 27108 * @animations 27109 * **.enter** - when a new item is added to the list or when an item is revealed after a filter 27110 * 27111 * **.leave** - when an item is removed from the list or when an item is filtered out 27112 * 27113 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 27114 * 27115 * @element ANY 27116 * @scope 27117 * @priority 1000 27118 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 27119 * formats are currently supported: 27120 * 27121 * * `variable in expression` â where variable is the user defined loop variable and `expression` 27122 * is a scope expression giving the collection to enumerate. 27123 * 27124 * For example: `album in artist.albums`. 27125 * 27126 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 27127 * and `expression` is the scope expression giving the collection to enumerate. 27128 * 27129 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 27130 * 27131 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking expression 27132 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression 27133 * is specified, ng-repeat associates elements by identity. It is an error to have 27134 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are 27135 * mapped to the same DOM element, which is not possible.) 27136 * 27137 * Note that the tracking expression must come last, after any filters, and the alias expression. 27138 * 27139 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 27140 * will be associated by item identity in the array. 27141 * 27142 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 27143 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 27144 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 27145 * element in the same way in the DOM. 27146 * 27147 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 27148 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 27149 * property is same. 27150 * 27151 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 27152 * to items in conjunction with a tracking expression. 27153 * 27154 * * `variable in expression as alias_expression` â You can also provide an optional alias expression which will then store the 27155 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 27156 * when a filter is active on the repeater, but the filtered result set is empty. 27157 * 27158 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 27159 * the items have been processed through the filter. 27160 * 27161 * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax s
27161o it can be used only at the end 27162 * (and not as operator, inside an expression). 27163 * 27164 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . 27165 * 27166 * @example 27167 * This example initializes the scope to a list of names and 27168 * then uses `ngRepeat` to display every person: 27169 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 27170 <file name="index.html"> 27171 <div ng-init="friends = [ 27172 {name:'John', age:25, gender:'boy'}, 27173 {name:'Jessie', age:30, gender:'girl'}, 27174 {name:'Johanna', age:28, gender:'girl'}, 27175 {name:'Joy', age:15, gender:'girl'}, 27176 {name:'Mary', age:28, gender:'girl'}, 27177 {name:'Peter', age:95, gender:'boy'}, 27178 {name:'Sebastian', age:50, gender:'boy'}, 27179 {name:'Erika', age:27, gender:'girl'}, 27180 {name:'Patrick', age:40, gender:'boy'}, 27181 {name:'Samantha', age:60, gender:'girl'} 27182 ]"> 27183 I have {{friends.length}} friends. They are: 27184 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> 27185 <ul class="example-animate-container"> 27186 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> 27187 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 27188 </li> 27189 <li class="animate-repeat" ng-if="results.length == 0"> 27190 <strong>No results found...</strong> 27191 </li> 27192 </ul> 27193 </div> 27194 </file> 27195 <file name="animations.css"> 27196 .example-animate-container { 27197 background:white; 27198 border:1px solid black; 27199 list-style:none; 27200 margin:0; 27201 padding:0 10px; 27202 } 27203 27204 .animate-repeat { 27205 line-height:40px; 27206 list-style:none; 27207 box-sizing:border-box; 27208 } 27209 27210 .animate-repeat.ng-move, 27211 .animate-repeat.ng-enter, 27212 .animate-repeat.ng-leave { 27213 transition:all linear 0.5s; 27214 } 27215 27216 .animate-repeat.ng-leave.ng-leave-active, 27217 .animate-repeat.ng-move, 27218 .animate-repeat.ng-enter { 27219 opacity:0; 27220 max-height:0; 27221 } 27222 27223 .animate-repeat.ng-leave, 27224 .animate-repeat.ng-move.ng-move-active, 27225 .animate-repeat.ng-enter.ng-enter-active { 27226 opacity:1; 27227 max-height:40px; 27228 } 27229 </file> 27230 <file name="protractor.js" type="protractor"> 27231 var friends = element.all(by.repeater('friend in friends')); 27232 27233 it('should render initial data set', function() { 27234 expect(friends.count()).toBe(10); 27235 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 27236 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 27237 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 27238 expect(element(by.binding('friends.length')).getText()) 27239 .toMatch("I have 10 friends. They are:"); 27240 }); 27241 27242 it('should update repeater when filter predicate changes', function() { 27243 expect(friends.count()).toBe(10); 27244 27245 element(by.model('q')).sendKeys('ma'); 27246 27247 expect(friends.count()).toBe(2); 27248 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 27249 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 27250 }); 27251 </file> 27252 </example> 27253 */ 27254var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 27255 var NG_REMOVED = '$$NG_REMOVED'; 27256 var ngRepeatMinErr = minErr('ngRepeat'); 27257 27258 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 27259 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 27260 scope[valueIdentifier] = value; 27261 if (keyIdentifier) scope[keyIdentifier] = key; 27262 scope.$index = index; 27263 scope.$first = (index === 0); 27264 scope.$last = (index === (arrayLength - 1)); 27265 scope.$middle = !(scope.$first || scope.$last); 27266 // jshint bitwise: false 27267 scope.$odd = !(scope.$even = (index&1) === 0); 27268 // jshint bitwise: true 27269 }; 27270 27271 var getBlockStart = function(block) { 27272 return block.clone[0]; 27273 }; 27274 27275 var getBlockEnd = function(block) { 27276 return block.clone[block.clone.length - 1]; 27277 }; 27278 27279 27280 return { 27281 restrict: 'A', 27282 multiElement: true, 27283 transclude: 'element', 27284 priority: 1000, 27285 terminal: true, 27286 $$tlb: true, 27287 compile: function ngRepeatCompile($element, $attr) { 27288 var expression = $attr.ngRepeat; 27289 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' '); 27290 27291 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*$/); 27292 27293 if (!match) { 27294 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 27295 expression); 27296 } 27297 27298 var lhs = match[1]; 27299 var rhs = match[2]; 27300 var aliasAs = match[3]; 27301 var trackByExp = match[4]; 27302 27303 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); 27304 27305 if (!match) { 27306 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 27307 lhs); 27308 } 27309 var valueIdentifier = match[3] || match[1]; 27310 var keyIdentifier = match[2]; 27311 27312 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 27313 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 27314 throw ngRepeatMinErr('badident', "alias '{0}
vendor: 12,206 bytes, lines 27314-27586
27314' is invalid --- must be a valid JS identifier which is not a reserved name.", 27315 aliasAs); 27316 } 27317 27318 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; 27319 var hashFnLocals = {$id: hashKey}; 27320 27321 if (trackByExp) { 27322 trackByExpGetter = $parse(trackByExp); 27323 } else { 27324 trackByIdArrayFn = function(key, value) { 27325 return hashKey(value); 27326 }; 27327 trackByIdObjFn = function(key) { 27328 return key; 27329 }; 27330 } 27331 27332 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 27333 27334 if (trackByExpGetter) { 27335 trackByIdExpFn = function(key, value, index) { 27336 // assign key, value, and $index to the locals so that they can be used in hash functions 27337 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 27338 hashFnLocals[valueIdentifier] = value; 27339 hashFnLocals.$index = index; 27340 return trackByExpGetter($scope, hashFnLocals); 27341 }; 27342 } 27343 27344 // Store a list of elements from previous run. This is a hash where key is the item from the 27345 // iterator, and the value is objects with following properties. 27346 // - scope: bound scope 27347 // - element: previous element. 27348 // - index: position 27349 // 27350 // We are using no-proto object so that we don't need to guard against inherited props via 27351 // hasOwnProperty. 27352 var lastBlockMap = createMap(); 27353 27354 //watch props 27355 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 27356 var index, length, 27357 previousNode = $element[0], // node that cloned nodes should be inserted after 27358 // initialized to the comment node anchor 27359 nextNode, 27360 // Same as lastBlockMap but it has the current state. It will become the 27361 // lastBlockMap on the next iteration. 27362 nextBlockMap = createMap(), 27363 collectionLength, 27364 key, value, // key/value of iteration 27365 trackById, 27366 trackByIdFn, 27367 collectionKeys, 27368 block, // last object information {scope, element, id} 27369 nextBlockOrder, 27370 elementsToRemove; 27371 27372 if (aliasAs) { 27373 $scope[aliasAs] = collection; 27374 } 27375 27376 if (isArrayLike(collection)) { 27377 collectionKeys = collection; 27378 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 27379 } else { 27380 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 27381 // if object, extract keys, in enumeration order, unsorted 27382 collectionKeys = []; 27383 for (var itemKey in collection) { 27384 if (hasOwnProperty.call(collection, itemKey) && itemKey.charAt(0) !== '$') { 27385 collectionKeys.push(itemKey); 27386 } 27387 } 27388 } 27389 27390 collectionLength = collectionKeys.length; 27391 nextBlockOrder = new Array(collectionLength); 27392 27393 // locate existing items 27394 for (index = 0; index < collectionLength; index++) { 27395 key = (collection === collectionKeys) ? index : collectionKeys[index]; 27396 value = collection[key]; 27397 trackById = trackByIdFn(key, value, index); 27398 if (lastBlockMap[trackById]) { 27399 // found previously seen block 27400 block = lastBlockMap[trackById]; 27401 delete lastBlockMap[trackById]; 27402 nextBlockMap[trackById] = block; 27403 nextBlockOrder[index] = block; 27404 } else if (nextBlockMap[trackById]) { 27405 // if collision detected. restore lastBlockMap and throw an error 27406 forEach(nextBlockOrder, function(block) { 27407 if (block && block.scope) lastBlockMap[block.id] = block; 27408 }); 27409 throw ngRepeatMinErr('dupes', 27410 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", 27411 expression, trackById, value); 27412 } else { 27413 // new never before seen block 27414 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 27415 nextBlockMap[trackById] = true; 27416 } 27417 } 27418 27419 // remove leftover items 27420 for (var blockKey in lastBlockMap) { 27421 block = lastBlockMap[blockKey]; 27422 elementsToRemove = getBlockNodes(block.clone); 27423 $animate.leave(elementsToRemove); 27424 if (elementsToRemove[0].parentNode) { 27425 // if the element was not removed yet because of pending animation, mark it as deleted 27426 // so that we can ignore it later 27427 for (index = 0, length = elementsToRemove.length; index < length; index++) { 27428 elementsToRemove[index][NG_REMOVED] = true; 27429 } 27430 } 27431 block.scope.$destroy(); 27432 } 27433 27434 // we are not using forEach for perf reasons (trying to avoid #call) 27435 for (index = 0; index < collectionLength; index++) { 27436 key = (collection === collectionKeys) ? index : collectionKeys[index]; 27437 value = collection[key]; 27438 block = nextBlockOrder[index]; 27439 27440 if (block.scope) { 27441 // if we have already seen this object, then we need to reuse the 27442 // associated scope/element 27443 27444 nextNode = previousNode; 27445 27446 // skip nodes that are already pending removal via leave animation 27447 do { 27448 nextNode = nextNode.nextSibling; 27449 } while (nextNode && nextNode[NG_REMOVED]); 27450 27451 if (getBlockStart(block) != nextNode) { 27452 // existing item which got moved 27453 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); 27454 } 27455 previousNode = getBlockEnd(block); 27456 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 27457 } else { 27458 // new item which we don't know about 27459 $transclude(function ngRepeatTransclude(clone, scope) { 27460 block.scope = scope; 27461 // http://jsperf.com/clone-vs-createcomment 27462 var endNode = ngRepeatEndComment.cloneNode(false); 27463 clone[clone.length++] = endNode; 27464 27465 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper? 27466 $animate.enter(clone, null, jqLite(previousNode)); 27467 previousNode = endNode; 27468 // Note: We only need the first/last node of the cloned nodes. 27469 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 27470 // by a directive with templateUrl when its template arrives. 27471 block.clone = clone; 27472 nextBlockMap[block.id] = block; 27473 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 27474 }); 27475 } 27476 } 27477 lastBlockMap = nextBlockMap; 27478 }); 27479 }; 27480 } 27481 }; 27482}]; 27483 27484var NG_HIDE_CLASS = 'ng-hide'; 27485var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 27486/** 27487 * @ngdoc directive 27488 * @name ngShow 27489 * @multiElement 27490 * 27491 * @description 27492 * The `ngShow` directive shows or hides the given HTML element based on the expression 27493 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding 27494 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 27495 * in AngularJS and sets the display style to none (using an !important flag). 27496 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 27497 * 27498 * ```html 27499 * <!-- when $scope.myValue is truthy (element is visible) --> 27500 * <div ng-show="myValue"></div> 27501 * 27502 * <!-- when $scope.myValue is falsy (element is hidden) --> 27503 * <div ng-show="myValue" class="ng-hide"></div> 27504 * ``` 27505 * 27506 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class 27507 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed 27508 * from the element causing the element not to appear hidden. 27509 * 27510 * ## Why is !important used? 27511 * 27512 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 27513 * can be easily overridden by heavier selectors. For example, something as simple 27514 * as changing the display style on a HTML list item would make hidden elements appear visible. 27515 * This also becomes a bigger issue when dealing with CSS frameworks. 27516 * 27517 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 27518 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 27519 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 27520 * 27521 * ### Overriding `.ng-hide` 27522 * 27523 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 27524 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 27525 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope 27526 * with extra animation classes that can be added. 27527 * 27528 * ```css 27529 * .ng-hide:not(.ng-hide-animate) { 27530 * /* this is just another form of hiding an element */ 27531 * display: block!important; 27532 * position: absolute; 27533 * top: -9999px; 27534 * left: -9999px; 27535 * } 27536 * ``` 27537 * 27538 * By default you don't need to override in CSS anything and the animations will work around the display style. 27539 * 27540 * ## A note about animations with `ngShow` 27541 * 27542 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 27543 * is true and false. This system works like the animation system present with ngClass except that 27544 * you must also include the !important flag to override the display property 27545 * so that you can perform an animation when the element is hidden during the time of the animation. 27546 * 27547 * ```css 27548 * // 27549 * //a working example can be found at the bottom of this page 27550 * // 27551 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 27552 * /* this is required as of 1.3x to properly 27553 * apply all styling in a show/hide animation */ 27554 * transition: 0s linear all; 27555 * } 27556 * 27557 * .my-element.ng-hide-add-active, 27558 * .my-element.ng-hide-remove-active { 27559 * /* the transition is defined in the active class */ 27560 * transition: 1s linear all; 27561 * } 27562 * 27563 * .my-element.ng-hide-add { ... } 27564 * .my-element.ng-hide-add.ng-hide-add-active { ... } 27565 * .my-element.ng-hide-remove { ... } 27566 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 27567 * ``` 27568 * 27569 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 27570 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 27571 * 27572 * @animations 27573 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible 27574 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden 27575 * 27576 * @element ANY 27577 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 27578 * then the element is shown or hidden respectively. 27579 * 27580 * @example 27581 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 27582 <file name="index.html"> 27583 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/> 27584 <div> 27585 Show: 27586 <div class="check-element animate-show" ng-show="checked">
27587 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 27588 </div> 27589 </div> 27590 <div> 27591 Hide: 27592 <div class="check-element animate-show" ng-hide="checked"> 27593 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 27594 </div> 27595 </div> 27596 </file> 27597 <file name="glyphicons.css"> 27598 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 27599 </file> 27600 <file name="animations.css"> 27601 .animate-show { 27602 line-height: 20px; 27603 opacity: 1; 27604 padding: 10px; 27605 border: 1px solid black; 27606 background: white; 27607 } 27608 27609 .animate-show.ng-hide-add, .animate-show.ng-hide-remove { 27610 transition: all linear 0.5s; 27611 } 27612 27613 .animate-show.ng-hide { 27614 line-height: 0; 27615 opacity: 0; 27616 padding: 0 10px; 27617 } 27618 27619 .check-element { 27620 padding: 10px; 27621 border: 1px solid black; 27622 background: white; 27623 } 27624 </file> 27625 <file name="protractor.js" type="protractor"> 27626 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 27627 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 27628 27629 it('should check ng-show / ng-hide', function() { 27630 expect(thumbsUp.isDisplayed()).toBeFalsy(); 27631 expect(thumbsDown.isDisplayed()).toBeTruthy(); 27632 27633 element(by.model('checked')).click(); 27634 27635 expect(thumbsUp.isDisplayed()).toBeTruthy(); 27636 expect(thumbsDown.isDisplayed()).toBeFalsy(); 27637 }); 27638 </file> 27639 </example> 27640 */ 27641var ngShowDirective = ['$animate', function($animate) { 27642 return { 27643 restrict: 'A', 27644 multiElement: true, 27645 link: function(scope, element, attr) { 27646 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 27647 // we're adding a temporary, animation-specific class for ng-hide since this way 27648 // we can control when the element is actually displayed on screen without having 27649 // to have a global/greedy CSS selector that breaks when other animations are run. 27650 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 27651 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { 27652 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 27653 }); 27654 }); 27655 } 27656 }; 27657}]; 27658 27659 27660/** 27661 * @ngdoc directive 27662 * @name ngHide 27663 * @multiElement 27664 * 27665 * @description 27666 * The `ngHide` directive shows or hides the given HTML element based on the expression 27667 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding 27668 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 27669 * in AngularJS and sets the display style to none (using an !important flag). 27670 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 27671 * 27672 * ```html 27673 * <!-- when $scope.myValue is truthy (element is hidden) --> 27674 * <div ng-hide="myValue" class="ng-hide"></div> 27675 * 27676 * <!-- when $scope.myValue is falsy (element is visible) --> 27677 * <div ng-hide="myValue"></div> 27678 * ``` 27679 * 27680 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class 27681 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed 27682 * from the element causing the element not to appear hidden. 27683 * 27684 * ## Why is !important used? 27685 * 27686 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 27687 * can be easily overridden by heavier selectors. For example, something as simple 27688 * as changing the display style on a HTML list item would make hidden elements appear visible. 27689 * This also becomes a bigger issue when dealing with CSS frameworks. 27690 * 27691 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 27692 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 27693 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 27694 * 27695 * ### Overriding `.ng-hide` 27696 * 27697 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 27698 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 27699 * class in CSS: 27700 * 27701 * ```css 27702 * .ng-hide { 27703 * /* this is just another form of hiding an element */ 27704 * display: block!important; 27705 * position: absolute; 27706 * top: -9999px; 27707 * left: -9999px; 27708 * } 27709 * ``` 27710 * 27711 * By default you don't need to override in CSS anything and the animations will work around the display style. 27712 * 27713 * ## A note about animations with `ngHide` 27714 * 27715 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 27716 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 27717 * CSS class is added and removed for you instead of your own CSS class. 27718 * 27719 * ```css 27720 * // 27721 * //a working example can be found at the bottom of this page 27722 * // 27723 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 27724 * transition: 0.5s linear all; 27725 * } 27726 * 27727 * .my-element.ng-hide-add { ... } 27728 * .my-element.ng-hide-add.ng-hide-add-active { ... } 27729 * .my-element.ng-hide-remove { ... } 27730 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 27731 * ``` 27732 * 27733 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 27734 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 27735 * 27736 * @animations 27737 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden 27738 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible 27739 * 27740 * @element ANY 27741 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 27742 * the element is shown or hidden respectively. 27743 * 27744 * @example 27745 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 27746 <file name="index.html"> 27747 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/> 27748 <div> 27749 Show: 27750 <div class="check-element animate-hide" ng-show="checked">
27751 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 27752 </div> 27753 </div> 27754 <div> 27755 Hide: 27756 <div class="check-element animate-hide" ng-hide="checked"> 27757 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 27758 </div> 27759 </div> 27760 </file> 27761 <file name="glyphicons.css"> 27762 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 27763 </file> 27764 <file name="animations.css"> 27765 .animate-hide { 27766 transition: all linear 0.5s; 27767 line-height: 20px; 27768 opacity: 1; 27769 padding: 10px; 27770 border: 1px solid black; 27771 background: white; 27772 } 27773 27774 .animate-hide.ng-hide { 27775 line-height: 0; 27776 opacity: 0; 27777 padding: 0 10px; 27778 } 27779 27780 .check-element { 27781 padding: 10px; 27782 border: 1px solid black; 27783 background: white; 27784 } 27785 </file> 27786 <file name="protractor.js" type="protractor"> 27787 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 27788 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 27789 27790 it('should check ng-show / ng-hide', function() { 27791 expect(thumbsUp.isDisplayed()).toBeFalsy(); 27792 expect(thumbsDown.isDisplayed()).toBeTruthy(); 27793 27794 element(by.model('checked')).click(); 27795 27796 expect(thumbsUp.isDisplayed()).toBeTruthy(); 27797 expect(thumbsDown.isDisplayed()).toBeFalsy(); 27798 }); 27799 </file> 27800 </example> 27801 */ 27802var ngHideDirective = ['$animate', function($animate) { 27803 return { 27804 restrict: 'A', 27805 multiElement: true, 27806 link: function(scope, element, attr) { 27807 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 27808 // The comment inside of the ngShowDirective explains why we add and 27809 // remove a temporary class for the show/hide animation 27810 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { 27811 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 27812 }); 27813 }); 27814 } 27815 }; 27816}]; 27817 27818/** 27819 * @ngdoc directive 27820 * @name ngStyle 27821 * @restrict AC 27822 * 27823 * @description 27824 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 27825 * 27826 * @element ANY 27827 * @param {expression} ngStyle 27828 * 27829 * {@link guide/expression Expression} which evals to an 27830 * object whose keys are CSS style names and values are corresponding values for those CSS 27831 * keys. 27832 * 27833 * Since some CSS style names are not valid keys for an object, they must be quoted. 27834 * See the 'background-color' style in the example below. 27835 * 27836 * @example 27837 <example> 27838 <file name="index.html"> 27839 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 27840 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 27841 <input type="button" value="clear" ng-click="myStyle={}"> 27842 <br/> 27843 <span ng-style="myStyle">Sample Text</span> 27844 <pre>myStyle={{myStyle}}</pre> 27845 </file> 27846 <file name="style.css"> 27847 span { 27848 color: black; 27849 } 27850 </file> 27851 <file name="protractor.js" type="protractor"> 27852 var colorSpan = element(by.css('span')); 27853 27854 it('should check ng-style', function() { 27855 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 27856 element(by.css('input[value=\'set color\']')).click(); 27857 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 27858 element(by.css('input[value=clear]')).click(); 27859 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 27860 }); 27861 </file> 27862 </example> 27863 */ 27864var ngStyleDirective = ngDirective(function(scope, element, attr) { 27865 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 27866 if (oldStyles && (newStyles !== oldStyles)) { 27867 forEach(oldStyles, function(val, style) { element.css(style, '');}); 27868 } 27869 if (newStyles) element.css(newStyles); 27870 }, true); 27871}); 27872 27873/** 27874 * @ngdoc directive 27875 * @name ngSwitch 27876 * @restrict EA 27877 * 27878 * @description 27879 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 27880 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 27881 * as specified in the template. 27882 * 27883 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 27884 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 27885 * matches the value obtained from the evaluated expression. In other words, you define a container element 27886 * (where you place the directive), place an expression on the **`on="..."` attribute** 27887 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 27888 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 27889 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 27890 * attribute is displayed. 27891 * 27892 * <div class="alert alert-info"> 27893 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 27894 * as literal string values to match against. 27895 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
27895omeVal"` not against the 27896 * value of the expression `$scope.someVal`. 27897 * </div> 27898 27899 * @animations 27900 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 27901 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 27902 * 27903 * @usage 27904 * 27905 * ``` 27906 * <ANY ng-switch="expression"> 27907 * <ANY ng-switch-when="matchValue1">...</ANY> 27908 * <ANY ng-switch-when="matchValue2">...</ANY> 27909 * <ANY ng-switch-default>...</ANY> 27910 * </ANY> 27911 * ``` 27912 * 27913 * 27914 * @scope 27915 * @priority 1200 27916 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. 27917 * On child elements add: 27918 * 27919 * * `ngSwitchWhen`: the case statement to match against. If match then this 27920 * case will be displayed. If the same match appears multiple times, all the 27921 * elements will be displayed. 27922 * * `ngSwitchDefault`: the default case when no other case match. If there 27923 * are multiple default cases, all of them will be displayed when no other 27924 * case match. 27925 * 27926 * 27927 * @example 27928 <example module="switchExample" deps="angular-animate.js" animations="true"> 27929 <file name="index.html">
27930 <div ng-controller="ExampleController"> 27931 <select ng-model="selection" ng-options="item for item in items"> 27932 </select> 27933 <code>selection={{selection}}</code> 27934 <hr/> 27935 <div class="animate-switch-container" 27936 ng-switch on="selection"> 27937 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 27938 <div class="animate-switch" ng-switch-when="home">Home Span</div> 27939 <div class="animate-switch" ng-switch-default>default</div> 27940 </div> 27941 </div> 27942 </file> 27943 <file name="script.js"> 27944 angular.module('switchExample', ['ngAnimate']) 27945 .controller('ExampleController', ['$scope', function($scope) { 27946 $scope.items = ['settings', 'home', 'other']; 27947 $scope.selection = $scope.items[0]; 27948 }]); 27949 </file> 27950 <file name="animations.css"> 27951 .animate-switch-container { 27952 position:relative; 27953 background:white; 27954 border:1px solid black; 27955 height:40px; 27956 overflow:hidden; 27957 } 27958 27959 .animate-switch { 27960 padding:10px; 27961 } 27962 27963 .animate-switch.ng-animate { 27964 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 27965 27966 position:absolute; 27967 top:0; 27968 left:0; 27969 right:0; 27970 bottom:0; 27971 } 27972 27973 .animate-switch.ng-leave.ng-leave-active, 27974 .animate-switch.ng-enter { 27975 top:-50px; 27976 } 27977 .animate-switch.ng-leave, 27978 .animate-switch.ng-enter.ng-enter-active { 27979 top:0; 27980 } 27981 </file> 27982 <file name="protractor.js" type="protractor"> 27983 var switchElem = element(by.css('[ng-switch]')); 27984 var select = element(by.model('selection')); 27985 27986 it('should start in settings', function() { 27987 expect(switchElem.getText()).toMatch(/Settings Div/); 27988 }); 27989 it('should change to home', function() { 27990 select.all(by.css('option')).get(1).click(); 27991 expect(switchElem.getText()).toMatch(/Home Span/); 27992 }); 27993 it('should select default', function() { 27994 select.all(by.css('option')).get(2).click(); 27995 expect(switchElem.getText()).toMatch(/default/); 27996 }); 27997 </file> 27998 </example> 27999 */ 28000var ngSwitchDirective = ['$animate', function($animate) { 28001 return { 28002 require: 'ngSwitch', 28003 28004 // asks for $scope to fool the BC controller module 28005 controller: ['$scope', function ngSwitchController() { 28006 this.cases = {}; 28007 }], 28008 link: function(scope, element, attr, ngSwitchController) { 28009 var watchExpr = attr.ngSwitch || attr.on, 28010 selectedTranscludes = [], 28011 selectedElements = [], 28012 previousLeaveAnimations = [], 28013 selectedScopes = []; 28014 28015 var spliceFactory = function(array, index) { 28016 return function() { array.splice(index, 1); }; 28017 }; 28018 28019 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 28020 var i, ii; 28021 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { 28022 $animate.cancel(previousLeaveAnimations[i]); 28023 } 28024 previousLeaveAnimations.length = 0; 28025 28026 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 28027 var selected = getBlockNodes(selectedElements[i].clone); 28028 selectedScopes[i].$destroy(); 28029 var promise = previousLeaveAnimations[i] = $animate.leave(selected); 28030 promise.then(spliceFactory(previousLeaveAnimations, i)); 28031 } 28032 28033 selectedElements.length = 0; 28034 selectedScopes.length = 0; 28035 28036 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 28037 forEach(selectedTranscludes, function(selectedTransclude) { 28038 selectedTransclude.transclude(function(caseElement, selectedScope) { 28039 selectedScopes.push(selectedScope); 28040 var anchor = selectedTransclude.element; 28041 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: '); 28042 var block = { clone: caseElement }; 28043 28044 selectedElements.push(block);
28045 $animate.enter(caseElement, anchor.parent(), anchor); 28046 }); 28047 }); 28048 } 28049 }); 28050 } 28051 }; 28052}]; 28053 28054var ngSwitchWhenDirective = ngDirective({ 28055 transclude: 'element', 28056 priority: 1200, 28057 require: '^ngSwitch', 28058 multiElement: true, 28059 link: function(scope, element, attrs, ctrl, $transclude) { 28060 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 28061 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 28062 } 28063}); 28064 28065var ngSwitchDefaultDirective = ngDirective({ 28066 transclude: 'element', 28067 priority: 1200, 28068 require: '^ngSwitch', 28069 multiElement: true, 28070 link: function(scope, element, attr, ctrl, $transclude) { 28071 ctrl.cases['?'] = (ctrl.cases['?'] || []); 28072 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 28073 } 28074}); 28075 28076/** 28077 * @ngdoc directive 28078 * @name ngTransclude 28079 * @restrict EAC 28080 * 28081 * @description 28082 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 28083 * 28084 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 28085 * 28086 * @element ANY 28087 * 28088 * @example 28089 <example module="transcludeExample"> 28090 <file name="index.html"> 28091 <script> 28092 angular.module('transcludeExample', []) 28093 .directive('pane', function(){ 28094 return { 28095 restrict: 'E', 28096 transclude: true, 28097 scope: { title:'@' }, 28098 template: '<div style="border: 1px solid black;">' + 28099 '<div style="background-color: gray">{{title}}</div>' + 28100 '<ng-transclude></ng-transclude>' + 28101 '</div>' 28102 }; 28103 }) 28104 .controller('ExampleController', ['$scope', function($scope) { 28105 $scope.title = 'Lorem Ipsum'; 28106 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 28107 }]); 28108 </script>
28109 <div ng-controller="ExampleController"> 28110 <input ng-model="title" aria-label="title"> <br/> 28111 <textarea ng-model="text" aria-label="text"></textarea> <br/> 28112 <pane title="{{title}}">{{text}}</pane> 28113 </div> 28114 </file> 28115 <file name="protractor.js" type="protractor"> 28116 it('should have transcluded', function() { 28117 var titleElement = element(by.model('title')); 28118 titleElement.clear(); 28119 titleElement.sendKeys('TITLE'); 28120 var textElement = element(by.model('text')); 28121 textElement.clear(); 28122 textElement.sendKeys('TEXT'); 28123 expect(element(by.binding('title')).getText()).toEqual('TITLE'); 28124 expect(element(by.binding('text')).getText()).toEqual('TEXT'); 28125 }); 28126 </file> 28127 </example> 28128 * 28129 */ 28130var ngTranscludeDirective = ngDirective({ 28131 restrict: 'EAC', 28132 link: function($scope, $element, $attrs, controller, $transclude) { 28133 if (!$transclude) { 28134 throw minErr('ngTransclude')('orphan', 28135 'Illegal use of ngTransclude directive in the template! ' + 28136 'No parent directive that requires a transclusion found. ' + 28137 'Element: {0}', 28138 startingTag($element)); 28139 } 28140 28141 $transclude(function(clone) { 28142 $element.empty(); 28143 $element.append(clone); 28144 }); 28145 } 28146}); 28147 28148/** 28149 * @ngdoc directive 28150 * @name script 28151 * @restrict E 28152 * 28153 * @description 28154 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 28155 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 28156 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 28157 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 28158 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 28159 * 28160 * @param {string} type Must be set to `'text/ng-template'`. 28161 * @param {string} id Cache name of the template. 28162 * 28163 * @example 28164 <example> 28165 <file name="index.html"> 28166 <script type="text/ng-template" id="/tpl.html"> 28167 Content of the template. 28168 </script> 28169 28170 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 28171 <div id="tpl-content" ng-include src="currentTpl"></div> 28172 </file> 28173 <file name="protractor.js" type="protractor"> 28174 it('should load template defined inside script tag', function() { 28175 element(by.css('#tpl-link')).click(); 28176 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 28177 }); 28178 </file> 28179 </example> 28180 */ 28181var scriptDirective = ['$templateCache', function($templateCache) { 28182 return { 28183 restrict: 'E', 28184 terminal: true, 28185 compile: function(element, attr) { 28186 if (attr.type == 'text/ng-template') { 28187 var templateUrl = attr.id, 28188 text = element[0].text; 28189 28190 $templateCache.put(templateUrl, text); 28191 } 28192 } 28193 }; 28194}]; 28195 28196var noopNgModelController = { $setViewValue: noop, $render: noop }; 28197 28198/** 28199 * @ngdoc type 28200 * @name select.SelectController 28201 * @description 28202 * The controller for the `<select>` directive. This provides support for reading 28203 * and writing the selected value(s) of the control and also coordinates dynamically 28204 * added `<option>` elements, perhaps by an `ngRepeat` directive. 28205 */ 28206var SelectController = 28207 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 28208 28209 var self = this, 28210 optionsMap = new HashMap(); 28211 28212 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors 28213 self.ngModelCtrl = noopNgModelController; 28214 28215 // The "unknown" option is one that is prepended to the list if the viewValue 28216 // does not match any of the options. When it is rendered the value of the unknown 28217 // option is '? XXX ?' where XXX is the hashKey of the value that is not known. 28218 // 28219 // We can't just jqLite('<option>') since jqLite is not smart enough 28220 // to create it in <select> and IE barfs otherwise. 28221 self.unknownOption = jqLite(document.createElement('option')); 28222 self.renderUnknownOption = function(val) { 28223 var unknownVal = '? ' + hashKey(val) + ' ?'; 28224 self.unknownOption.val(unknownVal);
28225 $element.prepend(self.unknownOption); 28226 $element.val(unknownVal); 28227 }; 28228 28229 $scope.$on('$destroy', function() { 28230 // disable unknown option so that we don't do work when the whole select is being destroyed 28231 self.renderUnknownOption = noop; 28232 }); 28233 28234 self.removeUnknownOption = function() { 28235 if (self.unknownOption.parent()) self.unknownOption.remove(); 28236 }; 28237 28238 28239 // Read the value of the select control, the implementation of this changes depending 28240 // upon whether the select can have multiple values and whether ngOptions is at work. 28241 self.readValue = function readSingleValue() { 28242 self.removeUnknownOption(); 28243 return $element.val(); 28244 }; 28245 28246 28247 // Write the value to the select control, the implementation of this changes depending 28248 // upon whether the select can have multiple values and whether ngOptions is at work. 28249 self.writeValue = function writeSingleValue(value) { 28250 if (self.hasOption(value)) { 28251 self.removeUnknownOption(); 28252 $element.val(value); 28253 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy 28254 } else { 28255 if (value == null && self.emptyOption) { 28256 self.removeUnknownOption(); 28257 $element.val(''); 28258 } else { 28259 self.renderUnknownOption(value); 28260 } 28261 } 28262 }; 28263 28264 28265 // Tell the select control that an option, with the given value, has been added 28266 self.addOption = function(value, element) { 28267 assertNotHasOwnProperty(value, '"option value"'); 28268 if (value === '') { 28269 self.emptyOption = element; 28270 } 28271 var count = optionsMap.get(value) || 0; 28272 optionsMap.put(value, count + 1); 28273 }; 28274 28275 // Tell the select control that an option, with the given value, has been removed 28276 self.removeOption = function(value) { 28277 var count = optionsMap.get(value); 28278 if (count) { 28279 if (count === 1) { 28280 optionsMap.remove(value); 28281 if (value === '') { 28282 self.emptyOption = undefined; 28283 } 28284 } else { 28285 optionsMap.put(value, count - 1); 28286 } 28287 } 28288 }; 28289 28290 // Check whether the select control has an option matching the given value 28291 self.hasOption = function(value) { 28292 return !!optionsMap.get(value); 28293 }; 28294}]; 28295 28296/** 28297 * @ngdoc directive 28298 * @name select 28299 * @restrict E 28300 * 28301 * @description 28302 * HTML `SELECT` element with angular data-binding. 28303 * 28304 * The `select` directive is used together with {@link ngModel `ngModel`} to provide data-binding 28305 * between the scope and the `<select>` control (including setting default values). 28306 * Ãt also handles dynamic `<option>` elements, which can be added using the {@link ngRepeat `ngRepeat}` or 28307 * {@link ngOptions `ngOptions`} directives. 28308 * 28309 * When an item in the `<select>` menu is selected, the value of the selected option will be bound 28310 * to the model identified by the `ngModel` directive. With static or repeated options, this is 28311 * the content of the `value` attribute or the textContent of the `<option>`, if the value attribute is missing. 28312 * If you want dynamic value attributes, you can use interpolation inside the value attribute. 28313 * 28314 * <div class="alert alert-warning"> 28315 * Note that the value of a `select` directive used without `ngOptions` is always a string. 28316 * When the model needs to be bound to a non-string value, you must either explictly convert it 28317 * using a directive (see example below) or use `ngOptions` to specify the set of options. 28318 * This is because an option element can only be bound to string values at present. 28319 * </div> 28320 * 28321 * If the viewValue of `ngModel` does not match any of the options, then the control 28322 * will automatically add an "unknown" option, which it then removes when the mismatch is resolved. 28323 * 28324 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 28325 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 28326 * option. See example below for demonstration. 28327 * 28328 * <div class="alert alert-info"> 28329 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions 28330 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benef
28330its, such as 28331 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 28332 * comprehension expression, and additionally in reducing memory and increasing speed by not creating 28333 * a new scope for each repeated instance. 28334 * </div> 28335 * 28336 * 28337 * @param {string} ngModel Assignable angular expression to data-bind to. 28338 * @param {string=} name Property name of the form under which the control is published. 28339 * @param {string=} required Sets `required` validation error key if the value is not entered. 28340 * @param {string=} ngRequired Adds required attribute and required validation constraint to 28341 * the element when the ngRequired expression evaluates to true. Use ngRequired instead of required 28342 * when you want to data-bind to the required attribute. 28343 * @param {string=} ngChange Angular expression to be executed when selected option(s) changes due to user 28344 * interaction with the select element. 28345 * @param {string=} ngOptions sets the options that the select is populated with and defines what is 28346 * set on the model on selection. See {@link ngOptions `ngOptions`}. 28347 * 28348 * @example 28349 * ### Simple `select` elements with static options 28350 * 28351 * <example name="static-select" module="staticSelect"> 28352 * <file name="index.html">
28353 * <div ng-controller="ExampleController"> 28354 * <form name="myForm"> 28355 * <label for="singleSelect"> Single select: </label><br> 28356 * <select name="singleSelect" ng-model="data.singleSelect"> 28357 * <option value="option-1">Option 1</option> 28358 * <option value="option-2">Option 2</option> 28359 * </select><br> 28360 * 28361 * <label for="singleSelect"> Single select with "not selected" option and dynamic option values: </label><br> 28362 * <select name="singleSelect" id="singleSelect" ng-model="data.singleSelect"> 28363 * <option value="">---Please select---</option> <!-- not selected / blank option --> 28364 * <option value="{{data.option1}}">Option 1</option> <!-- interpolation --> 28365 * <option value="option-2">Option 2</option> 28366 * </select><br> 28367 * <button ng-click="forceUnknownOption()">Force unknown option</button><br> 28368 * <tt>singleSelect = {{data.singleSelect}}</tt> 28369 * 28370 * <hr> 28371 * <label for="multipleSelect"> Multiple select: </label><br> 28372 * <select name="multipleSelect" id="multipleSelect" ng-model="data.multipleSelect" multiple> 28373 * <option value="option-1">Option 1</option> 28374 * <option value="option-2">Option 2</option> 28375 * <option value="option-3">Option 3</option> 28376 * </select><br> 28377 * <tt>multipleSelect = {{data.multipleSelect}}</tt><br/> 28378 * </form> 28379 * </div> 28380 * </file> 28381 * <file name="app.js"> 28382 * angular.module('staticSelect', []) 28383 * .controller('ExampleController', ['$scope', function($scope) { 28384 * $scope.data = { 28385 * singleSelect: null, 28386 * multipleSelect: [], 28387 * option1: 'option-1', 28388 * }; 28389 * 28390 * $scope.forceUnknownOption = function() { 28391 * $scope.data.singleSelect = 'nonsense'; 28392 * }; 28393 * }]); 28394 * </file> 28395 *</example> 28396 * 28397 * ### Using `ngRepeat` to generate `select` options 28398 * <example name="ngrepeat-select" module="ngrepeatSelect"> 28399 * <file name="index.html">
28400 * <div ng-controller="ExampleController"> 28401 * <form name="myForm"> 28402 * <label for="repeatSelect"> Repeat select: </label> 28403 * <select name="repeatSelect" id="repeatSelect" ng-model="data.repeatSelect"> 28404 * <option ng-repeat="option in data.availableOptions" value="{{option.id}}">{{option.name}}</option> 28405 * </select> 28406 * </form> 28407 * <hr> 28408 * <tt>repeatSelect = {{data.repeatSelect}}</tt><br/> 28409 * </div> 28410 * </file> 28411 * <file name="app.js"> 28412 * angular.module('ngrepeatSelect', []) 28413 * .controller('ExampleController', ['$scope', function($scope) { 28414 * $scope.data = { 28415 * repeatSelect: null, 28416 * availableOptions: [ 28417 * {id: '1', name: 'Option A'}, 28418 * {id: '2', name: 'Option B'}, 28419 * {id: '3', name: 'Option C'} 28420 * ], 28421 * }; 28422 * }]); 28423 * </file> 28424 *</example> 28425 * 28426 * 28427 * ### Using `select` with `ngOptions` and setting a default value 28428 * See the {@link ngOptions ngOptions documentation} for more `ngOptions` usage examples. 28429 * 28430 * <example name="select-with-default-values" module="defaultValueSelect"> 28431 * <file name="index.html">
28432 * <div ng-controller="ExampleController"> 28433 * <form name="myForm"> 28434 * <label for="mySelect">Make a choice:</label> 28435 * <select name="mySelect" id="mySelect" 28436 * ng-options="option.name for option in data.availableOptions track by option.id" 28437 * ng-model="data.selectedOption"></select> 28438 * </form> 28439 * <hr> 28440 * <tt>option = {{data.selectedOption}}</tt><br/> 28441 * </div> 28442 * </file> 28443 * <file name="app.js"> 28444 * angular.module('defaultValueSelect', []) 28445 * .controller('ExampleController', ['$scope', function($scope) { 28446 * $scope.data = { 28447 * availableOptions: [ 28448 * {id: '1', name: 'Option A'}, 28449 * {id: '2', name: 'Option B'}, 28450 * {id: '3', name: 'Option C'} 28451 * ], 28452 * selectedOption: {id: '3', name: 'Option C'} //This sets the default value of the select in the ui 28453 * }; 28454 * }]); 28455 * </file> 28456 *</example> 28457 * 28458 * 28459 * ### Binding `select` to a non-string value via `ngModel` parsing / formatting 28460 * 28461 * <example name="select-with-non-string-options" module="nonStringSelect"> 28462 * <file name="index.html"> 28463 * <select ng-model="model.id" convert-to-number> 28464 * <option value="0">Zero</option> 28465 * <option value="1">One</option> 28466 * <option value="2">Two</option> 28467 * </select> 28468 * {{ model }} 28469 * </file> 28470 * <file name="app.js"> 28471 * angular.module('nonStringSelect', []) 28472 * .run(function($rootScope) { 28473 * $rootScope.model = { id: 2 }; 28474 * }) 28475 * .directive('convertToNumber', function() { 28476 * return { 28477 * require: 'ngModel', 28478 * link: function(scope, element, attrs, ngModel) { 28479 * ngModel.$parsers.push(function(val) { 28480 * return parseInt(val, 10); 28481 * }); 28482 * ngModel.$formatters.push(function(val) { 28483 * return '' + val; 28484 * }); 28485 * } 28486 * }; 28487 * }); 28488 * </file> 28489 * <file name="protractor.js" type="protractor"> 28490 * it('should initialize to model', function() { 28491 * var select = element(by.css('select')); 28492 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); 28493 * }); 28494 * </file> 28495 * </example> 28496 * 28497 */ 28498var selectDirective = function() { 28499 28500 return { 28501 restrict: 'E', 28502 require: ['select', '?ngModel'], 28503 controller: SelectController, 28504 link: function(scope, element, attr, ctrls) { 28505 28506 // if ngModel is not defined, we don't need to do anything 28507 var ngModelCtrl = ctrls[1]; 28508 if (!ngModelCtrl) return; 28509 28510 var selectCtrl = ctrls[0]; 28511 28512 selectCtrl.ngModelCtrl = ngModelCtrl; 28513 28514 // We delegate rendering to the `writeValue` method, which can be changed 28515 // if the select can have multiple selected values or if the options are being 28516 // generated by `ngOptions` 28517 ngModelCtrl.$render = function() { 28518 selectCtrl.writeValue(ngModelCtrl.$viewValue); 28519 }; 28520 28521 // When the selected item(s) changes we delegate getting the value of the select control 28522 // to the `readValue` method, which can be changed if the select can have multiple 28523 // selected values or if the options are being generated by `ngOptions` 28524 element.on('change', function() { 28525 scope.$apply(function() { 28526 ngModelCtrl.$setViewValue(selectCtrl.readValue()); 28527 }); 28528 }); 28529 28530 // If the select allows multiple values then we need to modify how we read and write 28531 // values from and to the control; also what it means for the value to be empty and 28532 // we have to add an extra watch since ngModel doesn't work well with arrays - it 28533 // doesn't trigger rendering if only an item in the array changes. 28534 if (attr.multiple) { 28535 28536 // Read value now needs to check each option to see if it is selected 28537 selectCtrl.readValue = function readMultipleValue() { 28538 var array = []; 28539 forEach(element.find('option'), function(option) { 28540 if (option.selected) { 28541 array.push(option.value); 28542 } 28543 }); 28544 return array; 28545 }; 28546 28547 // Write value now needs to set the selected property of each matching option 28548 selectCtrl.writeValue = function writeMultipleValue(value) { 28549 var items = new HashMap(value);
28550 forEach(element.find('option'), function(option) { 28551 option.selected = isDefined(items.get(option.value)); 28552 }); 28553 }; 28554 28555 // we have to do it on each watch since ngModel watches reference, but 28556 // we need to work of an array, so we need to see if anything was inserted/removed 28557 var lastView, lastViewRef = NaN; 28558 scope.$watch(function selectMultipleWatch() { 28559 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { 28560 lastView = shallowCopy(ngModelCtrl.$viewValue); 28561 ngModelCtrl.$render(); 28562 } 28563 lastViewRef = ngModelCtrl.$viewValue; 28564 }); 28565 28566 // If we are a multiple select then value is now a collection 28567 // so the meaning of $isEmpty changes 28568 ngModelCtrl.$isEmpty = function(value) { 28569 return !value || value.length === 0; 28570 }; 28571 28572 } 28573 } 28574 }; 28575}; 28576 28577 28578// The option directive is purely designed to communicate the existence (or lack of) 28579// of dynamically created (and destroyed) option elements to their containing select 28580// directive via its controller. 28581var optionDirective = ['$interpolate', function($interpolate) { 28582 28583 function chromeHack(optionElement) { 28584 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 28585 // Adding an <option selected="selected"> element to a <select required="required"> should 28586 // automatically select the new element 28587 if (optionElement[0].hasAttribute('selected')) { 28588 optionElement[0].selected = true; 28589 } 28590 } 28591 28592 return { 28593 restrict: 'E', 28594 priority: 100, 28595 compile: function(element, attr) { 28596 28597 if (isDefined(attr.value)) { 28598 // If the value attribute is defined, check if it contains an interpolation 28599 var valueInterpolated = $interpolate(attr.value, true); 28600 } else { 28601 // If the value attribute is not defined then we fall back to the 28602 // text content of the option element, which may be interpolated 28603 var interpolateFn = $interpolate(element.text(), true); 28604 if (!interpolateFn) { 28605 attr.$set('value', element.text()); 28606 } 28607 } 28608 28609 return function(scope, element, attr) { 28610 28611 // This is an optimization over using ^^ since we don't want to have to search 28612 // all the way to the root of the DOM for every single option element 28613 var selectCtrlName = '$selectController', 28614 parent = element.parent(), 28615 selectCtrl = parent.data(selectCtrlName) || 28616 parent.parent().data(selectCtrlName); // in case we are in optgroup 28617 28618 function addOption(optionValue) { 28619 selectCtrl.addOption(optionValue, element); 28620 selectCtrl.ngModelCtrl.$render(); 28621 chromeHack(element); 28622 } 28623 28624 // Only update trigger option updates if this is an option within a `select` 28625 // that also has `ngModel` attached 28626 if (selectCtrl && selectCtrl.ngModelCtrl) { 28627 28628 if (valueInterpolated) { 28629 // The value attribute is interpolated 28630 var oldVal; 28631 attr.$observe('value', function valueAttributeObserveAction(newVal) { 28632 if (isDefined(oldVal)) { 28633 selectCtrl.removeOption(oldVal); 28634 } 28635 oldVal = newVal; 28636 addOption(newVal); 28637 }); 28638 } else if (interpolateFn) { 28639 // The text content is interpolated 28640 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 28641 attr.$set('value', newVal); 28642 if (oldVal !== newVal) { 28643 selectCtrl.removeOption(oldVal); 28644 } 28645 addOption(newVal); 28646 }); 28647 } else { 28648 // The value attribute is static 28649 addOption(attr.value); 28650 } 28651 28652 element.on('$destroy', function() { 28653 selectCtrl.removeOption(attr.value); 28654 selectCtrl.ngModelCtrl.$render(); 28655 }); 28656 } 28657 }; 28658 } 28659 }; 28660}]; 28661 28662var styleDirective = valueFn({ 28663 restrict: 'E', 28664 terminal: false 28665}); 28666 28667var requiredDirective = function() { 28668 return { 28669 restrict: 'A', 28670 require: '?ngModel', 28671 link: function(scope, elm, attr, ctrl) { 28672 if (!ctrl) return; 28673 attr.required = true; // force truthy in case we are on non input element 28674 28675 ctrl.$validators.required = function(modelValue, viewValue) { 28676 return !attr.required || !ctrl.$isEmpty(viewValue); 28677 }; 28678 28679 attr.$observe('required', function() { 28680 ctrl.$validate(); 28681 }); 28682 } 28683 }; 28684};
28685 28686 28687var patternDirective = function() { 28688 return { 28689 restrict: 'A', 28690 require: '?ngModel', 28691 link: function(scope, elm, attr, ctrl) { 28692 if (!ctrl) return; 28693 28694 var regexp, patternExp = attr.ngPattern || attr.pattern; 28695 attr.$observe('pattern', function(regex) { 28696 if (isString(regex) && regex.length > 0) { 28697 regex = new RegExp('^' + regex + '$'); 28698 } 28699 28700 if (regex && !regex.test) { 28701 throw minErr('ngPattern')('noregexp', 28702 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 28703 regex, startingTag(elm)); 28704 } 28705 28706 regexp = regex || undefined; 28707 ctrl.$validate(); 28708 }); 28709 28710 ctrl.$validators.pattern = function(modelValue, viewValue) { 28711 // HTML5 pattern constraint validates the input value, so we validate the viewValue 28712 return ctrl.$isEmpty(viewValue) || isUndefined(regexp) || regexp.test(viewValue); 28713 }; 28714 } 28715 }; 28716}; 28717 28718 28719var maxlengthDirective = function() { 28720 return { 28721 restrict: 'A', 28722 require: '?ngModel', 28723 link: function(scope, elm, attr, ctrl) { 28724 if (!ctrl) return; 28725 28726 var maxlength = -1; 28727 attr.$observe('maxlength', function(value) { 28728 var intVal = toInt(value); 28729 maxlength = isNaN(intVal) ? -1 : intVal; 28730 ctrl.$validate(); 28731 }); 28732 ctrl.$validators.maxlength = function(modelValue, viewValue) { 28733 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); 28734 }; 28735 } 28736 }; 28737}; 28738 28739var minlengthDirective = function() { 28740 return { 28741 restrict: 'A', 28742 require: '?ngModel', 28743 link: function(scope, elm, attr, ctrl) { 28744 if (!ctrl) return; 28745 28746 var minlength = 0; 28747 attr.$observe('minlength', function(value) { 28748 minlength = toInt(value) || 0; 28749 ctrl.$validate(); 28750 }); 28751 ctrl.$validators.minlength = function(modelValue, viewValue) { 28752 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; 28753 }; 28754 } 28755 }; 28756}; 28757 28758if (window.angular.bootstrap) { 28759 //AngularJS is already loaded, so we can return here... 28760 console.log('WARNING: Tried to load angular more than once.'); 28761 return; 28762} 28763 28764//try to bind to jquery now so that one can write jqLite(document).ready() 28765//but we will rebind on bootstrap again. 28766bindJQuery(); 28767 28768publishExternalAPI(angular); 28769 28770angular.module("ngLocale", [], ["$provide", function($provide) { 28771var PLURAL_CATEGORY = {ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"}; 28772function getDecimals(n) { 28773 n = n + ''; 28774 var i = n.indexOf('.'); 28775 return (i == -1) ? 0 : n.length - i - 1; 28776} 28777 28778function getVF(n, opt_precision) { 28779 var v = opt_precision; 28780 28781 if (undefined === v) { 28782 v = Math.min(getDecimals(n), 3); 28783 } 28784 28785 var base = Math.pow(10, v); 28786 var f = ((n * base) | 0) % base; 28787 return {v: v, f: f}; 28788} 28789 28790$provide.value("$locale", { 28791 "DATETIME_FORMATS": { 28792 "AMPMS": [ 28793 "AM", 28794 "PM" 28795 ], 28796 "DAY": [ 28797 "Sunday", 28798 "Monday", 28799 "Tuesday", 28800 "Wednesday", 28801 "Thursday", 28802 "Friday", 28803 "Saturday" 28804 ], 28805 "ERANAMES": [ 28806 "Before Christ", 28807 "Anno Domini" 28808 ], 28809 "ERAS": [ 28810 "BC", 28811 "AD" 28812 ], 28813 "FIRSTDAYOFWEEK": 6, 28814 "MONTH": [ 28815 "January", 28816 "February", 28817 "March", 28818 "April", 28819 "May", 28820 "June", 28821 "July", 28822 "August", 28823 "September", 28824 "October", 28825 "November", 28826 "December" 28827 ], 28828 "SHORTDAY": [ 28829 "Sun", 28830 "Mon", 28831 "Tue", 28832 "Wed", 28833 "Thu", 28834 "Fri", 28835 "Sat" 28836 ], 28837 "SHORTMONTH": [ 28838 "Jan", 28839 "Feb", 28840 "Mar", 28841 "Apr", 28842 "May", 28843 "Jun", 28844 "Jul", 28845 "Aug", 28846 "Sep", 28847 "Oct", 28848 "Nov", 28849 "Dec" 28850 ], 28851 "WEEKENDRANGE": [ 28852 5, 28853 6 28854 ], 28855 "fullDate": "EEEE, MMMM d, y", 28856 "longDate": "MMMM d, y", 28857 "medium": "MMM d, y h:mm:ss a", 28858 "mediumDate": "MMM d, y", 28859 "mediumTime": "h:mm:ss a", 28860 "short": "M/d/yy h:mm a", 28861 "shortDate": "M/d/yy", 28862 "shortTime": "h:mm a" 28863 }, 28864 "NUMBER_FORMATS": { 28865 "CURRENCY_SYM": "$", 28866 "DECIMAL_SEP": ".", 28867 "GROUP_SEP": ",", 28868 "PATTERNS": [ 28869 { 28870 "gSize": 3, 28871 "lgSize": 3, 28872 "maxFrac": 3, 28873 "minFrac": 0, 28874 "minInt": 1, 28875 "negPre": "-", 28876 "negSuf": "", 28877 "posPre": "", 28878 "posSuf": "" 28879 }, 28880 { 28881 "gSize": 3, 28882 "lgSize": 3, 28883 "maxFrac": 2, 28884 "minFrac": 2, 28885 "minInt": 1, 28886 "negPre": "-\u00a4", 28887 "negSuf": "", 28888 "posPre": "\u00a4", 28889 "posSuf": "" 28890 } 28891 ] 28892 }, 28893 "id": "en-us", 28894 "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;} 28895}); 28896}]); 28897 28898 jqLite(document).ready(function() { 28899 angularInit(document, bootstrap); 28900 }); 28901 28902})(window, document); 28903 28904!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.