1/** 2 * @license AngularJS v1.2.22 3 * (c) 2010-2014 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 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 34 */ 35 36function minErr(module) { 37 return function () { 38 var code = arguments[0], 39 prefix = '[' + (module ? module + ':' : '') + code + '] ', 40 template = arguments[1], 41 templateArgs = arguments, 42 stringify = function (obj) { 43 if (typeof obj === 'function') { 44 return obj.toString().replace(/ \{[\s\S]*$/, ''); 45 } else if (typeof obj === 'undefined') { 46 return 'undefined'; 47 } else if (typeof obj !== 'string') { 48 return JSON.stringify(obj); 49 } 50 return obj; 51 }, 52 message, i; 53 54 message = prefix + template.replace(/\{\d+\}/g, function (match) { 55 var index = +match.slice(1, -1), arg; 56 57 if (index + 2 < templateArgs.length) { 58 arg = templateArgs[index + 2]; 59 if (typeof arg === 'function') { 60 return arg.toString().replace(/ ?\{[\s\S]*$/, ''); 61 } else if (typeof arg === 'undefined') { 62 return 'undefined'; 63 } else if (typeof arg !== 'string') { 64 return toJson(arg); 65 } 66 return arg; 67 } 68 return match; 69 }); 70 71 message = message + '\nhttp://errors.angularjs.org/1.2.22/' + 72 (module ? module + '/' : '') + code; 73 for (i = 2; i < arguments.length; i++) { 74 message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + 75 encodeURIComponent(stringify(arguments[i])); 76 } 77 78 return new Error(message); 79 }; 80} 81 82/* We need to tell jshint what variables are being exported */ 83/* global angular: true, 84 msie: true, 85 jqLite: true, 86 jQuery: true, 87 slice: true, 88 push: true, 89 toString: true, 90 ngMinErr: true, 91 angularModule: true, 92 nodeName_: true, 93 uid: true, 94 VALIDITY_STATE_PROPERTY: true, 95 96 lowercase: true, 97 uppercase: true, 98 manualLowercase: true, 99 manualUppercase: true, 100 nodeName_: true, 101 isArrayLike: true, 102 forEach: true, 103 sortedKeys: true, 104 forEachSorted: true, 105 reverseParams: true, 106 nextUid: true, 107 setHashKey: true, 108 extend: true, 109 int: true, 110 inherit: true, 111 noop: true, 112 identity: true, 113 valueFn: true, 114 isUndefined: true, 115 isDefined: true, 116 isObject: true, 117 isString: true, 118 isNumber: true, 119 isDate: true, 120 isArray: true, 121 isFunction: true, 122 isRegExp: true, 123 isWindow: true, 124 isScope: true, 125 isFile: true, 126 isBlob: true, 127 isBoolean: true, 128 isPromiseLike: true, 129 trim: true, 130 isElement: true, 131 makeMap: true, 132 map: true, 133 size: true, 134 includes: true, 135 indexOf: true, 136 arrayRemove: true, 137 isLeafNode: true, 138 copy: true, 139 shallowCopy: true, 140 equals: true, 141 csp: true, 142 concat: true, 143 sliceArgs: true, 144 bind: true, 145 toJsonReplacer: true, 146 toJson: true, 147 fromJson: true,
148 toBoolean: true, 149 startingTag: true, 150 tryDecodeURIComponent: true, 151 parseKeyValue: true, 152 toKeyValue: true, 153 encodeUriSegment: true, 154 encodeUriQuery: true, 155 angularInit: true, 156 bootstrap: true, 157 snake_case: true, 158 bindJQuery: true, 159 assertArg: true, 160 assertArgFn: true, 161 assertNotHasOwnProperty: true, 162 getter: true, 163 getBlockElements: true, 164 hasOwnProperty: true, 165*/ 166 167//////////////////////////////////// 168 169/** 170 * @ngdoc module 171 * @name ng 172 * @module ng 173 * @description 174 * 175 * # ng (core module) 176 * The ng module is loaded by default when an AngularJS application is started. The module itself 177 * contains the essential components for an AngularJS application to function. The table below 178 * lists a high level breakdown of each of the services/factories, filters, directives and testing 179 * components available within this core module. 180 * 181 * <div doc-module-components="ng"></div> 182 */ 183 184// The name of a form control's ValidityState property. 185// This is used so that it's possible for internal tests to create mock ValidityStates. 186var VALIDITY_STATE_PROPERTY = 'validity'; 187 188/** 189 * @ngdoc function 190 * @name angular.lowercase 191 * @module ng 192 * @kind function 193 * 194 * @description Converts the specified string to lowercase. 195 * @param {string} string String to be converted to lowercase. 196 * @returns {string} Lowercased string. 197 */ 198var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; 199var hasOwnProperty = Object.prototype.hasOwnProperty; 200 201/** 202 * @ngdoc function 203 * @name angular.uppercase 204 * @module ng 205 * @kind function 206 * 207 * @description Converts the specified string to uppercase. 208 * @param {string} string String to be converted to uppercase. 209 * @returns {string} Uppercased string. 210 */ 211var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; 212 213 214var manualLowercase = function(s) { 215 /* jshint bitwise: false */ 216 return isString(s) 217 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 218 : s; 219}; 220var manualUppercase = function(s) { 221 /* jshint bitwise: false */ 222 return isString(s) 223 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 224 : s; 225}; 226 227 228// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 229// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 230// with correct but slower alternatives. 231if ('i' !== 'I'.toLowerCase()) { 232 lowercase = manualLowercase; 233 uppercase = manualUppercase; 234} 235 236 237var /** holds major version number for IE or NaN for real browsers */ 238 msie, 239 jqLite, // delay binding since jQuery could be loaded after us. 240 jQuery, // delay binding 241 slice = [].slice, 242 push = [].push, 243 toString = Object.prototype.toString, 244 ngMinErr = minErr('ng'), 245 246 /** @name angular */ 247 angular = window.angular || (window.angular = {}), 248 angularModule, 249 nodeName_, 250 uid = ['0', '0', '0']; 251 252/** 253 * IE 11 changed the format of the UserAgent string. 254 * See http://msdn.microsoft.com/en-us/library/ms537503.aspx 255 */ 256msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); 257if (isNaN(msie)) { 258 msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); 259} 260 261 262/** 263 * @private 264 * @param {*} obj 265 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 266 * String ...) 267 */ 268function isArrayLike(obj) { 269 if (obj == null || isWindow(obj)) { 270 return false; 271 } 272 273 var length = obj.length; 274 275 if (obj.nodeType === 1 && length) { 276 return true; 277 } 278 279 return isString(obj) || isArray(obj) || length === 0 || 280 typeof length === 'number' && length > 0 && (length - 1) in obj; 281} 282 283/** 284 * @ngdoc function 285 * @name angular.forEach 286 * @module ng 287 * @kind function 288 * 289 * @description 290 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 291 * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` 292 * is the value of an object property or an array element and `key` is the object property key or 293 * array element index. Specifying a `context` for the function is optional. 294 * 295 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 296 * using the `hasOwnProperty` method. 297 * 298 ```js 299 var values = {name: 'misko', gender: 'male'}; 300 var log = []; 301 angular.forEach(values, function(value, key) { 302 this.push(key + ': ' + value); 303 }, log); 304 expect(log).toEqual(['name: misko', 'gender: male']); 305 ``` 306 * 307 * @param {Object|Array} obj Object to iterate over. 308 * @param {Function} iterator Iterator function. 309 * @param {Object=} context Object to become context (`this`) for the iterator function. 310 * @returns {Object|Array} Reference to `obj`. 311 */ 312function forEach(obj, iterator, context) { 313 var key; 314 if (obj) { 315 if (isFunction(obj)) { 316 for (key in obj) { 317 // Need to check if hasOwnProperty exists, 318 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 319 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 320 iterator.call(context, obj[key], key); 321 } 322 } 323 } else if (isArray(obj) || isArrayLike(obj)) { 324 for (key = 0; key < obj.length; key++) { 325 iterator.call(context, obj[key], key); 326 } 327 }
327 else if (obj.forEach && obj.forEach !== forEach) { 328 obj.forEach(iterator, context); 329 } else { 330 for (key in obj) { 331 if (obj.hasOwnProperty(key)) { 332 iterator.call(context, obj[key], key); 333 } 334 } 335 } 336 } 337 return obj; 338} 339 340function sortedKeys(obj) { 341 var keys = []; 342 for (var key in obj) { 343 if (obj.hasOwnProperty(key)) { 344 keys.push(key); 345 } 346 } 347 return keys.sort(); 348} 349 350function forEachSorted(obj, iterator, context) { 351 var keys = sortedKeys(obj); 352 for ( var i = 0; i < keys.length; i++) { 353 iterator.call(context, obj[keys[i]], keys[i]); 354 } 355 return keys; 356} 357 358 359/** 360 * when using forEach the params are value, key, but it is often useful to have key, value. 361 * @param {function(string, *)} iteratorFn 362 * @returns {function(*, string)} 363 */ 364function reverseParams(iteratorFn) { 365 return function(value, key) { iteratorFn(key, value); }; 366} 367 368/** 369 * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric 370 * characters such as '012ABC'. The reason why we are not using simply a number counter is that 371 * the number string gets longer over time, and it can also overflow, where as the nextId 372 * will grow much slower, it is a string, and it will never overflow. 373 * 374 * @returns {string} an unique alpha-numeric string 375 */ 376function nextUid() { 377 var index = uid.length; 378 var digit; 379 380 while(index) { 381 index--;
382 digit = uid[index].charCodeAt(0); 383 if (digit == 57 /*'9'*/) { 384 uid[index] = 'A'; 385 return uid.join(''); 386 } 387 if (digit == 90 /*'Z'*/) { 388 uid[index] = '0'; 389 } else { 390 uid[index] = String.fromCharCode(digit + 1); 391 return uid.join(''); 392 } 393 } 394 uid.unshift('0'); 395 return uid.join(''); 396} 397 398 399/** 400 * Set or clear the hashkey for an object. 401 * @param obj object 402 * @param h the hashkey (!truthy to delete the hashkey) 403 */ 404function setHashKey(obj, h) { 405 if (h) { 406 obj.$$hashKey = h; 407 } 408 else { 409 delete obj.$$hashKey; 410 } 411} 412 413/** 414 * @ngdoc function 415 * @name angular.extend 416 * @module ng 417 * @kind function 418 * 419 * @description 420 * Extends the destination object `dst` by copying all of the properties from the `src` object(s) 421 * to `dst`. You can specify multiple `src` objects. 422 * 423 * @param {Object} dst Destination object. 424 * @param {...Object} src Source object(s). 425 * @returns {Object} Reference to `dst`. 426 */ 427function extend(dst) { 428 var h = dst.$$hashKey; 429 forEach(arguments, function(obj) { 430 if (obj !== dst) { 431 forEach(obj, function(value, key) { 432 dst[key] = value; 433 }); 434 } 435 }); 436 437 setHashKey(dst,h); 438 return dst; 439} 440 441function int(str) { 442 return parseInt(str, 10); 443} 444 445 446function inherit(parent, extra) { 447 return extend(new (extend(function() {}, {prototype:parent}))(), extra); 448} 449 450/** 451 * @ngdoc function 452 * @name angular.noop 453 * @module ng 454 * @kind function 455 * 456 * @description 457 * A function that performs no operations. This function can be useful when writing code in the 458 * functional style. 459 ```js 460 function foo(callback) { 461 var result = calculateResult(); 462 (callback || angular.noop)(result); 463 } 464 ``` 465 */ 466function noop() {} 467noop.$inject = []; 468 469 470/** 471 * @ngdoc function 472 * @name angular.identity 473 * @module ng 474 * @kind function 475 * 476 * @description 477 * A function that returns its first argument. This function is useful when writing code in the 478 * functional style. 479 * 480 ```js 481 function transformer(transformationFn, value) { 482 return (transformationFn || angular.identity)(value); 483 }; 484 ``` 485 */ 486function identity($) {return $;} 487identity.$inject = []; 488 489 490function valueFn(value) {return function() {return value;};} 491 492/** 493 * @ngdoc function 494 * @name angular.isUndefined 495 * @module ng 496 * @kind function 497 * 498 * @description 499 * Determines if a reference is undefined. 500 * 501 * @param {*} value Reference to check. 502 * @returns {boolean} True if `value` is undefined. 503 */ 504function isUndefined(value){return typeof value === 'undefined';} 505 506 507/** 508 * @ngdoc function 509 * @name angular.isDefined 510 * @module ng 511 * @kind function 512 * 513 * @description 514 * Determines if a reference is defined. 515 * 516 * @param {*} value Reference to check. 517 * @returns {boolean} True if `value` is defined. 518 */ 519function isDefined(value){return typeof value !== 'undefined';} 520 521 522/** 523 * @ngdoc function 524 * @name angular.isObject 525 * @module ng 526 * @kind function 527 * 528 * @description 529 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 530 * considered to be objects. Note that JavaScript arrays are objects. 531 * 532 * @param {*} value Reference to check. 533 * @returns {boolean} True if `value` is an `Object` but not `null`. 534 */ 535function isObject(value){return value != null && typeof value === 'object';} 536 537 538/** 539 * @ngdoc function 540 * @name angular.isString 541 * @module ng 542 * @kind function 543 * 544 * @description 545 * Determines if a reference is a `String`. 546 * 547 * @param {*} value Reference to check. 548 * @returns {boolean} True if `value` is a `String`. 549 */ 550function isString(value){return typeof value === 'string';} 551 552 553/** 554 * @ngdoc function 555 * @name angular.isNumber 556 * @module ng 557 * @kind function 558 * 559 * @description 560 * Determines if a reference is a `Number`. 561 * 562 * @param {*} value Reference to check. 563 * @returns {boolean} True if `value` is a `Number`. 564 */ 565function isNumber(value){return typeof value === 'number';} 566 567 568/** 569 * @ngdoc function 570 * @name angular.isDate 571 * @module ng 572 * @kind function 573 * 574 * @description 575 * Determines if a value is a date. 576 * 577 * @param {*} value Reference to check. 578 * @returns {boolean} True if `value` is a `Date`. 579 */ 580function isDate(value) { 581 return toString.call(value) === '[object Date]'; 582} 583 584 585/** 586 * @ngdoc function 587 * @name angular.isArray 588 * @module ng 589 * @kind function 590 * 591 * @description 592 * Determines if a reference is an `Array`. 593 * 594 * @param {*} value Reference to check. 595 * @returns {boolean} True if `value` is an `Array`. 596 */ 597var isArray = (function() { 598 if (!isFunction(Array.isArray)) { 599 return function(value) { 600 return toString.call(value) === '[object Array]'; 601 }; 602 } 603 return Array.isArray; 604})(); 605 606/** 607 * @ngdoc function 608 * @name angular.isFunction 609 * @module ng 610 * @kind function 611 * 612 * @description 613 * Determines if a reference is a `Function`. 614 * 615 * @param {*} value Reference to check. 616 * @returns {boolean} True if `value` is a `Function`. 617 */ 618function isFunction(value){return typeof value === 'function';} 619 620 621/** 622 * Determines if a value is a regular expression object. 623 * 624 * @private 625 * @param {*} value Reference to check. 626 * @returns {boolean} True if `value` is a `RegExp`. 627 */ 628function isRegExp(value) { 629 return toString.call(value) === '[object RegExp]'; 630} 631 632 633/** 634 * Checks if `obj` is a window object. 635 * 636 * @private 637 * @param {*} obj Object to check 638 * @returns {boolean} True if `obj` is a window obj. 639 */ 640function isWindow(obj) { 641 return obj && obj.document && obj.location && obj.alert && obj.setInterval; 642} 643 644 645function isScope(obj) { 646 return obj && obj.$evalAsync && obj.$watch; 647} 648 649 650function isFile(obj) { 651 return toString.call(obj) === '[object File]'; 652} 653 654 655function isBlob(obj) { 656 return toString.call(obj) === '[object Blob]'; 657} 658 659 660function isBoolean(value) { 661 return typeof value === 'boolean'; 662} 663 664 665function isPromiseLike(obj) { 666 return obj && isFunction(obj.then); 667} 668 669 670var trim = (function() { 671 // native trim is way faster: http://jsperf.com/angular-trim-test 672 // but IE doesn't have it... :-( 673 // TODO: we should move this into IE/ES5 polyfill 674 if (!String.prototype.trim) { 675 return function(value) { 676 return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value; 677 }; 678 } 679 return function(value) { 680 return isString(value) ? value.trim() : value; 681 }; 682})(); 683 684 685/** 686 * @ngdoc function 687 * @name angular.isElement 688 * @module ng 689 * @kind function 690 * 691 * @description 692 * Determines if a reference is a DOM element (or wrapped jQuery element). 693 * 694 * @param {*} value Reference to check. 695 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 696 */ 697function isElement(node) { 698 return !!(node && 699 (node.nodeName // we are a direct element 700 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 701} 702 703/** 704 * @param str 'key1,key2,...' 705 * @returns {object} in the form of {key1:true, key2:true, ...} 706 */ 707function makeMap(str) { 708 var obj = {}, items = str.split(","), i; 709 for ( i = 0; i < items.length; i++ ) 710 obj[ items[i] ] = true; 711 return obj; 712} 713 714 715if (msie < 9) { 716 nodeName_ = function(element) { 717 element = element.nodeName ? element : element[0]; 718 return (element.scopeName && element.scopeName != 'HTML') 719 ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; 720 }; 721} else { 722 nodeName_ = function(element) { 723 return element.nodeName ? element.nodeName : element[0].nodeName; 724 }; 725} 726 727 728function map(obj, iterator, context) { 729 var results = []; 730 forEach(obj, function(value, index, list) { 731 results.push(iterator.call(context, value, index, list)); 732 }); 733 return results; 734} 735 736 737/** 738 * @description 739 * Determines the number of elements in an array, the number of properties an object has, or 740 * the length of a string. 741 *
742 * Note: This function is used to augment the Object type in Angular expressions. See 743 * {@link angular.Object} for more information about Angular arrays. 744 * 745 * @param {Object|Array|string} obj Object, array, or string to inspect. 746 * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object 747 * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. 748 */ 749function size(obj, ownPropsOnly) { 750 var count = 0, key; 751 752 if (isArray(obj) || isString(obj)) { 753 return obj.length; 754 } else if (isObject(obj)) { 755 for (key in obj) 756 if (!ownPropsOnly || obj.hasOwnProperty(key)) 757 count++; 758 } 759 760 return count; 761} 762 763 764function includes(array, obj) { 765 return indexOf(array, obj) != -1; 766} 767 768function indexOf(array, obj) { 769 if (array.indexOf) return array.indexOf(obj); 770 771 for (var i = 0; i < array.length; i++) { 772 if (obj === array[i]) return i; 773 } 774 return -1; 775} 776 777function arrayRemove(array, value) { 778 var index = indexOf(array, value); 779 if (index >=0) 780 array.splice(index, 1); 781 return value; 782} 783 784function isLeafNode (node) { 785 if (node) { 786 switch (node.nodeName) { 787 case "OPTION": 788 case "PRE": 789 case "TITLE": 790 return true; 791 } 792 } 793 return false; 794} 795 796/** 797 * @ngdoc function 798 * @name angular.copy 799 * @module ng 800 * @kind function 801 * 802 * @description 803 * Creates a deep copy of `source`, which should be an object or an array. 804 * 805 * * If no destination is supplied, a copy of the object or array is created. 806 * * If a destination is provided, all of its elements (for array) or properties (for objects) 807 * are deleted and then all elements/properties from the source are copied to it. 808 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 809 * * If `source` is identical to 'destination' an exception will be thrown. 810 * 811 * @param {*} source The source that will be used to make a copy. 812 * Can be any type, including primitives, `null`, and `undefined`. 813 * @param {(Object|Array)=} destination Destination into which the source is copied. If 814 * provided, must be of the same type as `source`. 815 * @returns {*} The copy or updated `destination`, if `destination` was specified. 816 * 817 * @example 818 <example module="copyExample"> 819 <file name="index.html">
820 <div ng-controller="ExampleController"> 821 <form novalidate class="simple-form"> 822 Name: <input type="text" ng-model="user.name" /><br /> 823 E-mail: <input type="email" ng-model="user.email" /><br /> 824 Gender: <input type="radio" ng-model="user.gender" value="male" />male 825 <input type="radio" ng-model="user.gender" value="female" />female<br /> 826 <button ng-click="reset()">RESET</button> 827 <button ng-click="update(user)">SAVE</button> 828 </form> 829 <pre>form = {{user | json}}</pre> 830 <pre>master = {{master | json}}</pre> 831 </div> 832 833 <script> 834 angular.module('copyExample', []) 835 .controller('ExampleController', ['$scope', function($scope) { 836 $scope.master= {}; 837 838 $scope.update = function(user) { 839 // Example with 1 argument 840 $scope.master= angular.copy(user); 841 }; 842 843 $scope.reset = function() { 844 // Example with 2 arguments 845 angular.copy($scope.master, $scope.user); 846 }; 847 848 $scope.reset(); 849 }]); 850 </script> 851 </file> 852 </example> 853 */ 854function copy(source, destination, stackSource, stackDest) { 855 if (isWindow(source) || isScope(source)) { 856 throw ngMinErr('cpws', 857 "Can't copy! Making copies of Window or Scope instances is not supported."); 858 } 859 860 if (!destination) { 861 destination = source; 862 if (source) { 863 if (isArray(source)) { 864 destination = copy(source, [], stackSource, stackDest); 865 } else if (isDate(source)) { 866 destination = new Date(source.getTime()); 867 } else if (isRegExp(source)) { 868 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); 869 destination.lastIndex = source.lastIndex; 870 } else if (isObject(source)) { 871 destination = copy(source, {}, stackSource, stackDest); 872 } 873 } 874 } else { 875 if (source === destination) throw ngMinErr('cpi', 876 "Can't copy! Source and destination are identical."); 877 878 stackSource = stackSource || []; 879 stackDest = stackDest || []; 880 881 if (isObject(source)) { 882 var index = indexOf(stackSource, source); 883 if (index !== -1) return stackDest[index]; 884 885 stackSource.push(source); 886 stackDest.push(destination); 887 } 888 889 var result; 890 if (isArray(source)) { 891 destination.length = 0; 892 for ( var i = 0; i < source.length; i++) { 893 result = copy(source[i], null, stackSource, stackDest); 894 if (isObject(source[i])) { 895 stackSource.push(source[i]); 896 stackDest.push(result); 897 } 898 destination.push(result); 899 } 900 } else { 901 var h = destination.$$hashKey; 902 forEach(destination, function(value, key) { 903 delete destination[key]; 904 }); 905 for ( var key in source) { 906 result = copy(source[key], null, stackSource, stackDest); 907 if (isObject(source[key])) { 908 stackSource.push(source[key]); 909 stackDest.push(result); 910 } 911 destination[key] = result; 912 } 913 setHashKey(destination,h); 914 } 915 916 } 917 return destination; 918} 919 920/** 921 * Creates a shallow copy of an object, an array or a primitive 922 */ 923function shallowCopy(src, dst) { 924 if (isArray(src)) { 925 dst = dst || []; 926 927 for ( var i = 0; i < src.length; i++) { 928 dst[i] = src[i]; 929 } 930 } else if (isObject(src)) { 931 dst = dst || {}; 932 933 for (var key in src) { 934 if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { 935 dst[key] = src[key]; 936 } 937 } 938 } 939 940 return dst || src; 941} 942 943 944/** 945 * @ngdoc function 946 * @name angular.equals 947 * @module ng 948 * @kind function 949 * 950 * @description 951 * Determines if two objects or two values are equivalent. Supports value types, regular 952 * expressions, arrays and objects. 953 * 954 * Two objects or values are considered equivalent if at least one of the following is true: 955 * 956 * * Both objects or values pass `===` comparison. 957 * * Both objects or values are of the same type and all of their properties are equal by 958 * comparing them with `angular.equals`. 959 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 960 * * Both values represent the same regular expression (In JavaScript,
961 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 962 * representation matches). 963 * 964 * During a property comparison, properties of `function` type and properties with names 965 * that begin with `$` are ignored. 966 * 967 * Scope and DOMWindow objects are being compared only by identify (`===`). 968 * 969 * @param {*} o1 Object or value to compare. 970 * @param {*} o2 Object or value to compare. 971 * @returns {boolean} True if arguments are equal. 972 */ 973function equals(o1, o2) { 974 if (o1 === o2) return true; 975 if (o1 === null || o2 === null) return false; 976 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 977 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 978 if (t1 == t2) { 979 if (t1 == 'object') { 980 if (isArray(o1)) { 981 if (!isArray(o2)) return false; 982 if ((length = o1.length) == o2.length) { 983 for(key=0; key<length; key++) { 984 if (!equals(o1[key], o2[key])) return false; 985 } 986 return true; 987 } 988 } else if (isDate(o1)) { 989 return isDate(o2) && o1.getTime() == o2.getTime(); 990 } else if (isRegExp(o1) && isRegExp(o2)) { 991 return o1.toString() == o2.toString(); 992 } else { 993 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false; 994 keySet = {}; 995 for(key in o1) { 996 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 997 if (!equals(o1[key], o2[key])) return false; 998 keySet[key] = true; 999 } 1000 for(key in o2) { 1001 if (!keySet.hasOwnProperty(key) && 1002 key.charAt(0) !== '$' && 1003 o2[key] !== undefined && 1004 !isFunction(o2[key])) return false; 1005 } 1006 return true; 1007 } 1008 } 1009 } 1010 return false; 1011} 1012 1013var csp = function() { 1014 if (isDefined(csp.isActive_)) return csp.isActive_; 1015 1016 var active = !!(document.querySelector('[ng-csp]') || 1017 document.querySelector('[data-ng-csp]')); 1018 1019 if (!active) { 1020 try { 1021 /* jshint -W031, -W054 */ 1022 new Function(''); 1023 /* jshint +W031, +W054 */ 1024 } catch (e) { 1025 active = true; 1026 } 1027 } 1028 1029 return (csp.isActive_ = active); 1030}; 1031 1032 1033 1034function concat(array1, array2, index) { 1035 return array1.concat(slice.call(array2, index)); 1036} 1037 1038function sliceArgs(args, startIndex) { 1039 return slice.call(args, startIndex || 0); 1040} 1041 1042 1043/* jshint -W101 */ 1044/** 1045 * @ngdoc function 1046 * @name angular.bind 1047 * @module ng 1048 * @kind function 1049 * 1050 * @description 1051 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1052 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1053 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1054 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1055 * 1056 * @param {Object} self Context which `fn` should be evaluated in. 1057 * @param {function()} fn Function to be bound. 1058 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1059 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1060 */ 1061/* jshint +W101 */ 1062function bind(self, fn) { 1063 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1064 if (isFunction(fn) && !(fn instanceof RegExp)) { 1065 return curryArgs.length 1066 ? function() { 1067 return arguments.length 1068 ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) 1069 : fn.apply(self, curryArgs); 1070 } 1071 : function() { 1072 return arguments.length 1073 ? fn.apply(self, arguments) 1074 : fn.call(self); 1075 }; 1076 } else { 1077 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1078 return fn; 1079 } 1080} 1081 1082 1083function toJsonReplacer(key, value) { 1084 var val = value; 1085 1086 if (typeof key === 'string' && key.charAt(0) === '$') { 1087 val = undefined; 1088 } else if (isWindow(value)) { 1089 val = '$WINDOW'; 1090 } else if (value && document === value) { 1091 val = '$DOCUMENT'; 1092 } else if (isScope(value)) { 1093 val = '$SCOPE'; 1094 } 1095 1096 return val; 1097} 1098 1099 1100/** 1101 * @ngdoc function 1102 * @name angular.toJson 1103 * @module ng 1104 * @kind function 1105 * 1106 * @description 1107 * Serializes input into a JSON-formatted string. Properties with leading $ characters will be 1108 * stripped since angular uses this notation internally. 1109 * 1110 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1111 * @param {boolean=}
1111 pretty If set to true, the JSON output will contain newlines and whitespace. 1112 * @returns {string|undefined} JSON-ified string representing `obj`. 1113 */ 1114function toJson(obj, pretty) { 1115 if (typeof obj === 'undefined') return undefined; 1116 return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); 1117} 1118 1119 1120/** 1121 * @ngdoc function 1122 * @name angular.fromJson 1123 * @module ng 1124 * @kind function 1125 * 1126 * @description 1127 * Deserializes a JSON string. 1128 * 1129 * @param {string} json JSON string to deserialize. 1130 * @returns {Object|Array|string|number} Deserialized thingy. 1131 */ 1132function fromJson(json) { 1133 return isString(json) 1134 ? JSON.parse(json) 1135 : json; 1136} 1137 1138 1139function toBoolean(value) { 1140 if (typeof value === 'function') { 1141 value = true; 1142 } else if (value && value.length !== 0) { 1143 var v = lowercase("" + value); 1144 value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); 1145 } else { 1146 value = false; 1147 } 1148 return value; 1149} 1150 1151/** 1152 * @returns {string} Returns the string representation of the element. 1153 */ 1154function startingTag(element) { 1155 element = jqLite(element).clone(); 1156 try { 1157 // turns out IE does not let you set .html() on elements which 1158 // are not allowed to have children. So we just ignore it. 1159 element.empty(); 1160 } catch(e) {} 1161 // As Per DOM Standards 1162 var TEXT_NODE = 3; 1163 var elemHtml = jqLite('<div>').append(element).html(); 1164 try { 1165 return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : 1166 elemHtml. 1167 match(/^(<[^>]+>)/)[1]. 1168 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1169 } catch(e) { 1170 return lowercase(elemHtml); 1171 } 1172 1173} 1174 1175 1176///////////////////////////////////////////////// 1177 1178/** 1179 * Tries to decode the URI component without throwing an exception. 1180 * 1181 * @private 1182 * @param str value potential URI component to check. 1183 * @returns {boolean} True if `value` can be decoded 1184 * with the decodeURIComponent function. 1185 */ 1186function tryDecodeURIComponent(value) { 1187 try { 1188 return decodeURIComponent(value); 1189 } catch(e) { 1190 // Ignore any invalid uri component 1191 } 1192} 1193 1194 1195/** 1196 * Parses an escaped url query string into key-value pairs. 1197 * @returns {Object.<string,boolean|Array>} 1198 */ 1199function parseKeyValue(/**string*/keyValue) { 1200 var obj = {}, key_value, key; 1201 forEach((keyValue || "").split('&'), function(keyValue) { 1202 if ( keyValue ) { 1203 key_value = keyValue.replace(/\+/g,'%20').split('='); 1204 key = tryDecodeURIComponent(key_value[0]); 1205 if ( isDefined(key) ) { 1206 var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; 1207 if (!hasOwnProperty.call(obj, key)) { 1208 obj[key] = val; 1209 } else if(isArray(obj[key])) { 1210 obj[key].push(val); 1211 } else { 1212 obj[key] = [obj[key],val]; 1213 } 1214 } 1215 } 1216 }); 1217 return obj; 1218} 1219 1220function toKeyValue(obj) { 1221 var parts = []; 1222 forEach(obj, function(value, key) { 1223 if (isArray(value)) { 1224 forEach(value, function(arrayValue) { 1225 parts.push(encodeUriQuery(key, true) + 1226 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1227 }); 1228 } else { 1229 parts.push(encodeUriQuery(key, true) + 1230 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1231 } 1232 }); 1233 return parts.length ? parts.join('&') : ''; 1234} 1235 1236 1237/** 1238 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1239 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1240 * segments: 1241 * segment = *pchar 1242 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1243 * pct-encoded = "%" HEXDIG HEXDIG 1244 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1245 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1246 * / "*" / "+" / "," / ";" / "=" 1247 */ 1248function encodeUriSegment(val) { 1249 return encodeUriQuery(val, true). 1250 replace(/%26/gi, '&'). 1251 replace(/%3D/gi, '='). 1252 replace(/%2B/gi, '+'); 1253} 1254 1255 1256/** 1257 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1258 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1259 * encoded per http://tools.ietf.org/html/rfc3986: 1260 * query = *( pchar / "/" / "?" ) 1261 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1262 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1263 * pct-encoded = "%" HEXDIG HEXDIG 1264 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1265 * / "*" / "+" / "," / ";" / "=" 1266 */ 1267function encodeUriQuery(val, pctEncodeSpaces) { 1268 return encodeURIComponent(val). 1269 replace(/%40/gi, '@'). 1270 replace(/%3A/gi, ':'). 1271 replace(/%24/g, '$'). 1272 replace(/%2C/gi, ','). 1273 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1274} 1275 1276 1277/** 1278 * @ngdoc directive 1279 * @name ngApp 1280 * @module ng 1281 * 1282 * @element ANY 1283 * @param {angular.Module} ngApp an optional application 1284 * {@link angular.module module} name to load. 1285 * 1286 * @description 1287 * 1288 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1289 * designates the **root element** of the application and is typically placed near the root element 1290 * of the page - e.g. on the `<body>` or `<html>` tags. 1291 * 1292 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngAp
1292p` 1293 * found in the document will be used to define the root element to auto-bootstrap as an 1294 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1295 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1296 * 1297 * You can specify an **AngularJS module** to be used as the root module for the application. This 1298 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and 1299 * should contain the application code needed or have dependencies on other modules that will 1300 * contain the code. See {@link angular.module} for more information. 1301 * 1302 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1303 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1304 * would not be resolved to `3`. 1305 * 1306 * `ngApp` is the easiest, and most common, way to bootstrap an application. 1307 * 1308 <example module="ngAppDemo"> 1309 <file name="index.html"> 1310 <div ng-controller="ngAppDemoController"> 1311 I can add: {{a}} + {{b}} = {{ a+b }} 1312 </div> 1313 </file> 1314 <file name="script.js"> 1315 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1316 $scope.a = 1; 1317 $scope.b = 2; 1318 }); 1319 </file> 1320 </example> 1321 * 1322 */ 1323function angularInit(element, bootstrap) { 1324 var elements = [element], 1325 appElement, 1326 module, 1327 names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], 1328 NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; 1329 1330 function append(element) { 1331 element && elements.push(element); 1332 } 1333 1334 forEach(names, function(name) { 1335 names[name] = true; 1336 append(document.getElementById(name)); 1337 name = name.replace(':', '\\:'); 1338 if (element.querySelectorAll) { 1339 forEach(element.querySelectorAll('.' + name), append); 1340 forEach(element.querySelectorAll('.' + name + '\\:'), append); 1341 forEach(element.querySelectorAll('[' + name + ']'), append); 1342 } 1343 }); 1344 1345 forEach(elements, function(element) { 1346 if (!appElement) { 1347 var className = ' ' + element.className + ' '; 1348 var match = NG_APP_CLASS_REGEXP.exec(className); 1349 if (match) { 1350 appElement = element; 1351 module = (match[2] || '').replace(/\s+/g, ','); 1352 } else { 1353 forEach(element.attributes, function(attr) { 1354 if (!appElement && names[attr.name]) { 1355 appElement = element; 1356 module = attr.value; 1357 } 1358 }); 1359 } 1360 } 1361 }); 1362 if (appElement) { 1363 bootstrap(appElement, module ? [module] : []); 1364 } 1365} 1366 1367/** 1368 * @ngdoc function 1369 * @name angular.bootstrap 1370 * @module ng 1371 * @description 1372 * Use this function to manually start up angular application. 1373 * 1374 * See: {@link guide/bootstrap Bootstrap} 1375 * 1376 * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. 1377 * They must use {@link ng.directive:ngApp ngApp}. 1378 * 1379 * Angular will detect if it has been loaded into the browser more than once and only allow the 1380 * first loaded script to be bootstrapped and will report a warning to the browser console for 1381 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1382 * multiple instances of Angular try to work on the DOM. 1383 * 1384 * <example name="multi-bootstrap" module="multi-bootstrap"> 1385 * <file name="index.html"> 1386 * <script src="../../../angular.js"></script> 1387 * <div ng-controller="BrokenTable"> 1388 * <table> 1389 * <tr> 1390 * <th ng-repeat="heading in headings">{{heading}}</th> 1391 * </tr> 1392 * <tr ng-repeat="filling in fillings"> 1393 * <td ng-repeat="fill in filling">{{fill}}</td> 1394 * </tr> 1395 * </table> 1396 * </div> 1397 * </file> 1398 * <file name="controller.js"> 1399 * var app = angular.module('multi-bootstrap', []) 1400 * 1401 * .controller('BrokenTable', function($scope) { 1402 * $scope.headings = ['One', 'Two', 'Three']; 1403 * $scope.fillings = [[1, 2, 3], ['A', 'B', 'C'], [7, 8, 9]]; 1404 * }); 1405 * </file> 1406 * <file name="protractor.js" type="protractor"> 1407 * it('should only insert one table cell for each item in $scope.fillings', function() { 1408 * expect(element.all(by.css('td')).count()) 1409 * .toBe(9); 1410 * }); 1411 * </file> 1412 * </example> 1413 * 1414 * @param {DOMElement} element DOM element which is the root of angular application. 1415 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1416 * Each item in the array should be the name of a predefined module or a (DI annotated) 1417 * function that will be invoked by the injector as a run block. 1418 * See: {@link angular.module modules} 1419 * @returns {auto.$injector} Returns the newly created injector for this app. 1420 */ 1421function bootstrap(element, modules) { 1422 var doBootstrap = function() { 1423 element = jqLite(element); 1424 1425 if (element.injector()) { 1426 var tag = (element[0] === document) ? 'document' : startingTag(element); 1427 throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); 1428 } 1429 1430 modules = modules || []; 1431 modules.unshift(['$provide', function($provide) { 1432 $provide.value('$rootElement', element); 1433 }]); 1434 modules.unshift('ng'); 1435 var injector = createInjector(modules); 1436 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', 1437 function(scope, element, compile, injector, animate) { 1438 scope.$apply(function() { 1439 element.data('$injector', injector); 1440 compile(element)(scope); 1441 }); 1442 }] 1443 ); 1444 return injector; 1445 }; 1446 1447 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1448 1449 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1450 return doBootstrap(); 1451 } 1452 1453 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1454 angular.resumeBootstrap = function(extraModules) {
1455 forEach(extraModules, function(module) { 1456 modules.push(module); 1457 }); 1458 doBootstrap(); 1459 }; 1460} 1461 1462var SNAKE_CASE_REGEXP = /[A-Z]/g; 1463function snake_case(name, separator) { 1464 separator = separator || '_'; 1465 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1466 return (pos ? separator : '') + letter.toLowerCase(); 1467 }); 1468} 1469 1470function bindJQuery() { 1471 // bind to jQuery if present; 1472 jQuery = window.jQuery; 1473 // Use jQuery if it exists with proper functionality, otherwise default to us. 1474 // Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support. 1475 if (jQuery && jQuery.fn.on) { 1476 jqLite = jQuery; 1477 extend(jQuery.fn, { 1478 scope: JQLitePrototype.scope, 1479 isolateScope: JQLitePrototype.isolateScope, 1480 controller: JQLitePrototype.controller, 1481 injector: JQLitePrototype.injector, 1482 inheritedData: JQLitePrototype.inheritedData 1483 }); 1484 // Method signature: 1485 // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) 1486 jqLitePatchJQueryRemove('remove', true, true, false); 1487 jqLitePatchJQueryRemove('empty', false, false, false); 1488 jqLitePatchJQueryRemove('html', false, false, true); 1489 } else { 1490 jqLite = JQLite; 1491 } 1492 angular.element = jqLite; 1493} 1494 1495/** 1496 * throw error if the argument is falsy. 1497 */ 1498function assertArg(arg, name, reason) { 1499 if (!arg) { 1500 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1501 } 1502 return arg; 1503} 1504 1505function assertArgFn(arg, name, acceptArrayAnnotation) { 1506 if (acceptArrayAnnotation && isArray(arg)) { 1507 arg = arg[arg.length - 1]; 1508 } 1509 1510 assertArg(isFunction(arg), name, 'not a function, got ' + 1511 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1512 return arg; 1513} 1514 1515/** 1516 * throw error if the name given is hasOwnProperty 1517 * @param {String} name the name to test 1518 * @param {String} context the context in which the name is used, such as module or directive 1519 */ 1520function assertNotHasOwnProperty(name, context) { 1521 if (name === 'hasOwnProperty') { 1522 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1523 } 1524} 1525 1526/** 1527 * Return the value accessible from the object by path. Any undefined traversals are ignored 1528 * @param {Object} obj starting object 1529 * @param {String} path path to traverse 1530 * @param {boolean} [bindFnToScope=true] 1531 * @returns {Object} value as accessible by path 1532 */ 1533//TODO(misko): this function needs to be removed 1534function getter(obj, path, bindFnToScope) { 1535 if (!path) return obj; 1536 var keys = path.split('.'); 1537 var key; 1538 var lastInstance = obj; 1539 var len = keys.length; 1540 1541 for (var i = 0; i < len; i++) { 1542 key = keys[i]; 1543 if (obj) { 1544 obj = (lastInstance = obj)[key]; 1545 } 1546 } 1547 if (!bindFnToScope && isFunction(obj)) { 1548 return bind(lastInstance, obj); 1549 } 1550 return obj; 1551} 1552 1553/** 1554 * Return the DOM siblings between the first and last node in the given array. 1555 * @param {Array} array like object 1556 * @returns {DOMElement}
1556 object containing the elements 1557 */ 1558function getBlockElements(nodes) { 1559 var startNode = nodes[0], 1560 endNode = nodes[nodes.length - 1]; 1561 if (startNode === endNode) { 1562 return jqLite(startNode); 1563 } 1564 1565 var element = startNode; 1566 var elements = [element]; 1567 1568 do { 1569 element = element.nextSibling; 1570 if (!element) break; 1571 elements.push(element); 1572 } while (element !== endNode); 1573 1574 return jqLite(elements); 1575} 1576 1577/** 1578 * @ngdoc type 1579 * @name angular.Module 1580 * @module ng 1581 * @description 1582 * 1583 * Interface for configuring angular {@link angular.module modules}. 1584 */ 1585 1586function setupModuleLoader(window) { 1587 1588 var $injectorMinErr = minErr('$injector'); 1589 var ngMinErr = minErr('ng'); 1590 1591 function ensure(obj, name, factory) { 1592 return obj[name] || (obj[name] = factory()); 1593 } 1594 1595 var angular = ensure(window, 'angular', Object); 1596 1597 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1598 angular.$$minErr = angular.$$minErr || minErr; 1599 1600 return ensure(angular, 'module', function() { 1601 /** @type {Object.<string, angular.Module>} */ 1602 var modules = {}; 1603 1604 /** 1605 * @ngdoc function 1606 * @name angular.module 1607 * @module ng 1608 * @description 1609 * 1610 * The `angular.module` is a global place for creating, registering and retrieving Angular 1611 * modules. 1612 * All modules (angular core or 3rd party) that should be available to an application must be 1613 * registered using this mechanism. 1614 * 1615 * When passed two or more arguments, a new module is created. If passed only one argument, an 1616 * existing module (the name passed as the first argument to `module`) is retrieved. 1617 * 1618 * 1619 * # Module 1620 * 1621 * A module is a collection of services, directives, controllers, filters, and configuration information. 1622 * `angular.module` is used to configure the {@link auto.$injector $injector}. 1623 * 1624 * ```js 1625 * // Create a new module 1626 * var myModule = angular.module('myModule', []); 1627 * 1628 * // register a new service 1629 * myModule.value('appName', 'MyCoolApp'); 1630 * 1631 * // configure existing services inside initialization blocks. 1632 * myModule.config(['$locationProvider', function($locationProvider) { 1633 * // Configure existing providers 1634 * $locationProvider.hashPrefix('!'); 1635 * }]); 1636 * ``` 1637 * 1638 * Then you can create an injector and load your modules like this: 1639 * 1640 * ```js 1641 * var injector = angular.injector(['ng', 'myModule']) 1642 * ``` 1643 * 1644 * However it's more likely that you'll just use 1645 * {@link ng.directive:ngApp ngApp} or 1646 * {@link angular.bootstrap} to simplify this process for you. 1647 * 1648 * @param {!string} name The name of the module to create or retrieve. 1649 * @param {!Array.<string>=} requires If specified then new module is being created. If 1650 * unspecified then the module is being retrieved for further configuration. 1651 * @param {Function=} configFn Optional configuration function for the module. Same as 1652 * {@link angular.Module#config Module#config()}. 1653 * @returns {module} new module with the {@link angular.Module} api. 1654 */ 1655 return function module(name, requires, configFn) { 1656 var assertNotHasOwnProperty = function(name, context) { 1657 if (name === 'hasOwnProperty') { 1658 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 1659 } 1660 }; 1661 1662 assertNotHasOwnProperty(name, 'module'); 1663 if (requires && modules.hasOwnProperty(name)) { 1664 modules[name] = null; 1665 } 1666 return ensure(modules, name, function() { 1667 if (!requires) { 1668 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 1669 "the module name or forgot to load it. If registering a module ensure that you " + 1670 "specify the dependencies as the second argument.", name); 1671 } 1672 1673 /** @type {!Array.<Array.<*>>} */ 1674 var invokeQueue = []; 1675 1676 /** @type {!Array.<Function>} */ 1677 var runBlocks = []; 1678 1679 var config = invokeLater('$injector', 'invoke'); 1680 1681 /** @type {angular.Module} */ 1682 var moduleInstance = { 1683 // Private state 1684 _invokeQueue: invokeQueue, 1685 _runBlocks: runBlocks, 1686 1687 /** 1688 * @ngdoc property 1689 * @name angular.Module#requires 1690 * @module ng 1691 * @returns {Array.<string>} List of module names which must be loaded before this module.
1692 * @description 1693 * Holds the list of modules which the injector will load before the current module is 1694 * loaded. 1695 */ 1696 requires: requires, 1697 1698 /** 1699 * @ngdoc property 1700 * @name angular.Module#name 1701 * @module ng 1702 * @returns {string} Name of the module. 1703 * @description 1704 */ 1705 name: name, 1706 1707 1708 /** 1709 * @ngdoc method 1710 * @name angular.Module#provider 1711 * @module ng 1712 * @param {string} name service name 1713 * @param {Function} providerType Construction function for creating new instance of the 1714 * service. 1715 * @description 1716 * See {@link auto.$provide#provider $provide.provider()}. 1717 */ 1718 provider: invokeLater('$provide', 'provider'), 1719 1720 /** 1721 * @ngdoc method 1722 * @name angular.Module#factory 1723 * @module ng 1724 * @param {string} name service name 1725 * @param {Function} providerFunction Function for creating new instance of the service. 1726 * @description 1727 * See {@link auto.$provide#factory $provide.factory()}. 1728 */ 1729 factory: invokeLater('$provide', 'factory'), 1730 1731 /** 1732 * @ngdoc method 1733 * @name angular.Module#service 1734 * @module ng 1735 * @param {string} name service name 1736 * @param {Function} constructor A constructor function that will be instantiated. 1737 * @description 1738 * See {@link auto.$provide#service $provide.service()}. 1739 */ 1740 service: invokeLater('$provide', 'service'), 1741 1742 /** 1743 * @ngdoc method 1744 * @name angular.Module#value 1745 * @module ng 1746 * @param {string} name service name 1747 * @param {*} object Service instance object. 1748 * @description 1749 * See {@link auto.$provide#value $provide.value()}. 1750 */ 1751 value: invokeLater('$provide', 'value'), 1752 1753 /** 1754 * @ngdoc method 1755 * @name angular.Module#constant 1756 * @module ng 1757 * @param {string} name constant name 1758 * @param {*} object Constant value. 1759 * @description 1760 * Because the constant are fixed, they get applied before other provide methods. 1761 * See {@link auto.$provide#constant $provide.constant()}. 1762 */ 1763 constant: invokeLater('$provide', 'constant', 'unshift'), 1764 1765 /** 1766 * @ngdoc method 1767 * @name angular.Module#animation 1768 * @module ng 1769 * @param {string} name animation name 1770 * @param {Function} animationFactory Factory function for creating new instance of an 1771 * animation. 1772 * @description 1773 * 1774 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 1775 * 1776 * 1777 * Defines an animation hook that can be later used with 1778 * {@link ngAnimate.$animate $animate} service and directives that use this service. 1779 * 1780 * ```js 1781 * module.animation('.animation-name', function($inject1, $inject2) { 1782 * return { 1783 * eventName : function(element, done) { 1784 * //code to run the animation 1785 * //once complete, then run done() 1786 * return function cancellationFunction(element) { 1787 * //code to cancel the animation 1788 * } 1789 * } 1790 * } 1791 * }) 1792 * ``` 1793 * 1794 * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and 1795 * {@link ngAnimate ngAnimate module} for more information. 1796 */ 1797 animation: invokeLater('$animateProvider', 'register'), 1798 1799 /** 1800 * @ngdoc method 1801 * @name angular.Module#filter 1802 * @module ng 1803 * @param {string} name Filter name. 1804 * @param {Function} filterFactory Factory function for creating new instance of filter.
1805 * @description 1806 * See {@link ng.$filterProvider#register $filterProvider.register()}. 1807 */ 1808 filter: invokeLater('$filterProvider', 'register'), 1809 1810 /** 1811 * @ngdoc method 1812 * @name angular.Module#controller 1813 * @module ng 1814 * @param {string|Object} name Controller name, or an object map of controllers where the 1815 * keys are the names and the values are the constructors. 1816 * @param {Function} constructor Controller constructor function. 1817 * @description 1818 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 1819 */ 1820 controller: invokeLater('$controllerProvider', 'register'), 1821 1822 /** 1823 * @ngdoc method 1824 * @name angular.Module#directive 1825 * @module ng 1826 * @param {string|Object} name Directive name, or an object map of directives where the 1827 * keys are the names and the values are the factories. 1828 * @param {Function} directiveFactory Factory function for creating new instance of 1829 * directives. 1830 * @description 1831 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 1832 */ 1833 directive: invokeLater('$compileProvider', 'directive'), 1834 1835 /** 1836 * @ngdoc method 1837 * @name angular.Module#config 1838 * @module ng 1839 * @param {Function} configFn Execute this function on module load. Useful for service 1840 * configuration. 1841 * @description 1842 * Use this method to register work which needs to be performed on module loading. 1843 * For more about how to configure services, see 1844 * {@link providers#providers_provider-recipe Provider Recipe}. 1845 */ 1846 config: config, 1847 1848 /** 1849 * @ngdoc method 1850 * @name angular.Module#run 1851 * @module ng 1852 * @param {Function} initializationFn Execute this function after injector creation. 1853 * Useful for application initialization. 1854 * @description 1855 * Use this method to register work which should be performed when the injector is done 1856 * loading all modules. 1857 */ 1858 run: function(block) { 1859 runBlocks.push(block); 1860 return this; 1861 } 1862 }; 1863 1864 if (configFn) { 1865 config(configFn); 1866 } 1867 1868 return moduleInstance; 1869 1870 /** 1871 * @param {string} provider 1872 * @param {string} method 1873 * @param {String=} insertMethod 1874 * @returns {angular.Module} 1875 */ 1876 function invokeLater(provider, method, insertMethod) { 1877 return function() { 1878 invokeQueue[insertMethod || 'push']([provider, method, arguments]); 1879 return moduleInstance; 1880 }; 1881 } 1882 }); 1883 }; 1884 }); 1885 1886} 1887 1888/* global angularModule: true, 1889 version: true, 1890 1891 $LocaleProvider, 1892 $CompileProvider, 1893 1894 htmlAnchorDirective, 1895 inputDirective, 1896 inputDirective, 1897 formDirective, 1898 scriptDirective, 1899 selectDirective, 1900 styleDirective, 1901 optionDirective, 1902 ngBindDirective, 1903 ngBindHtmlDirective, 1904 ngBindTemplateDirective, 1905 ngClassDirective, 1906 ngClassEvenDirective, 1907 ngClassOddDirective, 1908 ngCspDirective, 1909 ngCloakDirective, 1910 ngControllerDirective, 1911 ngFormDirective, 1912 ngHideDirective, 1913 ngIfDirective, 1914 ngIncludeDirective, 1915 ngIncludeFillContentDirective, 1916 ngInitDirective, 1917 ngNonBindableDirective, 1918 ngPluralizeDirective, 1919 ngRepeatDirective, 1920 ngShowDirective, 1921 ngStyleDirective, 1922 ngSwitchDirective, 1923 ngSwitchWhenDirective, 1924 ngSwitchDefaultDirective, 1925 ngOptionsDirective, 1926 ngTranscludeDirective, 1927 ngModelDirective, 1928 ngListDirective, 1929 ngChangeDirective, 1930 requiredDirective, 1931 requiredDirective, 1932 ngValueDirective, 1933 ngAttributeAliasDirectives, 1934 ngEventDirectives, 1935 1936 $AnchorScrollProvider, 1937 $AnimateProvider, 1938 $BrowserProvider, 1939 $CacheFactoryProvider, 1940 $ControllerProvider, 1941 $DocumentProvider, 1942 $ExceptionHandlerProvider, 1943 $FilterProvider, 1944 $InterpolateProvider, 1945 $IntervalProvider, 1946 $HttpProvider, 1947 $HttpBackendProvider, 1948 $LocationProvider, 1949 $LogProvider, 1950 $ParseProvider, 1951 $RootScopeProvider, 1952 $QProvider,
1953 $$SanitizeUriProvider, 1954 $SceProvider, 1955 $SceDelegateProvider, 1956 $SnifferProvider, 1957 $TemplateCacheProvider, 1958 $TimeoutProvider, 1959 $$RAFProvider, 1960 $$AsyncCallbackProvider, 1961 $WindowProvider 1962*/ 1963 1964 1965/** 1966 * @ngdoc object 1967 * @name angular.version 1968 * @module ng 1969 * @description 1970 * An object that contains information about the current AngularJS version. This object has the 1971 * following properties: 1972 * 1973 * - `full` â `{string}` â Full version string, such as "0.9.18". 1974 * - `major` â `{number}` â Major version number, such as "0". 1975 * - `minor` â `{number}` â Minor version number, such as "9". 1976 * - `dot` â `{number}` â Dot version number, such as "18". 1977 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 1978 */ 1979var version = { 1980 full: '1.2.22', // all of these placeholder strings will be replaced by grunt's 1981 major: 1, // package task 1982 minor: 2, 1983 dot: 22, 1984 codeName: 'finicky-pleasure' 1985}; 1986 1987 1988function publishExternalAPI(angular){ 1989 extend(angular, { 1990 'bootstrap': bootstrap, 1991 'copy': copy, 1992 'extend': extend, 1993 'equals': equals, 1994 'element': jqLite, 1995 'forEach': forEach, 1996 'injector': createInjector, 1997 'noop': noop, 1998 'bind': bind, 1999 'toJson': toJson, 2000 'fromJson': fromJson, 2001 'identity': identity, 2002 'isUndefined': isUndefined, 2003 'isDefined': isDefined, 2004 'isString': isString, 2005 'isFunction': isFunction, 2006 'isObject': isObject, 2007 'isNumber': isNumber, 2008 'isElement': isElement, 2009 'isArray': isArray, 2010 'version': version, 2011 'isDate': isDate, 2012 'lowercase': lowercase, 2013 'uppercase': uppercase, 2014 'callbacks': {counter: 0}, 2015 '$$minErr': minErr, 2016 '$$csp': csp 2017 }); 2018 2019 angularModule = setupModuleLoader(window); 2020 try { 2021 angularModule('ngLocale'); 2022 } catch (e) { 2023 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 2024 } 2025 2026 angularModule('ng', ['ngLocale'], ['$provide', 2027 function ngModule($provide) { 2028 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2029 $provide.provider({ 2030 $$sanitizeUri: $$SanitizeUriProvider 2031 }); 2032 $provide.provider('$compile', $CompileProvider). 2033 directive({ 2034 a: htmlAnchorDirective, 2035 input: inputDirective, 2036 textarea: inputDirective, 2037 form: formDirective, 2038 script: scriptDirective, 2039 select: selectDirective, 2040 style: styleDirective, 2041 option: optionDirective, 2042 ngBind: ngBindDirective, 2043 ngBindHtml: ngBindHtmlDirective, 2044 ngBindTemplate: ngBindTemplateDirective, 2045 ngClass: ngClassDirective, 2046 ngClassEven: ngClassEvenDirective, 2047 ngClassOdd: ngClassOddDirective, 2048 ngCloak: ngCloakDirective, 2049 ngController: ngControllerDirective, 2050 ngForm: ngFormDirective, 2051 ngHide: ngHideDirective, 2052 ngIf: ngIfDirective, 2053 ngInclude: ngIncludeDirective, 2054 ngInit: ngInitDirective, 2055 ngNonBindable: ngNonBindableDirective, 2056 ngPluralize: ngPluralizeDirective, 2057 ngRepeat: ngRepeatDirective, 2058 ngShow: ngShowDirective, 2059 ngStyle: ngStyleDirective, 2060 ngSwitch: ngSwitchDirective, 2061 ngSwitchWhen: ngSwitchWhenDirective, 2062 ngSwitchDefault: ngSwitchDefaultDirective, 2063 ngOptions: ngOptionsDirective, 2064 ngTransclude: ngTranscludeDirective, 2065 ngModel: ngModelDirective, 2066 ngList: ngListDirective, 2067 ngChange: ngChangeDirective, 2068 required: requiredDirective, 2069 ngRequired: requiredDirective, 2070 ngValue: ngValueDirective 2071 }). 2072 directive({ 2073 ngInclude: ngIncludeFillContentDirective 2074 }). 2075 directive(ngAttributeAliasDirectives). 2076 directive(ngEventDirectives); 2077 $provide.provider({ 2078 $anchorScroll: $AnchorScrollProvider, 2079 $animate: $AnimateProvider, 2080 $browser: $BrowserProvider, 2081 $cacheFactory: $CacheFactoryProvider, 2082 $controller: $ControllerProvider, 2083 $document: $DocumentProvider, 2084 $exceptionHandler: $ExceptionHandlerProvider, 2085 $filter: $FilterProvider, 2086 $interpolate: $InterpolateProvider, 2087 $interval: $IntervalProvider, 2088 $http: $HttpProvider, 2089 $httpBackend: $HttpBackendProvider, 2090 $location: $LocationProvider, 2091 $log: $LogProvider, 2092 $parse: $ParseProvider, 2093 $rootScope: $RootScopeProvider, 2094 $q: $QProvider, 2095 $sce: $SceProvider, 2096 $sceDelegate: $SceDelegateProvider, 2097 $sniffer: $SnifferProvider, 2098 $templateCache: $TemplateCacheProvider, 2099 $timeout: $TimeoutProvider, 2100 $window: $WindowProvider, 2101 $$rAF: $$RAFProvider, 2102 $$asyncCallback : $$AsyncCallbackProvider 2103 }); 2104 } 2105 ]); 2106} 2107 2108/* global JQLitePrototype: true, 2109 addEventListenerFn: true, 2110 removeEventListenerFn: true, 2111 BOOLEAN_ATTR: true 2112*/ 2113
2114////////////////////////////////// 2115//JQLite 2116////////////////////////////////// 2117 2118/** 2119 * @ngdoc function 2120 * @name angular.element 2121 * @module ng 2122 * @kind function 2123 * 2124 * @description 2125 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2126 * 2127 * If jQuery is available, `angular.element` is an alias for the 2128 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2129 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." 2130 * 2131 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows 2132 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most 2133 * commonly needed functionality with the goal of having a very small footprint.</div> 2134 * 2135 * To use jQuery, simply load it before `DOMContentLoaded` event fired. 2136 * 2137 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or 2138 * jqLite; they are never raw DOM references.</div> 2139 * 2140 * ## Angular's jqLite 2141 * jqLite provides only the following jQuery methods: 2142 * 2143 * - [`addClass()`](http://api.jquery.com/addClass/) 2144 * - [`after()`](http://api.jquery.com/after/) 2145 * - [`append()`](http://api.jquery.com/append/) 2146 * - [`attr()`](http://api.jquery.com/attr/) 2147 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2148 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2149 * - [`clone()`](http://api.jquery.com/clone/) 2150 * - [`contents()`](http://api.jquery.com/contents/) 2151 * - [`css()`](http://api.jquery.com/css/) 2152 * - [`data()`](http://api.jquery.com/data/) 2153 * - [`empty()`](http://api.jquery.com/empty/) 2154 * - [`eq()`](http://api.jquery.com/eq/) 2155 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2156 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2157 * - [`html()`](http://api.jquery.com/html/) 2158 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2159 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2160 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors 2161 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2162 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2163 * - [`prepend()`](http://api.jquery.com/prepend/) 2164 * - [`prop()`](http://api.jquery.com/prop/) 2165 * - [`ready()`](http://api.jquery.com/ready/) 2166 * - [`remove()`](http://api.jquery.com/remove/) 2167 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2168 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2169 * - [`removeData()`](http://api.jquery.com/removeData/) 2170 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2171 * - [`text()`](http://api.jquery.com/text/) 2172 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2173 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2174 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces 2175 * - [`val()`](http://api.jquery.com/val/) 2176 * - [`wrap()`](http://api.jquery.com/wrap/) 2177 * 2178 * ## jQuery/jqLite Extras 2179 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2180 * 2181 * ### Events 2182 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event 2183 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2184 * element before it is removed. 2185 * 2186 * ### Methods 2187 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2188 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2189 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2190 * `'ngModel'`). 2191 * - `injector()` - retrieves the injector of the current element or its parent. 2192 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2193 * element or its parent. 2194 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2195 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
2196 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2197 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2198 * parent element is reached. 2199 * 2200 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2201 * @returns {Object} jQuery object. 2202 */ 2203 2204JQLite.expando = 'ng339'; 2205 2206var jqCache = JQLite.cache = {}, 2207 jqId = 1, 2208 addEventListenerFn = (window.document.addEventListener 2209 ? function(element, type, fn) {element.addEventListener(type, fn, false);} 2210 : function(element, type, fn) {element.attachEvent('on' + type, fn);}), 2211 removeEventListenerFn = (window.document.removeEventListener 2212 ? function(element, type, fn) {element.removeEventListener(type, fn, false); } 2213 : function(element, type, fn) {element.detachEvent('on' + type, fn); }); 2214 2215/* 2216 * !!! This is an undocumented "private" function !!! 2217 */ 2218var jqData = JQLite._data = function(node) { 2219 //jQuery always returns an object on cache miss 2220 return this.cache[node[this.expando]] || {}; 2221}; 2222 2223function jqNextId() { return ++jqId; } 2224 2225 2226var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2227var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2228var jqLiteMinErr = minErr('jqLite'); 2229 2230/** 2231 * Converts snake_case to camelCase. 2232 * Also there is special case for Moz prefix starting with upper case letter. 2233 * @param name Name to normalize 2234 */ 2235function camelCase(name) { 2236 return name. 2237 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2238 return offset ? letter.toUpperCase() : letter; 2239 }). 2240 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2241} 2242 2243///////////////////////////////////////////// 2244// jQuery mutation patch 2245// 2246// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a 2247// $destroy event on all DOM nodes being removed. 2248// 2249///////////////////////////////////////////// 2250 2251function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { 2252 var originalJqFn = jQuery.fn[name]; 2253 originalJqFn = originalJqFn.$original || originalJqFn; 2254 removePatch.$original = originalJqFn; 2255 jQuery.fn[name] = removePatch; 2256 2257 function removePatch(param) { 2258 // jshint -W040 2259 var list = filterElems && param ? [this.filter(param)] : [this], 2260 fireEvent = dispatchThis, 2261 set, setIndex, setLength, 2262 element, childIndex, childLength, children; 2263 2264 if (!getterIfNoArguments || param != null) { 2265 while(list.length) { 2266 set = list.shift(); 2267 for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { 2268 element = jqLite(set[setIndex]); 2269 if (fireEvent) { 2270 element.triggerHandler('$destroy'); 2271 } else { 2272 fireEvent = !fireEvent; 2273 } 2274 for(childIndex = 0, childLength = (children = element.children()).length; 2275 childIndex < childLength; 2276 childIndex++) { 2277 list.push(jQuery(children[childIndex])); 2278 } 2279 } 2280 } 2281 } 2282 return originalJqFn.apply(this, arguments); 2283 } 2284} 2285 2286var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; 2287var HTML_REGEXP = /<|&#?\w+;/; 2288var TAG_NAME_REGEXP = /<([\w:]+)/; 2289var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; 2290 2291var wrapMap = { 2292 'option': [1, '<select multiple="multiple">', '</select>'], 2293 2294 'thead': [1, '<table>', '</table>'], 2295 'col': [2, '<table><colgroup>', '</colgroup></table>'], 2296 'tr': [2, '<table><tbody>', '</tbody></table>'], 2297 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2298 '_default': [0, "", ""] 2299}; 2300 2301wrapMap.optgroup = wrapMap.option; 2302wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2303wrapMap.th = wrapMap.td; 2304 2305function jqLiteIsTextNode(html) { 2306 return !HTML_REGEXP.test(html); 2307} 2308 2309function jqLiteBuildFragment(html, context) { 2310 var elem, tmp, tag, wrap, 2311 fragment = context.createDocumentFragment(), 2312 nodes = [], i, j, jj; 2313 2314 if (jqLiteIsTextNode(html)) { 2315 // Convert non-html into a text node 2316 nodes.push(context.createTextNode(html)); 2317 } else { 2318 tmp = fragment.appendChild(context.createElement('div')); 2319 // Convert html into DOM nodes 2320 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2321 wrap = wrapMap[tag] || wrapMap._default; 2322 tmp.innerHTML = '<div> </div>' + 2323 wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2324 tmp.removeChild(tmp.firstChild); 2325 2326 // Descend through wrappers to the right content 2327 i = wrap[0]; 2328 while (i--) { 2329 tmp = tmp.lastChild; 2330 } 2331 2332 for (j=0, jj=tmp.childNodes.length; j<jj; ++j) nodes.push(tmp.childNodes[j]); 2333 2334 tmp = fragment.firstChild; 2335 tmp.textContent = ""; 2336 } 2337 2338 // Remove wrapper from fragment 2339 fragment.textContent = ""; 2340 fragment.innerHTML = ""; // Clear inner HTML 2341 return nodes; 2342} 2343 2344function jqLiteParseHTML(html, context) { 2345 context = context || document; 2346 var parsed; 2347 2348 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2349 return [context.createElement(parsed[1])]; 2350 } 2351 2352 return jqLiteBuildFragment(html, context); 2353} 2354
2355///////////////////////////////////////////// 2356function JQLite(element) { 2357 if (element instanceof JQLite) { 2358 return element; 2359 } 2360 if (isString(element)) { 2361 element = trim(element); 2362 } 2363 if (!(this instanceof JQLite)) { 2364 if (isString(element) && element.charAt(0) != '<') { 2365 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2366 } 2367 return new JQLite(element); 2368 } 2369 2370 if (isString(element)) { 2371 jqLiteAddNodes(this, jqLiteParseHTML(element)); 2372 var fragment = jqLite(document.createDocumentFragment()); 2373 fragment.append(this); 2374 } else { 2375 jqLiteAddNodes(this, element); 2376 } 2377} 2378 2379function jqLiteClone(element) { 2380 return element.cloneNode(true); 2381} 2382 2383function jqLiteDealoc(element){ 2384 jqLiteRemoveData(element); 2385 for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { 2386 jqLiteDealoc(children[i]); 2387 } 2388} 2389 2390function jqLiteOff(element, type, fn, unsupported) { 2391 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2392 2393 var events = jqLiteExpandoStore(element, 'events'), 2394 handle = jqLiteExpandoStore(element, 'handle'); 2395 2396 if (!handle) return; //no listeners registered 2397 2398 if (isUndefined(type)) { 2399 forEach(events, function(eventHandler, type) { 2400 removeEventListenerFn(element, type, eventHandler); 2401 delete events[type]; 2402 }); 2403 } else { 2404 forEach(type.split(' '), function(type) { 2405 if (isUndefined(fn)) { 2406 removeEventListenerFn(element, type, events[type]); 2407 delete events[type]; 2408 } else { 2409 arrayRemove(events[type] || [], fn); 2410 } 2411 }); 2412 } 2413} 2414 2415function jqLiteRemoveData(element, name) { 2416 var expandoId = element.ng339, 2417 expandoStore = jqCache[expandoId]; 2418 2419 if (expandoStore) { 2420 if (name) { 2421 delete jqCache[expandoId].data[name]; 2422 return; 2423 } 2424 2425 if (expandoStore.handle) { 2426 expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); 2427 jqLiteOff(element); 2428 } 2429 delete jqCache[expandoId]; 2430 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 2431 } 2432} 2433 2434function jqLiteExpandoStore(element, key, value) { 2435 var expandoId = element.ng339, 2436 expandoStore = jqCache[expandoId || -1]; 2437 2438 if (isDefined(value)) { 2439 if (!expandoStore) { 2440 element.ng339 = expandoId = jqNextId(); 2441 expandoStore = jqCache[expandoId] = {}; 2442 } 2443 expandoStore[key] = value; 2444 } else { 2445 return expandoStore && expandoStore[key]; 2446 } 2447} 2448 2449function jqLiteData(element, key, value) { 2450 var data = jqLiteExpandoStore(element, 'data'), 2451 isSetter = isDefined(value), 2452 keyDefined = !isSetter && isDefined(key), 2453 isSimpleGetter = keyDefined && !isObject(key); 2454 2455 if (!data && !isSimpleGetter) { 2456 jqLiteExpandoStore(element, 'data', data = {}); 2457 } 2458 2459 if (isSetter) { 2460 data[key] = value; 2461 } else { 2462 if (keyDefined) { 2463 if (isSimpleGetter) { 2464 // don't create data in this case. 2465 return data && data[key]; 2466 } else { 2467 extend(data, key); 2468 } 2469 } else { 2470 return data; 2471 } 2472 } 2473} 2474 2475function jqLiteHasClass(element, selector) { 2476 if (!element.getAttribute) return false; 2477 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2478 indexOf( " " + selector + " " ) > -1); 2479} 2480 2481function jqLiteRemoveClass(element, cssClasses) { 2482 if (cssClasses && element.setAttribute) { 2483 forEach(cssClasses.split(' '), function(cssClass) { 2484 element.setAttribute('class', trim( 2485 (" " + (element.getAttribute('class') || '') + " ") 2486 .replace(/[\n\t]/g, " ") 2487 .replace(" " + trim(cssClass) + " ", " ")) 2488 ); 2489 }); 2490 } 2491} 2492 2493function jqLiteAddClass(element, cssClasses) { 2494 if (cssClasses && element.setAttribute) { 2495 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 2496 .replace(/[\n\t]/g, " "); 2497 2498 forEach(cssClasses.split(' '), function(cssClass) { 2499 cssClass = trim(cssClass); 2500 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 2501 existingClasses += cssClass + ' '; 2502 } 2503 }); 2504 2505 element.setAttribute('class', trim(existingClasses)); 2506 } 2507} 2508 2509function jqLiteAddNodes(root, elements) { 2510 if (elements) { 2511 elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) 2512 ? elements 2513 : [ elements ]; 2514 for(var i=0; i < elements.length; i++) { 2515 root.push(elements[i]); 2516 } 2517 } 2518} 2519 2520function jqLiteController(element, name) { 2521 return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); 2522} 2523 2524function jqLiteInheritedData(element, name, value) { 2525 // if element is the document object work with the html element instead 2526 // this makes $(document).scope() possible 2527 if(element.nodeType == 9) { 2528 element = element.documentElement; 2529 } 2530 var names = isArray(name) ? name : [name]; 2531 2532 while (element) { 2533 for (var i = 0, ii = names.length; i < ii; i++) { 2534 if ((value = jqLite.data(element, names[i])) !== undefined) return value; 2535 } 2536 2537 // If dealing with a document fragment node with a host element, and no parent, use the host 2538 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 2539 // to lookup parent controllers. 2540 element = element.parentNode || (element.nodeType === 11 && element.host); 2541 } 2542} 2543 2544function jqLiteEmpty(element) { 2545 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2546 jqLiteDealoc(childNodes[i]); 2547 } 2548 while (element.firstChild) { 2549 element.removeChild(element.firstChild); 2550 } 2551} 2552
2553////////////////////////////////////////// 2554// Functions which are declared directly. 2555////////////////////////////////////////// 2556var JQLitePrototype = JQLite.prototype = { 2557 ready: function(fn) { 2558 var fired = false; 2559 2560 function trigger() { 2561 if (fired) return; 2562 fired = true; 2563 fn(); 2564 } 2565 2566 // check if document already is loaded 2567 if (document.readyState === 'complete'){ 2568 setTimeout(trigger); 2569 } else { 2570 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 2571 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 2572 // jshint -W064 2573 JQLite(window).on('load', trigger); // fallback to window.onload for others 2574 // jshint +W064 2575 } 2576 }, 2577 toString: function() { 2578 var value = []; 2579 forEach(this, function(e){ value.push('' + e);}); 2580 return '[' + value.join(', ') + ']'; 2581 }, 2582 2583 eq: function(index) { 2584 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 2585 }, 2586 2587 length: 0, 2588 push: push, 2589 sort: [].sort, 2590 splice: [].splice 2591}; 2592 2593////////////////////////////////////////// 2594// Functions iterating getter/setters. 2595// these functions return self on setter and 2596// value on get. 2597////////////////////////////////////////// 2598var BOOLEAN_ATTR = {}; 2599forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 2600 BOOLEAN_ATTR[lowercase(value)] = value; 2601}); 2602var BOOLEAN_ELEMENTS = {}; 2603forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 2604 BOOLEAN_ELEMENTS[uppercase(value)] = true; 2605}); 2606 2607function getBooleanAttrName(element, name) { 2608 // check dom last since we will most likely fail on name 2609 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 2610 2611 // booleanAttr is here twice to minimize DOM access 2612 return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; 2613} 2614 2615forEach({ 2616 data: jqLiteData, 2617 removeData: jqLiteRemoveData 2618}, function(fn, name) { 2619 JQLite[name] = fn; 2620}); 2621 2622forEach({ 2623 data: jqLiteData, 2624 inheritedData: jqLiteInheritedData, 2625 2626 scope: function(element) { 2627 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2628 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 2629 }, 2630 2631 isolateScope: function(element) { 2632 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2633 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 2634 }, 2635 2636 controller: jqLiteController, 2637 2638 injector: function(element) { 2639 return jqLiteInheritedData(element, '$injector'); 2640 }, 2641 2642 removeAttr: function(element,name) { 2643 element.removeAttribute(name); 2644 }, 2645 2646 hasClass: jqLiteHasClass, 2647 2648 css: function(element, name, value) { 2649 name = camelCase(name); 2650 2651 if (isDefined(value)) { 2652 element.style[name] = value; 2653 } else { 2654 var val; 2655 2656 if (msie <= 8) { 2657 // this is some IE specific weirdness that jQuery 1.6.4 does not sure why 2658 val = element.currentStyle && element.currentStyle[name]; 2659 if (val === '') val = 'auto'; 2660 } 2661 2662 val = val || element.style[name]; 2663 2664 if (msie <= 8) { 2665 // jquery weirdness :-/ 2666 val = (val === '') ? undefined : val; 2667 } 2668 2669 return val; 2670 } 2671 }, 2672 2673 attr: function(element, name, value){ 2674 var lowercasedName = lowercase(name); 2675 if (BOOLEAN_ATTR[lowercasedName]) { 2676 if (isDefined(value)) { 2677 if (!!value) { 2678 element[name] = true; 2679 element.setAttribute(name, lowercasedName); 2680 } else { 2681 element[name] = false; 2682 element.removeAttribute(lowercasedName); 2683 } 2684 } else { 2685 return (element[name] || 2686 (element.attributes.getNamedItem(name)|| noop).specified) 2687 ? lowercasedName 2688 : undefined; 2689 } 2690 } else if (isDefined(value)) { 2691 element.setAttribute(name, value); 2692 } else if (element.getAttribute) { 2693 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 2694 // some elements (e.g. Document) don't have get attribute, so return undefined 2695 var ret = element.getAttribute(name, 2); 2696 // normalize non-existing attributes to undefined (as jQuery) 2697 return ret === null ? undefined : ret; 2698 } 2699 }, 2700 2701 prop: function(element, name, value) { 2702 if (isDefined(value)) { 2703 element[name] = value; 2704 } else { 2705 return element[name]; 2706 } 2707 }, 2708 2709 text: (function() { 2710 var NODE_TYPE_TEXT_PROPERTY = []; 2711 if (msie < 9) { 2712 NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ 2713 NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ 2714 } else { 2715 NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ 2716 NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ 2717 } 2718 getText.$dv = ''; 2719 return getText; 2720 2721 function getText(element, value) { 2722 var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; 2723 if (isUndefined(value)) { 2724 return textProp ? element[textProp] : ''; 2725 } 2726 element[textProp] = value; 2727 } 2728 })(), 2729 2730 val: function(element, value) { 2731 if (isUndefined(value)) { 2732 if (nodeName_(element) === 'SELECT' && element.multiple) { 2733 var result = [];
2734 forEach(element.options, function (option) { 2735 if (option.selected) { 2736 result.push(option.value || option.text); 2737 } 2738 }); 2739 return result.length === 0 ? null : result; 2740 } 2741 return element.value; 2742 } 2743 element.value = value; 2744 }, 2745 2746 html: function(element, value) { 2747 if (isUndefined(value)) { 2748 return element.innerHTML; 2749 } 2750 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2751 jqLiteDealoc(childNodes[i]); 2752 } 2753 element.innerHTML = value; 2754 }, 2755 2756 empty: jqLiteEmpty 2757}, function(fn, name){ 2758 /** 2759 * Properties: writes return selection, reads return first value 2760 */ 2761 JQLite.prototype[name] = function(arg1, arg2) { 2762 var i, key; 2763 var nodeCount = this.length; 2764 2765 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 2766 // in a way that survives minification. 2767 // jqLiteEmpty takes no arguments but is a setter. 2768 if (fn !== jqLiteEmpty && 2769 (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { 2770 if (isObject(arg1)) { 2771 2772 // we are a write, but the object properties are the key/values 2773 for (i = 0; i < nodeCount; i++) { 2774 if (fn === jqLiteData) { 2775 // data() takes the whole object in jQuery 2776 fn(this[i], arg1); 2777 } else { 2778 for (key in arg1) { 2779 fn(this[i], key, arg1[key]); 2780 } 2781 } 2782 } 2783 // return self for chaining 2784 return this; 2785 } else { 2786 // we are a read, so read the first child. 2787 // TODO: do we still need this? 2788 var value = fn.$dv; 2789 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 2790 var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount; 2791 for (var j = 0; j < jj; j++) { 2792 var nodeValue = fn(this[j], arg1, arg2); 2793 value = value ? value + nodeValue : nodeValue; 2794 } 2795 return value; 2796 } 2797 } else { 2798 // we are a write, so apply to all children 2799 for (i = 0; i < nodeCount; i++) { 2800 fn(this[i], arg1, arg2); 2801 } 2802 // return self for chaining 2803 return this; 2804 } 2805 }; 2806}); 2807 2808function createEventHandler(element, events) { 2809 var eventHandler = function (event, type) { 2810 if (!event.preventDefault) { 2811 event.preventDefault = function() { 2812 event.returnValue = false; //ie 2813 }; 2814 } 2815 2816 if (!event.stopPropagation) { 2817 event.stopPropagation = function() { 2818 event.cancelBubble = true; //ie 2819 }; 2820 } 2821 2822 if (!event.target) { 2823 event.target = event.srcElement || document; 2824 } 2825 2826 if (isUndefined(event.defaultPrevented)) { 2827 var prevent = event.preventDefault; 2828 event.preventDefault = function() { 2829 event.defaultPrevented = true; 2830 prevent.call(event); 2831 }; 2832 event.defaultPrevented = false; 2833 } 2834 2835 event.isDefaultPrevented = function() { 2836 return event.defaultPrevented || event.returnValue === false; 2837 }; 2838 2839 // Copy event handlers in case event handlers array is modified during execution. 2840 var eventHandlersCopy = shallowCopy(events[type || event.type] || []); 2841 2842 forEach(eventHandlersCopy, function(fn) { 2843 fn.call(element, event); 2844 }); 2845 2846 // Remove monkey-patched methods (IE), 2847 // as they would cause memory leaks in IE8. 2848 if (msie <= 8) { 2849 // IE7/8 does not allow to delete property on native object 2850 event.preventDefault = null; 2851 event.stopPropagation = null; 2852 event.isDefaultPrevented = null; 2853 } else { 2854 // It shouldn't affect normal browsers (native methods are defined on prototype). 2855 delete event.preventDefault; 2856 delete event.stopPropagation; 2857 delete event.isDefaultPrevented; 2858 } 2859 }; 2860 eventHandler.elem = element; 2861 return eventHandler; 2862} 2863 2864////////////////////////////////////////// 2865// Functions iterating traversal. 2866// These functions chain results into a single 2867// selector. 2868////////////////////////////////////////// 2869forEach({ 2870 removeData: jqLiteRemoveData, 2871 2872 dealoc: jqLiteDealoc, 2873 2874 on: function onFn(element, type, fn, unsupported){
2875 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 2876 2877 var events = jqLiteExpandoStore(element, 'events'), 2878 handle = jqLiteExpandoStore(element, 'handle'); 2879 2880 if (!events) jqLiteExpandoStore(element, 'events', events = {}); 2881 if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); 2882 2883 forEach(type.split(' '), function(type){ 2884 var eventFns = events[type]; 2885 2886 if (!eventFns) { 2887 if (type == 'mouseenter' || type == 'mouseleave') { 2888 var contains = document.body.contains || document.body.compareDocumentPosition ? 2889 function( a, b ) { 2890 // jshint bitwise: false 2891 var adown = a.nodeType === 9 ? a.documentElement : a, 2892 bup = b && b.parentNode; 2893 return a === bup || !!( bup && bup.nodeType === 1 && ( 2894 adown.contains ? 2895 adown.contains( bup ) : 2896 a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 2897 )); 2898 } : 2899 function( a, b ) { 2900 if ( b ) { 2901 while ( (b = b.parentNode) ) { 2902 if ( b === a ) { 2903 return true; 2904 } 2905 } 2906 } 2907 return false; 2908 }; 2909 2910 events[type] = []; 2911 2912 // Refer to jQuery's implementation of mouseenter & mouseleave 2913 // Read about mouseenter and mouseleave: 2914 // http://www.quirksmode.org/js/events_mouse.html#link8 2915 var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; 2916 2917 onFn(element, eventmap[type], function(event) { 2918 var target = this, related = event.relatedTarget; 2919 // For mousenter/leave call the handler if related is outside the target. 2920 // NB: No relatedTarget if the mouse left/entered the browser window 2921 if ( !related || (related !== target && !contains(target, related)) ){ 2922 handle(event, type); 2923 } 2924 }); 2925 2926 } else { 2927 addEventListenerFn(element, type, handle); 2928 events[type] = []; 2929 } 2930 eventFns = events[type]; 2931 } 2932 eventFns.push(fn); 2933 }); 2934 }, 2935 2936 off: jqLiteOff, 2937 2938 one: function(element, type, fn) { 2939 element = jqLite(element); 2940 2941 //add the listener twice so that when it is called 2942 //you can remove the original function and still be 2943 //able to call element.off(ev, fn) normally 2944 element.on(type, function onFn() { 2945 element.off(type, fn); 2946 element.off(type, onFn); 2947 }); 2948 element.on(type, fn); 2949 }, 2950 2951 replaceWith: function(element, replaceNode) { 2952 var index, parent = element.parentNode; 2953 jqLiteDealoc(element); 2954 forEach(new JQLite(replaceNode), function(node){ 2955 if (index) { 2956 parent.insertBefore(node, index.nextSibling); 2957 } else { 2958 parent.replaceChild(node, element); 2959 } 2960 index = node; 2961 }); 2962 }, 2963 2964 children: function(element) { 2965 var children = []; 2966 forEach(element.childNodes, function(element){ 2967 if (element.nodeType === 1) 2968 children.push(element); 2969 }); 2970 return children; 2971 }, 2972 2973 contents: function(element) { 2974 return element.contentDocument || element.childNodes || []; 2975 }, 2976 2977 append: function(element, node) { 2978 forEach(new JQLite(node), function(child){ 2979 if (element.nodeType === 1 || element.nodeType === 11) { 2980 element.appendChild(child); 2981 } 2982 }); 2983 }, 2984 2985 prepend: function(element, node) { 2986 if (element.nodeType === 1) { 2987 var index = element.firstChild; 2988 forEach(new JQLite(node), function(child){ 2989 element.insertBefore(child, index); 2990 }); 2991 } 2992 }, 2993 2994 wrap: function(element, wrapNode) { 2995 wrapNode = jqLite(wrapNode)[0]; 2996 var parent = element.parentNode; 2997 if (parent) { 2998 parent.replaceChild(wrapNode, element); 2999 } 3000 wrapNode.appendChild(element); 3001 }, 3002 3003 remove: function(element) { 3004 jqLiteDealoc(element); 3005 var parent = element.parentNode; 3006 if (parent) parent.removeChild(element); 3007 }, 3008 3009 after: function(element, newElement) { 3010 var index = element, parent = element.parentNode;
3011 forEach(new JQLite(newElement), function(node){ 3012 parent.insertBefore(node, index.nextSibling); 3013 index = node; 3014 }); 3015 }, 3016 3017 addClass: jqLiteAddClass, 3018 removeClass: jqLiteRemoveClass, 3019 3020 toggleClass: function(element, selector, condition) { 3021 if (selector) { 3022 forEach(selector.split(' '), function(className){ 3023 var classCondition = condition; 3024 if (isUndefined(classCondition)) { 3025 classCondition = !jqLiteHasClass(element, className); 3026 } 3027 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 3028 }); 3029 } 3030 }, 3031 3032 parent: function(element) { 3033 var parent = element.parentNode; 3034 return parent && parent.nodeType !== 11 ? parent : null; 3035 }, 3036 3037 next: function(element) { 3038 if (element.nextElementSibling) { 3039 return element.nextElementSibling; 3040 } 3041 3042 // IE8 doesn't have nextElementSibling 3043 var elm = element.nextSibling; 3044 while (elm != null && elm.nodeType !== 1) { 3045 elm = elm.nextSibling; 3046 } 3047 return elm; 3048 }, 3049 3050 find: function(element, selector) { 3051 if (element.getElementsByTagName) { 3052 return element.getElementsByTagName(selector); 3053 } else { 3054 return []; 3055 } 3056 }, 3057 3058 clone: jqLiteClone, 3059 3060 triggerHandler: function(element, event, extraParameters) { 3061 3062 var dummyEvent, eventFnsCopy, handlerArgs; 3063 var eventName = event.type || event; 3064 var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; 3065 3066 if (eventFns) { 3067 3068 // Create a dummy event to pass to the handlers 3069 dummyEvent = { 3070 preventDefault: function() { this.defaultPrevented = true; }, 3071 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 3072 stopPropagation: noop, 3073 type: eventName, 3074 target: element 3075 }; 3076 3077 // If a custom event was provided then extend our dummy event with it 3078 if (event.type) { 3079 dummyEvent = extend(dummyEvent, event); 3080 } 3081 3082 // Copy event handlers in case event handlers array is modified during execution. 3083 eventFnsCopy = shallowCopy(eventFns); 3084 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 3085 3086 forEach(eventFnsCopy, function(fn) { 3087 fn.apply(element, handlerArgs); 3088 }); 3089 3090 } 3091 } 3092}, function(fn, name){ 3093 /** 3094 * chaining functions 3095 */ 3096 JQLite.prototype[name] = function(arg1, arg2, arg3) { 3097 var value; 3098 for(var i=0; i < this.length; i++) { 3099 if (isUndefined(value)) { 3100 value = fn(this[i], arg1, arg2, arg3); 3101 if (isDefined(value)) { 3102 // any function which returns a value needs to be wrapped 3103 value = jqLite(value); 3104 } 3105 } else { 3106 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3107 } 3108 } 3109 return isDefined(value) ? value : this; 3110 }; 3111 3112 // bind legacy bind/unbind to on/off 3113 JQLite.prototype.bind = JQLite.prototype.on; 3114 JQLite.prototype.unbind = JQLite.prototype.off; 3115}); 3116 3117/** 3118 * Computes a hash of an 'obj'. 3119 * Hash of a: 3120 * string is string 3121 * number is number as string 3122 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3123 * that is also assigned to the $$hashKey property of the object. 3124 * 3125 * @param obj 3126 * @returns {string} hash string such that the same input will have the same hash string. 3127 * The resulting string key is in 'type:hashKey' format. 3128 */ 3129function hashKey(obj, nextUidFn) { 3130 var objType = typeof obj, 3131 key; 3132 3133 if (objType == 'function' || (objType == 'object' && obj !== null)) { 3134 if (typeof (key = obj.$$hashKey) == 'function') { 3135 // must invoke on object to keep the right this 3136 key = obj.$$hashKey(); 3137 } else if (key === undefined) { 3138 key = obj.$$hashKey = (nextUidFn || nextUid)(); 3139 } 3140 } else { 3141 key = obj; 3142 } 3143 3144 return objType + ':' + key; 3145} 3146 3147/** 3148 * HashMap which can use objects as keys 3149 */ 3150function HashMap(array, isolatedUid) { 3151 if (isolatedUid) { 3152 var uid = 0; 3153 this.nextUid = function() { 3154 return ++uid; 3155 }; 3156 } 3157 forEach(array, this.put, this); 3158} 3159HashMap.prototype = { 3160 /** 3161 * Store key value pair 3162 * @param key key to store can be any type 3163 * @param value value to store can be any type 3164 */ 3165 put: function(key, value) { 3166 this[hashKey(key, this.nextUid)] = value; 3167 }, 3168 3169 /** 3170 * @param key 3171 * @returns {Object} the value for the key 3172 */ 3173 get: function(key) { 3174 return this[hashKey(key, this.nextUid)]; 3175 }, 3176 3177 /** 3178 * Remove the key/value pair 3179 * @param key 3180 */ 3181 remove: function(key) { 3182 var value = this[key = hashKey(key, this.nextUid)]; 3183 delete this[key]; 3184 return value; 3185 } 3186}; 3187 3188/** 3189 * @ngdoc function 3190 * @module ng 3191 * @name angular.injector 3192 * @kind function 3193 * 3194 * @description 3195 * Creates an injector function that can be used for retrieving services as well as for 3196 * dependency injection (see {@link guide/di dependency injection}). 3197 * 3198 3199 * @param {Array.<string|Function>}
3199 modules A list of module functions or their aliases. See 3200 * {@link angular.module}. The `ng` module must be explicitly added. 3201 * @returns {function()} Injector function. See {@link auto.$injector $injector}. 3202 * 3203 * @example 3204 * Typical usage 3205 * ```js 3206 * // create an injector 3207 * var $injector = angular.injector(['ng']); 3208 * 3209 * // use the injector to kick off your application 3210 * // use the type inference to auto inject arguments, or use implicit injection 3211 * $injector.invoke(function($rootScope, $compile, $document){ 3212 * $compile($document)($rootScope); 3213 * $rootScope.$digest(); 3214 * }); 3215 * ``` 3216 * 3217 * Sometimes you want to get access to the injector of a currently running Angular app 3218 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3219 * application has been bootstrapped. You can do this using the extra `injector()` added 3220 * to JQuery/jqLite elements. See {@link angular.element}. 3221 * 3222 * *This is fairly rare but could be the case if a third party library is injecting the 3223 * markup.* 3224 * 3225 * In the following example a new block of HTML containing a `ng-controller` 3226 * directive is added to the end of the document body by JQuery. We then compile and link 3227 * it into the current AngularJS scope. 3228 * 3229 * ```js 3230 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3231 * $(document.body).append($div); 3232 * 3233 * angular.element(document).injector().invoke(function($compile) { 3234 * var scope = angular.element($div).scope(); 3235 * $compile($div)(scope); 3236 * }); 3237 * ``` 3238 */ 3239 3240 3241/** 3242 * @ngdoc module 3243 * @name auto 3244 * @description 3245 * 3246 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3247 */ 3248 3249var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; 3250var FN_ARG_SPLIT = /,/; 3251var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3252var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3253var $injectorMinErr = minErr('$injector'); 3254function annotate(fn) { 3255 var $inject, 3256 fnText, 3257 argDecl, 3258 last; 3259 3260 if (typeof fn === 'function') { 3261 if (!($inject = fn.$inject)) { 3262 $inject = []; 3263 if (fn.length) { 3264 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3265 argDecl = fnText.match(FN_ARGS); 3266 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ 3267 arg.replace(FN_ARG, function(all, underscore, name){ 3268 $inject.push(name); 3269 }); 3270 }); 3271 } 3272 fn.$inject = $inject; 3273 } 3274 } else if (isArray(fn)) { 3275 last = fn.length - 1; 3276 assertArgFn(fn[last], 'fn'); 3277 $inject = fn.slice(0, last); 3278 } else { 3279 assertArgFn(fn, 'fn', true); 3280 } 3281 return $inject; 3282} 3283 3284/////////////////////////////////////// 3285 3286/** 3287 * @ngdoc service 3288 * @name $injector 3289 * @kind function 3290 * 3291 * @description 3292 * 3293 * `$injector` is used to retrieve object instances as defined by 3294 * {@link auto.$provide provider}, instantiate types, invoke methods, 3295 * and load modules. 3296 * 3297 * The following always holds true: 3298 * 3299 * ```js 3300 * var $injector = angular.injector(); 3301 * expect($injector.get('$injector')).toBe($injector); 3302 * expect($injector.invoke(function($injector){ 3303 * return $injector; 3304 * }).toBe($injector); 3305 * ``` 3306 * 3307 * # Injection Function Annotation 3308 * 3309 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3310 * following are all valid ways of annotating function with injection arguments and are equivalent. 3311 * 3312 * ```js 3313 * // inferred (only works if code not minified/obfuscated) 3314 * $injector.invoke(function(serviceA){}); 3315 * 3316 * // annotated 3317 * function explicit(serviceA) {}; 3318 * explicit.$inject = ['serviceA']; 3319 * $injector.invoke(explicit); 3320 * 3321 * // inline 3322 * $injector.invoke(['serviceA', function(serviceA){}]); 3323 * ``` 3324 * 3325 * ## Inference 3326 * 3327 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3328 * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with 3329 * minification, and obfuscation tools since these tools change the argument names. 3330 * 3331 * ## `$inject` Annotation 3332 * By adding an `$inject` property onto a function the injection parameters can be specified. 3333 * 3334 * ## Inline 3335 * As an array of injection names, where the last item in the array is the function to call. 3336 */ 3337 3338/** 3339 * @ngdoc method 3340 * @name $injector#get 3341 * 3342 * @description 3343 * Return an instance of the service. 3344 * 3345 * @param {string} name The name of the instance to retrieve. 3346 * @return {*} The instance. 3347 */ 3348 3349/** 3350 * @ngdoc method 3351 * @name $injector#invoke 3352 * 3353 * @description 3354 * Invoke the method and supply the method arguments from the `$injector`. 3355 * 3356 * @param {!Function} fn The function to invoke. Function parameters are injected according to the 3357 * {@link guide/di $inject Annotation} rules. 3358 * @param {Object=} self The `this` for the invoked method. 3359 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3360 * object first, before the `$injector` is consulted. 3361 * @returns {*} the value returned by the invoked `fn` function. 3362 */ 3363 3364/** 3365 * @ngdoc method 3366 * @name $injector#has 3367 * 3368 * @description 3369 * Allows the user to query if the particular service exists. 3370 * 3371 * @param {string} Name of the service to query. 3372 * @returns {boolean} returns true if injector has given service. 3373 */ 3374 3375/** 3376 * @ngdoc method 3377 * @name $injector#instantiate 3378 * @description 3379 * Create a new instance of JS type. The method takes a constructor function, invokes the new 3380 * operator, and supplies all of the arguments to the constructor function as specified by the 3381 * constructor annotation. 3382 * 3383 * @param {Function} Type Annotated constructor function. 3384 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3385 * object first, before the `$injector` is consulted. 3386 * @returns {Object} new instance of `Type`. 3387 */ 3388 3389/** 3390 * @ngdoc method 3391 * @name $injector#annotate 3392 * 3393 * @description 3394 * Returns an array of service names which the function is requesting for injection. This API is 3395 * used by the injector to determine which services need to be injected into the function when the 3396 * function is invoked. There are three ways in which the function can be annotated with the needed 3397 * dependencies. 3398 * 3399 * # Argument names 3400 * 3401 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3402 * by converting the function into a string using `toString()` method and extracting the argument 3403 * names. 3404 * ```js 3405 * // Given 3406 * function MyController($scope, $route) { 3407 * // ... 3408 * } 3409 * 3410 * // Then 3411 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3412 * ``` 3413 * 3414 * This method does not work with code minification / obfuscation. For this reason the following 3415 * annotation strategies are supported. 3416 * 3417 * # The `$inject` property 3418 * 3419 * If a function has an `$inject` property and its value is an array of strings, then the strings 3420 * represent names of services to be injected into the function. 3421 * ```js 3422 * // Given 3423 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 3424 * // ... 3425 * } 3426 * // Define function dependencies 3427 * MyController['$inject'] = ['$scope', '$route']; 3428 * 3429 * // Then 3430 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3431 * ``` 3432 * 3433 * # The array notation 3434 * 3435 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 3436 * is very inconvenient. In these situations using the array notation to specify the dependencies in 3437 * a way that survives minification is a better choice: 3438 * 3439 * ```js 3440 * // We wish to write this (not minification / obfuscation safe) 3441 * injector.invoke(function($compile, $rootScope) { 3442 * // ... 3443 * }); 3444 * 3445 * // We are forced to write break inlining 3446 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 3447 * // ... 3448 * }; 3449 * tmpFn.$inject = ['$compile', '$rootScope']; 3450 * injector.invoke(tmpFn); 3451 * 3452 * // To better support inline function the inline annotation is supported 3453 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 3454 * // ... 3455 * }]); 3456 * 3457 * // Therefore 3458 * expect(injector.annotate( 3459 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 3460 * ).toEqual(['$compile', '$rootScope']); 3461 * ``` 3462 * 3463 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 3464 * be retrieved as described above. 3465 * 3466 * @returns {Array.<string>} The names of the services which the function requires. 3467 */ 3468 3469 3470 3471 3472/** 3473 * @ngdoc service 3474 * @name $provide 3475 * 3476 * @description 3477 * 3478 * The {@link auto.$provide $provide} service has a number of methods for registering components 3479 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 3480 * {@link angular.Module}. 3481 * 3482 * An Angular **service** is a singleton object created by a **service factory**. These **service 3483 * factories** are functions which, in turn, are created by a **service provider**. 3484 * The **service providers** are constructor functions. When instantiated they must contain a 3485 * property called `$get`, which holds the **service factory** function. 3486 * 3487 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 3488 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 3489 * function to get the instance of the **service**. 3490 * 3491 * Often services have no configuration options and there is no need to add methods to the service 3492 * provider. The provider will be no more than a constructor function with a `$get` property. For 3493 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 3494 * services without specifying a provider. 3495 * 3496 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 3497 * {@link auto.$injector $injector} 3498 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 3499 * providers and services. 3500 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 3501 * services, not providers. 3502 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 3503 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 3504 * given factory function. 3505 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 3506 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 3507 * a new object using the given constructor function. 3508 * 3509 * See the individual methods for more information and examples. 3510 */ 3511 3512/** 3513 * @ngdoc method 3514 * @name $provide#provider 3515 * @description 3516 * 3517 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 3518 * are constructor functions, whose instances are responsible for "providing" a factory for a 3519 * service. 3520 * 3521 * Service provider names start with the name of the service they provide followed by `Provider`. 3522 * For example, the {@link ng.$log $log} service has a provider called 3523 * {@link ng.$logProvider $logProvider}. 3524 * 3525 * Service provider objects can have additional methods which allow configuration of the provider 3526 * and its service. Importantly, you can configure what kind of service is created by the `$get` 3527 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 3528 * method {@link ng.$logProvider#debugEnabled debugEnabled} 3529 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 3530 * console or not. 3531 * 3532 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 3533 'Provider'` key. 3534 * @param {(Object|function())} provider If the provider is: 3535 * 3536 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 3537 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 3538 * - `Constructor`: a new instance of the provider will be created using 3539 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 3540 * 3541 * @returns {Object} registered provider instance 3542 3543 * @example 3544 * 3545 * The following example shows how to create a simple event tracking service and register it using 3546 * {@link auto.$provide#provider $provide.provider()}. 3547 * 3548 * ```js 3549 * // Define the eventTracker provider 3550 * function EventTrackerProvider() { 3551 * var trackingUrl = '/track'; 3552 * 3553 * // A provider method for configuring where the tracked events should been saved 3554 * this.setTrackingUrl = function(url) { 3555 * trackingUrl = url; 3556 * }; 3557 * 3558 * // The service factory function 3559 * this.$get = ['$http', function($http) { 3560 * var trackedEvents = {}; 3561 * return { 3562 * // Call this to track an event 3563 * event: function(event) { 3564 * var count = trackedEvents[event] || 0; 3565 * count += 1; 3566 * trackedEvents[event] = count; 3567 * return count; 3568 * }, 3569 * // Call this to save the tracked events to the trackingUrl 3570 * save: function() { 3571 * $http.post(trackingUrl, trackedEvents); 3572 * } 3573 * }; 3574 * }]; 3575 * } 3576 * 3577 * describe('eventTracker', function() { 3578 * var postSpy; 3579 * 3580 * beforeEach(module(function($provide) { 3581 * // Register the eventTracker provider 3582 * $provide.provider('eventTracker', EventTrackerProvider); 3583 * })); 3584 * 3585 * beforeEach(module(function(eventTrackerProvider) { 3586 * // Configure eventTracker provider 3587 * eventTrackerProvider.setTrackingUrl('/custom-track'); 3588 * })); 3589 * 3590 * it('tracks events', inject(function(eventTracker) { 3591 * expect(eventTracker.event('login')).toEqual(1); 3592 * expect(eventTracker.event('login')).toEqual(2); 3593 * })); 3594 * 3595 * it('saves to the tracking url', inject(function(eventTracker, $http) { 3596 * postSpy = spyOn($http, 'post'); 3597 * eventTracker.event('login'); 3598 * eventTracker.save(); 3599 * expect(postSpy).toHaveBeenCalled(); 3600 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 3601 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 3602 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 3603 * })); 3604 * }); 3605 * ``` 3606 */ 3607 3608/** 3609 * @ngdoc method 3610 * @name $provide#factory 3611 * @description 3612 * 3613 * Register a **service factory**, which will be called to return the service instance. 3614 * This is short for registering a service where its provider consists of only a `$get` property, 3615 * which is the given service factory function. 3616 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 3617 * configure your service in a provider. 3618 * 3619 * @param {string} name The name of the instance. 3620 * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand 3621 * for `$provide.provider(name, {$get: $getFn})`. 3622 * @returns {Object} registered provider instance 3623 * 3624 * @example 3625 * Here is an example of registering a service 3626 * ```js 3627 * $provide.factory('ping', ['$http', function($http) { 3628 * return function ping() { 3629 * return $http.send('/ping'); 3630 * }; 3631 * }]); 3632 * ``` 3633 * You would then inject and use this service like this: 3634 * ```js 3635 * someModule.controller('Ctrl', ['ping', function(ping) { 3636 * ping(); 3637 * }]); 3638 * ``` 3639 */ 3640 3641 3642/** 3643 * @ngdoc method 3644 * @name $provide#service 3645 * @description 3646 * 3647 * Register a **service constructor**, which will be invoked with `new` to create the service 3648 * instance. 3649 * This is short for registering a service where its provider's `$get` property is the service 3650 * constructor function that will be used to instantiate the service instance. 3651 * 3652 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 3653 * as a type/class. 3654 * 3655 * @param {string} name The name of the instance. 3656 * @param {Function} constructor A class (constructor function) that will be instantiated. 3657 * @returns {Object} registered provider instance 3658 * 3659 * @example 3660 * Here is an example of registering a service using 3661 * {@link auto.$provide#service $provide.service(class)}. 3662 * ```js 3663 * var Ping = function($http) { 3664 * this.$http = $http; 3665 * }; 3666 * 3667 * Ping.$inject = ['$http']; 3668 * 3669 * Ping.prototype.send = function() { 3670 * return this.$http.get('/ping'); 3671 * }; 3672 * $provide.service('ping', Ping); 3673 * ``` 3674 * You would then inject and use this service like this: 3675 * ```js 3676 * someModule.controller('Ctrl', ['ping', function(ping) { 3677 * ping.send(); 3678 * }]); 3679 * ``` 3680 */ 3681 3682 3683/** 3684 * @ngdoc method 3685 * @name $provide#value 3686 * @description 3687 * 3688 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 3689 * number, an array, an object or a function. This is short for registering a service where its 3690 * provider's `$get` property is a factory function that takes no arguments and returns the **value 3691 * service**. 3692 * 3693 * Value services are similar to constant services, except that they cannot be injected into a 3694 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 3695 * an Angular 3696 * {@link auto.$provide#decorator decorator}. 3697 * 3698 * @param {string} name The name of the instance. 3699 * @param {*} value The value. 3700 * @returns {Object} registered provider instance 3701 * 3702 * @example 3703 * Here are some examples of creating value services. 3704 * ```js 3705 * $provide.value('ADMIN_USER', 'admin'); 3706 *
3707 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 3708 * 3709 * $provide.value('halfOf', function(value) { 3710 * return value / 2; 3711 * }); 3712 * ``` 3713 */ 3714 3715 3716/** 3717 * @ngdoc method 3718 * @name $provide#constant 3719 * @description 3720 * 3721 * Register a **constant service**, such as a string, a number, an array, an object or a function, 3722 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be 3723 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 3724 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 3725 * 3726 * @param {string} name The name of the constant. 3727 * @param {*} value The constant value. 3728 * @returns {Object} registered instance 3729 * 3730 * @example 3731 * Here a some examples of creating constants: 3732 * ```js 3733 * $provide.constant('SHARD_HEIGHT', 306); 3734 * 3735 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 3736 * 3737 * $provide.constant('double', function(value) { 3738 * return value * 2; 3739 * }); 3740 * ``` 3741 */ 3742 3743 3744/** 3745 * @ngdoc method 3746 * @name $provide#decorator 3747 * @description 3748 * 3749 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 3750 * intercepts the creation of a service, allowing it to override or modify the behaviour of the 3751 * service. The object returned by the decorator may be the original service, or a new service 3752 * object which replaces or wraps and delegates to the original service. 3753 * 3754 * @param {string} name The name of the service to decorate. 3755 * @param {function()} decorator This function will be invoked when the service needs to be 3756 * instantiated and should return the decorated service instance. The function is called using 3757 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 3758 * Local injection arguments: 3759 * 3760 * * `$delegate` - The original service instance, which can be monkey patched, configured, 3761 * decorated or delegated to. 3762 * 3763 * @example 3764 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 3765 * calls to {@link ng.$log#error $log.warn()}. 3766 * ```js 3767 * $provide.decorator('$log', ['$delegate', function($delegate) { 3768 * $delegate.warn = $delegate.error; 3769 * return $delegate; 3770 * }]); 3771 * ``` 3772 */ 3773 3774 3775function createInjector(modulesToLoad) { 3776 var INSTANTIATING = {}, 3777 providerSuffix = 'Provider', 3778 path = [], 3779 loadedModules = new HashMap([], true), 3780 providerCache = { 3781 $provide: { 3782 provider: supportObject(provider), 3783 factory: supportObject(factory), 3784 service: supportObject(service), 3785 value: supportObject(value), 3786 constant: supportObject(constant), 3787 decorator: decorator 3788 } 3789 }, 3790 providerInjector = (providerCache.$injector = 3791 createInternalInjector(providerCache, function() { 3792 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 3793 })), 3794 instanceCache = {}, 3795 instanceInjector = (instanceCache.$injector = 3796 createInternalInjector(instanceCache, function(servicename) { 3797 var provider = providerInjector.get(servicename + providerSuffix); 3798 return instanceInjector.invoke(provider.$get, provider); 3799 })); 3800 3801 3802 forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); 3803 3804 return instanceInjector; 3805 3806 //////////////////////////////////// 3807 // $provider 3808 //////////////////////////////////// 3809 3810 function supportObject(delegate) { 3811 return function(key, value) { 3812 if (isObject(key)) { 3813 forEach(key, reverseParams(delegate)); 3814 } else { 3815 return delegate(key, value); 3816 } 3817 }; 3818 } 3819 3820 function provider(name, provider_) { 3821 assertNotHasOwnProperty(name, 'service'); 3822 if (isFunction(provider_) || isArray(provider_)) { 3823 provider_ = providerInjector.instantiate(provider_); 3824 } 3825 if (!provider_.$get) { 3826 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 3827 } 3828 return providerCache[name + providerSuffix] = provider_; 3829 } 3830 3831 function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } 3832 3833 function service(name, constructor) { 3834 return factory(name, ['$injector', function($injector) { 3835 return $injector.instantiate(constructor); 3836 }]); 3837 } 3838 3839 function value(name, val) { return factory(name, valueFn(val)); } 3840 3841 function constant(name, value) { 3842 assertNotHasOwnProperty(name, 'constant'); 3843 providerCache[name] = value; 3844 instanceCache[name] = value; 3845 } 3846 3847 function decorator(serviceName, decorFn) { 3848 var origProvider = providerInjector.get(serviceName + providerSuffix), 3849 orig$get = origProvider.$get; 3850 3851 origProvider.$get = function() { 3852 var origInstance = instanceInjector.invoke(orig$get, origProvider); 3853 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 3854 }; 3855 } 3856 3857 //////////////////////////////////// 3858 // Module Loading 3859 //////////////////////////////////// 3860 function loadModules(modulesToLoad){ 3861 var runBlocks = [], moduleFn, invokeQueue, i, ii;
3862 forEach(modulesToLoad, function(module) { 3863 if (loadedModules.get(module)) return; 3864 loadedModules.put(module, true); 3865 3866 try { 3867 if (isString(module)) { 3868 moduleFn = angularModule(module); 3869 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 3870 3871 for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { 3872 var invokeArgs = invokeQueue[i], 3873 provider = providerInjector.get(invokeArgs[0]); 3874 3875 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 3876 } 3877 } else if (isFunction(module)) { 3878 runBlocks.push(providerInjector.invoke(module)); 3879 } else if (isArray(module)) { 3880 runBlocks.push(providerInjector.invoke(module)); 3881 } else { 3882 assertArgFn(module, 'module'); 3883 } 3884 } catch (e) { 3885 if (isArray(module)) { 3886 module = module[module.length - 1]; 3887 } 3888 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 3889 // Safari & FF's stack traces don't contain error.message content 3890 // unlike those of Chrome and IE 3891 // So if stack doesn't contain message, we create a new string that contains both. 3892 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 3893 /* jshint -W022 */ 3894 e = e.message + '\n' + e.stack; 3895 } 3896 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 3897 module, e.stack || e.message || e); 3898 } 3899 }); 3900 return runBlocks; 3901 } 3902 3903 //////////////////////////////////// 3904 // internal Injector 3905 //////////////////////////////////// 3906 3907 function createInternalInjector(cache, factory) { 3908 3909 function getService(serviceName) { 3910 if (cache.hasOwnProperty(serviceName)) { 3911 if (cache[serviceName] === INSTANTIATING) { 3912 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 3913 serviceName + ' <- ' + path.join(' <- ')); 3914 } 3915 return cache[serviceName]; 3916 } else { 3917 try { 3918 path.unshift(serviceName); 3919 cache[serviceName] = INSTANTIATING; 3920 return cache[serviceName] = factory(serviceName); 3921 } catch (err) { 3922 if (cache[serviceName] === INSTANTIATING) { 3923 delete cache[serviceName]; 3924 } 3925 throw err; 3926 } finally { 3927 path.shift(); 3928 } 3929 } 3930 } 3931 3932 function invoke(fn, self, locals){ 3933 var args = [], 3934 $inject = annotate(fn), 3935 length, i, 3936 key; 3937 3938 for(i = 0, length = $inject.length; i < length; i++) { 3939 key = $inject[i]; 3940 if (typeof key !== 'string') { 3941 throw $injectorMinErr('itkn', 3942 'Incorrect injection token! Expected service name as string, got {0}', key); 3943 } 3944 args.push( 3945 locals && locals.hasOwnProperty(key) 3946 ? locals[key] 3947 : getService(key) 3948 ); 3949 } 3950 if (isArray(fn)) { 3951 fn = fn[length]; 3952 } 3953 3954 // http://jsperf.com/angularjs-invoke-apply-vs-switch 3955 // #5388 3956 return fn.apply(self, args); 3957 } 3958 3959 function instantiate(Type, locals) { 3960 var Constructor = function() {}, 3961 instance, returnedValue; 3962 3963 // Check if Type is annotated and use just the given function at n-1 as parameter 3964 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 3965 Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; 3966 instance = new Constructor(); 3967 returnedValue = invoke(Type, instance, locals); 3968 3969 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 3970 } 3971 3972 return { 3973 invoke: invoke, 3974 instantiate: instantiate, 3975 get: getService, 3976 annotate: annotate, 3977 has: function(name) { 3978 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 3979 } 3980 }; 3981 } 3982} 3983 3984/** 3985 * @ngdoc service 3986 * @name $anchorScroll 3987 * @kind function 3988 * @requires $window 3989 * @requires $location 3990 * @requires $rootScope 3991 * 3992 * @description 3993 * When called, it checks current value of `$location.hash()` and scrolls to the related element, 3994 * according to rules specified in 3995 * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). 3996 * 3997 * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. 3998 * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. 3999 * 4000 * @example 4001 <example> 4002 <file name="index.html"> 4003 <div id="scrollArea" ng-controller="ScrollCtrl"> 4004 <a ng-click="gotoBottom()">Go to bottom</a> 4005 <a id="bottom"></a> You're at the bottom! 4006 </div> 4007 </file> 4008 <file name="script.js"> 4009 function ScrollCtrl($scope, $location, $anchorScroll) { 4010 $scope.gotoBottom = function (){ 4011 // set the location.hash to the id of
4012 // the element you wish to scroll to. 4013 $location.hash('bottom'); 4014 4015 // call $anchorScroll() 4016 $anchorScroll(); 4017 }; 4018 } 4019 </file> 4020 <file name="style.css"> 4021 #scrollArea { 4022 height: 350px; 4023 overflow: auto; 4024 } 4025 4026 #bottom { 4027 display: block; 4028 margin-top: 2000px; 4029 } 4030 </file> 4031 </example> 4032 */ 4033function $AnchorScrollProvider() { 4034 4035 var autoScrollingEnabled = true; 4036 4037 this.disableAutoScrolling = function() { 4038 autoScrollingEnabled = false; 4039 }; 4040 4041 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 4042 var document = $window.document; 4043 4044 // helper function to get first anchor from a NodeList 4045 // can't use filter.filter, as it accepts only instances of Array 4046 // and IE can't convert NodeList to an array using [].slice 4047 // TODO(vojta): use filter if we change it to accept lists as well 4048 function getFirstAnchor(list) { 4049 var result = null; 4050 forEach(list, function(element) { 4051 if (!result && lowercase(element.nodeName) === 'a') result = element; 4052 }); 4053 return result; 4054 } 4055 4056 function scroll() { 4057 var hash = $location.hash(), elm; 4058 4059 // empty hash, scroll to the top of the page 4060 if (!hash) $window.scrollTo(0, 0); 4061 4062 // element with given id 4063 else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); 4064 4065 // first anchor with given name :-D 4066 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); 4067 4068 // no element and hash == 'top', scroll to the top of the page 4069 else if (hash === 'top') $window.scrollTo(0, 0); 4070 } 4071 4072 // does not scroll when user clicks on anchor link that is currently on 4073 // (no url change, no $location.hash() change), browser native does scroll 4074 if (autoScrollingEnabled) { 4075 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 4076 function autoScrollWatchAction() { 4077 $rootScope.$evalAsync(scroll); 4078 }); 4079 } 4080 4081 return scroll; 4082 }]; 4083} 4084 4085var $animateMinErr = minErr('$animate'); 4086 4087/** 4088 * @ngdoc provider 4089 * @name $animateProvider 4090 * 4091 * @description 4092 * Default implementation of $animate that doesn't perform any animations, instead just 4093 * synchronously performs DOM 4094 * updates and calls done() callbacks. 4095 * 4096 * In order to enable animations the ngAnimate module has to be loaded. 4097 * 4098 * To see the functional implementation check out src/ngAnimate/animate.js 4099 */ 4100var $AnimateProvider = ['$provide', function($provide) { 4101 4102 4103 this.$$selectors = {}; 4104 4105 4106 /** 4107 * @ngdoc method 4108 * @name $animateProvider#register 4109 * 4110 * @description 4111 * Registers a new injectable animation factory function. The factory function produces the 4112 * animation object which contains callback functions for each event that is expected to be 4113 * animated. 4114 * 4115 * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` 4116 * must be called once the element animation is complete. If a function is returned then the 4117 * animation service will use this function to cancel the animation whenever a cancel event is 4118 * triggered. 4119 * 4120 * 4121 * ```js 4122 * return { 4123 * eventFn : function(element, done) { 4124 * //code to run the animation 4125 * //once complete, then run done() 4126 * return function cancellationFunction() { 4127 * //code to cancel the animation 4128 * } 4129 * } 4130 * } 4131 * ``` 4132 * 4133 * @param {string} name The name of the animation. 4134 * @param {Function} factory The factory function that will be executed to return the animation 4135 * object. 4136 */ 4137 this.register = function(name, factory) { 4138 var key = name + '-animation'; 4139 if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', 4140 "Expecting class selector starting with '.' got '{0}'.", name); 4141 this.$$selectors[name.substr(1)] = key; 4142 $provide.factory(key, factory); 4143 }; 4144 4145 /** 4146 * @ngdoc method 4147 * @name $animateProvider#classNameFilter 4148 * 4149 * @description 4150 * Sets and/or returns the CSS class regular expression that is checked when performing 4151 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 4152 * therefore enable $animate to attempt to perform an animation on any element. 4153 * When setting the classNameFilter value, animations will only be performed on elements 4154 * that successfully match the filter expression. This in turn can boost performance 4155 * for low-powered devices as well as applications containing a lot of structural operations. 4156 * @param {RegExp=} expression The className expression which will be checked against all animations 4157 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 4158 */ 4159 this.classNameFilter = function(expression) { 4160 if(arguments.length === 1) { 4161 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 4162 } 4163 return this.$$classNameFilter; 4164 }; 4165 4166 this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) { 4167 4168 function async(fn) { 4169 fn && $$asyncCallback(fn); 4170 } 4171 4172 /** 4173 * 4174 * @ngdoc service 4175 * @name $animate
4176 * @description The $animate service provides rudimentary DOM manipulation functions to 4177 * insert, remove and move elements within the DOM, as well as adding and removing classes. 4178 * This service is the core service used by the ngAnimate $animator service which provides 4179 * high-level animation hooks for CSS and JavaScript. 4180 * 4181 * $animate is available in the AngularJS core, however, the ngAnimate module must be included 4182 * to enable full out animation support. Otherwise, $animate will only perform simple DOM 4183 * manipulation operations. 4184 * 4185 * To learn more about enabling animation support, click here to visit the {@link ngAnimate 4186 * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service 4187 * page}. 4188 */ 4189 return { 4190 4191 /** 4192 * 4193 * @ngdoc method 4194 * @name $animate#enter 4195 * @kind function 4196 * @description Inserts the element into the DOM either after the `after` element or within 4197 * the `parent` element. Once complete, the done() callback will be fired (if provided). 4198 * @param {DOMElement} element the element which will be inserted into the DOM 4199 * @param {DOMElement} parent the parent element which will append the element as 4200 * a child (if the after element is not present) 4201 * @param {DOMElement} after the sibling element which will append the element 4202 * after itself 4203 * @param {Function=} done callback function that will be called after the element has been 4204 * inserted into the DOM 4205 */ 4206 enter : function(element, parent, after, done) { 4207 if (after) { 4208 after.after(element); 4209 } else { 4210 if (!parent || !parent[0]) { 4211 parent = after.parent(); 4212 } 4213 parent.append(element); 4214 } 4215 async(done); 4216 }, 4217 4218 /** 4219 * 4220 * @ngdoc method 4221 * @name $animate#leave 4222 * @kind function 4223 * @description Removes the element from the DOM. Once complete, the done() callback will be 4224 * fired (if provided). 4225 * @param {DOMElement} element the element which will be removed from the DOM 4226 * @param {Function=} done callback function that will be called after the element has been 4227 * removed from the DOM 4228 */ 4229 leave : function(element, done) { 4230 element.remove(); 4231 async(done); 4232 }, 4233 4234 /** 4235 * 4236 * @ngdoc method 4237 * @name $animate#move 4238 * @kind function 4239 * @description Moves the position of the provided element within the DOM to be placed 4240 * either after the `after` element or inside of the `parent` element. Once complete, the 4241 * done() callback will be fired (if provided). 4242 * 4243 * @param {DOMElement} element the element which will be moved around within the 4244 * DOM 4245 * @param {DOMElement} parent the parent element where the element will be 4246 * inserted into (if the after element is not present) 4247 * @param {DOMElement} after the sibling element where the element will be 4248 * positioned next to 4249 * @param {Function=} done the callback function (if provided) that will be fired after the 4250 * element has been moved to its new position 4251 */ 4252 move : function(element, parent, after, done) { 4253 // Do not remove element before insert. Removing will cause data associated with the 4254 // element to be dropped. Insert will implicitly do the remove. 4255 this.enter(element, parent, after, done); 4256 }, 4257 4258 /** 4259 * 4260 * @ngdoc method 4261 * @name $animate#addClass 4262 * @kind function 4263 * @description Adds the provided className CSS class value to the provided element. Once 4264 * complete, the done() callback will be fired (if provided). 4265 * @param {DOMElement} element the element which will have the className value 4266 * added to it 4267 * @param {string} className the CSS class which will be added to the element 4268 * @param {Function=} done the callback function (if provided) that will be fired after the 4269 * className value has been added to the element 4270 */ 4271 addClass : function(element, className, done) { 4272 className = isString(className) ? 4273 className : 4274 isArray(className) ? className.join(' ') : '';
4275 forEach(element, function (element) { 4276 jqLiteAddClass(element, className); 4277 }); 4278 async(done); 4279 }, 4280 4281 /** 4282 * 4283 * @ngdoc method 4284 * @name $animate#removeClass 4285 * @kind function 4286 * @description Removes the provided className CSS class value from the provided element. 4287 * Once complete, the done() callback will be fired (if provided). 4288 * @param {DOMElement} element the element which will have the className value 4289 * removed from it 4290 * @param {string} className the CSS class which will be removed from the element 4291 * @param {Function=} done the callback function (if provided) that will be fired after the 4292 * className value has been removed from the element 4293 */ 4294 removeClass : function(element, className, done) { 4295 className = isString(className) ? 4296 className : 4297 isArray(className) ? className.join(' ') : ''; 4298 forEach(element, function (element) { 4299 jqLiteRemoveClass(element, className); 4300 }); 4301 async(done); 4302 }, 4303 4304 /** 4305 * 4306 * @ngdoc method 4307 * @name $animate#setClass 4308 * @kind function 4309 * @description Adds and/or removes the given CSS classes to and from the element. 4310 * Once complete, the done() callback will be fired (if provided). 4311 * @param {DOMElement} element the element which will have its CSS classes changed 4312 * removed from it 4313 * @param {string} add the CSS classes which will be added to the element 4314 * @param {string} remove the CSS class which will be removed from the element 4315 * @param {Function=} done the callback function (if provided) that will be fired after the 4316 * CSS classes have been set on the element 4317 */ 4318 setClass : function(element, add, remove, done) { 4319 forEach(element, function (element) { 4320 jqLiteAddClass(element, add); 4321 jqLiteRemoveClass(element, remove); 4322 }); 4323 async(done); 4324 }, 4325 4326 enabled : noop 4327 }; 4328 }]; 4329}]; 4330 4331function $$AsyncCallbackProvider(){ 4332 this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { 4333 return $$rAF.supported 4334 ? function(fn) { return $$rAF(fn); } 4335 : function(fn) { 4336 return $timeout(fn, 0, false); 4337 }; 4338 }]; 4339} 4340 4341/** 4342 * ! This is a private undocumented service ! 4343 * 4344 * @name $browser 4345 * @requires $log 4346 * @description 4347 * This object has two goals: 4348 * 4349 * - hide all the global state in the browser caused by the window object 4350 * - abstract away all the browser specific features and inconsistencies 4351 * 4352 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 4353 * service, which can be used for convenient testing of the application without the interaction with 4354 * the real browser apis. 4355 */ 4356/** 4357 * @param {object} window The global window object. 4358 * @param {object} document jQuery wrapped document. 4359 * @param {function()} XHR XMLHttpRequest constructor. 4360 * @param {object} $log console.log or an object with the same interface. 4361 * @param {object} $sniffer $sniffer service 4362 */ 4363function Browser(window, document, $log, $sniffer) { 4364 var self = this, 4365 rawDocument = document[0], 4366 location = window.location, 4367 history = window.history, 4368 setTimeout = window.setTimeout, 4369 clearTimeout = window.clearTimeout, 4370 pendingDeferIds = {}; 4371 4372 self.isMock = false; 4373 4374 var outstandingRequestCount = 0; 4375 var outstandingRequestCallbacks = []; 4376 4377 // TODO(vojta): remove this temporary api 4378 self.$$completeOutstandingRequest = completeOutstandingRequest; 4379 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 4380 4381 /** 4382 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 4383 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 4384 */ 4385 function completeOutstandingRequest(fn) { 4386 try { 4387 fn.apply(null, sliceArgs(arguments, 1)); 4388 } finally { 4389 outstandingRequestCount--; 4390 if (outstandingRequestCount === 0) { 4391 while(outstandingRequestCallbacks.length) { 4392 try { 4393 outstandingRequestCallbacks.pop()(); 4394 } catch (e) { 4395 $log.error(e); 4396 } 4397 } 4398 } 4399 } 4400 } 4401 4402 /** 4403 * @private 4404 * Note: this method is used only by scenario runner 4405 * TODO(vojta): prefix this method with $$ ? 4406 * @param {function()} callback Function that will be called when no outstanding request 4407 */ 4408 self.notifyWhenNoOutstandingRequests = function(callback) { 4409 // force browser to execute all pollFns - this is needed so that cookies and other pollers fire 4410 // at some deterministic time in respect to the test runner's actions. Leaving things up to the 4411 // regular poller would result in flaky tests.
4412 forEach(pollFns, function(pollFn){ pollFn(); }); 4413 4414 if (outstandingRequestCount === 0) { 4415 callback(); 4416 } else { 4417 outstandingRequestCallbacks.push(callback); 4418 } 4419 }; 4420 4421 ////////////////////////////////////////////////////////////// 4422 // Poll Watcher API 4423 ////////////////////////////////////////////////////////////// 4424 var pollFns = [], 4425 pollTimeout; 4426 4427 /** 4428 * @name $browser#addPollFn 4429 * 4430 * @param {function()} fn Poll function to add 4431 * 4432 * @description 4433 * Adds a function to the list of functions that poller periodically executes, 4434 * and starts polling if not started yet. 4435 * 4436 * @returns {function()} the added function 4437 */ 4438 self.addPollFn = function(fn) { 4439 if (isUndefined(pollTimeout)) startPoller(100, setTimeout); 4440 pollFns.push(fn); 4441 return fn; 4442 }; 4443 4444 /** 4445 * @param {number} interval How often should browser call poll functions (ms) 4446 * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. 4447 * 4448 * @description 4449 * Configures the poller to run in the specified intervals, using the specified 4450 * setTimeout fn and kicks it off. 4451 */ 4452 function startPoller(interval, setTimeout) { 4453 (function check() { 4454 forEach(pollFns, function(pollFn){ pollFn(); }); 4455 pollTimeout = setTimeout(check, interval); 4456 })(); 4457 } 4458 4459 ////////////////////////////////////////////////////////////// 4460 // URL API 4461 ////////////////////////////////////////////////////////////// 4462 4463 var lastBrowserUrl = location.href, 4464 baseElement = document.find('base'), 4465 newLocation = null; 4466 4467 /** 4468 * @name $browser#url 4469 * 4470 * @description 4471 * GETTER: 4472 * Without any argument, this method just returns current value of location.href. 4473 * 4474 * SETTER: 4475 * With at least one argument, this method sets url to new value. 4476 * If html5 history api supported, pushState/replaceState is used, otherwise 4477 * location.href/location.replace is used. 4478 * Returns its own instance to allow chaining 4479 * 4480 * NOTE: this api is intended for use only by the $location service. Please use the 4481 * {@link ng.$location $location service} to change url. 4482 * 4483 * @param {string} url New url (when used as setter) 4484 * @param {boolean=} replace Should new url replace current history record ? 4485 */ 4486 self.url = function(url, replace) { 4487 // Android Browser BFCache causes location, history reference to become stale. 4488 if (location !== window.location) location = window.location; 4489 if (history !== window.history) history = window.history; 4490 4491 // setter 4492 if (url) { 4493 if (lastBrowserUrl == url) return; 4494 lastBrowserUrl = url; 4495 if ($sniffer.history) { 4496 if (replace) history.replaceState(null, '', url); 4497 else { 4498 history.pushState(null, '', url); 4499 // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 4500 baseElement.attr('href', baseElement.attr('href')); 4501 } 4502 } else { 4503 newLocation = url; 4504 if (replace) { 4505 location.replace(url); 4506 } else { 4507 location.href = url; 4508 } 4509 } 4510 return self; 4511 // getter 4512 } else { 4513 // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href 4514 // methods not updating location.href synchronously. 4515 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 4516 return newLocation || location.href.replace(/%27/g,"'"); 4517 } 4518 }; 4519 4520 var urlChangeListeners = [], 4521 urlChangeInit = false; 4522 4523 function fireUrlChange() { 4524 newLocation = null; 4525 if (lastBrowserUrl == self.url()) return; 4526 4527 lastBrowserUrl = self.url(); 4528 forEach(urlChangeListeners, function(listener) { 4529 listener(self.url()); 4530 }); 4531 } 4532 4533 /** 4534 * @name $browser#onUrlChange 4535 * 4536 * @description 4537 * Register callback function that will be called, when url changes. 4538 * 4539 * It's only called when the url is changed from outside of angular: 4540 * - user types different url into address bar 4541 * - user clicks on history (forward/back) button 4542 * - user clicks on a link 4543 * 4544 * It's not called when url is changed by $browser.url() method 4545 * 4546 * The listener gets called with new url as parameter. 4547 * 4548 * NOTE: this api is intended for use only by the $location service. Please use the 4549 * {@link ng.$location $location service} to monitor url changes in angular apps. 4550 * 4551 * @param {function(string)} listener Listener function to be called when url changes. 4552 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 4553 */ 4554 self.onUrlChange = function(callback) { 4555 // TODO(vojta): refactor to use node's syntax for events 4556 if (!urlChangeInit) { 4557 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 4558 // don't fire popstate when user change the address bar and don't fire hashchange when url 4559 // changed by push/replaceState 4560 4561 // html5 history api - popstate event 4562 if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); 4563 // hashchange event 4564 if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); 4565 // polling 4566 else self.addPollFn(fireUrlChange); 4567 4568 urlChangeInit = true; 4569 } 4570 4571 urlChangeListeners.push(callback); 4572 return callback; 4573 }; 4574 4575 ////////////////////////////////////////////////////////////// 4576 // Misc API 4577 ////////////////////////////////////////////////////////////// 4578 4579 /** 4580 * @name $browser#baseHref 4581 * 4582 * @description 4583 * Returns current <base href> 4584 * (always relative - without domain) 4585 * 4586 * @returns {string} The current base href 4587 */ 4588 self.baseHref = function() { 4589 var href = baseElement.attr('href'); 4590 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 4591 }; 4592 4593 ////////////////////////////////////////////////////////////// 4594 // Cookies API 4595 ////////////////////////////////////////////////////////////// 4596 var lastCookies = {}; 4597 var lastCookieString = ''; 4598 var cookiePath = self.baseHref(); 4599 4600 /** 4601 * @name $browser#cookies 4602 * 4603 * @param {string=} name Cookie name 4604 * @param {string=} value Cookie value 4605 * 4606 * @description 4607 * The cookies method provides a 'private' low level access to browser cookies. 4608 * It is not meant to be used directly, use the $cookie service instead. 4609 * 4610 * The return values vary depending on the arguments that the method was called with as follows: 4611 * 4612 * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify 4613 * it 4614 * - cookies(name, value) -> set name to value, if value is undefined delete the cookie 4615 * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that 4616 * way) 4617 * 4618 * @returns {Object} Hash of all cookies (if called without any parameter) 4619 */ 4620 self.cookies = function(name, value) { 4621 /* global escape: false, unescape: false */ 4622 var cookieLength, cookieArray, cookie, i, index; 4623 4624 if (name) { 4625 if (value === undefined) { 4626 rawDocument.cookie = escape(name) + "=;
4626path=" + cookiePath + 4627 ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; 4628 } else { 4629 if (isString(value)) { 4630 cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + 4631 ';path=' + cookiePath).length + 1; 4632 4633 // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: 4634 // - 300 cookies 4635 // - 20 cookies per unique domain 4636 // - 4096 bytes per cookie 4637 if (cookieLength > 4096) { 4638 $log.warn("Cookie '"+ name + 4639 "' possibly not set or overflowed because it was too large ("+ 4640 cookieLength + " > 4096 bytes)!"); 4641 } 4642 } 4643 } 4644 } else { 4645 if (rawDocument.cookie !== lastCookieString) { 4646 lastCookieString = rawDocument.cookie; 4647 cookieArray = lastCookieString.split("; "); 4648 lastCookies = {}; 4649 4650 for (i = 0; i < cookieArray.length; i++) { 4651 cookie = cookieArray[i]; 4652 index = cookie.indexOf('='); 4653 if (index > 0) { //ignore nameless cookies 4654 name = unescape(cookie.substring(0, index)); 4655 // the first value that is seen for a cookie is the most 4656 // specific one. values for the same cookie name that 4657 // follow are for less specific paths. 4658 if (lastCookies[name] === undefined) { 4659 lastCookies[name] = unescape(cookie.substring(index + 1)); 4660 } 4661 } 4662 } 4663 } 4664 return lastCookies; 4665 } 4666 }; 4667 4668 4669 /** 4670 * @name $browser#defer 4671 * @param {function()} fn A function, who's execution should be deferred. 4672 * @param {number=} [delay=0] of milliseconds to defer the function execution. 4673 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 4674 * 4675 * @description 4676 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 4677 * 4678 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 4679 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 4680 * via `$browser.defer.flush()`. 4681 * 4682 */ 4683 self.defer = function(fn, delay) { 4684 var timeoutId; 4685 outstandingRequestCount++; 4686 timeoutId = setTimeout(function() { 4687 delete pendingDeferIds[timeoutId]; 4688 completeOutstandingRequest(fn); 4689 }, delay || 0); 4690 pendingDeferIds[timeoutId] = true; 4691 return timeoutId; 4692 }; 4693 4694 4695 /** 4696 * @name $browser#defer.cancel 4697 * 4698 * @description 4699 * Cancels a deferred task identified with `deferId`. 4700 * 4701 * @param {*} deferId Token returned by the `$browser.defer` function. 4702 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 4703 * canceled. 4704 */ 4705 self.defer.cancel = function(deferId) { 4706 if (pendingDeferIds[deferId]) { 4707 delete pendingDeferIds[deferId]; 4708 clearTimeout(deferId); 4709 completeOutstandingRequest(noop); 4710 return true; 4711 } 4712 return false; 4713 }; 4714 4715} 4716 4717function $BrowserProvider(){ 4718 this.$get = ['$window', '$log', '$sniffer', '$document', 4719 function( $window, $log, $sniffer, $document){ 4720 return new Browser($window, $document, $log, $sniffer); 4721 }]; 4722} 4723 4724/** 4725 * @ngdoc service 4726 * @name $cacheFactory 4727 * 4728 * @description 4729 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 4730 * them. 4731 * 4732 * ```js 4733 * 4734 * var cache = $cacheFactory('cacheId'); 4735 * expect($cacheFactory.get('cacheId')).toBe(cache); 4736 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 4737 * 4738 * cache.put("key", "value"); 4739 * cache.put("another key", "another value"); 4740 * 4741 * // We've specified no options on creation 4742 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 4743 * 4744 * ``` 4745 * 4746 * 4747 * @param {string} cacheId Name or id of the newly created cache. 4748 * @param {object=} options Options object that specifies the cache behavior. Properties: 4749 * 4750 * - `{number=}` `capacity` â turns the cache into LRU cache. 4751 * 4752 * @returns {object} Newly created cache object with the following set of methods: 4753 * 4754 * - `{object}` `info()` â Returns id, size, and options of cache. 4755 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 4756 * it. 4757 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 4758 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 4759 * - `{void}` `removeAll()` â Removes all cached values. 4760 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 4761 * 4762 * @example 4763 <example module="cacheExampleApp"> 4764 <file name="index.html"> 4765 <div ng-controller="CacheController"> 4766 <input ng-model="newCacheKey" placeholder="Key"> 4767 <input ng-model="newCacheValue" placeholder="Value"> 4768 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 4769 4770 <p ng-if="keys.length">Cached Values</p> 4771 <div ng-repeat="key in keys">
4772 <span ng-bind="key"></span> 4773 <span>: </span> 4774 <b ng-bind="cache.get(key)"></b> 4775 </div> 4776 4777 <p>Cache Info</p> 4778 <div ng-repeat="(key, value) in cache.info()"> 4779 <span ng-bind="key"></span> 4780 <span>: </span> 4781 <b ng-bind="value"></b> 4782 </div> 4783 </div> 4784 </file> 4785 <file name="script.js"> 4786 angular.module('cacheExampleApp', []). 4787 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 4788 $scope.keys = []; 4789 $scope.cache = $cacheFactory('cacheId'); 4790 $scope.put = function(key, value) { 4791 $scope.cache.put(key, value); 4792 $scope.keys.push(key); 4793 }; 4794 }]); 4795 </file> 4796 <file name="style.css"> 4797 p { 4798 margin: 10px 0 3px; 4799 } 4800 </file> 4801 </example> 4802 */ 4803function $CacheFactoryProvider() { 4804 4805 this.$get = function() { 4806 var caches = {}; 4807 4808 function cacheFactory(cacheId, options) { 4809 if (cacheId in caches) { 4810 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 4811 } 4812 4813 var size = 0, 4814 stats = extend({}, options, {id: cacheId}), 4815 data = {}, 4816 capacity = (options && options.capacity) || Number.MAX_VALUE, 4817 lruHash = {}, 4818 freshEnd = null, 4819 staleEnd = null; 4820 4821 /** 4822 * @ngdoc type 4823 * @name $cacheFactory.Cache 4824 * 4825 * @description 4826 * A cache object used to store and retrieve data, primarily used by 4827 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 4828 * templates and other data. 4829 * 4830 * ```js 4831 * angular.module('superCache') 4832 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 4833 * return $cacheFactory('super-cache'); 4834 * }]); 4835 * ``` 4836 * 4837 * Example test: 4838 * 4839 * ```js 4840 * it('should behave like a cache', inject(function(superCache) { 4841 * superCache.put('key', 'value'); 4842 * superCache.put('another key', 'another value'); 4843 * 4844 * expect(superCache.info()).toEqual({ 4845 * id: 'super-cache', 4846 * size: 2 4847 * }); 4848 * 4849 * superCache.remove('another key'); 4850 * expect(superCache.get('another key')).toBeUndefined(); 4851 * 4852 * superCache.removeAll(); 4853 * expect(superCache.info()).toEqual({ 4854 * id: 'super-cache', 4855 * size: 0 4856 * }); 4857 * })); 4858 * ``` 4859 */ 4860 return caches[cacheId] = { 4861 4862 /** 4863 * @ngdoc method 4864 * @name $cacheFactory.Cache#put 4865 * @kind function 4866 * 4867 * @description 4868 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 4869 * retrieved later, and incrementing the size of the cache if the key was not already 4870 * present in the cache. If behaving like an LRU cache, it will also remove stale 4871 * entries from the set. 4872 * 4873 * It will not insert undefined values into the cache. 4874 * 4875 * @param {string} key the key under which the cached data is stored. 4876 * @param {*} value the value to store alongside the key. If it is undefined, the key 4877 * will not be stored. 4878 * @returns {*} the value stored. 4879 */ 4880 put: function(key, value) { 4881 if (capacity < Number.MAX_VALUE) { 4882 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 4883 4884 refresh(lruEntry); 4885 } 4886 4887 if (isUndefined(value)) return; 4888 if (!(key in data)) size++; 4889 data[key] = value; 4890 4891 if (size > capacity) { 4892 this.remove(staleEnd.key); 4893 } 4894 4895 return value; 4896 }, 4897 4898 /** 4899 * @ngdoc method 4900 * @name $cacheFactory.Cache#get 4901 * @kind function 4902 * 4903 * @description 4904 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 4905 * 4906 * @param {string} key the key of the data to be retrieved 4907 * @returns {*} the value stored. 4908 */ 4909 get: function(key) { 4910 if (capacity < Number.MAX_VALUE) { 4911 var lruEntry = lruHash[key]; 4912 4913 if (!lruEntry) return; 4914 4915 refresh(lruEntry); 4916 } 4917 4918 return data[key]; 4919 }, 4920 4921 4922 /** 4923 * @ngdoc method 4924 * @name $cacheFactory.Cache#remove 4925 * @kind function 4926 *
4927 * @description 4928 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 4929 * 4930 * @param {string} key the key of the entry to be removed 4931 */ 4932 remove: function(key) { 4933 if (capacity < Number.MAX_VALUE) { 4934 var lruEntry = lruHash[key]; 4935 4936 if (!lruEntry) return; 4937 4938 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 4939 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 4940 link(lruEntry.n,lruEntry.p); 4941 4942 delete lruHash[key]; 4943 } 4944 4945 delete data[key]; 4946 size--; 4947 }, 4948 4949 4950 /** 4951 * @ngdoc method 4952 * @name $cacheFactory.Cache#removeAll 4953 * @kind function 4954 * 4955 * @description 4956 * Clears the cache object of any entries. 4957 */ 4958 removeAll: function() { 4959 data = {}; 4960 size = 0; 4961 lruHash = {}; 4962 freshEnd = staleEnd = null; 4963 }, 4964 4965 4966 /** 4967 * @ngdoc method 4968 * @name $cacheFactory.Cache#destroy 4969 * @kind function 4970 * 4971 * @description 4972 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 4973 * removing it from the {@link $cacheFactory $cacheFactory} set. 4974 */ 4975 destroy: function() { 4976 data = null; 4977 stats = null; 4978 lruHash = null; 4979 delete caches[cacheId]; 4980 }, 4981 4982 4983 /** 4984 * @ngdoc method 4985 * @name $cacheFactory.Cache#info 4986 * @kind function 4987 * 4988 * @description 4989 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 4990 * 4991 * @returns {object} an object with the following properties: 4992 * <ul> 4993 * <li>**id**: the id of the cache instance</li> 4994 * <li>**size**: the number of entries kept in the cache instance</li> 4995 * <li>**...**: any additional properties from the options object when creating the 4996 * cache.</li> 4997 * </ul> 4998 */ 4999 info: function() { 5000 return extend({}, stats, {size: size}); 5001 } 5002 }; 5003 5004 5005 /** 5006 * makes the `entry` the freshEnd of the LRU linked list 5007 */ 5008 function refresh(entry) { 5009 if (entry != freshEnd) { 5010 if (!staleEnd) { 5011 staleEnd = entry; 5012 } else if (staleEnd == entry) { 5013 staleEnd = entry.n; 5014 } 5015 5016 link(entry.n, entry.p); 5017 link(entry, freshEnd); 5018 freshEnd = entry; 5019 freshEnd.n = null; 5020 } 5021 } 5022 5023 5024 /** 5025 * bidirectionally links two entries of the LRU linked list 5026 */ 5027 function link(nextEntry, prevEntry) { 5028 if (nextEntry != prevEntry) { 5029 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 5030 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 5031 } 5032 } 5033 } 5034 5035 5036 /** 5037 * @ngdoc method 5038 * @name $cacheFactory#info 5039 * 5040 * @description 5041 * Get information about all the caches that have been created 5042 * 5043 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 5044 */ 5045 cacheFactory.info = function() { 5046 var info = {}; 5047 forEach(caches, function(cache, cacheId) { 5048 info[cacheId] = cache.info(); 5049 }); 5050 return info; 5051 }; 5052 5053 5054 /** 5055 * @ngdoc method 5056 * @name $cacheFactory#get 5057 * 5058 * @description 5059 * Get access to a cache object by the `cacheId` used when it was created. 5060 * 5061 * @param {string} cacheId Name or id of a cache to access. 5062 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 5063 */ 5064 cacheFactory.get = function(cacheId) { 5065 return caches[cacheId]; 5066 }; 5067 5068 5069 return cacheFactory; 5070 }; 5071} 5072 5073/** 5074 * @ngdoc service 5075 * @name $templateCache 5076 * 5077 * @description 5078 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 5079 * can load templates directly into the cache in a `script` tag, or by consuming the 5080 * `$templateCache` service directly. 5081 * 5082 * Adding via the `script` tag: 5083 * 5084 * ```html 5085 * <script type="text/ng-template" id="templateId.html"> 5086 * <p>This is the content of the template</p> 5087 * </script> 5088 * ``` 5089 * 5090 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 5091 * the document, but it must be below the `ng-app` definition. 5092 * 5093 * Adding via the $templateCache service: 5094 * 5095 * ```js 5096 * var myApp = angular.module('myApp', []); 5097 * myApp.run(function($templateCache) { 5098 * $templateCache.put('templateId.html', 'This is the content of the template'); 5099 * }); 5100 * ``` 5101 * 5102 * To retrieve the template later, simply use it in your HTML: 5103 * ```html 5104 * <div ng-include=" 'templateId.html' "></div> 5105 * ``` 5106 * 5107 * or get it via Javascript: 5108 * ```js 5109 * $templateCache.get('templateId.html') 5110 * ``` 5111 * 5112 * See {@link ng.$cacheFactory $cacheFactory}. 5113 * 5114 */ 5115function $TemplateCacheProvider() { 5116 this.$get = ['$cacheFactory', function($cacheFactory) { 5117 return $cacheFactory('templates'); 5118 }]; 5119} 5120 5121/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 5122 * 5123 * DOM-related variables: 5124 * 5125 * - "node" - DOM Node 5126 * - "element" - DOM Element or Node 5127 * - "$node" or "$element" - jqLite-wrapped node or element 5128 * 5129 * 5130 * Compiler related stuff: 5131 * 5132 * - "linkFn" - linking fn of a single directive 5133 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 5134 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 5135 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 5136 */ 5137 5138 5139/** 5140 * @ngdoc service 5141 * @name $compile 5142 * @kind function 5143 * 5144 * @description 5145 * Compiles an HTML string or DOM into a template and produces a template function, which 5146 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 5147 * 5148 * The compilation is a process of walking the DOM tree and matching DOM elements to 5149 * {@link ng.$compileProvider#directive directives}. 5150 * 5151 * <div class="alert alert-warning"> 5152 * **Note:** This document is an in-depth reference of all directive options. 5153 * For a gentle introduction to directives with examples of common use cases, 5154 * see the {@link guide/directive directive guide}. 5155 * </div> 5156 * 5157 * ## Comprehensive Directive API 5158 * 5159 * There are many different options for a directive. 5160 * 5161 * The difference resides in the return value of the factory function. 5162 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 5163 * or just the `postLink` function (all other properties will have the default values). 5164 * 5165 * <div class="alert alert-success"> 5166 * **Best Practice:** It's recommended to use the "directive definition object" form. 5167 * </div> 5168 * 5169 * Here's an example directive declared with a Directive Definition Object: 5170 * 5171 * ```js 5172 * var myModule = angular.module(...); 5173 * 5174 * myModule.directive('directiveName', function factory(injectables) { 5175 * var directiveDefinitionObject = { 5176 * priority: 0, 5177 * template: '<div></div>
5177', // or // function(tElement, tAttrs) { ... }, 5178 * // or 5179 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 5180 * transclude: false, 5181 * restrict: 'A', 5182 * scope: false, 5183 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 5184 * controllerAs: 'stringAlias', 5185 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 5186 * compile: function compile(tElement, tAttrs, transclude) { 5187 * return { 5188 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 5189 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 5190 * } 5191 * // or 5192 * // return function postLink( ... ) { ... } 5193 * }, 5194 * // or 5195 * // link: { 5196 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 5197 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 5198 * // } 5199 * // or 5200 * // link: function postLink( ... ) { ... } 5201 * }; 5202 * return directiveDefinitionObject; 5203 * }); 5204 * ``` 5205 * 5206 * <div class="alert alert-warning"> 5207 * **Note:** Any unspecified options will use the default value. You can see the default values below. 5208 * </div> 5209 * 5210 * Therefore the above can be simplified as: 5211 * 5212 * ```js 5213 * var myModule = angular.module(...); 5214 * 5215 * myModule.directive('directiveName', function factory(injectables) { 5216 * var directiveDefinitionObject = { 5217 * link: function postLink(scope, iElement, iAttrs) { ... } 5218 * }; 5219 * return directiveDefinitionObject; 5220 * // or 5221 * // return function postLink(scope, iElement, iAttrs) { ... } 5222 * }); 5223 * ``` 5224 * 5225 * 5226 * 5227 * ### Directive Definition Object 5228 * 5229 * The directive definition object provides instructions to the {@link ng.$compile 5230 * compiler}. The attributes are: 5231 * 5232 * #### `priority` 5233 * When there are multiple directives defined on a single DOM element, sometimes it 5234 * is necessary to specify the order in which the directives are applied. The `priority` is used 5235 * to sort the directives before their `compile` functions get called. Priority is defined as a 5236 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 5237 * are also run in priority order, but post-link functions are run in reverse order. The order 5238 * of directives with the same priority is undefined. The default priority is `0`. 5239 * 5240 * #### `terminal` 5241 * If set to true then the current `priority` will be the last set of directives 5242 * which will execute (any directives at the current priority will still execute 5243 * as the order of execution on same `priority` is undefined). 5244 * 5245 * #### `scope` 5246 * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the 5247 * same element request a new scope, only one new scope is created. The new scope rule does not 5248 * apply for the root of the template since the root of the template always gets a new scope. 5249 * 5250 * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from 5251 * normal scope in that it does not prototypically inherit from the parent scope. This is useful 5252 * when creating reusable components, which should not accidentally read or modify data in the 5253 * parent scope. 5254 * 5255 * The 'isolate' scope takes an object hash which defines a set of local scope properties 5256 * derived from the parent scope. These local properties are useful for aliasing values for 5257 * templates. Locals definition is a hash of local scope property to its source: 5258 * 5259 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is 5260 * always a string since DOM attributes are strings. If no `attr` name is specified then the 5261 * attribute name is assumed to be the same as the local name. 5262 * Given `<widget my-attr="hello {{name}}">` and widget definition 5263 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 5264 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 5265 * `localName` property on the widget scope. The `name` is read from the parent scope (not 5266 * component scope). 5267 * 5268 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 5269 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 5270 * name is specified then the attribute name is assumed to be the same as the local name. 5271 * Given `<widget my-attr="parentModel">` and widget definition of 5272 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 5273 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 5274 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 5275 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 5276 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. 5277 * 5278 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 5279 * If no `attr` name is specified then the attribute name is assumed to be the same as the 5280 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 5281 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 5282 * a function wrapper for the `count = count + value` expression. Often it's desirable to 5283 * pass data from the isolated scope via an expression to the parent scope, this can be 5284 * done by passing a map of local variable names and values into the expression wrapper fn. 5285 * For example, if the expression is `increment(amount)` then we can specify the amount value 5286 * by calling the `localFn` as `localFn({amount: 22})`. 5287 * 5288 * 5289 * 5290 * #### `controller` 5291 * Controller constructor function. The controller is instantiated before the 5292 * pre-linking phase and it is shared with other directives (see 5293 * `require` attribute). This allows the directives to communicate with each other and augment 5294 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 5295 * 5296 * * `$scope` - Current scope associated with the element 5297 * * `$element` - Current element 5298 * * `$attrs` - Current attributes object for the element 5299 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. 5300 * The scope can be overridden by an optional first argument. 5301 * `function([scope], cloneLinkingFn)`. 5302 * 5303 * 5304 * #### `require` 5305 * Require another directive and inject its controller as the fourth argument to the linking function. The
5306 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 5307 * injected argument will be an array in corresponding order. If no such directive can be 5308 * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: 5309 * 5310 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 5311 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 5312 * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found. 5313 * * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the 5314 * `link` fn if not found. 5315 * 5316 * 5317 * #### `controllerAs` 5318 * Controller alias at the directive scope. An alias for the controller so it 5319 * can be referenced at the directive template. The directive needs to define a scope for this 5320 * configuration to be used. Useful in the case when directive is used as component. 5321 * 5322 * 5323 * #### `restrict` 5324 * String of subset of `EACM` which restricts the directive to a specific directive 5325 * declaration style. If omitted, the default (attributes only) is used. 5326 * 5327 * * `E` - Element name: `<my-directive></my-directive>` 5328 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 5329 * * `C` - Class: `<div class="my-directive: exp;"></div>` 5330 * * `M` - Comment: `<!-- directive: my-directive exp -->` 5331 * 5332 * 5333 * #### `template` 5334 * HTML markup that may: 5335 * * Replace the contents of the directive's element (default). 5336 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 5337 * * Wrap the contents of the directive's element (if `transclude` is true). 5338 * 5339 * Value may be: 5340 * 5341 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 5342 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` 5343 * function api below) and returns a string value. 5344 * 5345 * 5346 * #### `templateUrl` 5347 * Same as `template` but the template is loaded from the specified URL. Because 5348 * the template loading is asynchronous the compilation/linking is suspended until the template 5349 * is loaded. 5350 * 5351 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 5352 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 5353 * a string value representing the url. In either case, the template URL is passed through {@link 5354 * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 5355 * 5356 * 5357 * #### `replace` ([*DEPRECATED*!], will be removed in next major release) 5358 * specify what the template should replace. Defaults to `false`. 5359 * 5360 * * `true` - the template will replace the directive's element. 5361 * * `false` - the template will replace the contents of the directive's element. 5362 * 5363 * The replacement process migrates all of the attributes / classes from the old element to the new 5364 * one. See the {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive 5365 * Directives Guide} for an example. 5366 * 5367 * #### `transclude` 5368 * compile the content of the element and make it available to the directive. 5369 * Typically used with {@link ng.directive:ngTransclude 5370 * ngTransclude}. The advantage of transclusion is that the linking function receives a 5371 * transclusion function which is pre-bound to the correct scope. In a typical setup the widget 5372 * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` 5373 * scope. This makes it possible for the widget to have private state, and the transclusion to 5374 * be bound to the parent (pre-`isolate`) scope. 5375 * 5376 * * `true` - transclude the content of the directive. 5377 * * `'element'` - transclude the whole element including any directives defined at lower priority. 5378 * 5379 * <div class="alert alert-warning"> 5380 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 5381 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 5382 * Testing Transclusion Directives}. 5383 * </div> 5384 * 5385 * #### `compile` 5386 * 5387 * ```js 5388 * function compile(tElement, tAttrs, transclude) { ... } 5389 * ``` 5390 * 5391 * The compile function deals with transforming the template DOM. Since most directives do not do 5392 * template transformation, it is not used often. The compile function takes the following arguments: 5393 * 5394 * * `tElement` - template element - The element where the directive has been declared. It is 5395 * safe to do template transformation on the element and child elements only. 5396 * 5397 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 5398 * between all directive compile functions. 5399 * 5400 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 5401 * 5402 * <div class="alert alert-warning"> 5403 * **Note:** The template instance and the link instance may be different objects if the template has 5404 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 5405 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
5406 * should be done in a linking function rather than in a compile function. 5407 * </div> 5408 5409 * <div class="alert alert-warning"> 5410 * **Note:** The compile function cannot handle directives that recursively use themselves in their 5411 * own templates or compile functions. Compiling these directives results in an infinite loop and a 5412 * stack overflow errors. 5413 * 5414 * This can be avoided by manually using $compile in the postLink function to imperatively compile 5415 * a directive's template instead of relying on automatic template compilation via `template` or 5416 * `templateUrl` declaration or manual compilation inside the compile function. 5417 * </div> 5418 * 5419 * <div class="alert alert-error"> 5420 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 5421 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 5422 * to the link function instead. 5423 * </div> 5424 5425 * A compile function can have a return value which can be either a function or an object. 5426 * 5427 * * returning a (post-link) function - is equivalent to registering the linking function via the 5428 * `link` property of the config object when the compile function is empty. 5429 * 5430 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 5431 * control when a linking function should be called during the linking phase. See info about 5432 * pre-linking and post-linking functions below. 5433 * 5434 * 5435 * #### `link` 5436 * This property is used only if the `compile` property is not defined. 5437 * 5438 * ```js 5439 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 5440 * ``` 5441 * 5442 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 5443 * executed after the template has been cloned. This is where most of the directive logic will be 5444 * put. 5445 * 5446 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 5447 * directive for registering {@link ng.$rootScope.Scope#$watch watches}. 5448 * 5449 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 5450 * manipulate the children of the element only in `postLink` function since the children have 5451 * already been linked. 5452 * 5453 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 5454 * between all directive linking functions. 5455 * 5456 * * `controller` - a controller instance - A controller instance if at least one directive on the 5457 * element defines a controller. The controller is shared among all the directives, which allows 5458 * the directives to use the controllers as a communication channel. 5459 * 5460 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 5461 * The scope can be overridden by an optional first argument. This is the same as the `$transclude` 5462 * parameter of directive controllers. 5463 * `function([scope], cloneLinkingFn)`. 5464 * 5465 * 5466 * #### Pre-linking function 5467 * 5468 * Executed before the child elements are linked. Not safe to do DOM transformation since the 5469 * compiler linking function will fail to locate the correct elements for linking. 5470 * 5471 * #### Post-linking function 5472 * 5473 * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. 5474 * 5475 * <a name="Attributes"></a> 5476 * ### Attributes 5477 * 5478 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 5479 * `link()` or `compile()` functions. It has a variety of uses. 5480 * 5481 * accessing *Normalized attribute names:* 5482 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 5483 * the attributes object allows for normalized access to 5484 * the attributes. 5485 * 5486 * * *Directive inter-communication:* All directives share the same instance of the attributes 5487 * object which allows the directives to use the attributes object as inter directive 5488 * communication. 5489 * 5490 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 5491 * allowing other directives to read the interpolated value. 5492 * 5493 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 5494 * that contain interpolation (e.g. `src="{{bar}}
5494"`). Not only is this very efficient but it's also 5495 * the only way to easily get the actual value because during the linking phase the interpolation 5496 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 5497 * 5498 * ```js 5499 * function linkingFn(scope, elm, attrs, ctrl) { 5500 * // get the attribute value 5501 * console.log(attrs.ngModel); 5502 * 5503 * // change the attribute 5504 * attrs.$set('ngModel', 'new value'); 5505 * 5506 * // observe changes to interpolated attribute 5507 * attrs.$observe('ngModel', function(value) { 5508 * console.log('ngModel has changed value to ' + value); 5509 * }); 5510 * } 5511 * ``` 5512 * 5513 * Below is an example using `$compileProvider`. 5514 * 5515 * <div class="alert alert-warning"> 5516 * **Note**: Typically directives are registered with `module.directive`. The example below is 5517 * to illustrate how `$compile` works. 5518 * </div> 5519 * 5520 <example module="compileExample"> 5521 <file name="index.html"> 5522 <script> 5523 angular.module('compileExample', [], function($compileProvider) { 5524 // configure new 'compile' directive by passing a directive 5525 // factory function. The factory function injects the '$compile' 5526 $compileProvider.directive('compile', function($compile) { 5527 // directive factory creates a link function 5528 return function(scope, element, attrs) { 5529 scope.$watch( 5530 function(scope) { 5531 // watch the 'compile' expression for changes 5532 return scope.$eval(attrs.compile); 5533 }, 5534 function(value) { 5535 // when the 'compile' expression changes 5536 // assign it into the current DOM 5537 element.html(value); 5538 5539 // compile the new DOM and link it to the current 5540 // scope. 5541 // NOTE: we only compile .childNodes so that 5542 // we don't get into infinite loop compiling ourselves 5543 $compile(element.contents())(scope); 5544 } 5545 ); 5546 }; 5547 }); 5548 }) 5549 .controller('GreeterController', ['$scope', function($scope) { 5550 $scope.name = 'Angular'; 5551 $scope.html = 'Hello {{name}}'; 5552 }]); 5553 </script> 5554 <div ng-controller="GreeterController"> 5555 <input ng-model="name"> <br> 5556 <textarea ng-model="html"></textarea> <br> 5557 <div compile="html"></div> 5558 </div> 5559 </file> 5560 <file name="protractor.js" type="protractor"> 5561 it('should auto compile', function() { 5562 var textarea = $('textarea'); 5563 var output = $('div[compile]'); 5564 // The initial state reads 'Hello Angular'. 5565 expect(output.getText()).toBe('Hello Angular'); 5566 textarea.clear(); 5567 textarea.sendKeys('{{name}}!'); 5568 expect(output.getText()).toBe('Angular!'); 5569 }); 5570 </file> 5571 </example> 5572 5573 * 5574 * 5575 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 5576 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. 5577 * @param {number} maxPriority only apply directives lower than given priority (Only effects the 5578 * root element(s), not their children) 5579 * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template 5580 * (a DOM element/tree) to a scope. Where: 5581 * 5582 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 5583 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 5584 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 5585 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 5586 * called as: <br> `cloneAttachFn(clonedElement, scope)` where: 5587 * 5588 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 5589 * * `scope` - is the current scope with which the linking function is working with. 5590 * 5591 * Calling the linking function returns the element of the template. It is either the original 5592 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 5593 * 5594 * After linking the view is not updated until after a call to $digest which typically is done by 5595 * Angular automatically. 5596 * 5597 * If you need access to the bound view, there are two ways to do it: 5598 * 5599 * - If you are not asking the linking function to clone the template, create the DOM element(s) 5600 * before you send them to the compiler and keep this reference around. 5601 * ```js 5602 * var element = $compile('<p>{{total}}</p>')(scope); 5603 * ``` 5604 * 5605 * - if on the other hand, you need the element to be cloned, the view reference from the original 5606 * example would not point to the clone, but rather to the original template that was cloned. In 5607 * this case, you can access the clone via the cloneAttachFn: 5608 * ```js 5609 * var templateElement = angular.element('<p>{{total}}</p>'), 5610 * scope = ....; 5611 * 5612 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 5613 * //attach the clone to DOM document at the right place 5614 * }); 5615 * 5616 * //now we have reference to the cloned DOM via `clonedElement` 5617 * ``` 5618 * 5619 * 5620 * For information on how the compiler works, see the 5621 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 5622 */ 5623 5624var $compileMinErr = minErr('$compile'); 5625 5626/** 5627 * @ngdoc provider 5628 * @name $compileProvider 5629 * @kind function 5630 * 5631 * @description 5632 */
vendor: 4,560 bytes, lines 5633-5741
5633$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 5634function $CompileProvider($provide, $$sanitizeUriProvider) { 5635 var hasDirectives = {}, 5636 Suffix = 'Directive', 5637 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/, 5638 CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/; 5639 5640 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 5641 // The assumption is that future DOM event attribute names will begin with 5642 // 'on' and be composed of only English letters. 5643 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 5644 5645 /** 5646 * @ngdoc method 5647 * @name $compileProvider#directive 5648 * @kind function 5649 * 5650 * @description 5651 * Register a new directive with the compiler. 5652 * 5653 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 5654 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 5655 * names and the values are the factories. 5656 * @param {Function|Array} directiveFactory An injectable directive factory function. See 5657 * {@link guide/directive} for more info. 5658 * @returns {ng.$compileProvider} Self for chaining. 5659 */ 5660 this.directive = function registerDirective(name, directiveFactory) { 5661 assertNotHasOwnProperty(name, 'directive'); 5662 if (isString(name)) { 5663 assertArg(directiveFactory, 'directiveFactory'); 5664 if (!hasDirectives.hasOwnProperty(name)) { 5665 hasDirectives[name] = []; 5666 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 5667 function($injector, $exceptionHandler) { 5668 var directives = []; 5669 forEach(hasDirectives[name], function(directiveFactory, index) { 5670 try { 5671 var directive = $injector.invoke(directiveFactory); 5672 if (isFunction(directive)) { 5673 directive = { compile: valueFn(directive) }; 5674 } else if (!directive.compile && directive.link) { 5675 directive.compile = valueFn(directive.link); 5676 } 5677 directive.priority = directive.priority || 0; 5678 directive.index = index; 5679 directive.name = directive.name || name; 5680 directive.require = directive.require || (directive.controller && directive.name); 5681 directive.restrict = directive.restrict || 'A'; 5682 directives.push(directive); 5683 } catch (e) { 5684 $exceptionHandler(e); 5685 } 5686 }); 5687 return directives; 5688 }]); 5689 } 5690 hasDirectives[name].push(directiveFactory); 5691 } else { 5692 forEach(name, reverseParams(registerDirective)); 5693 } 5694 return this; 5695 }; 5696 5697 5698 /** 5699 * @ngdoc method 5700 * @name $compileProvider#aHrefSanitizationWhitelist 5701 * @kind function 5702 * 5703 * @description 5704 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5705 * urls during a[href] sanitization. 5706 * 5707 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5708 * 5709 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 5710 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 5711 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 5712 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5713 * 5714 * @param {RegExp=} regexp New regexp to whitelist urls with. 5715 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5716 * chaining otherwise. 5717 */ 5718 this.aHrefSanitizationWhitelist = function(regexp) { 5719 if (isDefined(regexp)) { 5720 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 5721 return this; 5722 } else { 5723 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 5724 } 5725 }; 5726 5727 5728 /** 5729 * @ngdoc method 5730 * @name $compileProvider#imgSrcSanitizationWhitelist 5731 * @kind function 5732 * 5733 * @description 5734 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5735 * urls during img[src] sanitization. 5736 * 5737 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5738 * 5739 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 5740 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 5741 * regular expression. If a match is found, the original url is
5741written into the dom. Otherwise, 5742 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5743 * 5744 * @param {RegExp=} regexp New regexp to whitelist urls with. 5745 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5746 * chaining otherwise. 5747 */ 5748 this.imgSrcSanitizationWhitelist = function(regexp) { 5749 if (isDefined(regexp)) { 5750 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 5751 return this; 5752 } else { 5753 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 5754 } 5755 }; 5756 5757 this.$get = [ 5758 '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', 5759 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 5760 function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, 5761 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 5762 5763 var Attributes = function(element, attr) { 5764 this.$$element = element; 5765 this.$attr = attr || {}; 5766 }; 5767 5768 Attributes.prototype = { 5769 $normalize: directiveNormalize, 5770 5771 5772 /** 5773 * @ngdoc method 5774 * @name $compile.directive.Attributes#$addClass 5775 * @kind function 5776 * 5777 * @description 5778 * Adds the CSS class value specified by the classVal parameter to the element. If animations 5779 * are enabled then an animation will be triggered for the class addition. 5780 * 5781 * @param {string} classVal The className value that will be added to the element 5782 */ 5783 $addClass : function(classVal) { 5784 if(classVal && classVal.length > 0) { 5785 $animate.addClass(this.$$element, classVal); 5786 } 5787 }, 5788 5789 /** 5790 * @ngdoc method 5791 * @name $compile.directive.Attributes#$removeClass 5792 * @kind function 5793 * 5794 * @description 5795 * Removes the CSS class value specified by the classVal parameter from the element. If 5796 * animations are enabled then an animation will be triggered for the class removal. 5797 * 5798 * @param {string} classVal The className value that will be removed from the element 5799 */ 5800 $removeClass : function(classVal) { 5801 if(classVal && classVal.length > 0) { 5802 $animate.removeClass(this.$$element, classVal); 5803 } 5804 }, 5805 5806 /** 5807 * @ngdoc method 5808 * @name $compile.directive.Attributes#$updateClass 5809 * @kind function 5810 * 5811 * @description 5812 * Adds and removes the appropriate CSS class values to the element based on the difference 5813 * between the new and old CSS class values (specified as newClasses and oldClasses). 5814 * 5815 * @param {string} newClasses The current CSS className value 5816 * @param {string} oldClasses The former CSS className value 5817 */ 5818 $updateClass : function(newClasses, oldClasses) { 5819 var toAdd = tokenDifference(newClasses, oldClasses); 5820 var toRemove = tokenDifference(oldClasses, newClasses); 5821 5822 if(toAdd.length === 0) { 5823 $animate.removeClass(this.$$element, toRemove); 5824 } else if(toRemove.length === 0) { 5825 $animate.addClass(this.$$element, toAdd); 5826 } else { 5827 $animate.setClass(this.$$element, toAdd, toRemove); 5828 } 5829 }, 5830 5831 /** 5832 * Set a normalized attribute on the element in a way such that all directives 5833 * can share the attribute. This function properly handles boolean attributes. 5834 * @param {string} key Normalized key. (ie ngAttribute) 5835 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 5836 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 5837 * Defaults to true. 5838 * @param {string=} attrName Optional none normalized name. Defaults to key. 5839 */ 5840 $set: function(key, value, writeAttr, attrName) { 5841 // TODO: decide whether or not to throw an error if "class" 5842 //is set through this function since it may cause $updateClass to 5843 //become unstable. 5844 5845 var booleanKey = getBooleanAttrName(this.$$element[0], key), 5846 normalizedVal, 5847 nodeName; 5848 5849 if (booleanKey) { 5850 this.$$element.prop(key, value); 5851 attrName = booleanKey; 5852 } 5853 5854 this[key] = value; 5855 5856 // translate normalized key to actual key 5857 if (attrName) { 5858 this.$attr[key] = attrName; 5859 } else { 5860 attrName = this.$attr[key]; 5861 if (!attrName) { 5862 this.$attr[key] = attrName = snake_case(key, '-'); 5863 } 5864 } 5865 5866 nodeName = nodeName_(this.$$element); 5867 5868 // sanitize a[href] and img[src] values 5869 if ((nodeName === 'A' && key === 'href') || 5870 (nodeName === 'IMG' && key === 'src')) { 5871 this[key] = value = $$sanitizeUri(value, key === 'src'); 5872 } 5873 5874 if (writeAttr !== false) { 5875 if (value === null || value === undefined) { 5876 this.$$element.removeAttr(attrName); 5877 } else { 5878 this.$$element.attr(attrName, value); 5879 } 5880 } 5881 5882 // fire observers 5883 var $$observers = this.$$observers;
5884 $$observers && forEach($$observers[key], function(fn) { 5885 try { 5886 fn(value); 5887 } catch (e) { 5888 $exceptionHandler(e); 5889 } 5890 }); 5891 }, 5892 5893 5894 /** 5895 * @ngdoc method 5896 * @name $compile.directive.Attributes#$observe 5897 * @kind function 5898 * 5899 * @description 5900 * Observes an interpolated attribute. 5901 * 5902 * The observer function will be invoked once during the next `$digest` following 5903 * compilation. The observer is then invoked whenever the interpolated value 5904 * changes. 5905 * 5906 * @param {string} key Normalized key. (ie ngAttribute) . 5907 * @param {function(interpolatedValue)} fn Function that will be called whenever 5908 the interpolated value of the attribute changes. 5909 * See the {@link guide/directive#Attributes Directives} guide for more info. 5910 * @returns {function()} the `fn` parameter. 5911 */ 5912 $observe: function(key, fn) { 5913 var attrs = this, 5914 $$observers = (attrs.$$observers || (attrs.$$observers = {})), 5915 listeners = ($$observers[key] || ($$observers[key] = [])); 5916 5917 listeners.push(fn); 5918 $rootScope.$evalAsync(function() { 5919 if (!listeners.$$inter) { 5920 // no one registered attribute interpolation function, so lets call it manually 5921 fn(attrs[key]); 5922 } 5923 }); 5924 return fn; 5925 } 5926 }; 5927 5928 var startSymbol = $interpolate.startSymbol(), 5929 endSymbol = $interpolate.endSymbol(), 5930 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 5931 ? identity 5932 : function denormalizeTemplate(template) { 5933 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 5934 }, 5935 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 5936 5937 5938 return compile; 5939 5940 //================================ 5941 5942 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 5943 previousCompileContext) { 5944 if (!($compileNodes instanceof jqLite)) { 5945 // jquery always rewraps, whereas we need to preserve the original selector so that we can 5946 // modify it. 5947 $compileNodes = jqLite($compileNodes); 5948 } 5949 // We can not compile top level text elements since text nodes can be merged and we will 5950 // not be able to attach scope data to them, so we will wrap them in <span> 5951 forEach($compileNodes, function(node, index){ 5952 if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { 5953 $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0]; 5954 } 5955 }); 5956 var compositeLinkFn = 5957 compileNodes($compileNodes, transcludeFn, $compileNodes, 5958 maxPriority, ignoreDirective, previousCompileContext); 5959 safeAddClass($compileNodes, 'ng-scope'); 5960 return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){ 5961 assertArg(scope, 'scope'); 5962 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 5963 // and sometimes changes the structure of the DOM. 5964 var $linkNode = cloneConnectFn 5965 ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! 5966 : $compileNodes; 5967 5968 forEach(transcludeControllers, function(instance, name) { 5969 $linkNode.data('$' + name + 'Controller', instance); 5970 }); 5971 5972 // Attach scope only to non-text nodes. 5973 for(var i = 0, ii = $linkNode.length; i<ii; i++) { 5974 var node = $linkNode[i], 5975 nodeType = node.nodeType; 5976 if (nodeType === 1 /* element */ || nodeType === 9 /* document */) { 5977 $linkNode.eq(i).data('$scope', scope); 5978 } 5979 } 5980 5981 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 5982 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 5983 return $linkNode; 5984 }; 5985 } 5986 5987 function safeAddClass($element, className) {
vendor: 4,887 bytes, lines 5988-6098
5988 try { 5989 $element.addClass(className); 5990 } catch(e) { 5991 // ignore, since it means that we are trying to set class on 5992 // SVG element, where class name is read-only. 5993 } 5994 } 5995 5996 /** 5997 * Compile function matches each node in nodeList against the directives. Once all directives 5998 * for a particular node are collected their compile functions are executed. The compile 5999 * functions return values - the linking functions - are combined into a composite linking 6000 * function, which is the a linking function for the node. 6001 * 6002 * @param {NodeList} nodeList an array of nodes or NodeList to compile 6003 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 6004 * scope argument is auto-generated to the new child of the transcluded parent scope. 6005 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 6006 * the rootElement must be set the jqLite collection of the compile root. This is 6007 * needed so that the jqLite collection items can be replaced with widgets. 6008 * @param {number=} maxPriority Max directive priority. 6009 * @returns {Function} A composite linking function of all of the matched directives or null. 6010 */ 6011 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 6012 previousCompileContext) { 6013 var linkFns = [], 6014 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound; 6015 6016 for (var i = 0; i < nodeList.length; i++) { 6017 attrs = new Attributes(); 6018 6019 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 6020 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 6021 ignoreDirective); 6022 6023 nodeLinkFn = (directives.length) 6024 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 6025 null, [], [], previousCompileContext) 6026 : null; 6027 6028 if (nodeLinkFn && nodeLinkFn.scope) { 6029 safeAddClass(attrs.$$element, 'ng-scope'); 6030 } 6031 6032 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 6033 !(childNodes = nodeList[i].childNodes) || 6034 !childNodes.length) 6035 ? null 6036 : compileNodes(childNodes, 6037 nodeLinkFn ? ( 6038 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 6039 && nodeLinkFn.transclude) : transcludeFn); 6040 6041 linkFns.push(nodeLinkFn, childLinkFn); 6042 linkFnFound = linkFnFound || nodeLinkFn || childLinkFn; 6043 //use the previous context only for the first element in the virtual group 6044 previousCompileContext = null; 6045 } 6046 6047 // return a linking function if we have found anything, null otherwise 6048 return linkFnFound ? compositeLinkFn : null; 6049 6050 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 6051 var nodeLinkFn, childLinkFn, node, childScope, i, ii, n, childBoundTranscludeFn; 6052 6053 // copy nodeList so that linking doesn't break due to live list updates. 6054 var nodeListLength = nodeList.length, 6055 stableNodeList = new Array(nodeListLength); 6056 for (i = 0; i < nodeListLength; i++) { 6057 stableNodeList[i] = nodeList[i]; 6058 } 6059 6060 for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) { 6061 node = stableNodeList[n]; 6062 nodeLinkFn = linkFns[i++]; 6063 childLinkFn = linkFns[i++]; 6064 6065 if (nodeLinkFn) { 6066 if (nodeLinkFn.scope) { 6067 childScope = scope.$new(); 6068 jqLite.data(node, '$scope', childScope); 6069 } else { 6070 childScope = scope; 6071 } 6072 6073 if ( nodeLinkFn.transcludeOnThisElement ) { 6074 childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude, parentBoundTranscludeFn); 6075 6076 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 6077 childBoundTranscludeFn = parentBoundTranscludeFn; 6078 6079 } else if (!parentBoundTranscludeFn && transcludeFn) { 6080 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 6081 6082 } else { 6083 childBoundTranscludeFn = null; 6084 } 6085 6086 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); 6087 6088 } else if (childLinkFn) { 6089 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 6090 } 6091 } 6092 } 6093 } 6094 6095 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 6096 6097 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) { 6098 var scopeCreated = false;
6099 6100 if (!transcludedScope) { 6101 transcludedScope = scope.$new(); 6102 transcludedScope.$$transcluded = true; 6103 scopeCreated = true; 6104 } 6105 6106 var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn); 6107 if (scopeCreated) { 6108 clone.on('$destroy', function() { transcludedScope.$destroy(); }); 6109 } 6110 return clone; 6111 }; 6112 6113 return boundTranscludeFn; 6114 } 6115 6116 /** 6117 * Looks for directives on the given node and adds them to the directive collection which is 6118 * sorted. 6119 * 6120 * @param node Node to search. 6121 * @param directives An array to which the directives are added to. This array is sorted before 6122 * the function returns. 6123 * @param attrs The shared attrs object which is used to populate the normalized attributes. 6124 * @param {number=} maxPriority Max directive priority. 6125 */ 6126 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 6127 var nodeType = node.nodeType, 6128 attrsMap = attrs.$attr, 6129 match, 6130 className; 6131 6132 switch(nodeType) { 6133 case 1: /* Element */ 6134 // use the node name: <directive> 6135 addDirective(directives, 6136 directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); 6137 6138 // iterate over the attributes 6139 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 6140 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 6141 var attrStartName = false; 6142 var attrEndName = false; 6143 6144 attr = nAttrs[j]; 6145 if (!msie || msie >= 8 || attr.specified) { 6146 name = attr.name; 6147 value = trim(attr.value); 6148 6149 // support ngAttr attribute binding 6150 ngAttrName = directiveNormalize(name); 6151 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 6152 name = snake_case(ngAttrName.substr(6), '-'); 6153 } 6154 6155 var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); 6156 if (ngAttrName === directiveNName + 'Start') { 6157 attrStartName = name; 6158 attrEndName = name.substr(0, name.length - 5) + 'end'; 6159 name = name.substr(0, name.length - 6); 6160 } 6161 6162 nName = directiveNormalize(name.toLowerCase()); 6163 attrsMap[nName] = name; 6164 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 6165 attrs[nName] = value; 6166 if (getBooleanAttrName(node, nName)) { 6167 attrs[nName] = true; // presence means true 6168 } 6169 } 6170 addAttrInterpolateDirective(node, directives, value, nName); 6171 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 6172 attrEndName); 6173 } 6174 } 6175 6176 // use class as directive 6177 className = node.className; 6178 if (isString(className) && className !== '') { 6179 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 6180 nName = directiveNormalize(match[2]); 6181 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 6182 attrs[nName] = trim(match[3]); 6183 } 6184 className = className.substr(match.index + match[0].length); 6185 } 6186 } 6187 break; 6188 case 3: /* Text Node */ 6189 addTextInterpolateDirective(directives, node.nodeValue); 6190 break; 6191 case 8: /* Comment */ 6192 try { 6193 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 6194 if (match) { 6195 nName = directiveNormalize(match[1]); 6196 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 6197 attrs[nName] = trim(match[2]); 6198 } 6199 } 6200 } catch (e) { 6201 // turns out that under some circumstances IE9 throws errors when one attempts to read 6202 // comment's node value. 6203 // Just ignore it and continue. (Can't seem to reproduce in test case.) 6204 } 6205 break; 6206 } 6207 6208 directives.sort(byPriority); 6209 return directives; 6210 } 6211 6212 /**
6213 * Given a node with an directive-start it collects all of the siblings until it finds 6214 * directive-end. 6215 * @param node 6216 * @param attrStart 6217 * @param attrEnd 6218 * @returns {*} 6219 */ 6220 function groupScan(node, attrStart, attrEnd) { 6221 var nodes = []; 6222 var depth = 0; 6223 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 6224 var startNode = node; 6225 do { 6226 if (!node) { 6227 throw $compileMinErr('uterdir', 6228 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 6229 attrStart, attrEnd); 6230 } 6231 if (node.nodeType == 1 /** Element **/) { 6232 if (node.hasAttribute(attrStart)) depth++; 6233 if (node.hasAttribute(attrEnd)) depth--; 6234 } 6235 nodes.push(node); 6236 node = node.nextSibling; 6237 } while (depth > 0); 6238 } else { 6239 nodes.push(node); 6240 } 6241 6242 return jqLite(nodes); 6243 } 6244 6245 /** 6246 * Wrapper for linking function which converts normal linking function into a grouped 6247 * linking function. 6248 * @param linkFn 6249 * @param attrStart 6250 * @param attrEnd 6251 * @returns {Function} 6252 */ 6253 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 6254 return function(scope, element, attrs, controllers, transcludeFn) { 6255 element = groupScan(element[0], attrStart, attrEnd); 6256 return linkFn(scope, element, attrs, controllers, transcludeFn); 6257 }; 6258 } 6259 6260 /** 6261 * Once the directives have been collected, their compile functions are executed. This method 6262 * is responsible for inlining directive templates as well as terminating the application 6263 * of the directives if the terminal directive has been reached. 6264 * 6265 * @param {Array} directives Array of collected directives to execute their compile function. 6266 * this needs to be pre-sorted by priority order. 6267 * @param {Node} compileNode The raw DOM node to apply the compile functions to 6268 * @param {Object} templateAttrs The shared attribute function 6269 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 6270 * scope argument is auto-generated to the new 6271 * child of the transcluded parent scope. 6272 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 6273 * argument has the root jqLite array so that we can replace nodes 6274 * on it. 6275 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 6276 * compiling the transclusion. 6277 * @param {Array.<Function>} preLinkFns 6278 * @param {Array.<Function>} postLinkFns 6279 * @param {Object} previousCompileContext Context used for previous compilation of the current 6280 * node 6281 * @returns {Function} linkFn 6282 */ 6283 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 6284 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 6285 previousCompileContext) { 6286 previousCompileContext = previousCompileContext || {}; 6287 6288 var terminalPriority = -Number.MAX_VALUE, 6289 newScopeDirective, 6290 controllerDirectives = previousCompileContext.controllerDirectives, 6291 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 6292 templateDirective = previousCompileContext.templateDirective, 6293 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 6294 hasTranscludeDirective = false, 6295 hasTemplate = false, 6296 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 6297 $compileNode = templateAttrs.$$element = jqLite(compileNode), 6298 directive, 6299 directiveName, 6300 $template, 6301 replaceDirective = originalReplaceDirective, 6302 childTranscludeFn = transcludeFn, 6303 linkFn, 6304 directiveValue; 6305 6306 // executes all directives on the current element 6307 for(var i = 0, ii = directives.length; i < ii; i++) { 6308 directive = directives[i]; 6309 var attrStart = directive.$$start; 6310 var attrEnd = directive.$$end; 6311 6312 // collect multiblock sections 6313 if (attrStart) { 6314 $compileNode = groupScan(compileNode, attrStart, attrEnd); 6315 } 6316 $template = undefined; 6317 6318 if (terminalPriority > directive.priority) { 6319 break; // prevent further processing of directives 6320 } 6321 6322 if (directiveValue = directive.scope) { 6323 newScopeDirective = newScopeDirective || directive; 6324 6325 // skip the check for directives with async templates, we'll check the derived sync 6326 // directive when the template arrives 6327 if (!directive.templateUrl) { 6328 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 6329 $compileNode); 6330 if (isObject(directiveValue)) { 6331 newIsolateScopeDirective = directive; 6332 } 6333 } 6334 } 6335 6336 directiveName = directive.name; 6337 6338 if (!directive.templateUrl && directive.controller) { 6339 directiveValue = directive.controller; 6340 controllerDirectives = controllerDirectives || {}; 6341 assertNoDuplicate("'" + directiveName + "' controller", 6342 controllerDirectives[directiveName], directive, $compileNode); 6343 controllerDirectives[directiveName] = directive; 6344 } 6345 6346 if (directiveValue = directive.transclude) { 6347 hasTranscludeDirective = true; 6348 6349 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 6350 // This option should only be used by directives that know how to safely handle element transclusion, 6351 // where the transcluded nodes are added or replaced after linking. 6352 if (!directive.$$tlb) { 6353 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 6354 nonTlbTranscludeDirective = directive; 6355 } 6356 6357 if (directiveValue == 'element') { 6358 hasElementTranscludeDirective = true; 6359 terminalPriority = directive.priority; 6360 $template = $compileNode; 6361 $compileNode = templateAttrs.$$element = 6362 jqLite(document.createComment(' ' + directiveName + ': ' + 6363 templateAttrs[directiveName] + ' ')); 6364 compileNode = $compileNode[0]; 6365 replaceWith(jqCollection, sliceArgs($template), compileNode); 6366 6367 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 6368 replaceDirective && replaceDirective.name, { 6369 // Don't pass in: 6370 // - controllerDirectives - otherwise we'll create duplicates controllers 6371 // - newIsolateScopeDirective or templateDirective - comb
vendor: 4,603 bytes, lines 6371-6478
6371ining templates with 6372 // element transclusion doesn't make sense. 6373 // 6374 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 6375 // on the same element more than once. 6376 nonTlbTranscludeDirective: nonTlbTranscludeDirective 6377 }); 6378 } else { 6379 $template = jqLite(jqLiteClone(compileNode)).contents(); 6380 $compileNode.empty(); // clear contents 6381 childTranscludeFn = compile($template, transcludeFn); 6382 } 6383 } 6384 6385 if (directive.template) { 6386 hasTemplate = true; 6387 assertNoDuplicate('template', templateDirective, directive, $compileNode); 6388 templateDirective = directive; 6389 6390 directiveValue = (isFunction(directive.template)) 6391 ? directive.template($compileNode, templateAttrs) 6392 : directive.template; 6393 6394 directiveValue = denormalizeTemplate(directiveValue); 6395 6396 if (directive.replace) { 6397 replaceDirective = directive; 6398 if (jqLiteIsTextNode(directiveValue)) { 6399 $template = []; 6400 } else { 6401 $template = jqLite(trim(directiveValue)); 6402 } 6403 compileNode = $template[0]; 6404 6405 if ($template.length != 1 || compileNode.nodeType !== 1) { 6406 throw $compileMinErr('tplrt', 6407 "Template for directive '{0}' must have exactly one root element. {1}", 6408 directiveName, ''); 6409 } 6410 6411 replaceWith(jqCollection, $compileNode, compileNode); 6412 6413 var newTemplateAttrs = {$attr: {}}; 6414 6415 // combine directives from the original node and from the template: 6416 // - take the array of directives for this element 6417 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 6418 // - collect directives from the template and sort them by priority 6419 // - combine directives as: processed + template + unprocessed 6420 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 6421 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 6422 6423 if (newIsolateScopeDirective) { 6424 markDirectivesAsIsolate(templateDirectives); 6425 } 6426 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 6427 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 6428 6429 ii = directives.length; 6430 } else { 6431 $compileNode.html(directiveValue); 6432 } 6433 } 6434 6435 if (directive.templateUrl) { 6436 hasTemplate = true; 6437 assertNoDuplicate('template', templateDirective, directive, $compileNode); 6438 templateDirective = directive; 6439 6440 if (directive.replace) { 6441 replaceDirective = directive; 6442 } 6443 6444 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 6445 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 6446 controllerDirectives: controllerDirectives, 6447 newIsolateScopeDirective: newIsolateScopeDirective, 6448 templateDirective: templateDirective, 6449 nonTlbTranscludeDirective: nonTlbTranscludeDirective 6450 }); 6451 ii = directives.length; 6452 } else if (directive.compile) { 6453 try { 6454 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 6455 if (isFunction(linkFn)) { 6456 addLinkFns(null, linkFn, attrStart, attrEnd); 6457 } else if (linkFn) { 6458 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 6459 } 6460 } catch (e) { 6461 $exceptionHandler(e, startingTag($compileNode)); 6462 } 6463 } 6464 6465 if (directive.terminal) { 6466 nodeLinkFn.terminal = true; 6467 terminalPriority = Math.max(terminalPriority, directive.priority); 6468 } 6469 6470 } 6471 6472 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 6473 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 6474 nodeLinkFn.templateOnThisElement = hasTemplate; 6475 nodeLinkFn.transclude = childTranscludeFn; 6476 6477 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 6478
6479 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 6480 return nodeLinkFn; 6481 6482 //////////////////// 6483 6484 function addLinkFns(pre, post, attrStart, attrEnd) { 6485 if (pre) { 6486 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 6487 pre.require = directive.require; 6488 pre.directiveName = directiveName; 6489 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6490 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 6491 } 6492 preLinkFns.push(pre); 6493 } 6494 if (post) { 6495 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 6496 post.require = directive.require; 6497 post.directiveName = directiveName; 6498 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6499 post = cloneAndAnnotateFn(post, {isolateScope: true}); 6500 } 6501 postLinkFns.push(post); 6502 } 6503 } 6504 6505 6506 function getControllers(directiveName, require, $element, elementControllers) { 6507 var value, retrievalMethod = 'data', optional = false;
6508 if (isString(require)) { 6509 while((value = require.charAt(0)) == '^' || value == '?') { 6510 require = require.substr(1); 6511 if (value == '^') { 6512 retrievalMethod = 'inheritedData'; 6513 } 6514 optional = optional || value == '?'; 6515 } 6516 value = null; 6517 6518 if (elementControllers && retrievalMethod === 'data') { 6519 value = elementControllers[require]; 6520 } 6521 value = value || $element[retrievalMethod]('$' + require + 'Controller'); 6522 6523 if (!value && !optional) { 6524 throw $compileMinErr('ctreq', 6525 "Controller '{0}', required by directive '{1}', can't be found!", 6526 require, directiveName); 6527 } 6528 return value; 6529 } else if (isArray(require)) { 6530 value = []; 6531 forEach(require, function(require) { 6532 value.push(getControllers(directiveName, require, $element, elementControllers)); 6533 }); 6534 } 6535 return value; 6536 } 6537 6538 6539 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 6540 var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; 6541 6542 attrs = (compileNode === linkNode) 6543 ? templateAttrs 6544 : shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); 6545 $element = attrs.$$element; 6546 6547 if (newIsolateScopeDirective) { 6548 var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; 6549 6550 isolateScope = scope.$new(true); 6551 6552 if (templateDirective && (templateDirective === newIsolateScopeDirective || 6553 templateDirective === newIsolateScopeDirective.$$originalDirective)) { 6554 $element.data('$isolateScope', isolateScope); 6555 } else { 6556 $element.data('$isolateScopeNoTemplate', isolateScope); 6557 } 6558 6559 6560 6561 safeAddClass($element, 'ng-isolate-scope'); 6562 6563 forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { 6564 var match = definition.match(LOCAL_REGEXP) || [], 6565 attrName = match[3] || scopeName, 6566 optional = (match[2] == '?'), 6567 mode = match[1], // @, =, or & 6568 lastValue, 6569 parentGet, parentSet, compare; 6570 6571 isolateScope.$$isolateBindings[scopeName] = mode + attrName; 6572 6573 switch (mode) { 6574 6575 case '@': 6576 attrs.$observe(attrName, function(value) { 6577 isolateScope[scopeName] = value; 6578 }); 6579 attrs.$$observers[attrName].$$scope = scope; 6580 if( attrs[attrName] ) { 6581 // If the attribute has been provided then we trigger an interpolation to ensure 6582 // the value is there for use in the link fn 6583 isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); 6584 } 6585 break; 6586 6587 case '=': 6588 if (optional && !attrs[attrName]) { 6589 return; 6590 } 6591 parentGet = $parse(attrs[attrName]); 6592 if (parentGet.literal) { 6593 compare = equals; 6594 } else { 6595 compare = function(a,b) { return a === b || (a !== a && b !== b); }; 6596 } 6597 parentSet = parentGet.assign || function() { 6598 // reset the change, or we will throw this exception on every $digest 6599 lastValue = isolateScope[scopeName] = parentGet(scope); 6600 throw $compileMinErr('nonassign', 6601 "Expression '{0}' used with directive '{1}' is non-assignable!", 6602 attrs[attrName], newIsolateScopeDirective.name); 6603 }; 6604 lastValue = isolateScope[scopeName] = parentGet(scope); 6605 isolateScope.$watch(function parentValueWatch() { 6606 var parentValue = parentGet(scope); 6607 if (!compare(parentValue, isolateScope[scopeName])) { 6608 // we are out of sync and need to copy 6609 if (!compare(parentValue, lastValue)) { 6610 // parent changed and it has precedence 6611 isolateScope[scopeName] = parentValue; 6612 } else { 6613 // if the parent can be assigned then do so 6614 parentSet(scope, parentValue = isolateScope[scopeName]); 6615 } 6616 } 6617 return lastValue = parentValue; 6618 }, null, parentGet.literal); 6619 break; 6620 6621 case '&': 6622 parentGet = $parse(attrs[attrName]); 6623 isolateScope[scopeName] = function(locals) { 6624 return parentGet(scope, locals); 6625 }; 6626 break; 6627 6628 default: 6629 throw $compileMinErr('iscp', 6630 "Invalid isolate scope definition for directive '{0}'." + 6631 " Definition: {... {1}: '{2}' ...}", 6632 newIsolateScopeDirective.name, scopeName, definition); 6633 } 6634 }); 6635 } 6636 transcludeFn = boundTranscludeFn && controllersBoundTransclude; 6637 if (controllerDirectives) {
6638 forEach(controllerDirectives, function(directive) { 6639 var locals = { 6640 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 6641 $element: $element, 6642 $attrs: attrs, 6643 $transclude: transcludeFn 6644 }, controllerInstance; 6645 6646 controller = directive.controller; 6647 if (controller == '@') { 6648 controller = attrs[directive.name]; 6649 } 6650 6651 controllerInstance = $controller(controller, locals); 6652 // For directives with element transclusion the element is a comment, 6653 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 6654 // clean up (http://bugs.jquery.com/ticket/8335). 6655 // Instead, we save the controllers for the element in a local hash and attach to .data 6656 // later, once we have the actual element. 6657 elementControllers[directive.name] = controllerInstance; 6658 if (!hasElementTranscludeDirective) { 6659 $element.data('$' + directive.name + 'Controller', controllerInstance); 6660 } 6661 6662 if (directive.controllerAs) { 6663 locals.$scope[directive.controllerAs] = controllerInstance; 6664 } 6665 }); 6666 } 6667 6668 // PRELINKING 6669 for(i = 0, ii = preLinkFns.length; i < ii; i++) { 6670 try { 6671 linkFn = preLinkFns[i]; 6672 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6673 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); 6674 } catch (e) { 6675 $exceptionHandler(e, startingTag($element)); 6676 } 6677 } 6678 6679 // RECURSION 6680 // We only pass the isolate scope, if the isolate directive has a template, 6681 // otherwise the child elements do not belong to the isolate directive. 6682 var scopeToChild = scope; 6683 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 6684 scopeToChild = isolateScope; 6685 } 6686 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 6687 6688 // POSTLINKING 6689 for(i = postLinkFns.length - 1; i >= 0; i--) { 6690 try { 6691 linkFn = postLinkFns[i]; 6692 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6693 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); 6694 } catch (e) { 6695 $exceptionHandler(e, startingTag($element)); 6696 } 6697 } 6698 6699 // This is the function that is injected as `$transclude`. 6700 function controllersBoundTransclude(scope, cloneAttachFn) { 6701 var transcludeControllers; 6702 6703 // no scope passed 6704 if (arguments.length < 2) { 6705 cloneAttachFn = scope; 6706 scope = undefined; 6707 } 6708 6709 if (hasElementTranscludeDirective) { 6710 transcludeControllers = elementControllers; 6711 } 6712 6713 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); 6714 } 6715 } 6716 } 6717 6718 function markDirectivesAsIsolate(directives) { 6719 // mark all directives as needing isolate scope. 6720 for (var j = 0, jj = directives.length; j < jj; j++) { 6721 directives[j] = inherit(directives[j], {$$isolateScope: true}); 6722 } 6723 } 6724 6725 /** 6726 * looks up the directive and decorates it with exception handling and proper parameters. We 6727 * call this the boundDirective. 6728 * 6729 * @param {string} name name of the directive to look up. 6730 * @param {string} location The directive must be found in specific format. 6731 * String containing any of theses characters: 6732 * 6733 * * `E`: element name 6734 * * `A': attribute 6735 * * `C`: class 6736 * * `M`: comment 6737 * @returns {boolean} true if directive was added. 6738 */ 6739 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 6740 endAttrName) { 6741 if (name === ignoreDirective) return null; 6742 var match = null; 6743 if (hasDirectives.hasOwnProperty(name)) { 6744 for(var directive, directives = $injector.get(name + Suffix), 6745 i = 0, ii = directives.length; i<ii; i++) { 6746 try { 6747 directive = directives[i]; 6748 if ( (maxPriority === undefined || maxPriority > directive.priority) && 6749 directive.restrict.indexOf(location) != -1) { 6750 if (startAttrName) { 6751 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 6752 } 6753 tDirectives.push(directive); 6754 match = directive; 6755 } 6756 } catch(e) { $exceptionHandler(e); } 6757 } 6758 } 6759 return match; 6760 } 6761 6762 6763 /** 6764 * When the element is replaced with HTML template then the new attributes 6765 * on the template need to be merged with the existing attributes in the DOM. 6766 * The desired effect is to have both of the attributes present. 6767 * 6768 * @param {object} dst destination attributes (original DOM) 6769 * @param {object} src source attributes (from the directive template) 6770 */ 6771 function mergeTemplateAttributes(dst, src) { 6772 var srcAttr = src.$attr, 6773 dstAttr = dst.$attr, 6774 $element = dst.$$element; 6775 6776 // reapply the old attributes to the new element
6777 forEach(dst, function(value, key) { 6778 if (key.charAt(0) != '$') { 6779 if (src[key] && src[key] !== value) { 6780 value += (key === 'style' ? ';' : ' ') + src[key]; 6781 } 6782 dst.$set(key, value, true, srcAttr[key]); 6783 } 6784 }); 6785 6786 // copy the new attributes on the old attrs object 6787 forEach(src, function(value, key) { 6788 if (key == 'class') { 6789 safeAddClass($element, value); 6790 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 6791 } else if (key == 'style') { 6792 $element.attr('style', $element.attr('style') + ';' + value); 6793 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 6794 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 6795 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 6796 // have an attribute like "has-own-property" or "data-has-own-property", etc. 6797 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 6798 dst[key] = value; 6799 dstAttr[key] = srcAttr[key]; 6800 } 6801 }); 6802 } 6803 6804 6805 function compileTemplateUrl(directives, $compileNode, tAttrs, 6806 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 6807 var linkQueue = [], 6808 afterTemplateNodeLinkFn, 6809 afterTemplateChildLinkFn, 6810 beforeTemplateCompileNode = $compileNode[0], 6811 origAsyncDirective = directives.shift(), 6812 // The fact that we have to copy and patch the directive seems wrong! 6813 derivedSyncDirective = extend({}, origAsyncDirective, { 6814 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 6815 }), 6816 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 6817 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 6818 : origAsyncDirective.templateUrl; 6819 6820 $compileNode.empty(); 6821 6822 $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). 6823 success(function(content) { 6824 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 6825 6826 content = denormalizeTemplate(content); 6827 6828 if (origAsyncDirective.replace) { 6829 if (jqLiteIsTextNode(content)) { 6830 $template = []; 6831 } else { 6832 $template = jqLite(trim(content)); 6833 } 6834 compileNode = $template[0]; 6835 6836 if ($template.length != 1 || compileNode.nodeType !== 1) { 6837 throw $compileMinErr('tplrt', 6838 "Template for directive '{0}' must have exactly one root element. {1}", 6839 origAsyncDirective.name, templateUrl); 6840 } 6841 6842 tempTemplateAttrs = {$attr: {}}; 6843 replaceWith($rootElement, $compileNode, compileNode); 6844 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 6845 6846 if (isObject(origAsyncDirective.scope)) { 6847 markDirectivesAsIsolate(templateDirectives); 6848 } 6849 directives = templateDirectives.concat(directives); 6850 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 6851 } else { 6852 compileNode = beforeTemplateCompileNode; 6853 $compileNode.html(content); 6854 } 6855 6856 directives.unshift(derivedSyncDirective); 6857 6858 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 6859 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 6860 previousCompileContext); 6861 forEach($rootElement, function(node, i) { 6862 if (node == compileNode) { 6863 $rootElement[i] = $compileNode[0]; 6864 } 6865 }); 6866 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 6867 6868 while(linkQueue.length) { 6869 var scope = linkQueue.shift(), 6870 beforeTemplateLinkNode = linkQueue.shift(), 6871 linkRootElement = linkQueue.shift(), 6872 boundTranscludeFn = linkQueue.shift(), 6873 linkNode = $compileNode[0]; 6874 6875 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
6876 var oldClasses = beforeTemplateLinkNode.className; 6877 6878 if (!(previousCompileContext.hasElementTranscludeDirective && 6879 origAsyncDirective.replace)) { 6880 // it was cloned therefore we have to clone as well. 6881 linkNode = jqLiteClone(compileNode); 6882 } 6883 6884 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 6885 6886 // Copy in CSS classes from original node 6887 safeAddClass(jqLite(linkNode), oldClasses); 6888 } 6889 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 6890 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 6891 } else { 6892 childBoundTranscludeFn = boundTranscludeFn; 6893 } 6894 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 6895 childBoundTranscludeFn); 6896 } 6897 linkQueue = null; 6898 }). 6899 error(function(response, code, headers, config) { 6900 throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); 6901 }); 6902 6903 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 6904 var childBoundTranscludeFn = boundTranscludeFn; 6905 if (linkQueue) { 6906 linkQueue.push(scope); 6907 linkQueue.push(node); 6908 linkQueue.push(rootElement); 6909 linkQueue.push(childBoundTranscludeFn); 6910 } else { 6911 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 6912 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 6913 } 6914 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); 6915 } 6916 }; 6917 } 6918 6919 6920 /** 6921 * Sorting function for bound directives. 6922 */ 6923 function byPriority(a, b) { 6924 var diff = b.priority - a.priority; 6925 if (diff !== 0) return diff; 6926 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 6927 return a.index - b.index; 6928 } 6929 6930 6931 function assertNoDuplicate(what, previousDirective, directive, element) { 6932 if (previousDirective) { 6933 throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', 6934 previousDirective.name, directive.name, what, startingTag(element)); 6935 } 6936 } 6937 6938 6939 function addTextInterpolateDirective(directives, text) { 6940 var interpolateFn = $interpolate(text, true); 6941 if (interpolateFn) { 6942 directives.push({ 6943 priority: 0, 6944 compile: function textInterpolateCompileFn(templateNode) { 6945 // when transcluding a template that has bindings in the root 6946 // then we don't have a parent and should do this in the linkFn 6947 var parent = templateNode.parent(), hasCompileParent = parent.length; 6948 if (hasCompileParent) safeAddClass(templateNode.parent(), 'ng-binding'); 6949 6950 return function textInterpolateLinkFn(scope, node) { 6951 var parent = node.parent(), 6952 bindings = parent.data('$binding') || []; 6953 bindings.push(interpolateFn); 6954 parent.data('$binding', bindings); 6955 if (!hasCompileParent) safeAddClass(parent, 'ng-binding'); 6956 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 6957 node[0].nodeValue = value; 6958 }); 6959 }; 6960 } 6961 }); 6962 } 6963 } 6964 6965 6966 function getTrustedContext(node, attrNormalizedName) { 6967 if (attrNormalizedName == "srcdoc") { 6968 return $sce.HTML; 6969 } 6970 var tag = nodeName_(node); 6971 // maction[xlink:href] can source SVG. It's not limited to <maction>. 6972 if (attrNormalizedName == "xlinkHref" || 6973 (tag == "FORM" && attrNormalizedName == "action") || 6974 (tag != "IMG" && (attrNormalizedName == "src" || 6975 attrNormalizedName == "ngSrc"))) { 6976 return $sce.RESOURCE_URL; 6977 } 6978 } 6979 6980 6981 function addAttrInterpolateDirective(node, directives, value, name) { 6982 var interpolateFn = $interpolate(value, true); 6983 6984 // no interpolation found -> ignore 6985 if (!interpolateFn) return; 6986 6987 6988 if (name === "multiple" && nodeName_(node) === "SELECT") { 6989 throw $compileMinErr("selmulti", 6990 "Binding to the 'multiple' attribute is not supported. Element: {0}", 6991 startingTag(node)); 6992 } 6993 6994 directives.push({ 6995 priority: 100, 6996 compile: function() { 6997 return { 6998 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 6999 var $$observers = (attr.$$observers || (attr.$$observers = {})); 7000 7001 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 7002 throw $compileMinErr('nodomevents', 7003 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 7004 "ng- versions (such as ng-click instead of onclick) instead."); 7005 } 7006 7007 // we need to interpolate again, in case the attribute value has been updated 7008 // (e.g. by another directive's compile function) 7009 interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); 7010 7011 // if attribute was updated so that there is no interpolation going on we don't want to 7012 // register any observers 7013 if (!interpolateFn) return; 7014 7015 // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the 7016 // actual attr value 7017 attr[name] = interpolateFn(scope); 7018 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 7019 (attr.$$observers && attr.$$observers[name].$$scope || scope). 7020 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 7021 //special case for class attribute addition + removal 7022 //so that class changes can tap into the animation 7023 //hooks provided by the $animate service. Be sure to 7024 //skip animations when the first digest occurs (when 7025 //both the new and the old values are the same) since 7026 //the CSS classes are the non-interpolated values 7027 if(name === 'class' && newValue != oldValue) { 7028 attr.$updateClass(newValue, oldValue); 7029 } else { 7030 attr.$set(name, newValue); 7031 } 7032 }); 7033 } 7034 }; 7035 } 7036 }); 7037 } 7038 7039 7040 /** 7041 * This is a special jqLite.replaceWith, which can replace items which 7042 * have no parents, provided that the containing jqLite collection is provided. 7043 * 7044 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 7045 * in the root of the tree. 7046 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 7047 * the shell, but replace its DOM node reference. 7048 * @param {Node} newNode The new DOM node. 7049 */ 7050 function replaceWith($rootElement, elementsToRemove, newNode) { 7051 var firstElementToRemove = elementsToRemove[0], 7052 removeCount = elementsToRemove.length, 7053 parent = firstElementToRemove.parentNode, 7054 i, ii; 7055 7056 if ($rootElement) { 7057 for(i = 0, ii = $rootElement.length; i < ii; i++) { 7058 if ($rootElement[i] == firstElementToRemove) { 7059 $rootElement[i++] = newNode; 7060 for (var j = i, j2 = j + removeCount - 1, 7061 jj = $rootElement.length; 7062 j < jj; j++, j2++) { 7063 if (j2 < jj) { 7064 $rootElement[j] = $rootElement[j2]; 7065 } else { 7066 delete $rootElement[j]; 7067 } 7068 } 7069 $rootElement.length -= removeCount - 1; 7070 break; 7071 } 7072 } 7073 } 7074 7075 if (parent) { 7076 parent.replaceChild(newNode, firstElementToRemove); 7077 } 7078 var fragment = document.createDocumentFragment(); 7079 fragment.appendChild(firstElementToRemove); 7080 newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; 7081 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 7082 var element = elementsToRemove[k]; 7083 jqLite(element).remove(); // must do this way to clean up expando 7084 fragment.appendChild(element); 7085 delete elementsToRemove[k]; 7086 } 7087 7088 elementsToRemove[0] = newNode; 7089 elementsToRemove.length = 1; 7090 } 7091 7092 7093 function cloneAndAnnotateFn(fn, annotation) { 7094 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 7095 } 7096 }]; 7097} 7098 7099var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; 7100/** 7101 * Converts all accepted directives format into proper directive name. 7102 * All of these will become 'myDirective': 7103 * my:Directive 7104 * my-directive 7105 * x-my-directive 7106 * data-my:directive 7107 * 7108 * Also there is special case for Moz prefix starting with upper case letter. 7109 * @param name Name to normalize 7110 */ 7111function directiveNormalize(name) { 7112 return camelCase(name.replace(PREFIX_REGEXP, '')); 7113} 7114 7115/** 7116 * @ngdoc type 7117 * @name $compile.directive.Attributes 7118 * 7119 * @description 7120 * A shared object between directive compile / linking functions which contains
7120normalized DOM 7121 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 7122 * needed since all of these are treated as equivalent in Angular: 7123 * 7124 * ``` 7125 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 7126 * ``` 7127 */ 7128 7129/** 7130 * @ngdoc property 7131 * @name $compile.directive.Attributes#$attr 7132 * @returns {object} A map of DOM element attribute names to the normalized name. This is 7133 * needed to do reverse lookup from normalized name back to actual name. 7134 */ 7135 7136 7137/** 7138 * @ngdoc method 7139 * @name $compile.directive.Attributes#$set 7140 * @kind function 7141 * 7142 * @description 7143 * Set DOM element attribute value. 7144 * 7145 * 7146 * @param {string} name Normalized element attribute name of the property to modify. The name is 7147 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 7148 * property to the original name. 7149 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 7150 */ 7151 7152 7153 7154/** 7155 * Closure compiler type information 7156 */ 7157 7158function nodesetLinkingFn( 7159 /* angular.Scope */ scope, 7160 /* NodeList */ nodeList, 7161 /* Element */ rootElement, 7162 /* function(Function) */ boundTranscludeFn 7163){} 7164 7165function directiveLinkingFn( 7166 /* nodesetLinkingFn */ nodesetLinkingFn, 7167 /* angular.Scope */ scope, 7168 /* Node */ node, 7169 /* Element */ rootElement, 7170 /* function(Function) */ boundTranscludeFn 7171){} 7172 7173function tokenDifference(str1, str2) { 7174 var values = '', 7175 tokens1 = str1.split(/\s+/), 7176 tokens2 = str2.split(/\s+/); 7177 7178 outer: 7179 for(var i = 0; i < tokens1.length; i++) { 7180 var token = tokens1[i]; 7181 for(var j = 0; j < tokens2.length; j++) { 7182 if(token == tokens2[j]) continue outer; 7183 } 7184 values += (values.length > 0 ? ' ' : '') + token; 7185 } 7186 return values; 7187} 7188 7189/** 7190 * @ngdoc provider 7191 * @name $controllerProvider 7192 * @description 7193 * The {@link ng.$controller $controller service} is used by Angular to create new 7194 * controllers. 7195 * 7196 * This provider allows controller registration via the 7197 * {@link ng.$controllerProvider#register register} method. 7198 */ 7199function $ControllerProvider() { 7200 var controllers = {}, 7201 CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 7202 7203 7204 /** 7205 * @ngdoc method 7206 * @name $controllerProvider#register 7207 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 7208 * the names and the values are the constructors. 7209 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 7210 * annotations in the array notation). 7211 */ 7212 this.register = function(name, constructor) { 7213 assertNotHasOwnProperty(name, 'controller'); 7214 if (isObject(name)) { 7215 extend(controllers, name); 7216 } else { 7217 controllers[name] = constructor; 7218 } 7219 }; 7220 7221 7222 this.$get = ['$injector', '$window', function($injector, $window) { 7223 7224 /** 7225 * @ngdoc service 7226 * @name $controller 7227 * @requires $injector 7228 * 7229 * @param {Function|string} constructor If called with a function then it's considered to be the 7230 * controller constructor function. Otherwise it's considered to be a string which is used 7231 * to retrieve the controller constructor using the following steps: 7232 * 7233 * * check if a controller with given name is registered via `$controllerProvider` 7234 * * check if evaluating the string on the current scope returns a constructor 7235 * * check `window[constructor]` on the global `window` object 7236 * 7237 * @param {Object} locals Injection locals for Controller. 7238 * @return {Object} Instance of given controller. 7239 * 7240 * @description 7241 * `$controller` service is responsible for instantiating controllers. 7242 * 7243 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 7244 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 7245 */ 7246 return function(expression, locals) { 7247 var instance, match, constructor, identifier; 7248 7249 if(isString(expression)) { 7250 match = expression.match(CNTRL_REG), 7251 constructor = match[1], 7252 identifier = match[3]; 7253 expression = controllers.hasOwnProperty(constructor) 7254 ? controllers[constructor] 7255 : getter(locals.$scope, constructor, true) || getter($window, constructor, true); 7256 7257 assertArgFn(expression, constructor, true); 7258 } 7259 7260 instance = $injector.instantiate(expression, locals); 7261 7262 if (identifier) { 7263 if (!(locals && typeof locals.$scope === 'object')) { 7264 throw minErr('$controller')('noscp', 7265 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 7266 constructor || expression.name, identifier); 7267 } 7268 7269 locals.$scope[identifier] = instance; 7270 } 7271 7272 return instance; 7273 }; 7274 }]; 7275} 7276 7277/** 7278 * @ngdoc service 7279 * @name $document 7280 * @requires $window 7281 * 7282 * @description 7283 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 7284 * 7285 * @example 7286 <example module="documentExample"> 7287 <file name="index.html">
7288 <div ng-controller="ExampleController"> 7289 <p>$document title: <b ng-bind="title"></b></p> 7290 <p>window.document title: <b ng-bind="windowTitle"></b></p> 7291 </div> 7292 </file> 7293 <file name="script.js"> 7294 angular.module('documentExample', []) 7295 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 7296 $scope.title = $document[0].title; 7297 $scope.windowTitle = angular.element(window.document)[0].title; 7298 }]); 7299 </file> 7300 </example> 7301 */ 7302function $DocumentProvider(){ 7303 this.$get = ['$window', function(window){ 7304 return jqLite(window.document); 7305 }]; 7306} 7307 7308/** 7309 * @ngdoc service 7310 * @name $exceptionHandler 7311 * @requires ng.$log 7312 * 7313 * @description 7314 * Any uncaught exception in angular expressions is delegated to this service. 7315 * The default implementation simply delegates to `$log.error` which logs it into 7316 * the browser console. 7317 * 7318 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 7319 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 7320 * 7321 * ## Example: 7322 * 7323 * ```js 7324 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { 7325 * return function (exception, cause) { 7326 * exception.message += ' (caused by "' + cause + '")'; 7327 * throw exception; 7328 * }; 7329 * }); 7330 * ``` 7331 * 7332 * This example will override the normal action of `$exceptionHandler`, to make angular 7333 * exceptions fail hard when they happen, instead of just logging to the console. 7334 * 7335 * @param {Error} exception Exception associated with the error. 7336 * @param {string=} cause optional information about the context in which 7337 * the error was thrown. 7338 * 7339 */ 7340function $ExceptionHandlerProvider() { 7341 this.$get = ['$log', function($log) { 7342 return function(exception, cause) { 7343 $log.error.apply($log, arguments); 7344 }; 7345 }]; 7346} 7347 7348/** 7349 * Parse headers into key value object 7350 * 7351 * @param {string} headers Raw headers as a string 7352 * @returns {Object} Parsed headers as key value object 7353 */ 7354function parseHeaders(headers) { 7355 var parsed = {}, key, val, i; 7356 7357 if (!headers) return parsed; 7358 7359 forEach(headers.split('\n'), function(line) { 7360 i = line.indexOf(':');
7361 key = lowercase(trim(line.substr(0, i))); 7362 val = trim(line.substr(i + 1)); 7363 7364 if (key) { 7365 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 7366 } 7367 }); 7368 7369 return parsed; 7370} 7371 7372 7373/** 7374 * Returns a function that provides access to parsed headers. 7375 * 7376 * Headers are lazy parsed when first requested. 7377 * @see parseHeaders 7378 * 7379 * @param {(string|Object)} headers Headers to provide access to. 7380 * @returns {function(string=)} Returns a getter function which if called with: 7381 * 7382 * - if called with single an argument returns a single header value or null 7383 * - if called with no arguments returns an object containing all headers. 7384 */ 7385function headersGetter(headers) { 7386 var headersObj = isObject(headers) ? headers : undefined; 7387 7388 return function(name) { 7389 if (!headersObj) headersObj = parseHeaders(headers); 7390 7391 if (name) { 7392 return headersObj[lowercase(name)] || null; 7393 } 7394 7395 return headersObj; 7396 }; 7397} 7398 7399 7400/** 7401 * Chain all given functions 7402 * 7403 * This function is used for both request and response transforming 7404 * 7405 * @param {*} data Data to transform. 7406 * @param {function(string=)} headers Http headers getter fn. 7407 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 7408 * @returns {*} Transformed data. 7409 */ 7410function transformData(data, headers, fns) { 7411 if (isFunction(fns)) 7412 return fns(data, headers); 7413 7414 forEach(fns, function(fn) { 7415 data = fn(data, headers); 7416 }); 7417 7418 return data; 7419} 7420 7421 7422function isSuccess(status) { 7423 return 200 <= status && status < 300; 7424} 7425 7426 7427/** 7428 * @ngdoc provider 7429 * @name $httpProvider 7430 * @description 7431 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 7432 * */ 7433function $HttpProvider() { 7434 var JSON_START = /^\s*(\[|\{[^\{])/, 7435 JSON_END = /[\}\]]\s*$/, 7436 PROTECTION_PREFIX = /^\)\]\}',?\n/, 7437 CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; 7438 7439 /** 7440 * @ngdoc property 7441 * @name $httpProvider#defaults 7442 * @description 7443 * 7444 * Object containing default values for all {@link ng.$http $http} requests. 7445 * 7446 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 7447 * Defaults value is `'XSRF-TOKEN'`. 7448 * 7449 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 7450 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 7451 * 7452 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 7453 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 7454 * setting default headers. 7455 * - **`defaults.headers.common`** 7456 * - **`defaults.headers.post`** 7457 * - **`defaults.headers.put`** 7458 * - **`defaults.headers.patch`** 7459 **/ 7460 var defaults = this.defaults = { 7461 // transform incoming response data 7462 transformResponse: [function(data) { 7463 if (isString(data)) { 7464 // strip json vulnerability protection prefix 7465 data = data.replace(PROTECTION_PREFIX, ''); 7466 if (JSON_START.test(data) && JSON_END.test(data)) 7467 data = fromJson(data); 7468 } 7469 return data; 7470 }], 7471 7472 // transform outgoing request data 7473 transformRequest: [function(d) { 7474 return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; 7475 }], 7476 7477 // default headers 7478 headers: { 7479 common: { 7480 'Accept': 'application/json, text/plain, */*' 7481 }, 7482 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 7483 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 7484 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 7485 }, 7486 7487 xsrfCookieName: 'XSRF-TOKEN', 7488 xsrfHeaderName: 'X-XSRF-TOKEN' 7489 }; 7490 7491 /** 7492 * Are ordered by request, i.e. they are applied in the same order as the 7493 * array, on request, but reverse order, on response. 7494 */ 7495 var interceptorFactories = this.interceptors = []; 7496 7497 /** 7498 * For historical reasons, response interceptors are ordered by the order in which 7499 * they are applied to the response. (This is the opposite of interceptorFactories) 7500 */ 7501 var responseInterceptorFactories = this.responseInterceptors = []; 7502 7503 this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', 7504 function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { 7505 7506 var defaultCache = $cacheFactory('$http'); 7507 7508 /** 7509 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 7510 * The reversal is needed so that we can build up the interception chain around the 7511 * server request. 7512 */ 7513 var reversedInterceptors = []; 7514
7515 forEach(interceptorFactories, function(interceptorFactory) { 7516 reversedInterceptors.unshift(isString(interceptorFactory) 7517 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 7518 }); 7519 7520 forEach(responseInterceptorFactories, function(interceptorFactory, index) { 7521 var responseFn = isString(interceptorFactory) 7522 ? $injector.get(interceptorFactory) 7523 : $injector.invoke(interceptorFactory); 7524 7525 /** 7526 * Response interceptors go before "around" interceptors (no real reason, just 7527 * had to pick one.) But they are already reversed, so we can't use unshift, hence 7528 * the splice. 7529 */ 7530 reversedInterceptors.splice(index, 0, { 7531 response: function(response) { 7532 return responseFn($q.when(response)); 7533 }, 7534 responseError: function(response) { 7535 return responseFn($q.reject(response)); 7536 } 7537 }); 7538 }); 7539 7540 7541 /** 7542 * @ngdoc service 7543 * @kind function 7544 * @name $http 7545 * @requires ng.$httpBackend 7546 * @requires $cacheFactory 7547 * @requires $rootScope 7548 * @requires $q 7549 * @requires $injector 7550 * 7551 * @description 7552 * The `$http` service is a core Angular service that facilitates communication with the remote 7553 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 7554 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 7555 * 7556 * For unit testing applications that use `$http` service, see 7557 * {@link ngMock.$httpBackend $httpBackend mock}. 7558 * 7559 * For a higher level of abstraction, please check out the {@link ngResource.$resource 7560 * $resource} service. 7561 * 7562 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 7563 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 7564 * it is important to familiarize yourself with these APIs and the guarantees they provide. 7565 * 7566 * 7567 * # General usage 7568 * The `$http` service is a function which takes a single argument â a configuration object â 7569 * that is used to generate an HTTP request and returns a {@link ng.$q promise} 7570 * with two $http specific methods: `success` and `error`. 7571 * 7572 * ```js 7573 * $http({method: 'GET', url: '/someUrl'}). 7574 * success(function(data, status, headers, config) { 7575 * // this callback will be called asynchronously 7576 * // when the response is available 7577 * }). 7578 * error(function(data, status, headers, config) { 7579 * // called asynchronously if an error occurs 7580 * // or server returns response with an error status. 7581 * }); 7582 * ``` 7583 * 7584 * Since the returned value of calling the $http function is a `promise`, you can also use 7585 * the `then` method to register callbacks, and these callbacks will receive a single argument â 7586 * an object representing the response. See the API signature and type info below for more 7587 * details. 7588 * 7589 * A response status code between 200 and 299 is considered a success status and 7590 * will result in the success callback being called. Note that if the response is a redirect, 7591 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 7592 * called for such responses. 7593 * 7594 * # Writing Unit Tests that use $http 7595 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 7596 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 7597 * request using trained responses. 7598 * 7599 * ``` 7600 * $httpBackend.expectGET(...); 7601 * $http.get(...); 7602 * $httpBackend.flush(); 7603 * ``` 7604 * 7605 * # Shortcut methods 7606 * 7607 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 7608 * request data must be passed in for POST/PUT requests. 7609 * 7610 * ```js 7611 * $http.get('/someUrl').success(successCallback); 7612 * $http.post('/someUrl', data).success(successCallback); 7613 * ``` 7614 * 7615 * Complete list of shortcut methods: 7616 * 7617 * - {@link ng.$http#get $http.get} 7618 * - {@link ng.$http#head $http.head} 7619 * - {@link ng.$http#post $http.post} 7620 * - {@link ng.$http#put $http.put} 7621 * - {@link ng.$http#delete $http.delete} 7622 * - {@link ng.$http#jsonp $http.jsonp} 7623 * - {@link ng.$http#patch $http.patch} 7624 * 7625 * 7626 * # Setting HTTP Headers 7627 * 7628 * The $http service will automatically add certain HTTP headers to all requests. These defaults 7629 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 7630 * object, which currently contains this default configuration: 7631 * 7632 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 7633 * - `Accept: application/json, text/plain, * / *` 7634 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 7635 * - `Content-Type: application/json` 7636 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 7637 * - `Content-Type: application/json` 7638 *
7639 * To add or overwrite these defaults, simply add or remove a property from these configuration 7640 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 7641 * with the lowercased HTTP method name as the key, e.g. 7642 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. 7643 * 7644 * The defaults can also be set at runtime via the `$http.defaults` object in the same 7645 * fashion. For example: 7646 * 7647 * ``` 7648 * module.run(function($http) { 7649 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' 7650 * }); 7651 * ``` 7652 * 7653 * In addition, you can supply a `headers` property in the config object passed when 7654 * calling `$http(config)`, which overrides the defaults without changing them globally. 7655 * 7656 * 7657 * # Transforming Requests and Responses 7658 * 7659 * Both requests and responses can be transformed using transform functions. By default, Angular 7660 * applies these transformations: 7661 * 7662 * Request transformations: 7663 * 7664 * - If the `data` property of the request configuration object contains an object, serialize it 7665 * into JSON format. 7666 * 7667 * Response transformations: 7668 * 7669 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 7670 * - If JSON response is detected, deserialize it using a JSON parser. 7671 * 7672 * To globally augment or override the default transforms, modify the 7673 * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` 7674 * properties. These properties are by default an array of transform functions, which allows you 7675 * to `push` or `unshift` a new transformation function into the transformation chain. You can 7676 * also decide to completely override any default transformations by assigning your 7677 * transformation functions to these properties directly without the array wrapper. These defaults 7678 * are again available on the $http factory at run-time, which may be useful if you have run-time 7679 * services you wish to be involved in your transformations. 7680 * 7681 * Similarly, to locally override the request/response transforms, augment the 7682 * `transformRequest` and/or `transformResponse` properties of the configuration object passed 7683 * into `$http`. 7684 * 7685 * 7686 * # Caching 7687 * 7688 * To enable caching, set the request configuration `cache` property to `true` (to use default 7689 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 7690 * When the cache is enabled, `$http` stores the response from the server in the specified 7691 * cache. The next time the same request is made, the response is served from the cache without 7692 * sending a request to the server. 7693 * 7694 * Note that even if the response is served from cache, delivery of the data is asynchronous in 7695 * the same way that real requests are. 7696 * 7697 * If there are multiple GET requests for the same URL that should be cached using the same 7698 * cache, but the cache is not populated yet, only one request to the server will be made and 7699 * the remaining requests will be fulfilled using the response from the first request. 7700 * 7701 * You can change the default cache to a new object (built with 7702 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 7703 * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set 7704 * their `cache` property to `true` will now use this cache object. 7705 * 7706 * If you set the default cache to `false` then only requests that specify their own custom 7707 * cache object will be cached. 7708 * 7709 * # Interceptors 7710 * 7711 * Before you start creating interceptors, be sure to understand the 7712 * {@link ng.$q $q and deferred/promise APIs}. 7713 * 7714 * For purposes of global error handling, authentication, or any kind of synchronous or 7715 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 7716 * able to intercept requests before they are handed to the server and 7717 * responses before they are handed over to the application code that 7718 * initiated these requests. The interceptors leverage the {@link ng.$q 7719 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 7720 * 7721 * The interceptors are service factories that are registered with the `$httpProvider` by 7722 * adding them to the `$httpProvider.interceptors` array. The factory is called and 7723 * injected with dependencies (if specified) and returns the interceptor. 7724 * 7725 * There are two kinds of interceptors (and two kinds of rejection interceptors): 7726 * 7727 * * `request`: interceptors get called with a http `config` object. The function is free to 7728 * modify the `config` object or create a new one. The function needs to return the `config` 7729 * object directly, or a promise containing the `config` or a new `config` object. 7730 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 7731 * resolved with a rejection. 7732 * * `response`: interceptors get called with http `response` object. The function is free to 7733 * modify the `response` object or create a new one. The function needs to return the `response` 7734 * object directly, or as a promise containing the `response` or a new `response` object. 7735 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 7736 * resolved with a rejection. 7737 * 7738 * 7739 * ```js 7740 * // register the interceptor as a service 7741 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7742 * return { 7743 * // optional method 7744 * 'request': function(config) {
7745 * // do something on success 7746 * return config; 7747 * }, 7748 * 7749 * // optional method 7750 * 'requestError': function(rejection) { 7751 * // do something on error 7752 * if (canRecover(rejection)) { 7753 * return responseOrNewPromise 7754 * } 7755 * return $q.reject(rejection); 7756 * }, 7757 * 7758 * 7759 * 7760 * // optional method 7761 * 'response': function(response) { 7762 * // do something on success 7763 * return response; 7764 * }, 7765 * 7766 * // optional method 7767 * 'responseError': function(rejection) { 7768 * // do something on error 7769 * if (canRecover(rejection)) { 7770 * return responseOrNewPromise 7771 * } 7772 * return $q.reject(rejection); 7773 * } 7774 * }; 7775 * }); 7776 * 7777 * $httpProvider.interceptors.push('myHttpInterceptor'); 7778 * 7779 * 7780 * // alternatively, register the interceptor via an anonymous factory 7781 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 7782 * return { 7783 * 'request': function(config) { 7784 * // same as above 7785 * }, 7786 * 7787 * 'response': function(response) { 7788 * // same as above 7789 * } 7790 * }; 7791 * }); 7792 * ``` 7793 * 7794 * # Response interceptors (DEPRECATED) 7795 * 7796 * Before you start creating interceptors, be sure to understand the 7797 * {@link ng.$q $q and deferred/promise APIs}. 7798 * 7799 * For purposes of global error handling, authentication or any kind of synchronous or 7800 * asynchronous preprocessing of received responses, it is desirable to be able to intercept 7801 * responses for http requests before they are handed over to the application code that 7802 * initiated these requests. The response interceptors leverage the {@link ng.$q 7803 * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. 7804 * 7805 * The interceptors are service factories that are registered with the $httpProvider by 7806 * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and 7807 * injected with dependencies (if specified) and returns the interceptor â a function that 7808 * takes a {@link ng.$q promise} and returns the original or a new promise. 7809 * 7810 * ```js 7811 * // register the interceptor as a service 7812 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7813 * return function(promise) { 7814 * return promise.then(function(response) { 7815 * // do something on success 7816 * return response; 7817 * }, function(response) { 7818 * // do something on error 7819 * if (canRecover(response)) { 7820 * return responseOrNewPromise 7821 * } 7822 * return $q.reject(response); 7823 * }); 7824 * } 7825 * }); 7826 * 7827 * $httpProvider.responseInterceptors.push('myHttpInterceptor'); 7828 * 7829 * 7830 * // register the interceptor via an anonymous factory 7831 * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) { 7832 * return function(promise) { 7833 * // same as above 7834 * } 7835 * }); 7836 * ``` 7837 * 7838 * 7839 * # Security Considerations 7840 * 7841 * When designing web applications, consider security threats from: 7842 * 7843 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 7844 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 7845 * 7846 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 7847 * pre-configured with strategies that address these issues, but for this to work backend server 7848 * cooperation is required. 7849 * 7850 * ## JSON Vulnerability Protection 7851 * 7852 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 7853 * allows third party website to turn your JSON resource URL into 7854 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 7855 * counter this your server can prefix all JSON requests with following str
7855ing `")]}',\n"`. 7856 * Angular will automatically strip the prefix before processing it as JSON. 7857 * 7858 * For example if your server needs to return: 7859 * ```js 7860 * ['one','two'] 7861 * ``` 7862 * 7863 * which is vulnerable to attack, your server can return: 7864 * ```js 7865 * )]}', 7866 * ['one','two'] 7867 * ``` 7868 * 7869 * Angular will strip the prefix, before processing the JSON. 7870 * 7871 * 7872 * ## Cross Site Request Forgery (XSRF) Protection 7873 * 7874 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which 7875 * an unauthorized site can gain your user's private data. Angular provides a mechanism 7876 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 7877 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 7878 * JavaScript that runs on your domain could read the cookie, your server can be assured that 7879 * the XHR came from JavaScript running on your domain. The header will not be set for 7880 * cross-domain requests. 7881 * 7882 * To take advantage of this, your server needs to set a token in a JavaScript readable session 7883 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 7884 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 7885 * that only JavaScript running on your domain could have sent the request. The token must be 7886 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 7887 * making up its own tokens). We recommend that the token is a digest of your site's 7888 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 7889 * for added security. 7890 * 7891 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 7892 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 7893 * or the per-request config object. 7894 * 7895 * 7896 * @param {object} config Object describing the request to be made and how it should be 7897 * processed. The object has following properties: 7898 * 7899 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 7900 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 7901 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be turned 7902 * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be 7903 * JSONified. 7904 * - **data** â `{string|Object}` â Data to be sent as the request message data. 7905 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 7906 * HTTP headers to send to the server. If the return value of a function is null, the 7907 * header will not be sent. 7908 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 7909 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 7910 * - **transformRequest** â 7911 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 7912 * transform function or an array of such functions. The transform function takes the http 7913 * request body and headers and returns its transformed (typically serialized) version. 7914 * - **transformResponse** â 7915 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 7916 * transform function or an array of such functions. The transform function takes the http 7917 * response body and headers and returns its transformed (typically deserialized) version. 7918 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 7919 * GET request, otherwise if a cache instance built with 7920 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 7921 * caching. 7922 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 7923 * that should abort the request when resolved. 7924 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 7925 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 7926 * for more information. 7927 * - **responseType** - `{string}` - see 7928 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). 7929 * 7930 * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the 7931 * standard `then` method and two http specific methods: `success` and `error`. The `then` 7932 * method takes two arguments a success and an error callback which will be called with a 7933 * response object. The `success` and `error` methods take a single argument - a function that 7934 * will be called when the request succeeds or fails respectively. The arguments passed into 7935 * these functions are destructured representation of the response object passed into the 7936 * `then` method. The response object has these properties: 7937 * 7938 * - **data** â `{string|Object}` â The response body transformed with the transform 7939 * functions. 7940 * - **status** â `{number}` â HTTP status code of the response. 7941 * - **headers** â `{function([headerName])}` â Header getter function. 7942 * - **config** â `{Object}` â The configuration object that was used to generate the request. 7943 * - **statusText** â `{string}` â HTTP status text of the response. 7944 * 7945 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 7946 * requests. This is primarily meant to be used for debugging purposes. 7947 * 7948 * 7949 * @example 7950<example module="httpExample"> 7951<file name="index.html"> 7952 <div ng-controller="FetchController"> 7953 <select ng-model="method"> 7954 <option>GET</option> 7955 <option>JSONP</option> 7956 </select> 7957 <input type="text" ng-model="url" size="80"/> 7958 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 7959 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 7960 <button id="samplejsonpbtn" 7961 ng-click="updateModel('JSONP', 7962 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 7963 Sample JSONP 7964 </button> 7965 <button id="invalidjsonpbtn" 7966 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 7967 Invalid JSONP 7968 </button> 7969 <pre>http status code: {{status}}</pre> 7970 <pre>http response data: {{data}}</pre> 7971 </div> 7972</file> 7973<file name="script.js"> 7974 angular.module('httpExample', []) 7975 .controller('FetchController', ['$scope', '$http', '$templateCache', 7976 function($scope, $http, $templateCache) { 7977 $scope.method = 'GET'; 7978 $scope.url = 'http-hello.html'; 7979 7980 $scope.fetch = function() { 7981 $scope.code = null; 7982 $scope.response = null; 7983 7984 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 7985 success(function(data, status) { 7986 $scope.status = status; 7987 $scope.data = data; 7988 }). 7989 error(function(data, status) { 7990 $scope.data = data || "Request failed"; 7991 $scope.status = status; 7992 }); 7993 }; 7994 7995 $scope.updateModel = function(method, url) { 7996 $scope.method = method; 7997 $scope.url = url; 7998 }; 7999 }]); 8000</file> 8001<file name="http-hello.html"> 8002 Hello, $http! 8003</file> 8004<file name="protractor.js" type="protractor"> 8005 var status = element(by.binding('status')); 8006 var data = element(by.binding('data')); 8007 var fetchBtn = element(by.id('fetchbtn')); 8008 var sampleGetBtn = element(by.id('samplegetbtn')); 8009 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 8010 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 8011 8012 it('should make an xhr GET request', function() { 8013 sampleGetBtn.click(); 8014 fetchBtn.click(); 8015 expect(status.getText()).toMatch('200'); 8016 expect(data.getText()).toMatch(/Hello, \$http!/); 8017 }); 8018 8019 it('should make a JSONP request to angularjs.org', function() { 8020 sampleJsonpBtn.click(); 8021 fetchBtn.click(); 8022 expect(status.getText()).toMatch('200'); 8023 expect(data.getText()).toMatch(/Super Hero!/); 8024 }); 8025
8026 it('should make JSONP request to invalid URL and invoke the error handler', 8027 function() { 8028 invalidJsonpBtn.click(); 8029 fetchBtn.click(); 8030 expect(status.getText()).toMatch('0'); 8031 expect(data.getText()).toMatch('Request failed'); 8032 }); 8033</file> 8034</example> 8035 */ 8036 function $http(requestConfig) { 8037 var config = { 8038 method: 'get', 8039 transformRequest: defaults.transformRequest, 8040 transformResponse: defaults.transformResponse 8041 }; 8042 var headers = mergeHeaders(requestConfig); 8043 8044 extend(config, requestConfig); 8045 config.headers = headers; 8046 config.method = uppercase(config.method); 8047 8048 var serverRequest = function(config) { 8049 headers = config.headers; 8050 var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); 8051 8052 // strip content-type if data is undefined 8053 if (isUndefined(reqData)) { 8054 forEach(headers, function(value, header) { 8055 if (lowercase(header) === 'content-type') { 8056 delete headers[header]; 8057 } 8058 }); 8059 } 8060 8061 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 8062 config.withCredentials = defaults.withCredentials; 8063 } 8064 8065 // send request 8066 return sendReq(config, reqData, headers).then(transformResponse, transformResponse); 8067 }; 8068 8069 var chain = [serverRequest, undefined]; 8070 var promise = $q.when(config); 8071 8072 // apply interceptors 8073 forEach(reversedInterceptors, function(interceptor) { 8074 if (interceptor.request || interceptor.requestError) { 8075 chain.unshift(interceptor.request, interceptor.requestError); 8076 } 8077 if (interceptor.response || interceptor.responseError) { 8078 chain.push(interceptor.response, interceptor.responseError); 8079 } 8080 }); 8081 8082 while(chain.length) { 8083 var thenFn = chain.shift(); 8084 var rejectFn = chain.shift(); 8085 8086 promise = promise.then(thenFn, rejectFn); 8087 } 8088 8089 promise.success = function(fn) { 8090 promise.then(function(response) { 8091 fn(response.data, response.status, response.headers, config); 8092 }); 8093 return promise; 8094 }; 8095 8096 promise.error = function(fn) { 8097 promise.then(null, function(response) { 8098 fn(response.data, response.status, response.headers, config); 8099 }); 8100 return promise; 8101 }; 8102 8103 return promise; 8104 8105 function transformResponse(response) { 8106 // make a copy since the response must be cacheable 8107 var resp = extend({}, response, { 8108 data: transformData(response.data, response.headers, config.transformResponse) 8109 }); 8110 return (isSuccess(response.status)) 8111 ? resp 8112 : $q.reject(resp); 8113 } 8114 8115 function mergeHeaders(config) { 8116 var defHeaders = defaults.headers, 8117 reqHeaders = extend({}, config.headers), 8118 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 8119 8120 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 8121 8122 // using for-in instead of forEach to avoid unecessary iteration after header has been found 8123 defaultHeadersIteration: 8124 for (defHeaderName in defHeaders) { 8125 lowercaseDefHeaderName = lowercase(defHeaderName); 8126 8127 for (reqHeaderName in reqHeaders) { 8128 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 8129 continue defaultHeadersIteration; 8130 } 8131 } 8132 8133 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 8134 } 8135 8136 // execute if header value is a function for merged headers 8137 execHeaders(reqHeaders); 8138 return reqHeaders; 8139 8140 function execHeaders(headers) { 8141 var headerContent; 8142 8143 forEach(headers, function(headerFn, header) { 8144 if (isFunction(headerFn)) { 8145 headerContent = headerFn(); 8146 if (headerContent != null) { 8147 headers[header] = headerContent; 8148 } else { 8149 delete headers[header]; 8150 } 8151 } 8152 }); 8153 } 8154 } 8155 } 8156 8157 $http.pendingRequests = []; 8158 8159 /** 8160 * @ngdoc method 8161 * @name $http#get 8162 *
8163 * @description 8164 * Shortcut method to perform `GET` request. 8165 * 8166 * @param {string} url Relative or absolute URL specifying the destination of the request 8167 * @param {Object=} config Optional configuration object 8168 * @returns {HttpPromise} Future object 8169 */ 8170 8171 /** 8172 * @ngdoc method 8173 * @name $http#delete 8174 * 8175 * @description 8176 * Shortcut method to perform `DELETE` request. 8177 * 8178 * @param {string} url Relative or absolute URL specifying the destination of the request 8179 * @param {Object=} config Optional configuration object 8180 * @returns {HttpPromise} Future object 8181 */ 8182 8183 /** 8184 * @ngdoc method 8185 * @name $http#head 8186 * 8187 * @description 8188 * Shortcut method to perform `HEAD` request. 8189 * 8190 * @param {string} url Relative or absolute URL specifying the destination of the request 8191 * @param {Object=} config Optional configuration object 8192 * @returns {HttpPromise} Future object 8193 */ 8194 8195 /** 8196 * @ngdoc method 8197 * @name $http#jsonp 8198 * 8199 * @description 8200 * Shortcut method to perform `JSONP` request. 8201 * 8202 * @param {string} url Relative or absolute URL specifying the destination of the request. 8203 * The name of the callback should be the string `JSON_CALLBACK`. 8204 * @param {Object=} config Optional configuration object 8205 * @returns {HttpPromise} Future object 8206 */ 8207 createShortMethods('get', 'delete', 'head', 'jsonp'); 8208 8209 /** 8210 * @ngdoc method 8211 * @name $http#post 8212 * 8213 * @description 8214 * Shortcut method to perform `POST` request. 8215 * 8216 * @param {string} url Relative or absolute URL specifying the destination of the request 8217 * @param {*} data Request content 8218 * @param {Object=} config Optional configuration object 8219 * @returns {HttpPromise} Future object 8220 */ 8221 8222 /** 8223 * @ngdoc method 8224 * @name $http#put 8225 * 8226 * @description 8227 * Shortcut method to perform `PUT` request. 8228 * 8229 * @param {string} url Relative or absolute URL specifying the destination of the request 8230 * @param {*} data Request content 8231 * @param {Object=} config Optional configuration object 8232 * @returns {HttpPromise} Future object 8233 */ 8234 createShortMethodsWithData('post', 'put'); 8235 8236 /** 8237 * @ngdoc property 8238 * @name $http#defaults 8239 * 8240 * @description 8241 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 8242 * default headers, withCredentials as well as request and response transformations. 8243 * 8244 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 8245 */ 8246 $http.defaults = defaults; 8247 8248 8249 return $http; 8250 8251 8252 function createShortMethods(names) { 8253 forEach(arguments, function(name) { 8254 $http[name] = function(url, config) { 8255 return $http(extend(config || {}, { 8256 method: name, 8257 url: url 8258 })); 8259 }; 8260 }); 8261 } 8262 8263 8264 function createShortMethodsWithData(name) { 8265 forEach(arguments, function(name) { 8266 $http[name] = function(url, data, config) { 8267 return $http(extend(config || {}, { 8268 method: name, 8269 url: url, 8270 data: data 8271 })); 8272 }; 8273 }); 8274 } 8275 8276 8277 /** 8278 * Makes the request. 8279 * 8280 * !!! ACCESSES CLOSURE VARS: 8281 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 8282 */ 8283 function sendReq(config, reqData, reqHeaders) { 8284 var deferred = $q.defer(), 8285 promise = deferred.promise, 8286 cache, 8287 cachedResp, 8288 url = buildUrl(config.url, config.params); 8289 8290 $http.pendingRequests.push(config); 8291 promise.then(removePendingReq, removePendingReq); 8292 8293 8294 if ((config.cache || defaults.cache) && config.cache !== false && 8295 (config.method === 'GET' || config.method === 'JSONP')) { 8296 cache = isObject(config.cache) ? config.cache 8297 : isObject(defaults.cache) ? defaults.cache 8298 : defaultCache; 8299 } 8300 8301 if (cache) { 8302 cachedResp = cache.get(url); 8303 if (isDefined(cachedResp)) {
8304 if (isPromiseLike(cachedResp)) { 8305 // cached request has already been sent, but there is no response yet 8306 cachedResp.then(removePendingReq, removePendingReq); 8307 return cachedResp; 8308 } else { 8309 // serving from cache 8310 if (isArray(cachedResp)) { 8311 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 8312 } else { 8313 resolvePromise(cachedResp, 200, {}, 'OK'); 8314 } 8315 } 8316 } else { 8317 // put the promise for the non-transformed response into cache as a placeholder 8318 cache.put(url, promise); 8319 } 8320 } 8321 8322 8323 // if we won't have the response in cache, set the xsrf headers and 8324 // send the request to the backend 8325 if (isUndefined(cachedResp)) { 8326 var xsrfValue = urlIsSameOrigin(config.url) 8327 ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] 8328 : undefined; 8329 if (xsrfValue) { 8330 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 8331 } 8332 8333 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 8334 config.withCredentials, config.responseType); 8335 } 8336 8337 return promise; 8338 8339 8340 /** 8341 * Callback registered to $httpBackend(): 8342 * - caches the response if desired 8343 * - resolves the raw $http promise 8344 * - calls $apply 8345 */ 8346 function done(status, response, headersString, statusText) { 8347 if (cache) { 8348 if (isSuccess(status)) { 8349 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 8350 } else { 8351 // remove promise from the cache 8352 cache.remove(url); 8353 } 8354 } 8355 8356 resolvePromise(response, status, headersString, statusText); 8357 if (!$rootScope.$$phase) $rootScope.$apply(); 8358 } 8359 8360 8361 /** 8362 * Resolves the raw $http promise. 8363 */ 8364 function resolvePromise(response, status, headers, statusText) { 8365 // normalize internal statuses to 0 8366 status = Math.max(status, 0); 8367 8368 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 8369 data: response, 8370 status: status, 8371 headers: headersGetter(headers), 8372 config: config, 8373 statusText : statusText 8374 }); 8375 } 8376 8377 8378 function removePendingReq() { 8379 var idx = indexOf($http.pendingRequests, config); 8380 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 8381 } 8382 } 8383 8384 8385 function buildUrl(url, params) { 8386 if (!params) return url; 8387 var parts = []; 8388 forEachSorted(params, function(value, key) { 8389 if (value === null || isUndefined(value)) return; 8390 if (!isArray(value)) value = [value]; 8391 8392 forEach(value, function(v) { 8393 if (isObject(v)) { 8394 if (isDate(v)){ 8395 v = v.toISOString(); 8396 } else if (isObject(v)) { 8397 v = toJson(v); 8398 } 8399 } 8400 parts.push(encodeUriQuery(key) + '=' + 8401 encodeUriQuery(v)); 8402 }); 8403 }); 8404 if(parts.length > 0) { 8405 url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); 8406 } 8407 return url; 8408 } 8409 }]; 8410} 8411 8412function createXhr(method) { 8413 //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest 8414 //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest 8415 //if it is available 8416 if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) || 8417 !window.XMLHttpRequest)) { 8418 return new window.ActiveXObject("Microsoft.XMLHTTP"); 8419 } else if (window.XMLHttpRequest) { 8420 return new window.XMLHttpRequest(); 8421 } 8422 8423 throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); 8424} 8425 8426/** 8427 * @ngdoc service 8428 * @name $httpBackend 8429 * @requires $window 8430 * @requires $document 8431 * 8432 * @description 8433 * HTTP backend used by the {@link ng.$http service} that delegates to 8434 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 8435 * 8436 * You should never need to use this service directly, instead use the higher-level abstractions: 8437 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 8438 * 8439 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 8440 * $httpBackend} which can be trained with responses. 8441 */ 8442function $HttpBackendProvider() { 8443 this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { 8444 return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); 8445 }]; 8446} 8447 8448function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 8449 var ABORTED = -1; 8450 8451 // TODO(vojta): fix the signature 8452 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 8453 var status; 8454 $browser.$$incOutstandingRequestCount(); 8455 url = url || $browser.url(); 8456 8457 if (lowercase(method) == 'jsonp') { 8458 var callbackId = '_' + (callbacks.counter++).toString(36); 8459 callbacks[callbackId] = function(data) { 8460 callbacks[callbackId].data = data; 8461 callbacks[callbackId].called = true; 8462 }; 8463 8464 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 8465 callbackId, function(status, text) {
8466 completeRequest(callback, status, callbacks[callbackId].data, "", text); 8467 callbacks[callbackId] = noop; 8468 }); 8469 } else { 8470 8471 var xhr = createXhr(method); 8472 8473 xhr.open(method, url, true); 8474 forEach(headers, function(value, key) { 8475 if (isDefined(value)) { 8476 xhr.setRequestHeader(key, value); 8477 } 8478 }); 8479 8480 // In IE6 and 7, this might be called synchronously when xhr.send below is called and the 8481 // response is in the cache. the promise api will ensure that to the app code the api is 8482 // always async 8483 xhr.onreadystatechange = function() { 8484 // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by 8485 // xhrs that are resolved while the app is in the background (see #5426). 8486 // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before 8487 // continuing 8488 // 8489 // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and 8490 // Safari respectively. 8491 if (xhr && xhr.readyState == 4) { 8492 var responseHeaders = null, 8493 response = null, 8494 statusText = ''; 8495 8496 if(status !== ABORTED) { 8497 responseHeaders = xhr.getAllResponseHeaders(); 8498 8499 // responseText is the old-school way of retrieving response (supported by IE8 & 9) 8500 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 8501 response = ('response' in xhr) ? xhr.response : xhr.responseText; 8502 } 8503 8504 // Accessing statusText on an aborted xhr object will 8505 // throw an 'c00c023f error' in IE9 and lower, don't touch it. 8506 if (!(status === ABORTED && msie < 10)) { 8507 statusText = xhr.statusText; 8508 } 8509 8510 completeRequest(callback, 8511 status || xhr.status, 8512 response, 8513 responseHeaders, 8514 statusText); 8515 } 8516 }; 8517 8518 if (withCredentials) { 8519 xhr.withCredentials = true; 8520 } 8521 8522 if (responseType) { 8523 try { 8524 xhr.responseType = responseType; 8525 } catch (e) { 8526 // WebKit added support for the json responseType value on 09/03/2013 8527 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 8528 // known to throw when setting the value "json" as the response type. Other older 8529 // browsers implementing the responseType 8530 // 8531 // The json response type can be ignored if not supported, because JSON payloads are 8532 // parsed on the client-side regardless. 8533 if (responseType !== 'json') { 8534 throw e; 8535 } 8536 } 8537 } 8538 8539 xhr.send(post || null); 8540 } 8541 8542 if (timeout > 0) { 8543 var timeoutId = $browserDefer(timeoutRequest, timeout); 8544 } else if (isPromiseLike(timeout)) { 8545 timeout.then(timeoutRequest); 8546 } 8547 8548 8549 function timeoutRequest() { 8550 status = ABORTED; 8551 jsonpDone && jsonpDone(); 8552 xhr && xhr.abort(); 8553 } 8554 8555 function completeRequest(callback, status, response, headersString, statusText) { 8556 // cancel timeout and subsequent timeout promise resolution 8557 timeoutId && $browserDefer.cancel(timeoutId); 8558 jsonpDone = xhr = null; 8559 8560 // fix status code when it is 0 (0 status is undocumented). 8561 // Occurs when accessing file resources or on Android 4.1 stock browser 8562 // while retrieving files from application cache. 8563 if (status === 0) { 8564 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 8565 } 8566 8567 // normalize IE bug (http://bugs.jquery.com/ticket/1450) 8568 status = status === 1223 ? 204 : status; 8569 statusText = statusText || ''; 8570 8571 callback(status, response, headersString, statusText); 8572 $browser.$$completeOutstandingRequest(noop); 8573 } 8574 }; 8575 8576 function jsonpReq(url, callbackId, done) { 8577 // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: 8578 // - fetches local scripts via XHR and evals them 8579 // - adds and immediately removes script elements from the document 8580 var script = rawDocument.createElement('script'), callback = null; 8581 script.type = "text/javascript"; 8582 script.src = url; 8583 script.async = true; 8584 8585 callback = function(event) { 8586 removeEventListenerFn(script, "load", callback); 8587 removeEventListenerFn(script, "error", callback); 8588 rawDocument.body.removeChild(script); 8589 script = null; 8590 var status = -1; 8591 var text = "unknown"; 8592 8593 if (event) { 8594 if (event.type === "load" && !callbacks[callbackId].called) { 8595 event = { type: "error" }; 8596 } 8597 text = event.type; 8598 status = event.type === "error" ? 404 : 200; 8599 } 8600 8601 if (done) { 8602 done(status, text); 8603 } 8604 }; 8605 8606 addEventListenerFn(script, "load", callback); 8607 addEventListenerFn(script, "error", callback); 8608 8609 if (msie <= 8) { 8610 script.onreadystatechange = function() { 8611 if (isString(script.readyState) && /loaded|complete/.test(script.readyState)) { 8612 script.onreadystatechange = null; 8613 callback({ 8614 type: 'load' 8615 }); 8616 } 8617 }; 8618 } 8619 8620 rawDocument.body.appendChild(script); 8621 return callback; 8622 } 8623} 8624 8625var $interpolateMinErr = minErr('$interpolate'); 8626 8627/** 8628 * @ngdoc provider 8629 * @name $interpolateProvider 8630 * @kind function 8631 * 8632 * @description 8633 *
8634 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 8635 * 8636 * @example 8637<example module="customInterpolationApp"> 8638<file name="index.html"> 8639<script> 8640 var customInterpolationApp = angular.module('customInterpolationApp', []); 8641 8642 customInterpolationApp.config(function($interpolateProvider) { 8643 $interpolateProvider.startSymbol('//'); 8644 $interpolateProvider.endSymbol('//'); 8645 }); 8646 8647 8648 customInterpolationApp.controller('DemoController', function() { 8649 this.label = "This binding is brought you by // interpolation symbols."; 8650 }); 8651</script> 8652<div ng-app="App" ng-controller="DemoController as demo"> 8653 //demo.label// 8654</div> 8655</file> 8656<file name="protractor.js" type="protractor"> 8657 it('should interpolate binding with custom symbols', function() { 8658 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 8659 }); 8660</file> 8661</example> 8662 */ 8663function $InterpolateProvider() { 8664 var startSymbol = '{{'; 8665 var endSymbol = '}}'; 8666 8667 /** 8668 * @ngdoc method 8669 * @name $interpolateProvider#startSymbol 8670 * @description 8671 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 8672 * 8673 * @param {string=} value new value to set the starting symbol to. 8674 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8675 */ 8676 this.startSymbol = function(value){ 8677 if (value) { 8678 startSymbol = value; 8679 return this; 8680 } else { 8681 return startSymbol; 8682 } 8683 }; 8684 8685 /** 8686 * @ngdoc method 8687 * @name $interpolateProvider#endSymbol 8688 * @description 8689 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8690 * 8691 * @param {string=} value new value to set the ending symbol to. 8692 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8693 */ 8694 this.endSymbol = function(value){ 8695 if (value) { 8696 endSymbol = value; 8697 return this; 8698 } else { 8699 return endSymbol; 8700 } 8701 }; 8702 8703 8704 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 8705 var startSymbolLength = startSymbol.length, 8706 endSymbolLength = endSymbol.length; 8707 8708 /** 8709 * @ngdoc service 8710 * @name $interpolate 8711 * @kind function 8712 * 8713 * @requires $parse 8714 * @requires $sce 8715 * 8716 * @description 8717 * 8718 * Compiles a string with markup into an interpolation function. This service is used by the 8719 * HTML {@link ng.$compile $compile} service for data binding. See 8720 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 8721 * interpolation markup. 8722 * 8723 * 8724 * ```js 8725 * var $interpolate = ...; // injected 8726 * var exp = $interpolate('Hello {{name | uppercase}}!'); 8727 * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); 8728 * ``` 8729 * 8730 * 8731 * @param {string} text The text with markup to interpolate. 8732 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 8733 * embedded expression in order to return an interpolation function. Strings with no 8734 * embedded expression will return null for the interpolation function. 8735 * @param {string=} trustedContext when provided, the returned function passes the interpolated 8736 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 8737 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 8738 * provides Strict Contextual Escaping for details. 8739 * @returns {function(context)} an interpolation function which is used to compute the 8740 * interpolated string. The function has these parameters: 8741 * 8742 * * `context`: an object against which any expressions embedded in the strings are evaluated 8743 * against. 8744 * 8745 */ 8746 function $interpolate(text, mustHaveExpression, trustedContext) { 8747 var startIndex, 8748 endIndex, 8749 index = 0, 8750 parts = [], 8751 length = text.length, 8752 hasInterpolation = false, 8753 fn, 8754 exp, 8755 concat = []; 8756 8757 while(index < length) { 8758 if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && 8759 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { 8760 (index != startIndex) && parts.push(text.substring(index, startIndex)); 8761 parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); 8762 fn.exp = exp;
8763 index = endIndex + endSymbolLength; 8764 hasInterpolation = true; 8765 } else { 8766 // we did not find anything, so we have to add the remainder to the parts array 8767 (index != length) && parts.push(text.substring(index)); 8768 index = length; 8769 } 8770 } 8771 8772 if (!(length = parts.length)) { 8773 // we added, nothing, must have been an empty string. 8774 parts.push(''); 8775 length = 1; 8776 } 8777 8778 // Concatenating expressions makes it hard to reason about whether some combination of 8779 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 8780 // single expression be used for iframe[src], object[src], etc., we ensure that the value 8781 // that's used is assigned or constructed by some JS code somewhere that is more testable or 8782 // make it obvious that you bound the value to some user controlled value. This helps reduce 8783 // the load when auditing for XSS issues. 8784 if (trustedContext && parts.length > 1) { 8785 throw $interpolateMinErr('noconcat', 8786 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 8787 "interpolations that concatenate multiple expressions when a trusted value is " + 8788 "required. See http://docs.angularjs.org/api/ng.$sce", text); 8789 } 8790 8791 if (!mustHaveExpression || hasInterpolation) { 8792 concat.length = length; 8793 fn = function(context) { 8794 try { 8795 for(var i = 0, ii = length, part; i<ii; i++) { 8796 if (typeof (part = parts[i]) == 'function') { 8797 part = part(context); 8798 if (trustedContext) { 8799 part = $sce.getTrusted(trustedContext, part); 8800 } else { 8801 part = $sce.valueOf(part); 8802 } 8803 if (part == null) { // null || undefined 8804 part = ''; 8805 } else { 8806 switch (typeof part) { 8807 case 'string': 8808 { 8809 break; 8810 } 8811 case 'number': 8812 { 8813 part = '' + part; 8814 break; 8815 } 8816 default: 8817 { 8818 part = toJson(part); 8819 } 8820 } 8821 } 8822 } 8823 concat[i] = part; 8824 } 8825 return concat.join(''); 8826 } 8827 catch(err) { 8828 var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, 8829 err.toString()); 8830 $exceptionHandler(newErr); 8831 } 8832 }; 8833 fn.exp = text; 8834 fn.parts = parts; 8835 return fn; 8836 } 8837 } 8838 8839 8840 /** 8841 * @ngdoc method 8842 * @name $interpolate#startSymbol 8843 * @description 8844 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 8845 * 8846 * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change 8847 * the symbol. 8848 * 8849 * @returns {string} start symbol. 8850 */ 8851 $interpolate.startSymbol = function() { 8852 return startSymbol; 8853 }; 8854 8855 8856 /** 8857 * @ngdoc method 8858 * @name $interpolate#endSymbol 8859 * @description 8860 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8861 * 8862 * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change 8863 * the symbol. 8864 * 8865 * @returns {string} end symbol. 8866 */ 8867 $interpolate.endSymbol = function() { 8868 return endSymbol; 8869 }; 8870 8871 return $interpolate; 8872 }]; 8873} 8874 8875function $IntervalProvider() { 8876 this.$get = ['$rootScope', '$window', '$q', 8877 function($rootScope, $window, $q) { 8878 var intervals = {}; 8879 8880 8881 /** 8882 * @ngdoc service 8883 * @name $interval 8884 * 8885 * @description 8886 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 8887 * milliseconds. 8888 * 8889 * The return value of registering an interval function is a promise. This promise will be 8890 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 8891 * run indefinitely if `count` is not defined. The value of the notification will be the 8892 * number of iterations that have run. 8893 * To cancel an interval, call `$interval.cancel(promise)`. 8894 * 8895 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 8896 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 8897 * time. 8898 * 8899 * <div class="alert alert-warning">
8900 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 8901 * with them. In particular they are not automatically destroyed when a controller's scope or a 8902 * directive's element are destroyed. 8903 * You should take this into consideration and make sure to always cancel the interval at the 8904 * appropriate moment. See the example below for more details on how and when to do this. 8905 * </div> 8906 * 8907 * @param {function()} fn A function that should be called repeatedly. 8908 * @param {number} delay Number of milliseconds between each function call. 8909 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 8910 * indefinitely. 8911 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 8912 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 8913 * @returns {promise} A promise which will be notified on each iteration. 8914 * 8915 * @example 8916 * <example module="intervalExample"> 8917 * <file name="index.html"> 8918 * <script> 8919 * angular.module('intervalExample', []) 8920 * .controller('ExampleController', ['$scope', '$interval', 8921 * function($scope, $interval) { 8922 * $scope.format = 'M/d/yy h:mm:ss a'; 8923 * $scope.blood_1 = 100; 8924 * $scope.blood_2 = 120; 8925 * 8926 * var stop; 8927 * $scope.fight = function() { 8928 * // Don't start a new fight if we are already fighting 8929 * if ( angular.isDefined(stop) ) return; 8930 * 8931 * stop = $interval(function() { 8932 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 8933 * $scope.blood_1 = $scope.blood_1 - 3; 8934 * $scope.blood_2 = $scope.blood_2 - 4; 8935 * } else { 8936 * $scope.stopFight(); 8937 * } 8938 * }, 100); 8939 * }; 8940 * 8941 * $scope.stopFight = function() { 8942 * if (angular.isDefined(stop)) { 8943 * $interval.cancel(stop); 8944 * stop = undefined; 8945 * } 8946 * }; 8947 * 8948 * $scope.resetFight = function() { 8949 * $scope.blood_1 = 100; 8950 * $scope.blood_2 = 120; 8951 * }; 8952 * 8953 * $scope.$on('$destroy', function() { 8954 * // Make sure that the interval nis destroyed too 8955 * $scope.stopFight(); 8956 * }); 8957 * }]) 8958 * // Register the 'myCurrentTime' directive factory method. 8959 * // We inject $interval and dateFilter service since the factory method is DI. 8960 * .directive('myCurrentTime', ['$interval', 'dateFilter', 8961 * function($interval, dateFilter) { 8962 * // return the directive link function. (compile function not needed) 8963 * return function(scope, element, attrs) { 8964 * var format, // date format 8965 * stopTime; // so that we can cancel the time updates 8966 * 8967 * // used to update the UI 8968 * function updateTime() { 8969 * element.text(dateFilter(new Date(), format)); 8970 * } 8971 * 8972 * // watch the expression, and update the UI on change. 8973 * scope.$watch(attrs.myCurrentTime, function(value) { 8974 * format = value; 8975 * updateTime(); 8976 * }); 8977 * 8978 * stopTime = $interval(updateTime, 1000); 8979 * 8980 * // listen on DOM destroy (removal) event, and cancel the next UI update 8981 * // to prevent updating time after the DOM element was removed. 8982 * element.bind('$destroy', function() { 8983 * $interval.cancel(stopTime); 8984 * }); 8985 * } 8986 * }]); 8987 * </script> 8988 * 8989 * <div>
8990 * <div ng-controller="ExampleController"> 8991 * Date format: <input ng-model="format"> <hr/> 8992 * Current time is: <span my-current-time="format"></span> 8993 * <hr/> 8994 * Blood 1 : <font color='red'>{{blood_1}}</font> 8995 * Blood 2 : <font color='red'>{{blood_2}}</font> 8996 * <button type="button" data-ng-click="fight()">Fight</button> 8997 * <button type="button" data-ng-click="stopFight()">StopFight</button> 8998 * <button type="button" data-ng-click="resetFight()">resetFight</button> 8999 * </div> 9000 * </div> 9001 * 9002 * </file> 9003 * </example> 9004 */ 9005 function interval(fn, delay, count, invokeApply) { 9006 var setInterval = $window.setInterval, 9007 clearInterval = $window.clearInterval, 9008 deferred = $q.defer(), 9009 promise = deferred.promise, 9010 iteration = 0, 9011 skipApply = (isDefined(invokeApply) && !invokeApply); 9012 9013 count = isDefined(count) ? count : 0; 9014 9015 promise.then(null, null, fn); 9016 9017 promise.$$intervalId = setInterval(function tick() { 9018 deferred.notify(iteration++); 9019 9020 if (count > 0 && iteration >= count) { 9021 deferred.resolve(iteration); 9022 clearInterval(promise.$$intervalId); 9023 delete intervals[promise.$$intervalId]; 9024 } 9025 9026 if (!skipApply) $rootScope.$apply(); 9027 9028 }, delay); 9029 9030 intervals[promise.$$intervalId] = deferred; 9031 9032 return promise; 9033 } 9034 9035 9036 /** 9037 * @ngdoc method 9038 * @name $interval#cancel 9039 * 9040 * @description 9041 * Cancels a task associated with the `promise`. 9042 * 9043 * @param {promise} promise returned by the `$interval` function. 9044 * @returns {boolean} Returns `true` if the task was successfully canceled. 9045 */ 9046 interval.cancel = function(promise) { 9047 if (promise && promise.$$intervalId in intervals) { 9048 intervals[promise.$$intervalId].reject('canceled'); 9049 $window.clearInterval(promise.$$intervalId); 9050 delete intervals[promise.$$intervalId]; 9051 return true; 9052 } 9053 return false; 9054 }; 9055 9056 return interval; 9057 }]; 9058} 9059 9060/** 9061 * @ngdoc service 9062 * @name $locale 9063 * 9064 * @description 9065 * $locale service provides localization rules for various Angular components. As of right now the 9066 * only public api is: 9067 * 9068 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 9069 */ 9070function $LocaleProvider(){ 9071 this.$get = function() { 9072 return { 9073 id: 'en-us', 9074 9075 NUMBER_FORMATS: { 9076 DECIMAL_SEP: '.', 9077 GROUP_SEP: ',', 9078 PATTERNS: [ 9079 { // Decimal Pattern 9080 minInt: 1, 9081 minFrac: 0, 9082 maxFrac: 3, 9083 posPre: '', 9084 posSuf: '', 9085 negPre: '-', 9086 negSuf: '', 9087 gSize: 3, 9088 lgSize: 3 9089 },{ //Currency Pattern 9090 minInt: 1, 9091 minFrac: 2, 9092 maxFrac: 2, 9093 posPre: '\u00A4', 9094 posSuf: '', 9095 negPre: '(\u00A4', 9096 negSuf: ')', 9097 gSize: 3, 9098 lgSize: 3 9099 } 9100 ], 9101 CURRENCY_SYM: '$' 9102 }, 9103 9104 DATETIME_FORMATS: { 9105 MONTH: 9106 'January,February,March,April,May,June,July,August,September,October,November,December' 9107 .split(','), 9108 SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), 9109 DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), 9110 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), 9111 AMPMS: ['AM','PM'], 9112 medium: 'MMM d, y h:mm:ss a', 9113 short: 'M/d/yy h:mm a', 9114 fullDate: 'EEEE, MMMM d, y', 9115 longDate: 'MMMM d, y', 9116 mediumDate: 'MMM d, y', 9117 shortDate: 'M/d/yy', 9118 mediumTime: 'h:mm:ss a', 9119 shortTime: 'h:mm a' 9120 }, 9121 9122 pluralCat: function(num) { 9123 if (num === 1) { 9124 return 'one'; 9125 } 9126 return 'other'; 9127 } 9128 }; 9129 }; 9130} 9131 9132var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 9133 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 9134var $locationMinErr = minErr('$location'); 9135 9136 9137/** 9138 * Encode path using encodeUriSegment, ignoring forward slashes 9139 * 9140 * @param {string} path Path to encode 9141 * @returns {string} 9142 */ 9143function encodePath(path) { 9144 var segments = path.split('/'), 9145 i = segments.length; 9146 9147 while (i--) { 9148 segments[i] = encodeUriSegment(segments[i]); 9149 } 9150 9151 return segments.join('/'); 9152} 9153 9154function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
9155 var parsedUrl = urlResolve(absoluteUrl, appBase); 9156 9157 locationObj.$$protocol = parsedUrl.protocol; 9158 locationObj.$$host = parsedUrl.hostname; 9159 locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 9160} 9161 9162 9163function parseAppUrl(relativeUrl, locationObj, appBase) { 9164 var prefixed = (relativeUrl.charAt(0) !== '/'); 9165 if (prefixed) { 9166 relativeUrl = '/' + relativeUrl; 9167 } 9168 var match = urlResolve(relativeUrl, appBase); 9169 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 9170 match.pathname.substring(1) : match.pathname); 9171 locationObj.$$search = parseKeyValue(match.search); 9172 locationObj.$$hash = decodeURIComponent(match.hash); 9173 9174 // make sure path starts with '/'; 9175 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 9176 locationObj.$$path = '/' + locationObj.$$path; 9177 } 9178} 9179 9180 9181/** 9182 * 9183 * @param {string} begin 9184 * @param {string} whole 9185 * @returns {string} returns text from whole after begin or undefined if it does not begin with 9186 * expected string. 9187 */ 9188function beginsWith(begin, whole) { 9189 if (whole.indexOf(begin) === 0) { 9190 return whole.substr(begin.length); 9191 } 9192} 9193 9194 9195function stripHash(url) { 9196 var index = url.indexOf('#'); 9197 return index == -1 ? url : url.substr(0, index); 9198} 9199 9200 9201function stripFile(url) { 9202 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 9203} 9204 9205/* return the server only (scheme://host:port) */ 9206function serverBase(url) { 9207 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 9208} 9209 9210 9211/** 9212 * LocationHtml5Url represents an url 9213 * This object is exposed as $location service when HTML5 mode is enabled and supported 9214 * 9215 * @constructor 9216 * @param {string} appBase application base URL 9217 * @param {string} basePrefix url path prefix 9218 */ 9219function LocationHtml5Url(appBase, basePrefix) { 9220 this.$$html5 = true; 9221 basePrefix = basePrefix || ''; 9222 var appBaseNoFile = stripFile(appBase); 9223 parseAbsoluteUrl(appBase, this, appBase); 9224 9225 9226 /** 9227 * Parse given html5 (regular) url string into properties 9228 * @param {string} newAbsoluteUrl HTML5 url 9229 * @private 9230 */ 9231 this.$$parse = function(url) { 9232 var pathUrl = beginsWith(appBaseNoFile, url); 9233 if (!isString(pathUrl)) { 9234 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 9235 appBaseNoFile); 9236 } 9237 9238 parseAppUrl(pathUrl, this, appBase); 9239 9240 if (!this.$$path) { 9241 this.$$path = '/'; 9242 } 9243 9244 this.$$compose(); 9245 }; 9246 9247 /** 9248 * Compose url and update `absUrl` property 9249 * @private 9250 */ 9251 this.$$compose = function() { 9252 var search = toKeyValue(this.$$search), 9253 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9254 9255 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9256 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 9257 }; 9258 9259 this.$$rewrite = function(url) { 9260 var appUrl, prevAppUrl; 9261 9262 if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { 9263 prevAppUrl = appUrl; 9264 if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { 9265 return appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 9266 } else { 9267 return appBase + prevAppUrl; 9268 } 9269 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { 9270 return appBaseNoFile + appUrl; 9271 } else if (appBaseNoFile == url + '/') { 9272 return appBaseNoFile; 9273 } 9274 }; 9275} 9276 9277 9278/** 9279 * LocationHashbangUrl represents url 9280 * This object is exposed as $location service when developer doesn't opt into html5 mode. 9281 * It also serves as the base class for html5 mode fallback on legacy browsers. 9282 * 9283 * @constructor 9284 * @param {string} appBase application base URL 9285 * @param {string} hashPrefix hashbang prefix 9286 */ 9287function LocationHashbangUrl(appBase, hashPrefix) { 9288 var appBaseNoFile = stripFile(appBase); 9289 9290 parseAbsoluteUrl(appBase, this, appBase); 9291 9292 9293 /** 9294 * Parse given hashbang url into properties 9295 * @param {string} url Hashbang url 9296 * @private 9297 */ 9298 this.$$parse = function(url) { 9299 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 9300 var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' 9301 ? beginsWith(hashPrefix, withoutBaseUrl) 9302 : (this.$$html5) 9303 ? withoutBaseUrl 9304 : ''; 9305 9306 if (!isString(withoutHashUrl)) { 9307 throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, 9308 hashPrefix); 9309 } 9310 parseAppUrl(withoutHashUrl, this, appBase); 9311 9312 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 9313 9314 this.$$compose(); 9315 9316 /* 9317 * In Windows, on an anchor node on documents loaded from 9318 * the filesystem, the browser will return a pathname
9319 * prefixed with the drive name ('/C:/path') when a 9320 * pathname without a drive is set: 9321 * * a.setAttribute('href', '/foo') 9322 * * a.pathname === '/C:/foo' //true 9323 * 9324 * Inside of Angular, we're always using pathnames that 9325 * do not include drive names for routing. 9326 */ 9327 function removeWindowsDriveName (path, url, base) { 9328 /* 9329 Matches paths for file protocol on windows, 9330 such as /C:/foo/bar, and captures only /foo/bar. 9331 */ 9332 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 9333 9334 var firstPathSegmentMatch; 9335 9336 //Get the relative path from the input URL. 9337 if (url.indexOf(base) === 0) { 9338 url = url.replace(base, ''); 9339 } 9340 9341 // The input URL intentionally contains a first path segment that ends with a colon. 9342 if (windowsFilePathExp.exec(url)) { 9343 return path; 9344 } 9345 9346 firstPathSegmentMatch = windowsFilePathExp.exec(path); 9347 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 9348 } 9349 }; 9350 9351 /** 9352 * Compose hashbang url and update `absUrl` property 9353 * @private 9354 */ 9355 this.$$compose = function() { 9356 var search = toKeyValue(this.$$search), 9357 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9358 9359 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9360 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 9361 }; 9362 9363 this.$$rewrite = function(url) { 9364 if(stripHash(appBase) == stripHash(url)) { 9365 return url; 9366 } 9367 }; 9368} 9369 9370 9371/** 9372 * LocationHashbangUrl represents url 9373 * This object is exposed as $location service when html5 history api is enabled but the browser 9374 * does not support it. 9375 * 9376 * @constructor 9377 * @param {string} appBase application base URL 9378 * @param {string} hashPrefix hashbang prefix 9379 */ 9380function LocationHashbangInHtml5Url(appBase, hashPrefix) { 9381 this.$$html5 = true; 9382 LocationHashbangUrl.apply(this, arguments); 9383 9384 var appBaseNoFile = stripFile(appBase); 9385 9386 this.$$rewrite = function(url) { 9387 var appUrl; 9388 9389 if ( appBase == stripHash(url) ) { 9390 return url; 9391 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { 9392 return appBase + hashPrefix + appUrl; 9393 } else if ( appBaseNoFile === url + '/') { 9394 return appBaseNoFile; 9395 } 9396 }; 9397 9398 this.$$compose = function() { 9399 var search = toKeyValue(this.$$search), 9400 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9401 9402 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9403 // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#' 9404 this.$$absUrl = appBase + hashPrefix + this.$$url; 9405 }; 9406 9407} 9408 9409 9410LocationHashbangInHtml5Url.prototype = 9411 LocationHashbangUrl.prototype = 9412 LocationHtml5Url.prototype = { 9413 9414 /** 9415 * Are we in html5 mode? 9416 * @private 9417 */ 9418 $$html5: false, 9419 9420 /** 9421 * Has any change been replacing ? 9422 * @private 9423 */ 9424 $$replace: false, 9425 9426 /** 9427 * @ngdoc method 9428 * @name $location#absUrl 9429 * 9430 * @description 9431 * This method is getter only. 9432 * 9433 * Return full url representation with all segments encoded according to rules specified in 9434 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 9435 * 9436 * @return {string} full url 9437 */ 9438 absUrl: locationGetter('$$absUrl'), 9439 9440 /** 9441 * @ngdoc method 9442 * @name $location#url 9443 * 9444 * @description 9445 * This method is getter / setter. 9446 * 9447 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 9448 * 9449 * Change path, search and hash, when called with parameter and return `$location`. 9450 * 9451 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 9452 * @param {string=} replace The path that will be changed 9453 * @return {string} url 9454 */ 9455 url: function(url, replace) { 9456 if (isUndefined(url)) 9457 return this.$$url; 9458 9459 var match = PATH_MATCH.exec(url); 9460 if (match[1]) this.path(decodeURIComponent(match[1])); 9461 if (match[2] || match[1]) this.search(match[3] || ''); 9462 this.hash(match[5] || '', replace); 9463 9464 return this; 9465 }, 9466 9467 /** 9468 * @ngdoc method 9469 * @name $location#protocol 9470 * 9471 * @description 9472 * This method is getter only. 9473 * 9474 * Return protocol of current url. 9475 * 9476 * @return {string} protocol of current url 9477 */ 9478 protocol: locationGetter('$$protocol'), 9479 9480 /** 9481 * @ngdoc method 9482 * @name $location#host 9483 * 9484 * @description 9485 * This method is getter only. 9486 * 9487 * Return host of current url. 9488 * 9489 * @return {string} host of current url. 9490 */ 9491 host: locationGetter('$$host'), 9492 9493 /** 9494 * @ngdoc method 9495 * @name $location#port 9496 * 9497 * @description 9498 * This method is getter only. 9499 * 9500 * Return port of current url. 9501 * 9502 * @return {Number} port 9503 */ 9504 port: locationGetter('$$port'), 9505 9506 /** 9507 * @ngdoc method 9508 * @name $location#path 9509 * 9510 * @description 9511 * This method is getter / setter. 9512 * 9513 * Return path of current url when called without any parameter. 9514 * 9515 * Change path when called with parameter and return `$location`. 9516 * 9517 * Note: Path should always begin with forward slash (/), this method will add the forward slash 9518 * if it is missing. 9519 * 9520 * @param {string=} path New path 9521 * @return {string} path 9522 */ 9523 path: locationGetterSetter('$$path', function(path) { 9524 return path.charAt(0) == '/' ? path : '/' + path; 9525 }), 9526 9527 /** 9528 * @ngdoc method 9529 * @name $location#search 9530 * 9531 * @description 9532 * This method is getter / setter. 9533 * 9534 * Return search part (as object) of current url when called without any parameter. 9535 * 9536 * Change search part when called with parameter and return `$location`. 9537 * 9538 * 9539 * ```js
9540 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 9541 * var searchObject = $location.search(); 9542 * // => {foo: 'bar', baz: 'xoxo'} 9543 * 9544 * 9545 * // set foo to 'yipee' 9546 * $location.search('foo', 'yipee'); 9547 * // => $location 9548 * ``` 9549 * 9550 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 9551 * hash object. 9552 * 9553 * When called with a single argument the method acts as a setter, setting the `search` component 9554 * of `$location` to the specified value. 9555 * 9556 * If the argument is a hash object containing an array of values, these values will be encoded 9557 * as duplicate search parameters in the url. 9558 * 9559 * @param {(string|Array<string>|boolean)=} paramValue If `search` is a string, then `paramValue` 9560 * will override only a single search property. 9561 * 9562 * If `paramValue` is an array, it will override the property of the `search` component of 9563 * `$location` specified via the first argument. 9564 * 9565 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 9566 * 9567 * If `paramValue` is `true`, the property specified via the first argument will be added with no 9568 * value nor trailing equal sign. 9569 * 9570 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 9571 * one or more arguments returns `$location` object itself. 9572 */ 9573 search: function(search, paramValue) { 9574 switch (arguments.length) { 9575 case 0: 9576 return this.$$search; 9577 case 1: 9578 if (isString(search)) { 9579 this.$$search = parseKeyValue(search); 9580 } else if (isObject(search)) { 9581 // remove object undefined or null properties 9582 forEach(search, function(value, key) { 9583 if (value == null) delete search[key]; 9584 }); 9585 9586 this.$$search = search; 9587 } else { 9588 throw $locationMinErr('isrcharg', 9589 'The first argument of the `$location#search()` call must be a string or an object.'); 9590 } 9591 break; 9592 default: 9593 if (isUndefined(paramValue) || paramValue === null) { 9594 delete this.$$search[search]; 9595 } else { 9596 this.$$search[search] = paramValue; 9597 } 9598 } 9599 9600 this.$$compose(); 9601 return this; 9602 }, 9603 9604 /** 9605 * @ngdoc method 9606 * @name $location#hash 9607 * 9608 * @description 9609 * This method is getter / setter. 9610 * 9611 * Return hash fragment when called without any parameter. 9612 * 9613 * Change hash fragment when called with parameter and return `$location`. 9614 * 9615 * @param {string=} hash New hash fragment 9616 * @return {string} hash 9617 */ 9618 hash: locationGetterSetter('$$hash', identity), 9619 9620 /** 9621 * @ngdoc method 9622 * @name $location#replace 9623 * 9624 * @description 9625 * If called, all changes to $location during current `$digest` will be replacing current history 9626 * record, instead of adding new one. 9627 */ 9628 replace: function() { 9629 this.$$replace = true; 9630 return this; 9631 } 9632}; 9633 9634function locationGetter(property) { 9635 return function() { 9636 return this[property]; 9637 }; 9638} 9639 9640 9641function locationGetterSetter(property, preprocess) { 9642 return function(value) { 9643 if (isUndefined(value)) 9644 return this[property]; 9645 9646 this[property] = preprocess(value); 9647 this.$$compose(); 9648 9649 return this; 9650 }; 9651} 9652 9653 9654/** 9655 * @ngdoc service 9656 * @name $location 9657 * 9658 * @requires $rootElement 9659 * 9660 * @description 9661 * The $location service parses the URL in the browser address bar (based on the 9662 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 9663 * available to your application. Changes to the URL in the address bar are reflected into 9664 * $location service and changes to $location are reflected into the browser address bar. 9665 * 9666 * **The $location service:** 9667 * 9668 * - Exposes the current URL in the browser address bar, so you can 9669 * - Watch and observe the URL. 9670 * - Change the URL. 9671 * - Synchronizes the URL with the browser when the user 9672 * - Changes the address bar. 9673 * - Clicks the back or forward button (or clicks a History link). 9674 * - Clicks on a link. 9675 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 9676 * 9677 * For more information see {@link guide/$location Developer Guide: Using $location} 9678 */ 9679
9680/** 9681 * @ngdoc provider 9682 * @name $locationProvider 9683 * @description 9684 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 9685 */ 9686function $LocationProvider(){ 9687 var hashPrefix = '', 9688 html5Mode = false; 9689 9690 /** 9691 * @ngdoc method 9692 * @name $locationProvider#hashPrefix 9693 * @description 9694 * @param {string=} prefix Prefix for hash part (containing path and search) 9695 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9696 */ 9697 this.hashPrefix = function(prefix) { 9698 if (isDefined(prefix)) { 9699 hashPrefix = prefix; 9700 return this; 9701 } else { 9702 return hashPrefix; 9703 } 9704 }; 9705 9706 /** 9707 * @ngdoc method 9708 * @name $locationProvider#html5Mode 9709 * @description 9710 * @param {boolean=} mode Use HTML5 strategy if available. 9711 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9712 */ 9713 this.html5Mode = function(mode) { 9714 if (isDefined(mode)) { 9715 html5Mode = mode; 9716 return this; 9717 } else { 9718 return html5Mode; 9719 } 9720 }; 9721 9722 /** 9723 * @ngdoc event 9724 * @name $location#$locationChangeStart 9725 * @eventType broadcast on root scope 9726 * @description 9727 * Broadcasted before a URL will change. This change can be prevented by calling 9728 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 9729 * details about event object. Upon successful change 9730 * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. 9731 * 9732 * @param {Object} angularEvent Synthetic event object. 9733 * @param {string} newUrl New URL 9734 * @param {string=} oldUrl URL that was before it was changed. 9735 */ 9736 9737 /** 9738 * @ngdoc event 9739 * @name $location#$locationChangeSuccess 9740 * @eventType broadcast on root scope 9741 * @description 9742 * Broadcasted after a URL was changed. 9743 * 9744 * @param {Object} angularEvent Synthetic event object. 9745 * @param {string} newUrl New URL 9746 * @param {string=} oldUrl URL that was before it was changed. 9747 */ 9748 9749 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', 9750 function( $rootScope, $browser, $sniffer, $rootElement) { 9751 var $location, 9752 LocationMode, 9753 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 9754 initialUrl = $browser.url(), 9755 appBase; 9756 9757 if (html5Mode) { 9758 appBase = serverBase(initialUrl) + (baseHref || '/'); 9759 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 9760 } else { 9761 appBase = stripHash(initialUrl); 9762 LocationMode = LocationHashbangUrl; 9763 } 9764 $location = new LocationMode(appBase, '#' + hashPrefix); 9765 $location.$$parse($location.$$rewrite(initialUrl)); 9766 9767 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 9768 9769 $rootElement.on('click', function(event) { 9770 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 9771 // currently we open nice url link and redirect then 9772 9773 if (event.ctrlKey || event.metaKey || event.which == 2) return; 9774 9775 var elm = jqLite(event.target); 9776 9777 // traverse the DOM up to find first A tag 9778 while (lowercase(elm[0].nodeName) !== 'a') { 9779 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 9780 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 9781 } 9782 9783 var absHref = elm.prop('href'); 9784 9785 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 9786 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 9787 // an animation. 9788 absHref = urlResolve(absHref.animVal).href; 9789 } 9790 9791 // Ignore when url is started with javascript: or mailto: 9792 if (IGNORE_URI_REGEXP.test(absHref)) return; 9793 9794 // Make relative links work in HTML5 mode for legacy browsers (or at least IE8 & 9) 9795 // The href should be a regular url e.g. /link/somewhere or link/somewhere or ../somewhere or 9796 // somewhere#anchor or http://example.com/somewhere 9797 if (LocationMode === LocationHashbangInHtml5Url) { 9798 // get the actual href attribute - see
9799 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 9800 var href = elm.attr('href') || elm.attr('xlink:href'); 9801 9802 if (href.indexOf('://') < 0) { // Ignore absolute URLs 9803 var prefix = '#' + hashPrefix; 9804 if (href[0] == '/') { 9805 // absolute path - replace old path 9806 absHref = appBase + prefix + href; 9807 } else if (href[0] == '#') { 9808 // local anchor 9809 absHref = appBase + prefix + ($location.path() || '/') + href; 9810 } else { 9811 // relative path - join with current path 9812 var stack = $location.path().split("/"), 9813 parts = href.split("/"); 9814 for (var i=0; i<parts.length; i++) { 9815 if (parts[i] == ".") 9816 continue; 9817 else if (parts[i] == "..") 9818 stack.pop(); 9819 else if (parts[i].length) 9820 stack.push(parts[i]); 9821 } 9822 absHref = appBase + prefix + stack.join('/'); 9823 } 9824 } 9825 } 9826 9827 var rewrittenUrl = $location.$$rewrite(absHref); 9828 9829 if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) { 9830 event.preventDefault(); 9831 if (rewrittenUrl != $browser.url()) { 9832 // update location manually 9833 $location.$$parse(rewrittenUrl); 9834 $rootScope.$apply(); 9835 // hack to work around FF6 bug 684208 when scenario runner clicks on links 9836 window.angular['ff-684208-preventDefault'] = true; 9837 } 9838 } 9839 }); 9840 9841 9842 // rewrite hashbang url <> html5 url 9843 if ($location.absUrl() != initialUrl) { 9844 $browser.url($location.absUrl(), true); 9845 } 9846 9847 // update $location when $browser url changes 9848 $browser.onUrlChange(function(newUrl) { 9849 if ($location.absUrl() != newUrl) { 9850 $rootScope.$evalAsync(function() { 9851 var oldUrl = $location.absUrl(); 9852 9853 $location.$$parse(newUrl); 9854 if ($rootScope.$broadcast('$locationChangeStart', newUrl, 9855 oldUrl).defaultPrevented) { 9856 $location.$$parse(oldUrl); 9857 $browser.url(oldUrl); 9858 } else { 9859 afterLocationChange(oldUrl); 9860 } 9861 }); 9862 if (!$rootScope.$$phase) $rootScope.$digest(); 9863 } 9864 }); 9865 9866 // update browser 9867 var changeCounter = 0; 9868 $rootScope.$watch(function $locationWatch() { 9869 var oldUrl = $browser.url(); 9870 var currentReplace = $location.$$replace; 9871 9872 if (!changeCounter || oldUrl != $location.absUrl()) { 9873 changeCounter++; 9874 $rootScope.$evalAsync(function() { 9875 if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). 9876 defaultPrevented) { 9877 $location.$$parse(oldUrl); 9878 } else { 9879 $browser.url($location.absUrl(), currentReplace); 9880 afterLocationChange(oldUrl); 9881 } 9882 }); 9883 } 9884 $location.$$replace = false; 9885 9886 return changeCounter; 9887 }); 9888 9889 return $location; 9890 9891 function afterLocationChange(oldUrl) { 9892 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); 9893 } 9894}]; 9895} 9896 9897/** 9898 * @ngdoc service 9899 * @name $log 9900 * @requires $window 9901 * 9902 * @description 9903 * Simple service for logging. Default implementation safely writes the message 9904 * into the browser's console (if present). 9905 * 9906 * The main purpose of this service is to simplify debugging and troubleshooting. 9907 * 9908 * The default is to log `debug` messages. You can use 9909 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 9910 * 9911 * @example 9912 <example module="logExample"> 9913 <file name="script.js"> 9914 angular.module('logExample', []) 9915 .controller('LogController', ['$scope', '$log', function($scope, $log) { 9916 $scope.$log = $log; 9917 $scope.message = 'Hello World!'; 9918 }]); 9919 </file> 9920 <file name="index.html"> 9921 <div ng-controller="LogController"> 9922 <p>Reload this page with open console, enter text and hit the log button...</p> 9923 Message: 9924 <input type="text" ng-model="message"/> 9925 <button ng-click="$log.log(message)">log</button> 9926 <button ng-click="$log.warn(message)">warn</button> 9927 <button ng-click="$log.info(message)">info</button> 9928 <button ng-click="$log.error(message)">error</button> 9929 </div> 9930 </file> 9931 </example> 9932 */ 9933 9934/** 9935 * @ngdoc provider 9936 * @name $logProvider 9937 * @description 9938 * Use the `$logProvider` to configure how the application logs messages 9939 */ 9940function $LogProvider(){ 9941 var debug = true, 9942 self = this; 9943 9944 /** 9945 * @ngdoc method 9946 * @name $logProvider#debugEnabled 9947 * @description 9948 * @param {boolean=} flag enable or disable debug level messages 9949 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9950 */ 9951 this.debugEnabled = function(flag) { 9952 if (isDefined(flag)) { 9953 debug = flag; 9954 return this; 9955 } else { 9956 return debug; 9957 } 9958 }; 9959 9960 this.$get = ['$window', function($window){ 9961 return { 9962 /** 9963 * @ngdoc method 9964 * @name $log#log 9965 *
9966 * @description 9967 * Write a log message 9968 */ 9969 log: consoleLog('log'), 9970 9971 /** 9972 * @ngdoc method 9973 * @name $log#info 9974 * 9975 * @description 9976 * Write an information message 9977 */ 9978 info: consoleLog('info'), 9979 9980 /** 9981 * @ngdoc method 9982 * @name $log#warn 9983 * 9984 * @description 9985 * Write a warning message 9986 */ 9987 warn: consoleLog('warn'), 9988 9989 /** 9990 * @ngdoc method 9991 * @name $log#error 9992 * 9993 * @description 9994 * Write an error message 9995 */ 9996 error: consoleLog('error'), 9997 9998 /** 9999 * @ngdoc method 10000 * @name $log#debug 10001 * 10002 * @description 10003 * Write a debug message 10004 */ 10005 debug: (function () { 10006 var fn = consoleLog('debug'); 10007 10008 return function() { 10009 if (debug) { 10010 fn.apply(self, arguments); 10011 } 10012 }; 10013 }()) 10014 }; 10015 10016 function formatError(arg) { 10017 if (arg instanceof Error) { 10018 if (arg.stack) { 10019 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 10020 ? 'Error: ' + arg.message + '\n' + arg.stack 10021 : arg.stack; 10022 } else if (arg.sourceURL) { 10023 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 10024 } 10025 } 10026 return arg; 10027 } 10028 10029 function consoleLog(type) { 10030 var console = $window.console || {}, 10031 logFn = console[type] || console.log || noop, 10032 hasApply = false; 10033 10034 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 10035 // The reason behind this is that console.log has type "object" in IE8... 10036 try { 10037 hasApply = !!logFn.apply; 10038 } catch (e) {} 10039 10040 if (hasApply) { 10041 return function() { 10042 var args = []; 10043 forEach(arguments, function(arg) { 10044 args.push(formatError(arg)); 10045 }); 10046 return logFn.apply(console, args); 10047 }; 10048 } 10049 10050 // we are IE which either doesn't have window.console => this is noop and we do nothing, 10051 // or we are IE where console.log doesn't have apply so we log at least first 2 args 10052 return function(arg1, arg2) { 10053 logFn(arg1, arg2 == null ? '' : arg2); 10054 }; 10055 } 10056 }]; 10057} 10058 10059var $parseMinErr = minErr('$parse'); 10060var promiseWarningCache = {}; 10061var promiseWarning; 10062 10063// Sandboxing Angular Expressions 10064// ------------------------------ 10065// Angular expressions are generally considered safe because these expressions only have direct 10066// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by 10067// obtaining a reference to native JS functions such as the Function constructor. 10068// 10069// As an example, consider the following Angular expression: 10070// 10071// {}.toString.constructor('alert("evil JS code")') 10072// 10073// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 10074// against the expression language, but not to prevent exploits that were enabled by exposing 10075// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good 10076// practice and therefore we are not even trying to protect against interaction with an object 10077// explicitly exposed in this way. 10078// 10079// In general, it is not possible to access a Window object from an angular expression unless a 10080// window or some DOM object that has a reference to window is published onto a Scope. 10081// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 10082// native objects. 10083 10084 10085function ensureSafeMemberName(name, fullExpression) { 10086 if (name === "__defineGetter__" || name === "__defineSetter__" 10087 || name === "__lookupGetter__" || name === "__lookupSetter__" 10088 || name === "__proto__") { 10089 throw $parseMinErr('isecfld', 10090 'Attempting to access a disallowed field in Angular expressions! ' 10091 +'Expression: {0}', fullExpression); 10092 } 10093 return name; 10094} 10095 10096function ensureSafeObject(obj, fullExpression) { 10097 // nifty check if obj is Function that is fast and works across iframes and other contexts 10098 if (obj) { 10099 if (obj.constructor === obj) { 10100 throw $parseMinErr('isecfn', 10101 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 10102 fullExpression); 10103 } else if (// isWindow(obj) 10104 obj.document && obj.location && obj.alert && obj.setInterval) { 10105 throw $parseMinErr('isecwindow', 10106 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 10107 fullExpression); 10108 } else if (// isElement(obj) 10109 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 10110 throw $parseMinErr('isecdom', 10111 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 10112 fullExpression); 10113 } else if (// block Object so that we can't get hold of dangerous Object.* methods 10114 obj === Object) { 10115 throw $parseMinErr('isecobj', 10116 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 10117 fullExpression); 10118 } 10119 } 10120 return obj; 10121} 10122 10123var CALL = Function.prototype.call; 10124var APPLY = Function.prototype.apply; 10125var BIND = Function.prototype.bind; 10126 10127function ensureSafeFunction(obj, fullExpression) { 10128 if (obj) { 10129 if (obj.constructor === obj) { 10130 throw $parseMinErr('isecfn', 10131 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 10132 fullExpression); 10133 } else if (obj === CALL || obj === APPLY || (BIND && obj === BIND)
10133) { 10134 throw $parseMinErr('isecff', 10135 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 10136 fullExpression); 10137 } 10138 } 10139} 10140 10141var OPERATORS = { 10142 /* jshint bitwise : false */ 10143 'null':function(){return null;}, 10144 'true':function(){return true;}, 10145 'false':function(){return false;}, 10146 undefined:noop, 10147 '+':function(self, locals, a,b){ 10148 a=a(self, locals); b=b(self, locals); 10149 if (isDefined(a)) { 10150 if (isDefined(b)) { 10151 return a + b; 10152 } 10153 return a; 10154 } 10155 return isDefined(b)?b:undefined;}, 10156 '-':function(self, locals, a,b){ 10157 a=a(self, locals); b=b(self, locals); 10158 return (isDefined(a)?a:0)-(isDefined(b)?b:0); 10159 }, 10160 '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, 10161 '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, 10162 '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, 10163 '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, 10164 '=':noop, 10165 '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, 10166 '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, 10167 '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, 10168 '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, 10169 '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);}, 10170 '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, 10171 '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, 10172 '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, 10173 '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, 10174 '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, 10175 '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, 10176// '|':function(self, locals, a,b){return a|b;}, 10177 '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, 10178 '!':function(self, locals, a){return !a(self, locals);} 10179}; 10180/* jshint bitwise: true */ 10181var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 10182 10183 10184///////////////////////////////////////// 10185 10186 10187/** 10188 * @constructor 10189 */ 10190var Lexer = function (options) { 10191 this.options = options; 10192}; 10193 10194Lexer.prototype = { 10195 constructor: Lexer, 10196 10197 lex: function (text) { 10198 this.text = text; 10199 10200 this.index = 0; 10201 this.ch = undefined; 10202 this.lastCh = ':'; // can start regexp 10203 10204 this.tokens = []; 10205 10206 while (this.index < this.text.length) { 10207 this.ch = this.text.charAt(this.index); 10208 if (this.is('"\'')) { 10209 this.readString(this.ch); 10210 } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { 10211 this.readNumber(); 10212 } else if (this.isIdent(this.ch)) { 10213 this.readIdent(); 10214 } else if (this.is('(){}[].,;:?')) { 10215 this.tokens.push({ 10216 index: this.index, 10217 text: this.ch 10218 }); 10219 this.index++; 10220 } else if (this.isWhitespace(this.ch)) { 10221 this.index++; 10222 continue; 10223 } else { 10224 var ch2 = this.ch + this.peek(); 10225 var ch3 = ch2 + this.peek(2); 10226 var fn = OPERATORS[this.ch]; 10227 var fn2 = OPERATORS[ch2]; 10228 var fn3 = OPERATORS[ch3]; 10229 if (fn3) { 10230 this.tokens.push({index: this.index, text: ch3, fn: fn3}); 10231 this.index += 3; 10232 } else if (fn2) { 10233 this.tokens.push({index: this.index, text: ch2, fn: fn2}); 10234 this.index += 2; 10235 } else if (fn) { 10236 this.tokens.push({ 10237 index: this.index, 10238 text: this.ch, 10239 fn: fn 10240 }); 10241 this.index += 1; 10242 } else { 10243 this.throwError('Unexpected next character ', this.index, this.index + 1); 10244 } 10245 } 10246 this.lastCh = this.ch; 10247 } 10248 return this.tokens; 10249 }, 10250 10251 is: function(chars) { 10252 return chars.indexOf(this.ch) !== -1; 10253 }, 10254 10255 was: function(chars) { 10256 return chars.indexOf(this.lastCh) !== -1; 10257 }, 10258 10259 peek: function(i) { 10260 var num = i || 1; 10261 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false;
10262 }, 10263 10264 isNumber: function(ch) { 10265 return ('0' <= ch && ch <= '9'); 10266 }, 10267 10268 isWhitespace: function(ch) { 10269 // IE treats non-breaking space as \u00A0 10270 return (ch === ' ' || ch === '\r' || ch === '\t' || 10271 ch === '\n' || ch === '\v' || ch === '\u00A0'); 10272 }, 10273 10274 isIdent: function(ch) { 10275 return ('a' <= ch && ch <= 'z' || 10276 'A' <= ch && ch <= 'Z' || 10277 '_' === ch || ch === '$'); 10278 }, 10279 10280 isExpOperator: function(ch) { 10281 return (ch === '-' || ch === '+' || this.isNumber(ch)); 10282 }, 10283 10284 throwError: function(error, start, end) { 10285 end = end || this.index; 10286 var colStr = (isDefined(start) 10287 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 10288 : ' ' + end); 10289 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 10290 error, colStr, this.text); 10291 }, 10292 10293 readNumber: function() { 10294 var number = ''; 10295 var start = this.index; 10296 while (this.index < this.text.length) { 10297 var ch = lowercase(this.text.charAt(this.index)); 10298 if (ch == '.' || this.isNumber(ch)) { 10299 number += ch; 10300 } else { 10301 var peekCh = this.peek(); 10302 if (ch == 'e' && this.isExpOperator(peekCh)) { 10303 number += ch; 10304 } else if (this.isExpOperator(ch) && 10305 peekCh && this.isNumber(peekCh) && 10306 number.charAt(number.length - 1) == 'e') { 10307 number += ch; 10308 } else if (this.isExpOperator(ch) && 10309 (!peekCh || !this.isNumber(peekCh)) && 10310 number.charAt(number.length - 1) == 'e') { 10311 this.throwError('Invalid exponent'); 10312 } else { 10313 break; 10314 } 10315 } 10316 this.index++; 10317 } 10318 number = 1 * number; 10319 this.tokens.push({ 10320 index: start, 10321 text: number, 10322 literal: true, 10323 constant: true, 10324 fn: function() { return number; } 10325 }); 10326 }, 10327 10328 readIdent: function() { 10329 var parser = this; 10330 10331 var ident = ''; 10332 var start = this.index; 10333 10334 var lastDot, peekIndex, methodName, ch; 10335 10336 while (this.index < this.text.length) { 10337 ch = this.text.charAt(this.index); 10338 if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { 10339 if (ch === '.') lastDot = this.index; 10340 ident += ch; 10341 } else { 10342 break; 10343 } 10344 this.index++; 10345 } 10346 10347 //check if this is not a method invocation and if it is back out to last dot 10348 if (lastDot) { 10349 peekIndex = this.index; 10350 while (peekIndex < this.text.length) { 10351 ch = this.text.charAt(peekIndex); 10352 if (ch === '(') { 10353 methodName = ident.substr(lastDot - start + 1); 10354 ident = ident.substr(0, lastDot - start); 10355 this.index = peekIndex; 10356 break; 10357 } 10358 if (this.isWhitespace(ch)) { 10359 peekIndex++; 10360 } else { 10361 break; 10362 } 10363 } 10364 } 10365 10366 10367 var token = { 10368 index: start, 10369 text: ident 10370 }; 10371 10372 // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn 10373 if (OPERATORS.hasOwnProperty(ident)) { 10374 token.fn = OPERATORS[ident]; 10375 token.literal = true; 10376 token.constant = true; 10377 } else { 10378 var getter = getterFn(ident, this.options, this.text); 10379 token.fn = extend(function(self, locals) { 10380 return (getter(self, locals)); 10381 }, { 10382 assign: function(self, value) { 10383 return setter(self, ident, value, parser.text, parser.options); 10384 } 10385 }); 10386 } 10387 10388 this.tokens.push(token); 10389 10390 if (methodName) { 10391 this.tokens.push({ 10392 index:lastDot, 10393 text: '.' 10394 }); 10395 this.tokens.push({ 10396 index: lastDot + 1, 10397 text: methodName 10398 }); 10399 } 10400 }, 10401 10402 readString: function(quote) { 10403 var start = this.index; 10404 this.index++; 10405 var string = ''; 10406 var rawString = quote; 10407 var escape = false;
10408 while (this.index < this.text.length) { 10409 var ch = this.text.charAt(this.index); 10410 rawString += ch; 10411 if (escape) { 10412 if (ch === 'u') { 10413 var hex = this.text.substring(this.index + 1, this.index + 5); 10414 if (!hex.match(/[\da-f]{4}/i)) 10415 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 10416 this.index += 4; 10417 string += String.fromCharCode(parseInt(hex, 16)); 10418 } else { 10419 var rep = ESCAPE[ch]; 10420 string = string + (rep || ch); 10421 } 10422 escape = false; 10423 } else if (ch === '\\') { 10424 escape = true; 10425 } else if (ch === quote) { 10426 this.index++; 10427 this.tokens.push({ 10428 index: start, 10429 text: rawString, 10430 string: string, 10431 literal: true, 10432 constant: true, 10433 fn: function() { return string; } 10434 }); 10435 return; 10436 } else { 10437 string += ch; 10438 } 10439 this.index++; 10440 } 10441 this.throwError('Unterminated quote', start); 10442 } 10443}; 10444 10445 10446/** 10447 * @constructor 10448 */ 10449var Parser = function (lexer, $filter, options) { 10450 this.lexer = lexer; 10451 this.$filter = $filter; 10452 this.options = options; 10453}; 10454 10455Parser.ZERO = extend(function () { 10456 return 0; 10457}, { 10458 constant: true 10459}); 10460 10461Parser.prototype = { 10462 constructor: Parser, 10463 10464 parse: function (text) { 10465 this.text = text; 10466 10467 this.tokens = this.lexer.lex(text); 10468 10469 var value = this.statements(); 10470 10471 if (this.tokens.length !== 0) { 10472 this.throwError('is an unexpected token', this.tokens[0]); 10473 } 10474 10475 value.literal = !!value.literal; 10476 value.constant = !!value.constant; 10477 10478 return value; 10479 }, 10480 10481 primary: function () { 10482 var primary; 10483 if (this.expect('(')) { 10484 primary = this.filterChain(); 10485 this.consume(')'); 10486 } else if (this.expect('[')) { 10487 primary = this.arrayDeclaration(); 10488 } else if (this.expect('{')) { 10489 primary = this.object(); 10490 } else { 10491 var token = this.expect(); 10492 primary = token.fn; 10493 if (!primary) { 10494 this.throwError('not a primary expression', token); 10495 } 10496 primary.literal = !!token.literal; 10497 primary.constant = !!token.constant; 10498 } 10499 10500 var next, context; 10501 while ((next = this.expect('(', '[', '.'))) { 10502 if (next.text === '(') { 10503 primary = this.functionCall(primary, context); 10504 context = null; 10505 } else if (next.text === '[') { 10506 context = primary; 10507 primary = this.objectIndex(primary); 10508 } else if (next.text === '.') { 10509 context = primary; 10510 primary = this.fieldAccess(primary); 10511 } else { 10512 this.throwError('IMPOSSIBLE'); 10513 } 10514 } 10515 return primary; 10516 }, 10517 10518 throwError: function(msg, token) { 10519 throw $parseMinErr('syntax', 10520 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 10521 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 10522 }, 10523 10524 peekToken: function() { 10525 if (this.tokens.length === 0) 10526 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 10527 return this.tokens[0]; 10528 }, 10529 10530 peek: function(e1, e2, e3, e4) { 10531 if (this.tokens.length > 0) { 10532 var token = this.tokens[0]; 10533 var t = token.text; 10534 if (t === e1 || t === e2 || t === e3 || t === e4 || 10535 (!e1 && !e2 && !e3 && !e4)) { 10536 return token; 10537 } 10538 } 10539 return false; 10540 }, 10541 10542 expect: function(e1, e2, e3, e4){ 10543 var token = this.peek(e1, e2, e3, e4); 10544 if (token) { 10545 this.tokens.shift(); 10546 return token; 10547 } 10548 return false; 10549 }, 10550 10551 consume: function(e1){ 10552 if (!this.expect(e1)) { 10553 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 10554 } 10555 }, 10556 10557 unaryFn: function(fn, right) { 10558 return extend(function(self, locals) { 10559 return fn(self, locals, right); 10560 }, { 10561 constant:right.constant 10562 }); 10563 }, 10564 10565 ternaryFn: function(left, middle, right){ 10566 return extend(function(self, locals){ 10567 return left(self, locals) ? middle(self, locals) : right(self, locals); 10568 }, {
10569 constant: left.constant && middle.constant && right.constant 10570 }); 10571 }, 10572 10573 binaryFn: function(left, fn, right) { 10574 return extend(function(self, locals) { 10575 return fn(self, locals, left, right); 10576 }, { 10577 constant:left.constant && right.constant 10578 }); 10579 }, 10580 10581 statements: function() { 10582 var statements = []; 10583 while (true) { 10584 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 10585 statements.push(this.filterChain()); 10586 if (!this.expect(';')) { 10587 // optimize for the common case where there is only one statement. 10588 // TODO(size): maybe we should not support multiple statements? 10589 return (statements.length === 1) 10590 ? statements[0] 10591 : function(self, locals) { 10592 var value; 10593 for (var i = 0; i < statements.length; i++) { 10594 var statement = statements[i]; 10595 if (statement) { 10596 value = statement(self, locals); 10597 } 10598 } 10599 return value; 10600 }; 10601 } 10602 } 10603 }, 10604 10605 filterChain: function() { 10606 var left = this.expression(); 10607 var token; 10608 while (true) { 10609 if ((token = this.expect('|'))) { 10610 left = this.binaryFn(left, token.fn, this.filter()); 10611 } else { 10612 return left; 10613 } 10614 } 10615 }, 10616 10617 filter: function() { 10618 var token = this.expect(); 10619 var fn = this.$filter(token.text); 10620 var argsFn = []; 10621 while (true) { 10622 if ((token = this.expect(':'))) { 10623 argsFn.push(this.expression()); 10624 } else { 10625 var fnInvoke = function(self, locals, input) { 10626 var args = [input]; 10627 for (var i = 0; i < argsFn.length; i++) { 10628 args.push(argsFn[i](self, locals)); 10629 } 10630 return fn.apply(self, args); 10631 }; 10632 return function() { 10633 return fnInvoke; 10634 }; 10635 } 10636 } 10637 }, 10638 10639 expression: function() { 10640 return this.assignment(); 10641 }, 10642 10643 assignment: function() { 10644 var left = this.ternary(); 10645 var right; 10646 var token; 10647 if ((token = this.expect('='))) { 10648 if (!left.assign) { 10649 this.throwError('implies assignment but [' + 10650 this.text.substring(0, token.index) + '] can not be assigned to', token); 10651 } 10652 right = this.ternary(); 10653 return function(scope, locals) { 10654 return left.assign(scope, right(scope, locals), locals); 10655 }; 10656 } 10657 return left; 10658 }, 10659 10660 ternary: function() { 10661 var left = this.logicalOR(); 10662 var middle; 10663 var token; 10664 if ((token = this.expect('?'))) { 10665 middle = this.assignment(); 10666 if ((token = this.expect(':'))) { 10667 return this.ternaryFn(left, middle, this.assignment()); 10668 } else { 10669 this.throwError('expected :', token); 10670 } 10671 } else { 10672 return left; 10673 } 10674 }, 10675 10676 logicalOR: function() { 10677 var left = this.logicalAND(); 10678 var token; 10679 while (true) { 10680 if ((token = this.expect('||'))) { 10681 left = this.binaryFn(left, token.fn, this.logicalAND()); 10682 } else { 10683 return left; 10684 } 10685 } 10686 }, 10687 10688 logicalAND: function() { 10689 var left = this.equality(); 10690 var token; 10691 if ((token = this.expect('&&'))) { 10692 left = this.binaryFn(left, token.fn, this.logicalAND()); 10693 } 10694 return left; 10695 }, 10696 10697 equality: function() { 10698 var left = this.relational(); 10699 var token; 10700 if ((token = this.expect('==','!=','===','!=='))) { 10701 left = this.binaryFn(left, token.fn, this.equality()); 10702 } 10703 return left; 10704 }, 10705 10706 relational: function() { 10707 var left = this.additive(); 10708 var token; 10709 if ((token = this.expect('<', '>', '<=', '>='))) { 10710 left = this.binaryFn(left, token.fn, this.relational()); 10711 } 10712 return left; 10713 }, 10714 10715 additive: function() { 10716 var left = this.multiplicative(); 10717 var token; 10718 while ((token = this.expect('+','-'))) { 10719 left = this.binaryFn(left, token.fn, this.multiplicative()); 10720 } 10721 return left; 10722 }, 10723 10724 multiplicative: function() { 10725 var left = this.unary(); 10726 var token; 10727 while ((token = this.expect('*','/','%'))) { 10728 left = this.binaryFn(left, token.fn, this.unary()); 10729 } 10730 return left; 10731 }, 10732 10733 unary: function() { 10734 var token; 10735 if (this.expect('+')) { 10736 return this.primary(); 10737 } else if ((token = this.expect('-'))) { 10738 return this.binaryFn(Parser.ZERO, token.fn, this.unary()); 10739 } else if ((token = this.expect('!'))) { 10740 return this.unaryFn(token.fn, this.unary()); 10741 } else { 10742 return this.primary(); 10743 } 10744 }, 10745 10746 fieldAccess: function(object) { 10747 var parser = this; 10748 var field = this.expect().text; 10749 var getter = getterFn(field, this.options, this.text); 10750 10751 return extend(function(scope, locals, self) { 10752 return getter(self || object(scope, locals)); 10753 }, {
10754 assign: function(scope, value, locals) { 10755 var o = object(scope, locals); 10756 if (!o) object.assign(scope, o = {}); 10757 return setter(o, field, value, parser.text, parser.options); 10758 } 10759 }); 10760 }, 10761 10762 objectIndex: function(obj) { 10763 var parser = this; 10764 10765 var indexFn = this.expression(); 10766 this.consume(']'); 10767 10768 return extend(function(self, locals) { 10769 var o = obj(self, locals), 10770 i = indexFn(self, locals), 10771 v, p; 10772 10773 ensureSafeMemberName(i, parser.text); 10774 if (!o) return undefined; 10775 v = ensureSafeObject(o[i], parser.text); 10776 if (v && v.then && parser.options.unwrapPromises) { 10777 p = v; 10778 if (!('$$v' in v)) { 10779 p.$$v = undefined; 10780 p.then(function(val) { p.$$v = val; }); 10781 } 10782 v = v.$$v; 10783 } 10784 return v; 10785 }, { 10786 assign: function(self, value, locals) { 10787 var key = ensureSafeMemberName(indexFn(self, locals), parser.text); 10788 // prevent overwriting of Function.constructor which would break ensureSafeObject check 10789 var o = ensureSafeObject(obj(self, locals), parser.text); 10790 if (!o) obj.assign(self, o = {}); 10791 return o[key] = value; 10792 } 10793 }); 10794 }, 10795 10796 functionCall: function(fn, contextGetter) { 10797 var argsFn = []; 10798 if (this.peekToken().text !== ')') { 10799 do { 10800 argsFn.push(this.expression()); 10801 } while (this.expect(',')); 10802 } 10803 this.consume(')'); 10804 10805 var parser = this; 10806 10807 return function(scope, locals) { 10808 var args = []; 10809 var context = contextGetter ? contextGetter(scope, locals) : scope; 10810 10811 for (var i = 0; i < argsFn.length; i++) { 10812 args.push(argsFn[i](scope, locals)); 10813 } 10814 var fnPtr = fn(scope, locals, context) || noop; 10815 10816 ensureSafeObject(context, parser.text); 10817 ensureSafeFunction(fnPtr, parser.text); 10818 10819 // IE stupidity! (IE doesn't have apply for some native functions) 10820 var v = fnPtr.apply 10821 ? fnPtr.apply(context, args) 10822 : fnPtr(args[0], args[1], args[2], args[3], args[4]); 10823 10824 return ensureSafeObject(v, parser.text); 10825 }; 10826 }, 10827 10828 // This is used with json array declaration 10829 arrayDeclaration: function () { 10830 var elementFns = []; 10831 var allConstant = true; 10832 if (this.peekToken().text !== ']') { 10833 do { 10834 if (this.peek(']')) { 10835 // Support trailing commas per ES5.1. 10836 break; 10837 } 10838 var elementFn = this.expression(); 10839 elementFns.push(elementFn); 10840 if (!elementFn.constant) { 10841 allConstant = false; 10842 } 10843 } while (this.expect(',')); 10844 } 10845 this.consume(']'); 10846 10847 return extend(function(self, locals) { 10848 var array = []; 10849 for (var i = 0; i < elementFns.length; i++) { 10850 array.push(elementFns[i](self, locals)); 10851 } 10852 return array; 10853 }, { 10854 literal: true, 10855 constant: allConstant 10856 }); 10857 }, 10858 10859 object: function () { 10860 var keyValues = []; 10861 var allConstant = true; 10862 if (this.peekToken().text !== '}') { 10863 do { 10864 if (this.peek('}')) { 10865 // Support trailing commas per ES5.1. 10866 break; 10867 } 10868 var token = this.expect(), 10869 key = token.string || token.text; 10870 this.consume(':'); 10871 var value = this.expression(); 10872 keyValues.push({key: key, value: value}); 10873 if (!value.constant) { 10874 allConstant = false; 10875 } 10876 } while (this.expect(',')); 10877 } 10878 this.consume('}'); 10879 10880 return extend(function(self, locals) { 10881 var object = {}; 10882 for (var i = 0; i < keyValues.length; i++) { 10883 var keyValue = keyValues[i]; 10884 object[keyValue.key] = keyValue.value(self, locals); 10885 } 10886 return object; 10887 }, { 10888 literal: true, 10889 constant: allConstant 10890 }); 10891 } 10892}; 10893 10894 10895////////////////////////////////////////////////// 10896// Parser helper functions 10897////////////////////////////////////////////////// 10898 10899function setter(obj, path, setValue, fullExp, options) { 10900 //needed? 10901 options = options || {}; 10902 10903 var element = path.split('.'), key; 10904 for (var i = 0; element.length > 1; i++) { 10905 key = ensureSafeMemberName(element.shift(), fullExp); 10906 var propertyObj = obj[key]; 10907 if (!propertyObj) { 10908 propertyObj = {}; 10909 obj[key] = propertyObj; 10910 } 10911 obj = propertyObj; 10912 if (obj.then && options.unwrapPromises) { 10913 promiseWarning(fullExp); 10914 if (!("$$v" in obj)) { 10915 (function(promise) { 10916 promise.then(function(val) { promise.$$v = val; }); } 10917 )(obj); 10918 } 10919 if (obj.$$v === undefined) { 10920 obj.$$v = {}; 10921 } 10922 obj = obj.$$v; 10923 } 10924 } 10925 key = ensureSafeMemberName(element.shift(), fullExp); 10926 ensureSafeObject(obj, fullExp); 10927 ensureSafeObject(obj[key], fullExp); 10928 obj[key] = setValue; 10929 return setValue; 10930} 10931 10932var getterFnCache = {}; 10933 10934/** 10935 * Implementation of the "Black Hole" variant from: 10936 * - http://jsperf.com/angularjs-parse-getter/4 10937 * - http://jsperf.com/path-evaluation-simplified/7 10938 */ 10939function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { 10940 ensureSafeMemberName(key0, fullExp); 10941 ensureSafeMemberName(key1, fullExp); 10942 ensureSafeMemberName(key2, fullExp); 10943 ensureSafeMemberName(key3, fullExp); 10944 ensureSafeMemberName(key4, fullExp); 10945 10946 return !options.unwrapPromises 10947 ? function cspSafeGetter(scope, locals) { 10948 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; 10949 10950 if (pathVal == null) return pathVal; 10951 pathVal = pathVal[key0]; 10952 10953 if (!key1) return pathVal; 10954 if (pathVal == null) return undefined; 10955 pathVal = pathVal[key1]; 10956 10957 if (!key2) return pathVal; 10958 if (pathVal == null) return undefined; 10959 pathVal = pathVal[key2]; 10960 10961 if (!key3) return pathVal; 10962 if (pathVal == null) return undefined; 10963 pathVal = pathVal[key3]; 10964 10965 if (!key4) return pathVal; 10966 if (pathVal == null) return undefined; 10967 pathVal = pathVal[key4]; 10968 10969 return pathVal; 10970 } 10971 : function cspSafePromiseEnabledGetter(scope, locals) { 10972 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, 10973 promise; 10974 10975 if (pathVal == null) return pathVal; 10976 10977 pathVal = pathVal[key0]; 10978 if (pathVal && pathVal.then) { 10979 promiseWarning(fullExp); 10980 if (!("$$v" in pathVal)) { 10981 promise = pathVal; 10982 promise.$$v = undefined; 10983 promise.then(function(val) { promise.$$v = val; }); 10984 } 10985 pathVal = pathVal.$$v; 10986 } 10987 10988 if (!key1) return pathVal; 10989 if (pathVal == null) return undefined; 10990 pathVal = pathVal[key1]; 10991 if (pathVal && pathVal.then) { 10992 promiseWarning(fullExp); 10993 if (!("$$v" in pathVal)) { 10994 promise = pathVal; 10995 promise.$$v = undefined; 10996 promise.then(function(val) { promise.$$v = val; }); 10997 } 10998 pathVal = pathVal.$$v; 10999 } 11000 11001 if (!key2) return pathVal; 11002 if (pathVal == null) return undefined; 11003 pathVal = pathVal[key2]; 11004 if (pathVal && pathVal.then) { 11005 promiseWarning(fullExp); 11006 if (!("$$v" in pathVal)) { 11007 promise = pathVal; 11008 promise.$$v = undefined; 11009 promise.then(function(val) { promise.$$v = val; }); 11010 } 11011 pathVal = pathVal.$$v; 11012 } 11013 11014 if (!key3) return pathVal; 11015 if (pathVal == null) return undefined; 11016 pathVal = pathVal[key3]; 11017 if (pathVal && pathVal.then) { 11018 promiseWarning(fullExp); 11019 if (!("$$v" in pathVal)) { 11020 promise = pathVal; 11021 promise.$$v = undefined; 11022 promise.then(function(val) { promise.$$v = val; }); 11023 } 11024 pathVal = pathVal.$$v; 11025 } 11026 11027 if (!key4) return pathVal; 11028 if (pathVal == null) return undefined; 11029 pathVal = pathVal[key4]; 11030 if (pathVal && pathVal.then) { 11031 promiseWarning(fullExp); 11032 if (!("$$v" in pathVal)) { 11033 promise = pathVal; 11034 promise.$$v = undefined; 11035 promise.then(function(val) { promise.$$v = val; }); 11036 } 11037 pathVal = pathVal.$$v; 11038 } 11039 return pathVal; 11040 }; 11041} 11042 11043function getterFn(path, options, fullExp) { 11044 // Check whether the cache has this getter already. 11045 // We can use hasOwnProperty directly on the cache because we ensure, 11046 // see below, that the cache never stores a path called 'hasOwnProperty' 11047 if (getterFnCache.hasOwnProperty(path)) { 11048 return getterFnCache[path]; 11049 } 11050 11051 var pathKeys = path.split('.'), 11052 pathKeysLength = pathKeys.length, 11053 fn; 11054 11055 // http://jsperf.com/angularjs-parse-getter/6 11056 if (options.csp) { 11057 if (pathKeysLength < 6) { 11058 fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, 11059 options); 11060 } else { 11061 fn = function(scope, locals) { 11062 var i = 0, val; 11063 do { 11064 val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], 11065 pathKeys[i++], fullExp, options)(scope, locals); 11066 11067 locals = undefined; // clear after first iteration 11068 scope = val; 11069 } while (i < pathKeysLength); 11070 return val; 11071 }; 11072 } 11073 } else { 11074 var code = 'var p;\n';
11075 forEach(pathKeys, function(key, index) { 11076 ensureSafeMemberName(key, fullExp); 11077 code += 'if(s == null) return undefined;\n' + 11078 's='+ (index 11079 // we simply dereference 's' on any .dot notation 11080 ? 's' 11081 // but if we are first then we check locals first, and if so read it first 11082 : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + 11083 (options.unwrapPromises 11084 ? 'if (s && s.then) {\n' + 11085 ' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' + 11086 ' if (!("$$v" in s)) {\n' + 11087 ' p=s;\n' + 11088 ' p.$$v = undefined;\n' + 11089 ' p.then(function(v) {p.$$v=v;});\n' + 11090 '}\n' + 11091 ' s=s.$$v\n' + 11092 '}\n' 11093 : ''); 11094 }); 11095 code += 'return s;'; 11096 11097 /* jshint -W054 */ 11098 var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning 11099 /* jshint +W054 */ 11100 evaledFnGetter.toString = valueFn(code); 11101 fn = options.unwrapPromises ? function(scope, locals) { 11102 return evaledFnGetter(scope, locals, promiseWarning); 11103 } : evaledFnGetter; 11104 } 11105 11106 // Only cache the value if it's not going to mess up the cache object 11107 // This is more performant that using Object.prototype.hasOwnProperty.call 11108 if (path !== 'hasOwnProperty') { 11109 getterFnCache[path] = fn; 11110 } 11111 return fn; 11112} 11113 11114/////////////////////////////////// 11115 11116/** 11117 * @ngdoc service 11118 * @name $parse 11119 * @kind function 11120 * 11121 * @description 11122 * 11123 * Converts Angular {@link guide/expression expression} into a function. 11124 * 11125 * ```js 11126 * var getter = $parse('user.name'); 11127 * var setter = getter.assign; 11128 * var context = {user:{name:'angular'}}; 11129 * var locals = {user:{name:'local'}}; 11130 * 11131 * expect(getter(context)).toEqual('angular'); 11132 * setter(context, 'newValue'); 11133 * expect(context.user.name).toEqual('newValue'); 11134 * expect(getter(context, locals)).toEqual('local'); 11135 * ``` 11136 * 11137 * 11138 * @param {string} expression String expression to compile. 11139 * @returns {function(context, locals)} a function which represents the compiled expression: 11140 * 11141 * * `context` â `{object}` â an object against which any expressions embedded in the strings 11142 * are evaluated against (typically a scope object). 11143 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 11144 * `context`. 11145 * 11146 * The returned function also has the following properties: 11147 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 11148 * literal. 11149 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 11150 * constant literals. 11151 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 11152 * set to a function to change its value on the given context. 11153 * 11154 */ 11155 11156 11157/** 11158 * @ngdoc provider 11159 * @name $parseProvider 11160 * @kind function 11161 * 11162 * @description 11163 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 11164 * service. 11165 */ 11166function $ParseProvider() { 11167 var cache = {}; 11168 11169 var $parseOptions = { 11170 csp: false, 11171 unwrapPromises: false, 11172 logPromiseWarnings: true 11173 }; 11174 11175 11176 /** 11177 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 11178 * 11179 * @ngdoc method 11180 * @name $parseProvider#unwrapPromises 11181 * @description 11182 * 11183 * **This feature is deprecated, see deprecation notes below for more info** 11184 * 11185 * If set to true (default is false), $parse will unwrap promises automatically when a promise is 11186 * found at any part of the expression. In other words, if set to true, the expression will always 11187 * result in a non-promise value. 11188 * 11189 * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, 11190 * the fulfillment value is used in place of the promise while evaluating the expression. 11191 * 11192 * **Deprecation notice** 11193 * 11194 * This is a feature that didn't prove to be wildly useful or popular, primarily because of the 11195 * dichotomy between data access in templates (accessed as raw values) and controller code 11196 * (accessed as promises). 11197 * 11198 * In most code we ended up resolving promises manually in controllers anyway and thus unifying 11199 * the model access there. 11200 * 11201 * Other downsides of automatic promise unwrapping: 11202 *
11203 * - when building components it's often desirable to receive the raw promises 11204 * - adds complexity and slows down expression evaluation 11205 * - makes expression code pre-generation unattractive due to the amount of code that needs to be 11206 * generated 11207 * - makes IDE auto-completion and tool support hard 11208 * 11209 * **Warning Logs** 11210 * 11211 * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a 11212 * promise (to reduce the noise, each expression is logged only once). To disable this logging use 11213 * `$parseProvider.logPromiseWarnings(false)` api. 11214 * 11215 * 11216 * @param {boolean=} value New value. 11217 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 11218 * setter. 11219 */ 11220 this.unwrapPromises = function(value) { 11221 if (isDefined(value)) { 11222 $parseOptions.unwrapPromises = !!value; 11223 return this; 11224 } else { 11225 return $parseOptions.unwrapPromises; 11226 } 11227 }; 11228 11229 11230 /** 11231 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 11232 * 11233 * @ngdoc method 11234 * @name $parseProvider#logPromiseWarnings 11235 * @description 11236 * 11237 * Controls whether Angular should log a warning on any encounter of a promise in an expression. 11238 * 11239 * The default is set to `true`. 11240 * 11241 * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. 11242 * 11243 * @param {boolean=} value New value. 11244 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 11245 * setter. 11246 */ 11247 this.logPromiseWarnings = function(value) { 11248 if (isDefined(value)) { 11249 $parseOptions.logPromiseWarnings = value; 11250 return this; 11251 } else { 11252 return $parseOptions.logPromiseWarnings; 11253 } 11254 }; 11255 11256 11257 this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { 11258 $parseOptions.csp = $sniffer.csp; 11259 11260 promiseWarning = function promiseWarningFn(fullExp) { 11261 if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; 11262 promiseWarningCache[fullExp] = true; 11263 $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + 11264 'Automatic unwrapping of promises in Angular expressions is deprecated.'); 11265 }; 11266 11267 return function(exp) { 11268 var parsedExpression; 11269 11270 switch (typeof exp) { 11271 case 'string': 11272 11273 if (cache.hasOwnProperty(exp)) { 11274 return cache[exp]; 11275 } 11276 11277 var lexer = new Lexer($parseOptions); 11278 var parser = new Parser(lexer, $filter, $parseOptions); 11279 parsedExpression = parser.parse(exp); 11280 11281 if (exp !== 'hasOwnProperty') { 11282 // Only cache the value if it's not going to mess up the cache object 11283 // This is more performant that using Object.prototype.hasOwnProperty.call 11284 cache[exp] = parsedExpression; 11285 } 11286 11287 return parsedExpression; 11288 11289 case 'function': 11290 return exp; 11291 11292 default: 11293 return noop; 11294 } 11295 }; 11296 }]; 11297} 11298 11299/** 11300 * @ngdoc service 11301 * @name $q 11302 * @requires $rootScope 11303 * 11304 * @description 11305 * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). 11306 * 11307 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 11308 * interface for interacting with an object that represents the result of an action that is 11309 * performed asynchronously, and may or may not be finished at any given point in time. 11310 * 11311 * From the perspective of dealing with error handling, deferred and promise APIs are to 11312 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 11313 * 11314 * ```js 11315 * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` 11316 * // are available in the current lexical scope (they could have been injected or passed in). 11317 * 11318 * function asyncGreet(name) { 11319 * var deferred = $q.defer(); 11320 * 11321 * setTimeout(function() { 11322 * deferred.notify('About to greet ' + name + '.'); 11323 * 11324 * if (okToGreet(name)) { 11325 * deferred.resolve('Hello, ' + name + '!'); 11326 * } else { 11327 * deferred.reject('Greeting ' + name + ' is not allowed.'); 11328 * } 11329 * }, 1000); 11330 * 11331 * return deferred.promise; 11332 * } 11333 * 11334 * var promise = asyncGreet('Robin Hood'); 11335 * promise.then(function(greeting) { 11336 * alert('Success: ' + greeting); 11337 * }, function(reason) { 11338 * alert('Failed: ' + reason); 11339 * }, function(update) { 11340 * alert('Got notification: ' + update); 11341 * }); 11342 * ``` 11343 * 11344 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 11345 * comes in the way of guarantees that promise and deferred APIs make, see 11346 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 11347 *
11348 * Additionally the promise api allows for composition that is very hard to do with the 11349 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 11350 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 11351 * section on serial or parallel joining of promises. 11352 * 11353 * 11354 * # The Deferred API 11355 * 11356 * A new instance of deferred is constructed by calling `$q.defer()`. 11357 * 11358 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 11359 * that can be used for signaling the successful or unsuccessful completion, as well as the status 11360 * of the task. 11361 * 11362 * **Methods** 11363 * 11364 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 11365 * constructed via `$q.reject`, the promise will be rejected instead. 11366 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 11367 * resolving it with a rejection constructed via `$q.reject`. 11368 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 11369 * multiple times before the promise is either resolved or rejected. 11370 * 11371 * **Properties** 11372 * 11373 * - promise â `{Promise}` â promise object associated with this deferred. 11374 * 11375 * 11376 * # The Promise API 11377 * 11378 * A new promise instance is created when a deferred instance is created and can be retrieved by 11379 * calling `deferred.promise`. 11380 * 11381 * The purpose of the promise object is to allow for interested parties to get access to the result 11382 * of the deferred task when it completes. 11383 * 11384 * **Methods** 11385 * 11386 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 11387 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 11388 * as soon as the result is available. The callbacks are called with a single argument: the result 11389 * or rejection reason. Additionally, the notify callback may be called zero or more times to 11390 * provide a progress indication, before the promise is resolved or rejected. 11391 * 11392 * This method *returns a new promise* which is resolved or rejected via the return value of the 11393 * `successCallback`, `errorCallback`. It also notifies via the return value of the 11394 * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback 11395 * method. 11396 * 11397 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 11398 * 11399 * - `finally(callback)` â allows you to observe either the fulfillment or rejection of a promise, 11400 * but to do so without modifying the final value. This is useful to release resources or do some 11401 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 11402 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 11403 * more information. 11404 * 11405 * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as 11406 * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to 11407 * make your code IE8 and Android 2.x compatible. 11408 * 11409 * # Chaining promises 11410 * 11411 * Because calling the `then` method of a promise returns a new derived promise, it is easily 11412 * possible to create a chain of promises: 11413 * 11414 * ```js 11415 * promiseB = promiseA.then(function(result) { 11416 * return result + 1; 11417 * }); 11418 * 11419 * // promiseB will be resolved immediately after promiseA is resolved and its value 11420 * // will be the result of promiseA incremented by 1 11421 * ``` 11422 * 11423 * It is possible to create chains of any length and since a promise can be resolved with another 11424 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 11425 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 11426 * $http's response interceptors. 11427 * 11428 * 11429 * # Differences between Kris Kowal's Q and $q 11430 * 11431 * There are two main differences: 11432 * 11433 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 11434 * mechanism in angular, which means faster propagation of resolution or rejection into your 11435 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
11436 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains 11437 * all the important functionality needed for common async tasks. 11438 * 11439 * # Testing 11440 * 11441 * ```js 11442 * it('should simulate promise', inject(function($q, $rootScope) { 11443 * var deferred = $q.defer(); 11444 * var promise = deferred.promise; 11445 * var resolvedValue; 11446 * 11447 * promise.then(function(value) { resolvedValue = value; }); 11448 * expect(resolvedValue).toBeUndefined(); 11449 * 11450 * // Simulate resolving of promise 11451 * deferred.resolve(123); 11452 * // Note that the 'then' function does not get called synchronously. 11453 * // This is because we want the promise API to always be async, whether or not 11454 * // it got called synchronously or asynchronously. 11455 * expect(resolvedValue).toBeUndefined(); 11456 * 11457 * // Propagate promise resolution to 'then' functions using $apply(). 11458 * $rootScope.$apply(); 11459 * expect(resolvedValue).toEqual(123); 11460 * })); 11461 * ``` 11462 */ 11463function $QProvider() { 11464 11465 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 11466 return qFactory(function(callback) { 11467 $rootScope.$evalAsync(callback); 11468 }, $exceptionHandler); 11469 }]; 11470} 11471 11472 11473/** 11474 * Constructs a promise manager. 11475 * 11476 * @param {function(Function)} nextTick Function for executing functions in the next turn. 11477 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 11478 * debugging purposes. 11479 * @returns {object} Promise manager. 11480 */ 11481function qFactory(nextTick, exceptionHandler) { 11482 11483 /** 11484 * @ngdoc method 11485 * @name $q#defer 11486 * @kind function 11487 * 11488 * @description 11489 * Creates a `Deferred` object which represents a task which will finish in the future. 11490 * 11491 * @returns {Deferred} Returns a new instance of deferred. 11492 */ 11493 var defer = function() { 11494 var pending = [], 11495 value, deferred; 11496 11497 deferred = { 11498 11499 resolve: function(val) { 11500 if (pending) { 11501 var callbacks = pending; 11502 pending = undefined; 11503 value = ref(val); 11504 11505 if (callbacks.length) { 11506 nextTick(function() { 11507 var callback; 11508 for (var i = 0, ii = callbacks.length; i < ii; i++) { 11509 callback = callbacks[i]; 11510 value.then(callback[0], callback[1], callback[2]); 11511 } 11512 }); 11513 } 11514 } 11515 }, 11516 11517 11518 reject: function(reason) { 11519 deferred.resolve(createInternalRejectedPromise(reason)); 11520 }, 11521 11522 11523 notify: function(progress) { 11524 if (pending) { 11525 var callbacks = pending; 11526 11527 if (pending.length) { 11528 nextTick(function() { 11529 var callback; 11530 for (var i = 0, ii = callbacks.length; i < ii; i++) { 11531 callback = callbacks[i]; 11532 callback[2](progress); 11533 } 11534 }); 11535 } 11536 } 11537 }, 11538 11539 11540 promise: { 11541 then: function(callback, errback, progressback) { 11542 var result = defer(); 11543 11544 var wrappedCallback = function(value) { 11545 try { 11546 result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); 11547 } catch(e) { 11548 result.reject(e); 11549 exceptionHandler(e); 11550 } 11551 }; 11552 11553 var wrappedErrback = function(reason) { 11554 try { 11555 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11556 } catch(e) { 11557 result.reject(e); 11558 exceptionHandler(e); 11559 } 11560 }; 11561 11562 var wrappedProgressback = function(progress) { 11563 try { 11564 result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); 11565 } catch(e) { 11566 exceptionHandler(e); 11567 } 11568 }; 11569 11570 if (pending) { 11571 pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); 11572 } else { 11573 value.then(wrappedCallback, wrappedErrback, wrappedProgressback); 11574 } 11575 11576 return result.promise; 11577 }, 11578 11579 "catch": function(callback) { 11580 return this.then(null, callback); 11581 }, 11582 11583 "finally": function(callback) { 11584 11585 function makePromise(value, resolved) { 11586 var result = defer(); 11587 if (resolved) { 11588 result.resolve(value); 11589 } else { 11590 result.reject(value); 11591 } 11592 return result.promise; 11593 } 11594 11595 function handleCallback(value, isResolved) { 11596 var callbackOutput = null; 11597 try { 11598 callbackOutput = (callback ||defaultCallback)(); 11599 } catch(e) { 11600 return makePromise(e, false); 11601 }
11602 if (isPromiseLike(callbackOutput)) { 11603 return callbackOutput.then(function() { 11604 return makePromise(value, isResolved); 11605 }, function(error) { 11606 return makePromise(error, false); 11607 }); 11608 } else { 11609 return makePromise(value, isResolved); 11610 } 11611 } 11612 11613 return this.then(function(value) { 11614 return handleCallback(value, true); 11615 }, function(error) { 11616 return handleCallback(error, false); 11617 }); 11618 } 11619 } 11620 }; 11621 11622 return deferred; 11623 }; 11624 11625 11626 var ref = function(value) { 11627 if (isPromiseLike(value)) return value; 11628 return { 11629 then: function(callback) { 11630 var result = defer(); 11631 nextTick(function() { 11632 result.resolve(callback(value)); 11633 }); 11634 return result.promise; 11635 } 11636 }; 11637 }; 11638 11639 11640 /** 11641 * @ngdoc method 11642 * @name $q#reject 11643 * @kind function 11644 * 11645 * @description 11646 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 11647 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 11648 * a promise chain, you don't need to worry about it. 11649 * 11650 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 11651 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 11652 * a promise error callback and you want to forward the error to the promise derived from the 11653 * current promise, you have to "rethrow" the error by returning a rejection constructed via 11654 * `reject`. 11655 * 11656 * ```js 11657 * promiseB = promiseA.then(function(result) { 11658 * // success: do something and resolve promiseB 11659 * // with the old or a new result 11660 * return result; 11661 * }, function(reason) { 11662 * // error: handle the error if possible and 11663 * // resolve promiseB with newPromiseOrValue, 11664 * // otherwise forward the rejection to promiseB 11665 * if (canHandle(reason)) { 11666 * // handle the error and recover 11667 * return newPromiseOrValue; 11668 * } 11669 * return $q.reject(reason); 11670 * }); 11671 * ``` 11672 * 11673 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 11674 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 11675 */ 11676 var reject = function(reason) { 11677 var result = defer(); 11678 result.reject(reason); 11679 return result.promise; 11680 }; 11681 11682 var createInternalRejectedPromise = function(reason) { 11683 return { 11684 then: function(callback, errback) { 11685 var result = defer(); 11686 nextTick(function() { 11687 try { 11688 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11689 } catch(e) { 11690 result.reject(e); 11691 exceptionHandler(e); 11692 } 11693 }); 11694 return result.promise; 11695 } 11696 }; 11697 }; 11698 11699 11700 /** 11701 * @ngdoc method 11702 * @name $q#when 11703 * @kind function 11704 * 11705 * @description 11706 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 11707 * This is useful when you are dealing with an object that might or might not be a promise, or if 11708 * the promise comes from a source that can't be trusted. 11709 * 11710 * @param {*} value Value or a promise 11711 * @returns {Promise} Returns a promise of the passed value or promise 11712 */ 11713 var when = function(value, callback, errback, progressback) { 11714 var result = defer(), 11715 done; 11716 11717 var wrappedCallback = function(value) { 11718 try { 11719 return (isFunction(callback) ? callback : defaultCallback)(value); 11720 } catch (e) { 11721 exceptionHandler(e); 11722 return reject(e); 11723 } 11724 }; 11725 11726 var wrappedErrback = function(reason) { 11727 try { 11728 return (isFunction(errback) ? errback : defaultErrback)(reason); 11729 } catch (e) { 11730 exceptionHandler(e); 11731 return reject(e); 11732 } 11733 }; 11734 11735 var wrappedProgressback = function(progress) { 11736 try { 11737 return (isFunction(progressback) ? progressback : defaultCallback)(progress); 11738 } catch (e) { 11739 exceptionHandler(e); 11740 } 11741 }; 11742 11743 nextTick(function() { 11744 ref(value).then(function(value) { 11745 if (done) return; 11746 done = true; 11747 result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); 11748 }, function(reason) { 11749 if (done) return; 11750 done = true; 11751 result.resolve(wrappedErrback(reason)); 11752 }, function(progress) { 11753 if (done) return; 11754 result.notify(wrappedProgressback(progress)); 11755 }); 11756 }); 11757 11758 return result.promise; 11759 }; 11760 11761 11762 function defaultCallback(value) { 11763 return value; 11764 } 11765 11766 11767 function defaultErrback(reason) { 11768 return reject(reason); 11769 } 11770 11771 11772 /** 11773 * @ngdoc method 11774 * @name $q#all 11775 * @kind function 11776 * 11777 * @description 11778 * Combines multiple promises into a single promise that is resolved when all of the input 11779 * promises are resolved. 11780 * 11781 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 11782 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 11783 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 11784 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 11785 * with the same rejection value. 11786 */ 11787 function all(promises) { 11788 var deferred = defer(), 11789 counter = 0, 11790 results = isArray(promises) ? [] : {}; 11791
11792 forEach(promises, function(promise, key) { 11793 counter++; 11794 ref(promise).then(function(value) { 11795 if (results.hasOwnProperty(key)) return; 11796 results[key] = value; 11797 if (!(--counter)) deferred.resolve(results); 11798 }, function(reason) { 11799 if (results.hasOwnProperty(key)) return; 11800 deferred.reject(reason); 11801 }); 11802 }); 11803 11804 if (counter === 0) { 11805 deferred.resolve(results); 11806 } 11807 11808 return deferred.promise; 11809 } 11810 11811 return { 11812 defer: defer, 11813 reject: reject, 11814 when: when, 11815 all: all 11816 }; 11817} 11818 11819function $$RAFProvider(){ //rAF 11820 this.$get = ['$window', '$timeout', function($window, $timeout) { 11821 var requestAnimationFrame = $window.requestAnimationFrame || 11822 $window.webkitRequestAnimationFrame || 11823 $window.mozRequestAnimationFrame; 11824 11825 var cancelAnimationFrame = $window.cancelAnimationFrame || 11826 $window.webkitCancelAnimationFrame || 11827 $window.mozCancelAnimationFrame || 11828 $window.webkitCancelRequestAnimationFrame; 11829 11830 var rafSupported = !!requestAnimationFrame; 11831 var raf = rafSupported 11832 ? function(fn) { 11833 var id = requestAnimationFrame(fn); 11834 return function() { 11835 cancelAnimationFrame(id); 11836 }; 11837 } 11838 : function(fn) { 11839 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 11840 return function() { 11841 $timeout.cancel(timer); 11842 }; 11843 }; 11844 11845 raf.supported = rafSupported; 11846 11847 return raf; 11848 }]; 11849} 11850 11851/** 11852 * DESIGN NOTES 11853 * 11854 * The design decisions behind the scope are heavily favored for speed and memory consumption. 11855 * 11856 * The typical use of scope is to watch the expressions, which most of the time return the same 11857 * value as last time so we optimize the operation. 11858 * 11859 * Closures construction is expensive in terms of speed as well as memory: 11860 * - No closures, instead use prototypical inheritance for API 11861 * - Internal state needs to be stored on scope directly, which means that private state is 11862 * exposed as $$____ properties 11863 * 11864 * Loop operations are optimized by using while(count--) { ... } 11865 * - this means that in order to keep the same order of execution as addition we have to add 11866 * items to the array at the beginning (unshift) instead of at the end (push) 11867 * 11868 * Child scopes are created and removed often 11869 * - Using an array would be slow since inserts in middle are expensive so we use linked list 11870 * 11871 * There are few watches then a lot of observers. This is why you don't want the observer to be 11872 * implemented in the same way as watch. Watch requires return of initialization function which 11873 * are expensive to construct. 11874 */ 11875 11876 11877/** 11878 * @ngdoc provider 11879 * @name $rootScopeProvider 11880 * @description 11881 * 11882 * Provider for the $rootScope service. 11883 */ 11884 11885/** 11886 * @ngdoc method 11887 * @name $rootScopeProvider#digestTtl 11888 * @description 11889 * 11890 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 11891 * assuming that the model is unstable. 11892 * 11893 * The current default is 10 iterations. 11894 * 11895 * In complex applications it's possible that the dependencies between `$watch`s will result in 11896 * several digest iterations. However if an application needs more than the default 10 digest 11897 * iterations for its model to stabilize then you should investigate what is causing the model to 11898 * continuously change during the digest. 11899 * 11900 * Increasing the TTL could have performance implications, so you should not change it without 11901 * proper justification. 11902 * 11903 * @param {number} limit The number of digest iterations. 11904 */ 11905 11906 11907/** 11908 * @ngdoc service 11909 * @name $rootScope 11910 * @description 11911 * 11912 * Every application has a single root {@link ng.$rootScope.Scope scope}. 11913 * All other scopes are descendant scopes of the root scope. Scopes provide separation 11914 * between the model and the view, via a mechanism for watching the model for changes. 11915 * They also provide an event emission/broadcast and subscription facility. See the 11916 * {@link guide/scope developer guide on scopes}. 11917 */ 11918function $RootScopeProvider(){ 11919 var TTL = 10; 11920 var $rootScopeMinErr = minErr('$rootScope'); 11921 var lastDirtyWatch = null; 11922 11923 this.digestTtl = function(value) { 11924 if (arguments.length) { 11925 TTL = value; 11926 } 11927 return TTL; 11928 }; 11929 11930 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 11931 function( $injector, $exceptionHandler, $parse, $browser) { 11932 11933 /** 11934 * @ngdoc type 11935 * @name $rootScope.Scope 11936 *
11937 * @description 11938 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 11939 * {@link auto.$injector $injector}. Child scopes are created using the 11940 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 11941 * compiled HTML template is executed.) 11942 * 11943 * Here is a simple scope snippet to show how you can interact with the scope. 11944 * ```html 11945 * <file src="./test/ng/rootScopeSpec.js" tag="docs1" /> 11946 * ``` 11947 * 11948 * # Inheritance 11949 * A scope can inherit from a parent scope, as in this example: 11950 * ```js 11951 var parent = $rootScope; 11952 var child = parent.$new(); 11953 11954 parent.salutation = "Hello"; 11955 child.name = "World"; 11956 expect(child.salutation).toEqual('Hello'); 11957 11958 child.salutation = "Welcome"; 11959 expect(child.salutation).toEqual('Welcome'); 11960 expect(parent.salutation).toEqual('Hello'); 11961 * ``` 11962 * 11963 * 11964 * @param {Object.<string, function()>=} providers Map of service factory which need to be 11965 * provided for the current scope. Defaults to {@link ng}. 11966 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 11967 * append/override services provided by `providers`. This is handy 11968 * when unit-testing and having the need to override a default 11969 * service. 11970 * @returns {Object} Newly created scope. 11971 * 11972 */ 11973 function Scope() { 11974 this.$id = nextUid(); 11975 this.$$phase = this.$parent = this.$$watchers = 11976 this.$$nextSibling = this.$$prevSibling = 11977 this.$$childHead = this.$$childTail = null; 11978 this['this'] = this.$root = this; 11979 this.$$destroyed = false; 11980 this.$$asyncQueue = []; 11981 this.$$postDigestQueue = []; 11982 this.$$listeners = {}; 11983 this.$$listenerCount = {}; 11984 this.$$isolateBindings = {}; 11985 } 11986 11987 /** 11988 * @ngdoc property 11989 * @name $rootScope.Scope#$id 11990 * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for 11991 * debugging. 11992 */ 11993 11994 11995 Scope.prototype = { 11996 constructor: Scope, 11997 /** 11998 * @ngdoc method 11999 * @name $rootScope.Scope#$new 12000 * @kind function 12001 * 12002 * @description 12003 * Creates a new child {@link ng.$rootScope.Scope scope}. 12004 * 12005 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and 12006 * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the 12007 * scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 12008 * 12009 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 12010 * desired for the scope and its child scopes to be permanently detached from the parent and 12011 * thus stop participating in model change detection and listener notification by invoking. 12012 * 12013 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 12014 * parent scope. The scope is isolated, as it can not see parent scope properties. 12015 * When creating widgets, it is useful for the widget to not accidentally read parent 12016 * state. 12017 * 12018 * @returns {Object} The newly created child scope. 12019 * 12020 */ 12021 $new: function(isolate) { 12022 var ChildScope, 12023 child; 12024 12025 if (isolate) { 12026 child = new Scope(); 12027 child.$root = this.$root; 12028 // ensure that there is just one async queue per $rootScope and its children 12029 child.$$asyncQueue = this.$$asyncQueue; 12030 child.$$postDigestQueue = this.$$postDigestQueue; 12031 } else { 12032 // Only create a child scope class if somebody asks for one, 12033 // but cache it to allow the VM to optimize lookups. 12034 if (!this.$$childScopeClass) { 12035 this.$$childScopeClass = function() {
12036 this.$$watchers = this.$$nextSibling = 12037 this.$$childHead = this.$$childTail = null; 12038 this.$$listeners = {}; 12039 this.$$listenerCount = {}; 12040 this.$id = nextUid(); 12041 this.$$childScopeClass = null; 12042 }; 12043 this.$$childScopeClass.prototype = this; 12044 } 12045 child = new this.$$childScopeClass(); 12046 } 12047 child['this'] = child; 12048 child.$parent = this; 12049 child.$$prevSibling = this.$$childTail; 12050 if (this.$$childHead) { 12051 this.$$childTail.$$nextSibling = child; 12052 this.$$childTail = child; 12053 } else { 12054 this.$$childHead = this.$$childTail = child; 12055 } 12056 return child; 12057 }, 12058 12059 /** 12060 * @ngdoc method 12061 * @name $rootScope.Scope#$watch 12062 * @kind function 12063 * 12064 * @description 12065 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 12066 * 12067 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 12068 * $digest()} and should return the value that will be watched. (Since 12069 * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the 12070 * `watchExpression` can execute multiple times per 12071 * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) 12072 * - The `listener` is called only when the value from the current `watchExpression` and the 12073 * previous call to `watchExpression` are not equal (with the exception of the initial run, 12074 * see below). Inequality is determined according to reference inequality, 12075 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 12076 * via the `!==` Javascript operator, unless `objectEquality == true` 12077 * (see next point) 12078 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 12079 * according to the {@link angular.equals} function. To save the value of the object for 12080 * later comparison, the {@link angular.copy} function is used. This therefore means that 12081 * watching complex objects will have adverse memory and performance implications. 12082 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 12083 * This is achieved by rerunning the watchers until no changes are detected. The rerun 12084 * iteration limit is 10 to prevent an infinite loop deadlock. 12085 * 12086 * 12087 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 12088 * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` 12089 * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a 12090 * change is detected, be prepared for multiple calls to your listener.) 12091 * 12092 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 12093 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 12094 * watcher. In rare cases, this is undesirable because the listener is called when the result 12095 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 12096 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 12097 * listener was called due to initialization. 12098 * 12099 * The example below contains an illustration of using a function as your $watch listener 12100 * 12101 * 12102 * # Example 12103 * ```js 12104 // let's assume that scope was dependency injected as the $rootScope 12105 var scope = $rootScope; 12106 scope.name = 'misko'; 12107 scope.counter = 0; 12108 12109 expect(scope.counter).toEqual(0); 12110 scope.$watch('name', function(newValue, oldValue) { 12111 scope.counter = scope.counter + 1; 12112 }); 12113 expect(scope.counter).toEqual(0); 12114 12115 scope.$digest(); 12116 // the listener is always called during the first $digest loop after it was registered 12117 expect(scope.counter).toEqual(1); 12118 12119 scope.$digest(); 12120 // but now it will not be called unless the value changes 12121 expect(scope.counter).toEqual(1); 12122 12123 scope.name = 'adam'; 12124 scope.$digest(); 12125 expect(scope.counter).toEqual(2); 12126 12127 12128 12129 // Using a listener function 12130 var food; 12131 scope.foodCounter = 0; 12132 expect(scope.foodCounter).toEqual(0); 12133 scope.$watch( 12134 // This is the listener function 12135 function() { return food; }, 12136 // This is the change handler 12137 function(newValue, oldValue) { 12138 if ( newValue !== oldValue ) { 12139 // Only increment the counter if the value changed 12140 scope.foodCounter = scope.foodCounter + 1; 12141 } 12142 } 12143 ); 12144 // No digest has been run so the counter will be zero 12145 expect(scope.foodCounter).toEqual(0); 12146 12147 // Run the digest but since food has not changed count will still be zero 12148 scope.$digest(); 12149 expect(scope.foodCounter).toEqual(0); 12150 12151 // Update food and run digest. Now the counter will increment 12152 food = 'cheeseburger'; 12153 scope.$digest(); 12154 expect(scope.foodCounter).toEqual(1); 12155 12156 * ``` 12157 * 12158 * 12159 * 12160 * @param {(function()|string)} watchExpression Expression that is evaluated on each 12161 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 12162 * a call to the `listener`. 12163 * 12164 * - `string`: Evaluated as {@link guide/expression expression} 12165 * - `function(scope)`: called with current `scope` as a parameter. 12166 * @param {(function()|string)=} listener Callback called whenever the return value of 12167 * the `watchExpression` changes. 12168 * 12169 * - `string`: Evaluated as {@link guide/expression expression} 12170 * - `function(newValue, oldValue, scope)`: called with current and previous values as 12171 * parameters. 12172 * 12173 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of 12174 * comparing for reference equality. 12175 * @returns {function()} Returns a deregistration function for this listener. 12176 */ 12177 $watch: function(watchExp, listener, objectEquality) { 12178 var scope = this, 12179 get = compileToFn(watchExp, 'watch'), 12180 array = scope.$$watchers, 12181 watcher = { 12182 fn: listener, 12183 last: initWatchVal, 12184 get: get, 12185 exp: watchExp, 12186 eq: !!objectEquality 12187 }; 12188 12189 lastDirtyWatch = null; 12190 12191 // in the case user pass string, we need to compile it, do we really need this ? 12192 if (!isFunction(listener)) { 12193 var listenFn = compileToFn(listener || noop, 'listener'); 12194 watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; 12195 } 12196 12197 if (typeof watchExp == 'string' && get.constant) { 12198 var originalFn = watcher.fn; 12199 watcher.fn = function(newVal, oldVal, scope) { 12200 originalFn.call(this, newVal, oldVal, scope); 12201 arrayRemove(array, watcher); 12202 }; 12203 } 12204 12205 if (!array) { 12206 array = scope.$$watchers = []; 12207 } 12208 // we use unshift since we use a while loop in $digest for speed. 12209 // the while loop reads in reverse order. 12210 array.unshift(watcher); 12211 12212 return function deregisterWatch() { 12213 arrayRemove(array, watcher); 12214 lastDirtyWatch = null; 12215 }; 12216 }, 12217 12218 12219 /** 12220 * @ngdoc method 12221 * @name $rootScope.Scope#$watchCollection 12222 * @kind function 12223 *
12224 * @description 12225 * Shallow watches the properties of an object and fires whenever any of the properties change 12226 * (for arrays, this implies watching the array items; for object maps, this implies watching 12227 * the properties). If a change is detected, the `listener` callback is fired. 12228 * 12229 * - The `obj` collection is observed via standard $watch operation and is examined on every 12230 * call to $digest() to see if any items have been added, removed, or moved. 12231 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 12232 * adding, removing, and moving items belonging to an object or array. 12233 * 12234 * 12235 * # Example 12236 * ```js 12237 $scope.names = ['igor', 'matias', 'misko', 'james']; 12238 $scope.dataCount = 4; 12239 12240 $scope.$watchCollection('names', function(newNames, oldNames) { 12241 $scope.dataCount = newNames.length; 12242 }); 12243 12244 expect($scope.dataCount).toEqual(4); 12245 $scope.$digest(); 12246 12247 //still at 4 ... no changes 12248 expect($scope.dataCount).toEqual(4); 12249 12250 $scope.names.pop(); 12251 $scope.$digest(); 12252 12253 //now there's been a change 12254 expect($scope.dataCount).toEqual(3); 12255 * ``` 12256 * 12257 * 12258 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 12259 * expression value should evaluate to an object or an array which is observed on each 12260 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 12261 * collection will trigger a call to the `listener`. 12262 * 12263 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 12264 * when a change is detected. 12265 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 12266 * - The `oldCollection` object is a copy of the former collection data. 12267 * Due to performance considerations, the`oldCollection` value is computed only if the 12268 * `listener` function declares two or more arguments. 12269 * - The `scope` argument refers to the current scope. 12270 * 12271 * @returns {function()} Returns a de-registration function for this listener. When the 12272 * de-registration function is executed, the internal watch operation is terminated. 12273 */ 12274 $watchCollection: function(obj, listener) { 12275 var self = this; 12276 // the current value, updated on each dirty-check run 12277 var newValue; 12278 // a shallow copy of the newValue from the last dirty-check run, 12279 // updated to match newValue during dirty-check run 12280 var oldValue; 12281 // a shallow copy of the newValue from when the last change happened 12282 var veryOldValue; 12283 // only track veryOldValue if the listener is asking for it 12284 var trackVeryOldValue = (listener.length > 1); 12285 var changeDetected = 0; 12286 var objGetter = $parse(obj); 12287 var internalArray = []; 12288 var internalObject = {}; 12289 var initRun = true; 12290 var oldLength = 0; 12291 12292 function $watchCollectionWatch() { 12293 newValue = objGetter(self); 12294 var newLength, key, bothNaN; 12295 12296 if (!isObject(newValue)) { // if primitive 12297 if (oldValue !== newValue) { 12298 oldValue = newValue; 12299 changeDetected++; 12300 } 12301 } else if (isArrayLike(newValue)) { 12302 if (oldValue !== internalArray) { 12303 // we are transitioning from something which was not an array into array. 12304 oldValue = internalArray; 12305 oldLength = oldValue.length = 0; 12306 changeDetected++; 12307 } 12308 12309 newLength = newValue.length; 12310 12311 if (oldLength !== newLength) { 12312 // if lengths do not match we need to trigger change notification 12313 changeDetected++; 12314 oldValue.length = oldLength = newLength; 12315 } 12316 // copy the items to oldValue and look for changes. 12317 for (var i = 0; i < newLength; i++) { 12318 bothNaN = (oldValue[i] !== oldValue[i]) && 12319 (newValue[i] !== newValue[i]);
12320 if (!bothNaN && (oldValue[i] !== newValue[i])) { 12321 changeDetected++; 12322 oldValue[i] = newValue[i]; 12323 } 12324 } 12325 } else { 12326 if (oldValue !== internalObject) { 12327 // we are transitioning from something which was not an object into object. 12328 oldValue = internalObject = {}; 12329 oldLength = 0; 12330 changeDetected++; 12331 } 12332 // copy the items to oldValue and look for changes. 12333 newLength = 0; 12334 for (key in newValue) { 12335 if (newValue.hasOwnProperty(key)) { 12336 newLength++; 12337 if (oldValue.hasOwnProperty(key)) { 12338 bothNaN = (oldValue[key] !== oldValue[key]) && 12339 (newValue[key] !== newValue[key]); 12340 if (!bothNaN && (oldValue[key] !== newValue[key])) { 12341 changeDetected++; 12342 oldValue[key] = newValue[key]; 12343 } 12344 } else { 12345 oldLength++; 12346 oldValue[key] = newValue[key]; 12347 changeDetected++; 12348 } 12349 } 12350 } 12351 if (oldLength > newLength) { 12352 // we used to have more keys, need to find them and destroy them. 12353 changeDetected++; 12354 for(key in oldValue) { 12355 if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { 12356 oldLength--; 12357 delete oldValue[key]; 12358 } 12359 } 12360 } 12361 } 12362 return changeDetected; 12363 } 12364 12365 function $watchCollectionAction() { 12366 if (initRun) { 12367 initRun = false; 12368 listener(newValue, newValue, self); 12369 } else { 12370 listener(newValue, veryOldValue, self); 12371 } 12372 12373 // make a copy for the next time a collection is changed 12374 if (trackVeryOldValue) { 12375 if (!isObject(newValue)) { 12376 //primitive 12377 veryOldValue = newValue; 12378 } else if (isArrayLike(newValue)) { 12379 veryOldValue = new Array(newValue.length); 12380 for (var i = 0; i < newValue.length; i++) { 12381 veryOldValue[i] = newValue[i]; 12382 } 12383 } else { // if object 12384 veryOldValue = {}; 12385 for (var key in newValue) { 12386 if (hasOwnProperty.call(newValue, key)) { 12387 veryOldValue[key] = newValue[key]; 12388 } 12389 } 12390 } 12391 } 12392 } 12393 12394 return this.$watch($watchCollectionWatch, $watchCollectionAction); 12395 }, 12396 12397 /** 12398 * @ngdoc method 12399 * @name $rootScope.Scope#$digest 12400 * @kind function 12401 * 12402 * @description 12403 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 12404 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 12405 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 12406 * until no more listeners are firing. This means that it is possible to get into an infinite 12407 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 12408 * iterations exceeds 10. 12409 * 12410 * Usually, you don't call `$digest()` directly in 12411 * {@link ng.directive:ngController controllers} or in 12412 * {@link ng.$compileProvider#directive directives}. 12413 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 12414 * a {@link ng.$compileProvider#directive directives}), which will force a `$digest()`. 12415 * 12416 * If you want to be notified whenever `$digest()` is called, 12417 * you can register a `watchExpression` function with 12418 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 12419 * 12420 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 12421 * 12422 * # Example 12423 * ```js 12424 var scope = ...; 12425 scope.name = 'misko'; 12426 scope.counter = 0; 12427 12428 expect(scope.counter).toEqual(0); 12429 scope.$watch('name', function(newValue, oldValue) { 12430 scope.counter = scope.counter + 1; 12431 }); 12432 expect(scope.counter).toEqual(0); 12433 12434 scope.$digest(); 12435 // the listener is always called during the first $digest loop after it was registered 12436 expect(scope.counter).toEqual(1); 12437 12438 scope.$digest(); 12439 // but now it will not be called unless the value changes 12440 expect(scope.counter).toEqual(1); 12441 12442 scope.name = 'adam'; 12443 scope.$digest(); 12444 expect(scope.counter).toEqual(2); 12445 * ``` 12446 * 12447 */ 12448 $digest: function() { 12449 var watch, value, last, 12450 watchers, 12451 asyncQueue = this.$$asyncQueue, 12452 postDigestQueue = this.$$postDigestQueue, 12453 length, 12454 dirty, ttl = TTL, 12455 next, current, target = this, 12456 watchLog = [], 12457 logIdx, logMsg, asyncTask; 12458 12459 beginPhase('$digest'); 12460 12461 lastDirtyWatch = null; 12462
12463 do { // "while dirty" loop 12464 dirty = false; 12465 current = target; 12466 12467 while(asyncQueue.length) { 12468 try { 12469 asyncTask = asyncQueue.shift(); 12470 asyncTask.scope.$eval(asyncTask.expression); 12471 } catch (e) { 12472 clearPhase(); 12473 $exceptionHandler(e); 12474 } 12475 lastDirtyWatch = null; 12476 } 12477 12478 traverseScopesLoop: 12479 do { // "traverse the scopes" loop 12480 if ((watchers = current.$$watchers)) { 12481 // process our watches 12482 length = watchers.length; 12483 while (length--) { 12484 try { 12485 watch = watchers[length]; 12486 // Most common watches are on primitives, in which case we can short 12487 // circuit it with === operator, only when === fails do we use .equals 12488 if (watch) { 12489 if ((value = watch.get(current)) !== (last = watch.last) && 12490 !(watch.eq 12491 ? equals(value, last) 12492 : (typeof value === 'number' && typeof last === 'number' 12493 && isNaN(value) && isNaN(last)))) { 12494 dirty = true; 12495 lastDirtyWatch = watch; 12496 watch.last = watch.eq ? copy(value, null) : value; 12497 watch.fn(value, ((last === initWatchVal) ? value : last), current); 12498 if (ttl < 5) { 12499 logIdx = 4 - ttl; 12500 if (!watchLog[logIdx]) watchLog[logIdx] = []; 12501 logMsg = (isFunction(watch.exp)) 12502 ? 'fn: ' + (watch.exp.name || watch.exp.toString()) 12503 : watch.exp; 12504 logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); 12505 watchLog[logIdx].push(logMsg); 12506 } 12507 } else if (watch === lastDirtyWatch) { 12508 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 12509 // have already been tested. 12510 dirty = false; 12511 break traverseScopesLoop; 12512 } 12513 } 12514 } catch (e) { 12515 clearPhase(); 12516 $exceptionHandler(e); 12517 } 12518 } 12519 } 12520 12521 // Insanity Warning: scope depth-first traversal 12522 // yes, this code is a bit crazy, but it works and we have tests to prove it! 12523 // this piece should be kept in sync with the traversal in $broadcast 12524 if (!(next = (current.$$childHead || 12525 (current !== target && current.$$nextSibling)))) { 12526 while(current !== target && !(next = current.$$nextSibling)) { 12527 current = current.$parent; 12528 } 12529 } 12530 } while ((current = next)); 12531 12532 // `break traverseScopesLoop;` takes us to here 12533 12534 if((dirty || asyncQueue.length) && !(ttl--)) { 12535 clearPhase(); 12536 throw $rootScopeMinErr('infdig', 12537 '{0} $digest() iterations reached. Aborting!\n' + 12538 'Watchers fired in the last 5 iterations: {1}', 12539 TTL, toJson(watchLog)); 12540 } 12541 12542 } while (dirty || asyncQueue.length); 12543 12544 clearPhase(); 12545 12546 while(postDigestQueue.length) { 12547 try { 12548 postDigestQueue.shift()(); 12549 } catch (e) { 12550 $exceptionHandler(e); 12551 } 12552 } 12553 }, 12554 12555 12556 /** 12557 * @ngdoc event 12558 * @name $rootScope.Scope#$destroy 12559 * @eventType broadcast on scope being destroyed 12560 * 12561 * @description 12562 * Broadcasted when a scope and its children are being destroyed. 12563 * 12564 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 12565 * clean up DOM bindings before an element is removed from the DOM. 12566 */ 12567 12568 /** 12569 * @ngdoc method 12570 * @name $rootScope.Scope#$destroy 12571 * @kind function 12572 *
12573 * @description 12574 * Removes the current scope (and all of its children) from the parent scope. Removal implies 12575 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 12576 * propagate to the current scope and its children. Removal also implies that the current 12577 * scope is eligible for garbage collection. 12578 * 12579 * The `$destroy()` is usually used by directives such as 12580 * {@link ng.directive:ngRepeat ngRepeat} for managing the 12581 * unrolling of the loop. 12582 * 12583 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 12584 * Application code can register a `$destroy` event handler that will give it a chance to 12585 * perform any necessary cleanup. 12586 * 12587 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 12588 * clean up DOM bindings before an element is removed from the DOM. 12589 */ 12590 $destroy: function() { 12591 // we can't destroy the root scope or a scope that has been already destroyed 12592 if (this.$$destroyed) return; 12593 var parent = this.$parent; 12594 12595 this.$broadcast('$destroy'); 12596 this.$$destroyed = true; 12597 if (this === $rootScope) return; 12598 12599 forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); 12600 12601 // sever all the references to parent scopes (after this cleanup, the current scope should 12602 // not be retained by any of our references and should be eligible for garbage collection) 12603 if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 12604 if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 12605 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 12606 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 12607 12608 12609 // All of the code below is bogus code that works around V8's memory leak via optimized code 12610 // and inline caches. 12611 // 12612 // see: 12613 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 12614 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 12615 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 12616 12617 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 12618 this.$$childTail = this.$root = null; 12619 12620 // don't reset these to null in case some async task tries to register a listener/watch/task 12621 this.$$listeners = {}; 12622 this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = []; 12623 12624 // prevent NPEs since these methods have references to properties we nulled out 12625 this.$destroy = this.$digest = this.$apply = noop; 12626 this.$on = this.$watch = function() { return noop; }; 12627 }, 12628 12629 /** 12630 * @ngdoc method 12631 * @name $rootScope.Scope#$eval 12632 * @kind function 12633 * 12634 * @description 12635 * Executes the `expression` on the current scope and returns the result. Any exceptions in 12636 * the expression are propagated (uncaught). This is useful when evaluating Angular 12637 * expressions. 12638 * 12639 * # Example 12640 * ```js 12641 var scope = ng.$rootScope.Scope(); 12642 scope.a = 1; 12643 scope.b = 2; 12644 12645 expect(scope.$eval('a+b')).toEqual(3); 12646 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 12647 * ``` 12648 * 12649 * @param {(string|function())=} expression An angular expression to be executed. 12650 * 12651 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12652 * - `function(scope)`: execute the function with the current `scope` parameter. 12653 * 12654 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 12655 * @returns {*} The result of evaluating the expression. 12656 */ 12657 $eval: function(expr, locals) { 12658 return $parse(expr)(this, locals); 12659 }, 12660 12661 /** 12662 * @ngdoc method 12663 * @name $rootScope.Scope#$evalAsync 12664 * @kind function 12665 *
12666 * @description 12667 * Executes the expression on the current scope at a later point in time. 12668 * 12669 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 12670 * that: 12671 * 12672 * - it will execute after the function that scheduled the evaluation (preferably before DOM 12673 * rendering). 12674 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 12675 * `expression` execution. 12676 * 12677 * Any exceptions from the execution of the expression are forwarded to the 12678 * {@link ng.$exceptionHandler $exceptionHandler} service. 12679 * 12680 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 12681 * will be scheduled. However, it is encouraged to always call code that changes the model 12682 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 12683 * 12684 * @param {(string|function())=} expression An angular expression to be executed. 12685 * 12686 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12687 * - `function(scope)`: execute the function with the current `scope` parameter. 12688 * 12689 */ 12690 $evalAsync: function(expr) { 12691 // if we are outside of an $digest loop and this is the first time we are scheduling async 12692 // task also schedule async auto-flush 12693 if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { 12694 $browser.defer(function() { 12695 if ($rootScope.$$asyncQueue.length) { 12696 $rootScope.$digest(); 12697 } 12698 }); 12699 } 12700 12701 this.$$asyncQueue.push({scope: this, expression: expr}); 12702 }, 12703 12704 $$postDigest : function(fn) { 12705 this.$$postDigestQueue.push(fn); 12706 }, 12707 12708 /** 12709 * @ngdoc method 12710 * @name $rootScope.Scope#$apply 12711 * @kind function 12712 * 12713 * @description 12714 * `$apply()` is used to execute an expression in angular from outside of the angular 12715 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 12716 * Because we are calling into the angular framework we need to perform proper scope life 12717 * cycle of {@link ng.$exceptionHandler exception handling}, 12718 * {@link ng.$rootScope.Scope#$digest executing watches}. 12719 * 12720 * ## Life cycle 12721 * 12722 * # Pseudo-Code of `$apply()` 12723 * ```js 12724 function $apply(expr) { 12725 try { 12726 return $eval(expr); 12727 } catch (e) { 12728 $exceptionHandler(e); 12729 } finally { 12730 $root.$digest(); 12731 } 12732 } 12733 * ``` 12734 * 12735 * 12736 * Scope's `$apply()` method transitions through the following stages: 12737 * 12738 * 1. The {@link guide/expression expression} is executed using the 12739 * {@link ng.$rootScope.Scope#$eval $eval()} method. 12740 * 2. Any exceptions from the execution of the expression are forwarded to the 12741 * {@link ng.$exceptionHandler $exceptionHandler} service. 12742 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 12743 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 12744 * 12745 * 12746 * @param {(string|function())=} exp An angular expression to be executed. 12747 * 12748 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12749 * - `function(scope)`: execute the function with current `scope` parameter. 12750 * 12751 * @returns {*} The result of evaluating the expression. 12752 */ 12753 $apply: function(expr) { 12754 try { 12755 beginPhase('$apply'); 12756 return this.$eval(expr); 12757 } catch (e) { 12758 $exceptionHandler(e); 12759 } finally { 12760 clearPhase(); 12761 try { 12762 $rootScope.$digest(); 12763 } catch (e) { 12764 $exceptionHandler(e); 12765 throw e; 12766 } 12767 } 12768 }, 12769 12770 /** 12771 * @ngdoc method 12772 * @name $rootScope.Scope#$on 12773 * @kind function 12774 *
12775 * @description 12776 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 12777 * discussion of event life cycle. 12778 * 12779 * The event listener function format is: `function(event, args...)`. The `event` object 12780 * passed into the listener has the following attributes: 12781 * 12782 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 12783 * `$broadcast`-ed. 12784 * - `currentScope` - `{Scope}`: the current scope which is handling the event. 12785 * - `name` - `{string}`: name of the event. 12786 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 12787 * further event propagation (available only for events that were `$emit`-ed). 12788 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 12789 * to true. 12790 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 12791 * 12792 * @param {string} name Event name to listen on. 12793 * @param {function(event, ...args)} listener Function to call when the event is emitted. 12794 * @returns {function()} Returns a deregistration function for this listener. 12795 */ 12796 $on: function(name, listener) { 12797 var namedListeners = this.$$listeners[name]; 12798 if (!namedListeners) { 12799 this.$$listeners[name] = namedListeners = []; 12800 } 12801 namedListeners.push(listener); 12802 12803 var current = this; 12804 do { 12805 if (!current.$$listenerCount[name]) { 12806 current.$$listenerCount[name] = 0; 12807 } 12808 current.$$listenerCount[name]++; 12809 } while ((current = current.$parent)); 12810 12811 var self = this; 12812 return function() { 12813 namedListeners[indexOf(namedListeners, listener)] = null; 12814 decrementListenerCount(self, 1, name); 12815 }; 12816 }, 12817 12818 12819 /** 12820 * @ngdoc method 12821 * @name $rootScope.Scope#$emit 12822 * @kind function 12823 * 12824 * @description 12825 * Dispatches an event `name` upwards through the scope hierarchy notifying the 12826 * registered {@link ng.$rootScope.Scope#$on} listeners. 12827 * 12828 * The event life cycle starts at the scope on which `$emit` was called. All 12829 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 12830 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 12831 * registered listeners along the way. The event will stop propagating if one of the listeners 12832 * cancels it. 12833 * 12834 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 12835 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 12836 * 12837 * @param {string} name Event name to emit. 12838 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 12839 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 12840 */ 12841 $emit: function(name, args) { 12842 var empty = [], 12843 namedListeners, 12844 scope = this, 12845 stopPropagation = false, 12846 event = { 12847 name: name, 12848 targetScope: scope, 12849 stopPropagation: function() {stopPropagation = true;}, 12850 preventDefault: function() { 12851 event.defaultPrevented = true; 12852 }, 12853 defaultPrevented: false 12854 }, 12855 listenerArgs = concat([event], arguments, 1), 12856 i, length; 12857 12858 do { 12859 namedListeners = scope.$$listeners[name] || empty; 12860 event.currentScope = scope; 12861 for (i=0, length=namedListeners.length; i<length; i++) { 12862 12863 // if listeners were deregistered, defragment the array 12864 if (!namedListeners[i]) { 12865 namedListeners.splice(i, 1); 12866 i--; 12867 length--; 12868 continue; 12869 } 12870 try { 12871 //allow all listeners attached to the current scope to run 12872 namedListeners[i].apply(null, listenerArgs); 12873 } catch (e) { 12874 $exceptionHandler(e); 12875 } 12876 } 12877 //if any listener on the current scope stops propagation, prevent bubbling 12878 if (stopPropagation) return event; 12879 //traverse upwards 12880 scope = scope.$parent; 12881 } while (scope); 12882 12883 return event; 12884 }, 12885 12886 12887 /** 12888 * @ngdoc method 12889 * @name $rootScope.Scope#$broadcast 12890 * @kind function 12891 *
12892 * @description 12893 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 12894 * registered {@link ng.$rootScope.Scope#$on} listeners. 12895 * 12896 * The event life cycle starts at the scope on which `$broadcast` was called. All 12897 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 12898 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 12899 * scope and calls all registered listeners along the way. The event cannot be canceled. 12900 * 12901 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 12902 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 12903 * 12904 * @param {string} name Event name to broadcast. 12905 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 12906 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 12907 */ 12908 $broadcast: function(name, args) { 12909 var target = this, 12910 current = target, 12911 next = target, 12912 event = { 12913 name: name, 12914 targetScope: target, 12915 preventDefault: function() { 12916 event.defaultPrevented = true; 12917 }, 12918 defaultPrevented: false 12919 }, 12920 listenerArgs = concat([event], arguments, 1), 12921 listeners, i, length; 12922 12923 //down while you can, then up and next sibling or up and next sibling until back at root 12924 while ((current = next)) { 12925 event.currentScope = current; 12926 listeners = current.$$listeners[name] || []; 12927 for (i=0, length = listeners.length; i<length; i++) { 12928 // if listeners were deregistered, defragment the array 12929 if (!listeners[i]) { 12930 listeners.splice(i, 1); 12931 i--; 12932 length--; 12933 continue; 12934 } 12935 12936 try { 12937 listeners[i].apply(null, listenerArgs); 12938 } catch(e) { 12939 $exceptionHandler(e); 12940 } 12941 } 12942 12943 // Insanity Warning: scope depth-first traversal 12944 // yes, this code is a bit crazy, but it works and we have tests to prove it! 12945 // this piece should be kept in sync with the traversal in $digest 12946 // (though it differs due to having the extra check for $$listenerCount) 12947 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 12948 (current !== target && current.$$nextSibling)))) { 12949 while(current !== target && !(next = current.$$nextSibling)) { 12950 current = current.$parent; 12951 } 12952 } 12953 } 12954 12955 return event; 12956 } 12957 }; 12958 12959 var $rootScope = new Scope(); 12960 12961 return $rootScope; 12962 12963 12964 function beginPhase(phase) { 12965 if ($rootScope.$$phase) { 12966 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 12967 } 12968 12969 $rootScope.$$phase = phase; 12970 } 12971 12972 function clearPhase() { 12973 $rootScope.$$phase = null; 12974 } 12975 12976 function compileToFn(exp, name) { 12977 var fn = $parse(exp); 12978 assertArgFn(fn, name); 12979 return fn; 12980 } 12981 12982 function decrementListenerCount(current, count, name) { 12983 do { 12984 current.$$listenerCount[name] -= count; 12985 12986 if (current.$$listenerCount[name] === 0) { 12987 delete current.$$listenerCount[name]; 12988 } 12989 } while ((current = current.$parent)); 12990 } 12991 12992 /** 12993 * function used as an initial value for watchers. 12994 * because it's unique we can easily tell it apart from other values 12995 */ 12996 function initWatchVal() {} 12997 }]; 12998} 12999 13000/** 13001 * @description 13002 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 13003 */ 13004function $$SanitizeUriProvider() { 13005 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 13006 imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//; 13007 13008 /** 13009 * @description 13010 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 13011 * urls during a[href] sanitization. 13012 * 13013 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 13014 * 13015 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 13016 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 13017 * regular expression. If a match is found, the original url is
13017written into the dom. Otherwise, 13018 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 13019 * 13020 * @param {RegExp=} regexp New regexp to whitelist urls with. 13021 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 13022 * chaining otherwise. 13023 */ 13024 this.aHrefSanitizationWhitelist = function(regexp) { 13025 if (isDefined(regexp)) { 13026 aHrefSanitizationWhitelist = regexp; 13027 return this; 13028 } 13029 return aHrefSanitizationWhitelist; 13030 }; 13031 13032 13033 /** 13034 * @description 13035 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 13036 * urls during img[src] sanitization. 13037 * 13038 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 13039 * 13040 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 13041 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 13042 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 13043 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 13044 * 13045 * @param {RegExp=} regexp New regexp to whitelist urls with. 13046 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 13047 * chaining otherwise. 13048 */ 13049 this.imgSrcSanitizationWhitelist = function(regexp) { 13050 if (isDefined(regexp)) { 13051 imgSrcSanitizationWhitelist = regexp; 13052 return this; 13053 } 13054 return imgSrcSanitizationWhitelist; 13055 }; 13056 13057 this.$get = function() { 13058 return function sanitizeUri(uri, isImage) { 13059 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 13060 var normalizedVal; 13061 // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case. 13062 if (!msie || msie >= 8 ) { 13063 normalizedVal = urlResolve(uri).href; 13064 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 13065 return 'unsafe:'+normalizedVal; 13066 } 13067 } 13068 return uri; 13069 }; 13070 }; 13071} 13072 13073var $sceMinErr = minErr('$sce'); 13074 13075var SCE_CONTEXTS = { 13076 HTML: 'html', 13077 CSS: 'css', 13078 URL: 'url', 13079 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 13080 // url. (e.g. ng-include, script src, templateUrl) 13081 RESOURCE_URL: 'resourceUrl', 13082 JS: 'js' 13083}; 13084 13085// Helper functions follow. 13086 13087// Copied from: 13088// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 13089// Prereq: s is a string. 13090function escapeForRegexp(s) { 13091 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 13092 replace(/\x08/g, '\\x08'); 13093} 13094 13095 13096function adjustMatcher(matcher) { 13097 if (matcher === 'self') { 13098 return matcher; 13099 } else if (isString(matcher)) { 13100 // Strings match exactly except for 2 wildcards - '*' and '**'. 13101 // '*' matches any character except those from the set ':/.?&'. 13102 // '**' matches any character (like .* in a RegExp). 13103 // More than 2 *'s raises an error as it's ill defined. 13104 if (matcher.indexOf('***') > -1) { 13105 throw $sceMinErr('iwcard', 13106 'Illegal sequence *** in string matcher. String: {0}', matcher); 13107 } 13108 matcher = escapeForRegexp(matcher).
13109 replace('\\*\\*', '.*'). 13110 replace('\\*', '[^:/.?&;]*'); 13111 return new RegExp('^' + matcher + '$'); 13112 } else if (isRegExp(matcher)) { 13113 // The only other type of matcher allowed is a Regexp. 13114 // Match entire URL / disallow partial matches. 13115 // Flags are reset (i.e. no global, ignoreCase or multiline) 13116 return new RegExp('^' + matcher.source + '$'); 13117 } else { 13118 throw $sceMinErr('imatcher', 13119 'Matchers may only be "self", string patterns or RegExp objects'); 13120 } 13121} 13122 13123 13124function adjustMatchers(matchers) { 13125 var adjustedMatchers = []; 13126 if (isDefined(matchers)) { 13127 forEach(matchers, function(matcher) { 13128 adjustedMatchers.push(adjustMatcher(matcher)); 13129 }); 13130 } 13131 return adjustedMatchers; 13132} 13133 13134 13135/** 13136 * @ngdoc service 13137 * @name $sceDelegate 13138 * @kind function 13139 * 13140 * @description 13141 * 13142 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 13143 * Contextual Escaping (SCE)} services to AngularJS. 13144 * 13145 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 13146 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 13147 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 13148 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 13149 * work because `$sce` delegates to `$sceDelegate` for these operations. 13150 * 13151 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 13152 * 13153 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 13154 * can override it completely to change the behavior of `$sce`, the common case would 13155 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 13156 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 13157 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 13158 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 13159 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 13160 */ 13161 13162/** 13163 * @ngdoc provider 13164 * @name $sceDelegateProvider 13165 * @description 13166 * 13167 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 13168 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 13169 * that the URLs used for sourcing Angular templates are safe. Refer {@link 13170 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 13171 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 13172 * 13173 * For the general details about this service in Angular, read the main page for {@link ng.$sce 13174 * Strict Contextual Escaping (SCE)}. 13175 * 13176 * **Example**: Consider the following case. <a name="example"></a> 13177 * 13178 * - your app is hosted at url `http://myapp.example.com/` 13179 * - but some of your templates are hosted on other domains you control such as 13180 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 13181 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 13182 * 13183 * Here is what a secure configuration for this scenario might look like: 13184 * 13185 * ``` 13186 * angular.module('myApp', []).config(function($sceDelegateProvider) { 13187 * $sceDelegateProvider.resourceUrlWhitelist([ 13188 * // Allow same origin resource loads. 13189 * 'self', 13190 * // Allow loading from our assets domain. Notice the difference between * and **. 13191 * 'http://srv*.assets.example.com/**' 13192 * ]); 13193 * 13194 * // The blacklist overrides the whitelist so the open redirect here is blocked. 13195 * $sceDelegateProvider.resourceUrlBlacklist([ 13196 * 'http://myapp.example.com/clickThru**' 13197 * ]); 13198 * }); 13199 * ``` 13200 */ 13201 13202function $SceDelegateProvider() { 13203 this.SCE_CONTEXTS = SCE_CONTEXTS; 13204 13205 // Resource URLs can also be trusted by policy. 13206 var resourceUrlWhitelist = ['self'], 13207 resourceUrlBlacklist = []; 13208 13209 /** 13210 * @ngdoc method 13211 * @name $sceDelegateProvider#resourceUrlWhitelist 13212 * @kind function 13213 * 13214 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 13215 * provided. This must be an array or null. A snapshot of this array is used so further 13216 * changes to the array are ignored. 13217 * 13218 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 13219 * allowed in this array.
vendor: 4,150 bytes, lines 13220-13344
13220 * 13221 * Note: **an empty whitelist array will block all URLs**! 13222 * 13223 * @return {Array} the currently set whitelist array. 13224 * 13225 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 13226 * same origin resource requests. 13227 * 13228 * @description 13229 * Sets/Gets the whitelist of trusted resource URLs. 13230 */ 13231 this.resourceUrlWhitelist = function (value) { 13232 if (arguments.length) { 13233 resourceUrlWhitelist = adjustMatchers(value); 13234 } 13235 return resourceUrlWhitelist; 13236 }; 13237 13238 /** 13239 * @ngdoc method 13240 * @name $sceDelegateProvider#resourceUrlBlacklist 13241 * @kind function 13242 * 13243 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 13244 * provided. This must be an array or null. A snapshot of this array is used so further 13245 * changes to the array are ignored. 13246 * 13247 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 13248 * allowed in this array. 13249 * 13250 * The typical usage for the blacklist is to **block 13251 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 13252 * these would otherwise be trusted but actually return content from the redirected domain. 13253 * 13254 * Finally, **the blacklist overrides the whitelist** and has the final say. 13255 * 13256 * @return {Array} the currently set blacklist array. 13257 * 13258 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 13259 * is no blacklist.) 13260 * 13261 * @description 13262 * Sets/Gets the blacklist of trusted resource URLs. 13263 */ 13264 13265 this.resourceUrlBlacklist = function (value) { 13266 if (arguments.length) { 13267 resourceUrlBlacklist = adjustMatchers(value); 13268 } 13269 return resourceUrlBlacklist; 13270 }; 13271 13272 this.$get = ['$injector', function($injector) { 13273 13274 var htmlSanitizer = function htmlSanitizer(html) { 13275 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 13276 }; 13277 13278 if ($injector.has('$sanitize')) { 13279 htmlSanitizer = $injector.get('$sanitize'); 13280 } 13281 13282 13283 function matchUrl(matcher, parsedUrl) { 13284 if (matcher === 'self') { 13285 return urlIsSameOrigin(parsedUrl); 13286 } else { 13287 // definitely a regex. See adjustMatchers() 13288 return !!matcher.exec(parsedUrl.href); 13289 } 13290 } 13291 13292 function isResourceUrlAllowedByPolicy(url) { 13293 var parsedUrl = urlResolve(url.toString()); 13294 var i, n, allowed = false; 13295 // Ensure that at least one item from the whitelist allows this url. 13296 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 13297 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 13298 allowed = true; 13299 break; 13300 } 13301 } 13302 if (allowed) { 13303 // Ensure that no item from the blacklist blocked this url. 13304 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 13305 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 13306 allowed = false; 13307 break; 13308 } 13309 } 13310 } 13311 return allowed; 13312 } 13313 13314 function generateHolderType(Base) { 13315 var holderType = function TrustedValueHolderType(trustedValue) { 13316 this.$$unwrapTrustedValue = function() { 13317 return trustedValue; 13318 }; 13319 }; 13320 if (Base) { 13321 holderType.prototype = new Base(); 13322 } 13323 holderType.prototype.valueOf = function sceValueOf() { 13324 return this.$$unwrapTrustedValue(); 13325 }; 13326 holderType.prototype.toString = function sceToString() { 13327 return this.$$unwrapTrustedValue().toString(); 13328 }; 13329 return holderType; 13330 } 13331 13332 var trustedValueHolderBase = generateHolderType(), 13333 byType = {}; 13334 13335 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 13336 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 13337 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 13338 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 13339 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 13340 13341 /** 13342 * @ngdoc method 13343 * @name $sceDelegate#trustAs 13344 *
13345 * @description 13346 * Returns an object that is trusted by angular for use in specified strict 13347 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 13348 * attribute interpolation, any dom event binding attribute interpolation 13349 * such as for onclick, etc.) that uses the provided value. 13350 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 13351 * 13352 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 13353 * resourceUrl, html, js and css. 13354 * @param {*} value The value that that should be considered trusted/safe. 13355 * @returns {*} A value that can be used to stand in for the provided `value` in places 13356 * where Angular expects a $sce.trustAs() return value. 13357 */ 13358 function trustAs(type, trustedValue) { 13359 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 13360 if (!Constructor) { 13361 throw $sceMinErr('icontext', 13362 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 13363 type, trustedValue); 13364 } 13365 if (trustedValue === null || trustedValue === undefined || trustedValue === '') { 13366 return trustedValue; 13367 } 13368 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 13369 // mutable objects, we ensure here that the value passed in is actually a string. 13370 if (typeof trustedValue !== 'string') { 13371 throw $sceMinErr('itype', 13372 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 13373 type); 13374 } 13375 return new Constructor(trustedValue); 13376 } 13377 13378 /** 13379 * @ngdoc method 13380 * @name $sceDelegate#valueOf 13381 * 13382 * @description 13383 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 13384 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 13385 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 13386 * 13387 * If the passed parameter is not a value that had been returned by {@link 13388 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 13389 * 13390 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 13391 * call or anything else. 13392 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 13393 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 13394 * `value` unchanged. 13395 */ 13396 function valueOf(maybeTrusted) { 13397 if (maybeTrusted instanceof trustedValueHolderBase) { 13398 return maybeTrusted.$$unwrapTrustedValue(); 13399 } else { 13400 return maybeTrusted; 13401 } 13402 } 13403 13404 /** 13405 * @ngdoc method 13406 * @name $sceDelegate#getTrusted 13407 * 13408 * @description 13409 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 13410 * returns the originally supplied value if the queried context type is a supertype of the 13411 * created type. If this condition isn't satisfied, throws an exception. 13412 * 13413 * @param {string} type The kind of context in which this value is to be used. 13414 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 13415 * `$sceDelegate.trustAs`} call. 13416 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 13417 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 13418 */ 13419 function getTrusted(type, maybeTrusted) { 13420 if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { 13421 return maybeTrusted; 13422 } 13423 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 13424 if (constructor && maybeTrusted instanceof constructor) { 13425 return maybeTrusted.$$unwrapTrustedValue(); 13426 } 13427 // If we get here, then we may only take one of two actions. 13428 // 1. sanitize the value for the requested type, or 13429 // 2. throw an exception. 13430 if (type === SCE_CONTEXTS.RESOURCE_URL) { 13431 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 13432 return maybeTrusted; 13433 } else { 13434 throw $sceMinErr('insecurl', 13435 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 13436 maybeTrusted.toString()); 13437 } 13438 } else if (type === SCE_CONTEXTS.HTML) { 13439 return htmlSanitizer(maybeTrusted); 13440 } 13441 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 13442 } 13443 13444 return { trustAs: trustAs, 13445 getTrusted: getTrusted,
13446 valueOf: valueOf }; 13447 }]; 13448} 13449 13450 13451/** 13452 * @ngdoc provider 13453 * @name $sceProvider 13454 * @description 13455 * 13456 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 13457 * - enable/disable Strict Contextual Escaping (SCE) in a module 13458 * - override the default implementation with a custom delegate 13459 * 13460 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 13461 */ 13462 13463/* jshint maxlen: false*/ 13464 13465/** 13466 * @ngdoc service 13467 * @name $sce 13468 * @kind function 13469 * 13470 * @description 13471 * 13472 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 13473 * 13474 * # Strict Contextual Escaping 13475 * 13476 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 13477 * contexts to result in a value that is marked as safe to use for that context. One example of 13478 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 13479 * to these contexts as privileged or SCE contexts. 13480 * 13481 * As of version 1.2, Angular ships with SCE enabled by default. 13482 * 13483 * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows 13484 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 13485 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
13486 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 13487 * to the top of your HTML document. 13488 * 13489 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 13490 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 13491 * 13492 * Here's an example of a binding in a privileged context: 13493 * 13494 * ``` 13495 * <input ng-model="userHtml"> 13496 * <div ng-bind-html="userHtml"></div> 13497 * ``` 13498 * 13499 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 13500 * disabled, this application allows the user to render arbitrary HTML into the DIV. 13501 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 13502 * bindings. (HTML is just one example of a context where rendering user controlled input creates 13503 * security vulnerabilities.) 13504 * 13505 * For the case of HTML, you might use a library, either on the client side, or on the server side, 13506 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 13507 * 13508 * How would you ensure that every place that used these types of bindings was bound to a value that 13509 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 13510 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 13511 * properties/fields and forgot to update the binding to the sanitized value? 13512 * 13513 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 13514 * determine that something explicitly says it's safe to use a value for binding in that
13515 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 13516 * for those values that you can easily tell are safe - because they were received from your server, 13517 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 13518 * allowing only the files in a specific directory to do this. Ensuring that the internal API 13519 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 13520 * 13521 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 13522 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 13523 * obtain values that will be accepted by SCE / privileged contexts. 13524 * 13525 * 13526 * ## How does it work? 13527 * 13528 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 13529 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 13530 * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 13531 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 13532 * 13533 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 13534 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 13535 * simplified): 13536 * 13537 * ``` 13538 * var ngBindHtmlDirective = ['$sce', function($sce) { 13539 * return function(scope, element, attr) { 13540 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 13541 * element.html(value || ''); 13542 * }); 13543 * }; 13544 * }]; 13545 * ``` 13546 * 13547 * ## Impact on loading templates 13548 * 13549 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 13550 * `templateUrl`'s specified by {@link guide/directive directives}. 13551 * 13552 * By default, Angular only loads templates from the same domain and protocol as the application 13553 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 13554 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 13555 * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 13556 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 13557 * 13558 * *Please note*: 13559 * The browser's 13560 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 13561 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 13562 * policy apply in addition to this and may further restrict whether the template is successfully 13563 * loaded. This means that without the right CORS policy, loading templates from a different domain 13564 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 13565 * browsers. 13566 * 13567 * ## This feels like too much overhead for the developer? 13568 * 13569 * It's important to remember that SCE only applies to interpolation expressions. 13570 * 13571 * If your expressions are constant literals, they're automatically trusted and you don't need to 13572 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 13573 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 13574 * 13575 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 13576 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 13577 * 13578 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 13579 * templates in `ng-include` from your application's domain without having to even know about SCE. 13580 * It blocks loading templates from other domains or loading templates over http from an https 13581 * served document. You can change these by setting your own custom {@link 13582 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 13583 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 13584 * 13585 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 13586 * application that's secure and can be audited to verify that with much more ease than bolting 13587 * security onto an application later. 13588 * 13589 * <a name="contexts"></a> 13590 * ## What trusted context types are supported? 13591 * 13592 * | Context | Notes | 13593 * |---------------------|----------------| 13594 * | `$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. | 13595 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 13596 * | `$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. | 13597 * | `$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. | 13598 * | `$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. | 13599 * 13600 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 13601 *
13602 * Each element in these arrays must be one of the following: 13603 * 13604 * - **'self'** 13605 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 13606 * domain** as the application document using the **same protocol**. 13607 * - **String** (except the special value `'self'`) 13608 * - The string is matched against the full *normalized / absolute URL* of the resource 13609 * being tested (substring matches are not good enough.) 13610 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 13611 * match themselves. 13612 * - `*`: matches zero or more occurrences of any character other than one of the following 6 13613 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use 13614 * in a whitelist. 13615 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 13616 * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. 13617 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 13618 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 13619 * http://foo.example.com/templates/**). 13620 * - **RegExp** (*see caveat below*) 13621 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 13622 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 13623 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 13624 * have good test coverage.). For instance, the use of `.` in the regex is correct only in a 13625 * small number of cases. A `.` character in the regex used when matching the scheme or a 13626 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 13627 * is highly recommended to use the string patterns and only fall back to regular expressions 13628 * if they as a last resort. 13629 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 13630 * matched against the **entire** *normalized / absolute URL* of the resource being tested 13631 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 13632 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 13633 * - If you are generating your JavaScript from some other templating engine (not 13634 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 13635 * remember to escape your regular expression (and be aware that you might need more than 13636 * one level of escaping depending on your templating engine and the way you interpolated 13637 * the value.) Do make use of your platform's escaping mechanism as it might be good 13638 * enough before coding your own. e.g. Ruby has 13639 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 13640 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 13641 * Javascript lacks a similar built in function for escaping. Take a look at Google 13642 * Closure library's [goog.string.regExpEscape(s)]( 13643 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 13644 * 13645 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 13646 * 13647 * ## Show me an example using SCE. 13648 * 13649 * <example module="mySceApp" deps="angular-sanitize.js"> 13650 * <file name="index.html"> 13651 * <div ng-controller="myAppController as myCtrl"> 13652 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 13653 * <b>User comments</b><br> 13654 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 13655 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 13656 * exploit. 13657 * <div class="well"> 13658 * <div ng-repeat="userComment in myCtrl.userComments"> 13659 * <b>{{userComment.name}}</b>: 13660 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 13661 * <br> 13662 * </div>
13663 * </div> 13664 * </div> 13665 * </file> 13666 * 13667 * <file name="script.js"> 13668 * var mySceApp = angular.module('mySceApp', ['ngSanitize']); 13669 * 13670 * mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { 13671 * var self = this; 13672 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 13673 * self.userComments = userComments; 13674 * }); 13675 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 13676 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 13677 * 'sanitization."">Hover over this text.</span>'); 13678 * }); 13679 * </file> 13680 * 13681 * <file name="test_data.json"> 13682 * [ 13683 * { "name": "Alice", 13684 * "htmlComment": 13685 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 13686 * }, 13687 * { "name": "Bob", 13688 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 13689 * } 13690 * ] 13691 * </file> 13692 * 13693 * <file name="protractor.js" type="protractor"> 13694 * describe('SCE doc demo', function() { 13695 * it('should sanitize untrusted values', function() { 13696 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) 13697 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 13698 * }); 13699 * 13700 * it('should NOT sanitize explicitly trusted values', function() { 13701 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 13702 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 13703 * 'sanitization."">Hover over this text.</span>'); 13704 * }); 13705 * }); 13706 * </file> 13707 * </example> 13708 * 13709 * 13710 * 13711 * ## Can I disable SCE completely? 13712 * 13713 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 13714 * for little coding overhead. It will be much harder to take an SCE disabled application and 13715 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 13716 * for cases where you have a lot of existing code that was written before SCE was introduced and 13717 * you're migrating them a module at a time. 13718 * 13719 * That said, here's how you can completely disable SCE: 13720 * 13721 * ``` 13722 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 13723 * // Completely disable SCE. For demonstration purposes only! 13724 * // Do not use in new projects. 13725 * $sceProvider.enabled(false); 13726 * }); 13727 * ``` 13728 * 13729 */ 13730/* jshint maxlen: 100 */ 13731 13732function $SceProvider() { 13733 var enabled = true; 13734 13735 /** 13736 * @ngdoc method 13737 * @name $sceProvider#enabled 13738 * @kind function 13739 * 13740 * @param {boolean=} value If provided, then enables/disables SCE. 13741 * @return {boolean} true if SCE is enabled, false otherwise. 13742 * 13743 * @description 13744 * Enables/disables SCE and returns the current value. 13745 */ 13746 this.enabled = function (value) { 13747 if (arguments.length) { 13748 enabled = !!value; 13749 } 13750 return enabled; 13751 }; 13752 13753 13754 /* Design notes on the default implementation for SCE. 13755 * 13756 * The API contract for the SCE delegate 13757 * ------------------------------------- 13758 * The SCE delegate object must provide the following 3 methods: 13759 * 13760 * - trustAs(contextEnum, value) 13761 * This method is used to tell the SCE service that the provided value is OK to use in the 13762 * contexts specified by contextEnum. It must return an object that will be accepted by 13763 * getTrusted() for a compatible contextEnum and return this value. 13764 * 13765 * - valueOf(value) 13766 * For values that were not produced by trustAs(), return them as is. For values that were 13767 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 13768 * trustAs is wrapping the given values into some type, this operation unwraps it when given 13769 * such a value. 13770 * 13771 * - getTrusted(contextEnum, value) 13772 * This function should return the a value that is safe to use in the context specified by 13773 * contextEnum or throw and exception otherwise. 13774 * 13775 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 13776 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 13777 * instance, an implementation could maintain a registry of all trusted objects by context. In 13778 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 13779 * return the same object passed in if it was found in the registry under a compatible context or 13780 * throw an exception otherwise. An implementation might only wrap values some of the time based 13781 * on some criteria. getTrusted() might return a value and not throw an exception for special 13782 * constants or objects even if not wrapped. All such implementations fulfill this contract. 13783 * 13784 * 13785 * A note on the inheritance model for SCE contexts 13786 * ------------------------------------------------ 13787 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 13788 * is purely an implementation details. 13789 * 13790 * The contract is simply this: 13791 * 13792 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 13793 * will also succeed. 13794 * 13795 * Inheritance happens to capture this in a natural way. In some future, we
13796 * may not use inheritance anymore. That is OK because no code outside of 13797 * sce.js and sceSpecs.js would need to be aware of this detail. 13798 */ 13799 13800 this.$get = ['$parse', '$sniffer', '$sceDelegate', function( 13801 $parse, $sniffer, $sceDelegate) { 13802 // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows 13803 // the "expression(javascript expression)" syntax which is insecure. 13804 if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { 13805 throw $sceMinErr('iequirks', 13806 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + 13807 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 13808 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 13809 } 13810 13811 var sce = shallowCopy(SCE_CONTEXTS); 13812 13813 /** 13814 * @ngdoc method 13815 * @name $sce#isEnabled 13816 * @kind function 13817 * 13818 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 13819 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 13820 * 13821 * @description 13822 * Returns a boolean indicating if SCE is enabled. 13823 */ 13824 sce.isEnabled = function () { 13825 return enabled; 13826 }; 13827 sce.trustAs = $sceDelegate.trustAs; 13828 sce.getTrusted = $sceDelegate.getTrusted; 13829 sce.valueOf = $sceDelegate.valueOf; 13830 13831 if (!enabled) { 13832 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 13833 sce.valueOf = identity; 13834 } 13835 13836 /** 13837 * @ngdoc method 13838 * @name $sce#parseAs 13839 * 13840 * @description 13841 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 13842 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 13843 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 13844 * *result*)} 13845 * 13846 * @param {string} type The kind of SCE context in which this result will be used. 13847 * @param {string} expression String expression to compile. 13848 * @returns {function(context, locals)} a function which represents the compiled expression: 13849 * 13850 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13851 * are evaluated against (typically a scope object). 13852 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13853 * `context`. 13854 */ 13855 sce.parseAs = function sceParseAs(type, expr) { 13856 var parsed = $parse(expr); 13857 if (parsed.literal && parsed.constant) { 13858 return parsed; 13859 } else { 13860 return function sceParseAsTrusted(self, locals) { 13861 return sce.getTrusted(type, parsed(self, locals)); 13862 }; 13863 } 13864 }; 13865 13866 /** 13867 * @ngdoc method 13868 * @name $sce#trustAs 13869 * 13870 * @description 13871 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 13872 * returns an object that is trusted by angular for use in specified strict contextual 13873 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 13874 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 13875 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 13876 * escaping. 13877 * 13878 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 13879 * resource_url, html, js and css. 13880 * @param {*} value The value that that should be considered trusted/safe. 13881 * @returns {*} A value that can be used to stand in for the provided `value` in places 13882 * where Angular expects a $sce.trustAs() return value. 13883 */ 13884 13885 /** 13886 * @ngdoc method 13887 * @name $sce#trustAsHtml 13888 * 13889 * @description 13890 * Shorthand method. `$sce.trustAsHtml(value)` â 13891 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 13892 * 13893 * @param {*} value The value to trustAs. 13894 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 13895 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 13896 * only accept expressions that are either literal constants or are the 13897 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 13898 */ 13899 13900 /** 13901 * @ngdoc method 13902 * @name $sce#trustAsUrl 13903 *
13904 * @description 13905 * Shorthand method. `$sce.trustAsUrl(value)` â 13906 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 13907 * 13908 * @param {*} value The value to trustAs. 13909 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 13910 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 13911 * only accept expressions that are either literal constants or are the 13912 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 13913 */ 13914 13915 /** 13916 * @ngdoc method 13917 * @name $sce#trustAsResourceUrl 13918 * 13919 * @description 13920 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 13921 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 13922 * 13923 * @param {*} value The value to trustAs. 13924 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 13925 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 13926 * only accept expressions that are either literal constants or are the return 13927 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 13928 */ 13929 13930 /** 13931 * @ngdoc method 13932 * @name $sce#trustAsJs 13933 * 13934 * @description 13935 * Shorthand method. `$sce.trustAsJs(value)` â 13936 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 13937 * 13938 * @param {*} value The value to trustAs. 13939 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 13940 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 13941 * only accept expressions that are either literal constants or are the 13942 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 13943 */ 13944 13945 /** 13946 * @ngdoc method 13947 * @name $sce#getTrusted 13948 * 13949 * @description 13950 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 13951 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 13952 * originally supplied value if the queried context type is a supertype of the created type. 13953 * If this condition isn't satisfied, throws an exception. 13954 * 13955 * @param {string} type The kind of context in which this value is to be used. 13956 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 13957 * call. 13958 * @returns {*} The value the was originally provided to 13959 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 13960 * Otherwise, throws an exception. 13961 */ 13962 13963 /** 13964 * @ngdoc method 13965 * @name $sce#getTrustedHtml 13966 * 13967 * @description 13968 * Shorthand method. `$sce.getTrustedHtml(value)` â 13969 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 13970 * 13971 * @param {*} value The value to pass to `$sce.getTrusted`. 13972 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 13973 */ 13974 13975 /** 13976 * @ngdoc method 13977 * @name $sce#getTrustedCss 13978 * 13979 * @description 13980 * Shorthand method. `$sce.getTrustedCss(value)` â 13981 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 13982 * 13983 * @param {*} value The value to pass to `$sce.getTrusted`. 13984 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 13985 */ 13986 13987 /** 13988 * @ngdoc method 13989 * @name $sce#getTrustedUrl 13990 * 13991 * @description 13992 * Shorthand method. `$sce.getTrustedUrl(value)` â 13993 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 13994 * 13995 * @param {*} value The value to pass to `$sce.getTrusted`. 13996 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 13997 */ 13998 13999 /** 14000 * @ngdoc method 14001 * @name $sce#getTrustedResourceUrl 14002 * 14003 * @description 14004 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 14005 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 14006 * 14007 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 14008 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 14009 */ 14010 14011 /** 14012 * @ngdoc method 14013 * @name $sce#getTrustedJs 14014 *
14015 * @description 14016 * Shorthand method. `$sce.getTrustedJs(value)` â 14017 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 14018 * 14019 * @param {*} value The value to pass to `$sce.getTrusted`. 14020 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 14021 */ 14022 14023 /** 14024 * @ngdoc method 14025 * @name $sce#parseAsHtml 14026 * 14027 * @description 14028 * Shorthand method. `$sce.parseAsHtml(expression string)` â 14029 * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} 14030 * 14031 * @param {string} expression String expression to compile. 14032 * @returns {function(context, locals)} a function which represents the compiled expression: 14033 * 14034 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14035 * are evaluated against (typically a scope object). 14036 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14037 * `context`. 14038 */ 14039 14040 /** 14041 * @ngdoc method 14042 * @name $sce#parseAsCss 14043 * 14044 * @description 14045 * Shorthand method. `$sce.parseAsCss(value)` â 14046 * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} 14047 * 14048 * @param {string} expression String expression to compile. 14049 * @returns {function(context, locals)} a function which represents the compiled expression: 14050 * 14051 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14052 * are evaluated against (typically a scope object). 14053 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14054 * `context`. 14055 */ 14056 14057 /** 14058 * @ngdoc method 14059 * @name $sce#parseAsUrl 14060 * 14061 * @description 14062 * Shorthand method. `$sce.parseAsUrl(value)` â 14063 * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} 14064 * 14065 * @param {string} expression String expression to compile. 14066 * @returns {function(context, locals)} a function which represents the compiled expression: 14067 * 14068 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14069 * are evaluated against (typically a scope object). 14070 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14071 * `context`. 14072 */ 14073 14074 /** 14075 * @ngdoc method 14076 * @name $sce#parseAsResourceUrl 14077 * 14078 * @description 14079 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 14080 * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} 14081 * 14082 * @param {string} expression String expression to compile. 14083 * @returns {function(context, locals)} a function which represents the compiled expression: 14084 * 14085 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14086 * are evaluated against (typically a scope object). 14087 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14088 * `context`. 14089 */ 14090 14091 /** 14092 * @ngdoc method 14093 * @name $sce#parseAsJs 14094 * 14095 * @description 14096 * Shorthand method. `$sce.parseAsJs(value)` â 14097 * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} 14098 * 14099 * @param {string} expression String expression to compile. 14100 * @returns {function(context, locals)} a function which represents the compiled expression: 14101 * 14102 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14103 * are evaluated against (typically a scope object). 14104 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14105 * `context`. 14106 */ 14107 14108 // Shorthand delegations. 14109 var parse = sce.parseAs, 14110 getTrusted = sce.getTrusted, 14111 trustAs = sce.trustAs; 14112 14113 forEach(SCE_CONTEXTS, function (enumValue, name) { 14114 var lName = lowercase(name); 14115 sce[camelCase("parse_as_" + lName)] = function (expr) { 14116 return parse(enumValue, expr); 14117 }; 14118 sce[camelCase("get_trusted_" + lName)] = function (value) { 14119 return getTrusted(enumValue, value); 14120 }; 14121 sce[camelCase("trust_as_" + lName)] = function (value) { 14122 return trustAs(enumValue, value); 14123 }; 14124 }); 14125 14126 return sce; 14127 }]; 14128} 14129 14130/** 14131 * !!! This is an undocumented "private" service !!! 14132 * 14133 * @name $sniffer 14134 * @requires $window 14135 * @requires $document 14136 * 14137 * @property {boolean} history Does the browser support html5 history api ? 14138 * @property {boolean} hashchange Does the browser support hashchange event ? 14139 * @property {boolean} transitions Does the browser support CSS transition events ? 14140 * @property {boolean} animations Does the browser support CSS animation events ? 14141 * 14142 * @description 14143 * This is very simple implementation of testing browser's features. 14144 */ 14145function $SnifferProvider() { 14146 this.$get = ['$window', '$document', function($window, $document) { 14147 var eventSupport = {}, 14148 android = 14149 int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 14150 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 14151 document = $document[0] || {}, 14152 documentMode = document.documentMode, 14153 vendorPrefix, 14154 vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, 14155 bodyStyle = document.body && document.body.style, 14156 transitions = false, 14157 animations = false, 14158 match; 14159 14160 if (bodyStyle) { 14161 for(var prop in bodyStyle) {
14162 if(match = vendorRegex.exec(prop)) { 14163 vendorPrefix = match[0]; 14164 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 14165 break; 14166 } 14167 } 14168 14169 if(!vendorPrefix) { 14170 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 14171 } 14172 14173 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 14174 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 14175 14176 if (android && (!transitions||!animations)) { 14177 transitions = isString(document.body.style.webkitTransition); 14178 animations = isString(document.body.style.webkitAnimation); 14179 } 14180 } 14181 14182 14183 return { 14184 // Android has history.pushState, but it does not update location correctly 14185 // so let's not use the history API at all. 14186 // http://code.google.com/p/android/issues/detail?id=17471 14187 // https://github.com/angular/angular.js/issues/904 14188 14189 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 14190 // so let's not use the history API also 14191 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 14192 // jshint -W018 14193 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 14194 // jshint +W018 14195 hashchange: 'onhashchange' in $window && 14196 // IE8 compatible mode lies 14197 (!documentMode || documentMode > 7), 14198 hasEvent: function(event) { 14199 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 14200 // it. In particular the event is not fired when backspace or delete key are pressed or 14201 // when cut operation is performed. 14202 if (event == 'input' && msie == 9) return false; 14203 14204 if (isUndefined(eventSupport[event])) { 14205 var divElm = document.createElement('div'); 14206 eventSupport[event] = 'on' + event in divElm; 14207 } 14208 14209 return eventSupport[event]; 14210 }, 14211 csp: csp(), 14212 vendorPrefix: vendorPrefix, 14213 transitions : transitions, 14214 animations : animations, 14215 android: android, 14216 msie : msie, 14217 msieDocumentMode: documentMode 14218 }; 14219 }]; 14220} 14221 14222function $TimeoutProvider() { 14223 this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', 14224 function($rootScope, $browser, $q, $exceptionHandler) { 14225 var deferreds = {}; 14226 14227 14228 /** 14229 * @ngdoc service 14230 * @name $timeout 14231 * 14232 * @description 14233 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 14234 * block and delegates any exceptions to 14235 * {@link ng.$exceptionHandler $exceptionHandler} service. 14236 * 14237 * The return value of registering a timeout function is a promise, which will be resolved when 14238 * the timeout is reached and the timeout function is executed. 14239 * 14240 * To cancel a timeout request, call `$timeout.cancel(promise)`. 14241 * 14242 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 14243 * synchronously flush the queue of deferred functions. 14244 * 14245 * @param {function()} fn A function, whose execution should be delayed. 14246 * @param {number=} [delay=0] Delay in milliseconds. 14247 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 14248 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 14249 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 14250 * promise will be resolved with is the return value of the `fn` function. 14251 * 14252 */ 14253 function timeout(fn, delay, invokeApply) { 14254 var deferred = $q.defer(), 14255 promise = deferred.promise, 14256 skipApply = (isDefined(invokeApply) && !invokeApply), 14257 timeoutId; 14258 14259 timeoutId = $browser.defer(function() { 14260 try { 14261 deferred.resolve(fn()); 14262 } catch(e) { 14263 deferred.reject(e); 14264 $exceptionHandler(e); 14265 } 14266 finally { 14267 delete deferreds[promise.$$timeoutId]; 14268 } 14269 14270 if (!skipApply) $rootScope.$apply(); 14271 }, delay); 14272 14273 promise.$$timeoutId = timeoutId; 14274 deferreds[timeoutId] = deferred; 14275 14276 return promise; 14277 } 14278 14279 14280 /** 14281 * @ngdoc method 14282 * @name $timeout#cancel 14283 *
14284 * @description 14285 * Cancels a task associated with the `promise`. As a result of this, the promise will be 14286 * resolved with a rejection. 14287 * 14288 * @param {Promise=} promise Promise returned by the `$timeout` function. 14289 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 14290 * canceled. 14291 */ 14292 timeout.cancel = function(promise) { 14293 if (promise && promise.$$timeoutId in deferreds) { 14294 deferreds[promise.$$timeoutId].reject('canceled'); 14295 delete deferreds[promise.$$timeoutId]; 14296 return $browser.defer.cancel(promise.$$timeoutId); 14297 } 14298 return false; 14299 }; 14300 14301 return timeout; 14302 }]; 14303} 14304 14305// NOTE: The usage of window and document instead of $window and $document here is 14306// deliberate. This service depends on the specific behavior of anchor nodes created by the 14307// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 14308// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 14309// doesn't know about mocked locations and resolves URLs to the real document - which is 14310// exactly the behavior needed here. There is little value is mocking these out for this 14311// service. 14312var urlParsingNode = document.createElement("a"); 14313var originUrl = urlResolve(window.location.href, true); 14314 14315 14316/** 14317 * 14318 * Implementation Notes for non-IE browsers 14319 * ---------------------------------------- 14320 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 14321 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 14322 * URL will be resolved into an absolute URL in the context of the application document. 14323 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 14324 * properties are all populated to reflect the normalized URL. This approach has wide 14325 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 14326 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 14327 * 14328 * Implementation Notes for IE 14329 * --------------------------- 14330 * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other 14331 * browsers. However, the parsed components will not be set if the URL assigned did not specify 14332 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 14333 * work around that by performing the parsing in a 2nd step by taking a previously normalized 14334 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 14335 * properties such as protocol, hostname, port, etc. 14336 * 14337 * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one 14338 * uses the inner HTML approach to assign the URL as part of an HTML snippet - 14339 * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. 14340 * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. 14341 * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that 14342 * method and IE < 8 is unsupported. 14343 * 14344 * References: 14345 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 14346 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 14347 * http://url.spec.whatwg.org/#urlutils 14348 * https://github.com/angular/angular.js/pull/2902 14349 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 14350 * 14351 * @kind function 14352 * @param {string} url The URL to be parsed. 14353 * @description Normalizes and parses a URL. 14354 * @returns {object} Returns the normalized URL as a dictionary. 14355 * 14356 * | member name | Description | 14357 * |---------------|----------------| 14358 * | href | A normalized version of the provided URL if it was not an absolute URL | 14359 * | protocol | The protocol including the trailing colon | 14360 * | host | The host and port (if the port is non-default) of the normalizedUrl | 14361 * | search | The search params, minus the question mark | 14362 * | hash | The hash string, minus the hash symbol 14363 * | hostname | The hostname 14364 * | port | The port, without ":" 14365 * | pathname | The pathname, beginning with "/" 14366 * 14367 */ 14368function urlResolve(url, base) { 14369 var href = url; 14370 14371 if (msie) { 14372 // Normalize before parse. Refer Implementation Notes on why this is 14373 // done in two steps on IE. 14374 urlParsingNode.setAttribute("href", href); 14375 href = urlParsingNode.href; 14376 } 14377 14378 urlParsingNode.setAttribute('href', href); 14379 14380 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 14381 return { 14382 href: urlParsingNode.href, 14383 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 14384 host: urlParsingNode.host, 14385 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 14386 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 14387 hostname: urlParsingNode.hostname, 14388 port: urlParsingNode.port, 14389 pathname: (urlParsingNode.pathname.charAt(0) === '/') 14390 ? urlParsingNode.pathname 14391 : '/' + urlParsingNode.pathname 14392 }; 14393} 14394 14395/** 14396 * Parse a request URL and determine whether this is a same-origin request as the application document. 14397 * 14398 * @param {string|object} requestUrl The url of the request as a string that will be resolved 14399 * or a parsed URL object. 14400 * @returns {boolean} Whether the request is for the same origin as the application document. 14401 */ 14402function urlIsSameOrigin(requestUrl) { 14403 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 14404 return (parsed.protocol === originUrl.protocol && 14405 parsed.host === originUrl.host); 14406} 14407 14408/** 14409 * @ngdoc service 14410 * @name $window 14411 * 14412 * @description 14413 * A reference to the browser's `window` object. While `window` 14414 * is globally available in JavaScript, it causes testability problems, because 14415 * it is a global variable. In angular we always refer to it through the 14416 * `$window` service, so it may be overridden, removed or mocked for testing. 14417 * 14418 * Expressions, like the one defined for the `ngClick` directive in the example 14419 * below, are evaluated with respect to the current scope. Therefore, there is 14420 * no risk of inadvertently coding in a dependency on a global value in such an 14421 * expression. 14422 * 14423 * @example 14424 <example module="windowExample"> 14425 <file name="index.html"> 14426 <script> 14427 angular.module('windowExample', [])
14428 .controller('ExampleController', ['$scope', '$window', function ($scope, $window) { 14429 $scope.greeting = 'Hello, World!'; 14430 $scope.doGreeting = function(greeting) { 14431 $window.alert(greeting); 14432 }; 14433 }]); 14434 </script> 14435 <div ng-controller="ExampleController"> 14436 <input type="text" ng-model="greeting" /> 14437 <button ng-click="doGreeting(greeting)">ALERT</button> 14438 </div> 14439 </file> 14440 <file name="protractor.js" type="protractor"> 14441 it('should display the greeting in the input box', function() { 14442 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 14443 // If we click the button it will block the test runner 14444 // element(':button').click(); 14445 }); 14446 </file> 14447 </example> 14448 */ 14449function $WindowProvider(){ 14450 this.$get = valueFn(window); 14451} 14452 14453/* global currencyFilter: true, 14454 dateFilter: true, 14455 filterFilter: true, 14456 jsonFilter: true, 14457 limitToFilter: true, 14458 lowercaseFilter: true, 14459 numberFilter: true, 14460 orderByFilter: true, 14461 uppercaseFilter: true, 14462 */ 14463 14464/** 14465 * @ngdoc provider 14466 * @name $filterProvider 14467 * @description 14468 * 14469 * Filters are just functions which transform input to an output. However filters need to be 14470 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 14471 * annotated with dependencies and is responsible for creating a filter function. 14472 * 14473 * ```js 14474 * // Filter registration 14475 * function MyModule($provide, $filterProvider) { 14476 * // create a service to demonstrate injection (not always needed) 14477 * $provide.value('greet', function(name){ 14478 * return 'Hello ' + name + '!'; 14479 * }); 14480 * 14481 * // register a filter factory which uses the 14482 * // greet service to demonstrate DI. 14483 * $filterProvider.register('greet', function(greet){ 14484 * // return the filter function which uses the greet service 14485 * // to generate salutation 14486 * return function(text) { 14487 * // filters need to be forgiving so check input validity 14488 * return text && greet(text) || text; 14489 * }; 14490 * }); 14491 * } 14492 * ``` 14493 * 14494 * The filter function is registered with the `$injector` under the filter name suffix with 14495 * `Filter`. 14496 * 14497 * ```js 14498 * it('should be the same instance', inject( 14499 * function($filterProvider) { 14500 * $filterProvider.register('reverse', function(){ 14501 * return ...; 14502 * }); 14503 * }, 14504 * function($filter, reverseFilter) { 14505 * expect($filter('reverse')).toBe(reverseFilter); 14506 * }); 14507 * ``` 14508 * 14509 * 14510 * For more information about how angular filters work, and how to create your own filters, see 14511 * {@link guide/filter Filters} in the Angular Developer Guide. 14512 */ 14513/** 14514 * @ngdoc method 14515 * @name $filterProvider#register 14516 * @description 14517 * Register filter factory function. 14518 * 14519 * @param {String} name Name of the filter. 14520 * @param {Function} fn The filter factory function which is injectable. 14521 */ 14522 14523 14524/** 14525 * @ngdoc service 14526 * @name $filter 14527 * @kind function 14528 * @description 14529 * Filters are used for formatting data displayed to the user. 14530 * 14531 * The general syntax in templates is as follows: 14532 * 14533 * {{ expression [| filter_name[:parameter_value] ... ] }} 14534 * 14535 * @param {String} name Name of the filter function to retrieve 14536 * @return {Function} the filter function 14537 * @example 14538 <example name="$filter" module="filterExample"> 14539 <file name="index.html"> 14540 <div ng-controller="MainCtrl"> 14541 <h3>{{ originalText }}</h3> 14542 <h3>{{ filteredText }}</h3> 14543 </div> 14544 </file> 14545 14546 <file name="script.js"> 14547 angular.module('filterExample', []) 14548 .controller('MainCtrl', function($scope, $filter) { 14549 $scope.originalText = 'hello'; 14550 $scope.filteredText = $filter('uppercase')($scope.originalText); 14551 }); 14552 </file> 14553 </example> 14554 */ 14555$FilterProvider.$inject = ['$provide']; 14556function $FilterProvider($provide) { 14557 var suffix = 'Filter'; 14558 14559 /** 14560 * @ngdoc method 14561 * @name $controllerProvider#register 14562 * @param {string|Object} name Name of the filter function, or an object map of filters where 14563 * the keys are the filter names and the values are the filter factories. 14564 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 14565 * of the registered filter instances. 14566 */ 14567 function register(name, factory) { 14568 if(isObject(name)) { 14569 var filters = {};
14570 forEach(name, function(filter, key) { 14571 filters[key] = register(key, filter); 14572 }); 14573 return filters; 14574 } else { 14575 return $provide.factory(name + suffix, factory); 14576 } 14577 } 14578 this.register = register; 14579 14580 this.$get = ['$injector', function($injector) { 14581 return function(name) { 14582 return $injector.get(name + suffix); 14583 }; 14584 }]; 14585 14586 //////////////////////////////////////// 14587 14588 /* global 14589 currencyFilter: false, 14590 dateFilter: false, 14591 filterFilter: false, 14592 jsonFilter: false, 14593 limitToFilter: false, 14594 lowercaseFilter: false, 14595 numberFilter: false, 14596 orderByFilter: false, 14597 uppercaseFilter: false, 14598 */ 14599 14600 register('currency', currencyFilter); 14601 register('date', dateFilter); 14602 register('filter', filterFilter); 14603 register('json', jsonFilter); 14604 register('limitTo', limitToFilter); 14605 register('lowercase', lowercaseFilter); 14606 register('number', numberFilter); 14607 register('orderBy', orderByFilter); 14608 register('uppercase', uppercaseFilter); 14609} 14610 14611/** 14612 * @ngdoc filter 14613 * @name filter 14614 * @kind function 14615 * 14616 * @description 14617 * Selects a subset of items from `array` and returns it as a new array. 14618 * 14619 * @param {Array} array The source array. 14620 * @param {string|Object|function()} expression The predicate to be used for selecting items from 14621 * `array`. 14622 * 14623 * Can be one of: 14624 * 14625 * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against 14626 * the contents of the `array`. All strings or objects with string properties in `array` that contain this string 14627 * will be returned. The predicate can be negated by prefixing the string with `!`. 14628 * 14629 * - `Object`: A pattern object can be used to filter specific properties on objects contained 14630 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 14631 * which have property `name` containing "M" and property `phone` containing "1". A special 14632 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 14633 * property of the object. That's equivalent to the simple substring match with a `string` 14634 * as described above. 14635 * 14636 * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is 14637 * called for each element of `array`. The final result is an array of those elements that 14638 * the predicate returned true for. 14639 * 14640 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 14641 * determining if the expected value (from the filter expression) and actual value (from 14642 * the object in the array) should be considered a match. 14643 * 14644 * Can be one of: 14645 * 14646 * - `function(actual, expected)`: 14647 * The function will be given the object value and the predicate value to compare and 14648 * should return true if the item should be included in filtered result. 14649 * 14650 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. 14651 * this is essentially strict comparison of expected and actual. 14652 * 14653 * - `false|undefined`: A short hand for a function which will look for a substring match in case 14654 * insensitive way. 14655 * 14656 * @example 14657 <example> 14658 <file name="index.html"> 14659 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 14660 {name:'Mary', phone:'800-BIG-MARY'}, 14661 {name:'Mike', phone:'555-4321'}, 14662 {name:'Adam', phone:'555-5678'}, 14663 {name:'Julie', phone:'555-8765'}, 14664 {name:'Juliette', phone:'555-5678'}]"></div> 14665 14666 Search: <input ng-model="searchText"> 14667 <table id="searchTextResults"> 14668 <tr><th>Name</th><th>Phone</th></tr> 14669 <tr ng-repeat="friend in friends | filter:searchText"> 14670 <td>{{friend.name}}</td> 14671 <td>{{friend.phone}}</td> 14672 </tr> 14673 </table> 14674 <hr> 14675 Any: <input ng-model="search.$"> <br> 14676 Name only <input ng-model="search.name"><br> 14677 Phone only <input ng-model="search.phone"><br> 14678 Equality <input type="checkbox" ng-model="strict"><br> 14679 <table id="searchObjResults"> 14680 <tr><th>
14680Name</th><th>Phone</th></tr> 14681 <tr ng-repeat="friendObj in friends | filter:search:strict"> 14682 <td>{{friendObj.name}}</td> 14683 <td>{{friendObj.phone}}</td> 14684 </tr> 14685 </table> 14686 </file> 14687 <file name="protractor.js" type="protractor"> 14688 var expectFriendNames = function(expectedNames, key) { 14689 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 14690 arr.forEach(function(wd, i) { 14691 expect(wd.getText()).toMatch(expectedNames[i]); 14692 }); 14693 }); 14694 }; 14695 14696 it('should search across all fields when filtering with a string', function() { 14697 var searchText = element(by.model('searchText')); 14698 searchText.clear(); 14699 searchText.sendKeys('m'); 14700 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 14701 14702 searchText.clear(); 14703 searchText.sendKeys('76'); 14704 expectFriendNames(['John', 'Julie'], 'friend'); 14705 }); 14706 14707 it('should search in specific fields when filtering with a predicate object', function() { 14708 var searchAny = element(by.model('search.$')); 14709 searchAny.clear(); 14710 searchAny.sendKeys('i'); 14711 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 14712 }); 14713 it('should use a equal comparison when comparator is true', function() { 14714 var searchName = element(by.model('search.name')); 14715 var strict = element(by.model('strict')); 14716 searchName.clear(); 14717 searchName.sendKeys('Julie'); 14718 strict.click(); 14719 expectFriendNames(['Julie'], 'friendObj'); 14720 }); 14721 </file> 14722 </example> 14723 */ 14724function filterFilter() { 14725 return function(array, expression, comparator) { 14726 if (!isArray(array)) return array; 14727 14728 var comparatorType = typeof(comparator), 14729 predicates = []; 14730 14731 predicates.check = function(value) { 14732 for (var j = 0; j < predicates.length; j++) { 14733 if(!predicates[j](value)) { 14734 return false; 14735 } 14736 } 14737 return true; 14738 }; 14739 14740 if (comparatorType !== 'function') { 14741 if (comparatorType === 'boolean' && comparator) { 14742 comparator = function(obj, text) { 14743 return angular.equals(obj, text); 14744 }; 14745 } else { 14746 comparator = function(obj, text) { 14747 if (obj && text && typeof obj === 'object' && typeof text === 'object') { 14748 for (var objKey in obj) { 14749 if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) && 14750 comparator(obj[objKey], text[objKey])) { 14751 return true; 14752 } 14753 } 14754 return false; 14755 } 14756 text = (''+text).toLowerCase(); 14757 return (''+obj).toLowerCase().indexOf(text) > -1; 14758 }; 14759 } 14760 } 14761 14762 var search = function(obj, text){ 14763 if (typeof text == 'string' && text.charAt(0) === '!') { 14764 return !search(obj, text.substr(1)); 14765 } 14766 switch (typeof obj) { 14767 case "boolean": 14768 case "number": 14769 case "string": 14770 return comparator(obj, text); 14771 case "object": 14772 switch (typeof text) { 14773 case "object": 14774 return comparator(obj, text); 14775 default: 14776 for ( var objKey in obj) { 14777 if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { 14778 return true; 14779 } 14780 } 14781 break; 14782 } 14783 return false; 14784 case "array": 14785 for ( var i = 0; i < obj.length; i++) { 14786 if (search(obj[i], text)) { 14787 return true; 14788 } 14789 } 14790 return false; 14791 default: 14792 return false; 14793 } 14794 }; 14795 switch (typeof expression) { 14796 case "boolean": 14797 case "number": 14798 case "string": 14799 // Set up expression object and fall through 14800 expression = {$:expression}; 14801 // jshint -W086 14802 case "object": 14803 // jshint +W086 14804 for (var key in expression) { 14805 (function(path) { 14806 if (typeof expression[path] === 'undefined') return; 14807 predicates.push(function(value) { 14808 return search(path == '$' ? value : (value && value[path]), expression[path]); 14809 }); 14810 })(key); 14811 } 14812 break; 14813 case 'function': 14814 predicates.push(expression); 14815 break; 14816 default: 14817 return array; 14818 } 14819 var filtered = []; 14820 for ( var j = 0; j < array.length; j++) { 14821 var value = array[j]; 14822 if (predicates.check(value)) { 14823 filtered.push(value); 14824 } 14825 } 14826 return filtered; 14827 }; 14828} 14829 14830/** 14831 * @ngdoc filter 14832 * @name currency 14833 * @kind function 14834 * 14835 * @description 14836 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 14837 * symbol for current locale is used. 14838 * 14839 * @param {number} amount Input to filter. 14840 * @param {string=} symbol Currency symbol or identifier to be displayed. 14841 * @returns {string} Formatted number. 14842 * 14843 * 14844 * @example 14845 <example module="currencyExample"> 14846 <file name="index.html"> 14847 <script>
14848 angular.module('currencyExample', []) 14849 .controller('ExampleController', ['$scope', function($scope) { 14850 $scope.amount = 1234.56; 14851 }]); 14852 </script> 14853 <div ng-controller="ExampleController"> 14854 <input type="number" ng-model="amount"> <br> 14855 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 14856 custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span> 14857 </div> 14858 </file> 14859 <file name="protractor.js" type="protractor"> 14860 it('should init with 1234.56', function() { 14861 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 14862 expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56'); 14863 }); 14864 it('should update', function() { 14865 if (browser.params.browser == 'safari') { 14866 // Safari does not understand the minus key. See 14867 // https://github.com/angular/protractor/issues/481 14868 return; 14869 } 14870 element(by.model('amount')).clear(); 14871 element(by.model('amount')).sendKeys('-1234'); 14872 expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); 14873 expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)'); 14874 }); 14875 </file> 14876 </example> 14877 */ 14878currencyFilter.$inject = ['$locale']; 14879function currencyFilter($locale) { 14880 var formats = $locale.NUMBER_FORMATS; 14881 return function(amount, currencySymbol){ 14882 if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; 14883 return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). 14884 replace(/\u00A4/g, currencySymbol); 14885 }; 14886} 14887 14888/** 14889 * @ngdoc filter 14890 * @name number 14891 * @kind function 14892 * 14893 * @description 14894 * Formats a number as text. 14895 * 14896 * If the input is not a number an empty string is returned. 14897 * 14898 * @param {number|string} number Number to format. 14899 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 14900 * If this is not provided then the fraction size is computed from the current locale's number 14901 * formatting pattern. In the case of the default locale, it will be 3. 14902 * @returns {string} Number rounded to decimalPlaces and places a â,â after each third digit. 14903 * 14904 * @example 14905 <example module="numberFilterExample"> 14906 <file name="index.html"> 14907 <script> 14908 angular.module('numberFilterExample', []) 14909 .controller('ExampleController', ['$scope', function($scope) { 14910 $scope.val = 1234.56789; 14911 }]); 14912 </script>
14913 <div ng-controller="ExampleController"> 14914 Enter number: <input ng-model='val'><br> 14915 Default formatting: <span id='number-default'>{{val | number}}</span><br> 14916 No fractions: <span>{{val | number:0}}</span><br> 14917 Negative number: <span>{{-val | number:4}}</span> 14918 </div> 14919 </file> 14920 <file name="protractor.js" type="protractor"> 14921 it('should format numbers', function() { 14922 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 14923 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 14924 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 14925 }); 14926 14927 it('should update', function() { 14928 element(by.model('val')).clear(); 14929 element(by.model('val')).sendKeys('3374.333'); 14930 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 14931 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 14932 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 14933 }); 14934 </file> 14935 </example> 14936 */ 14937 14938 14939numberFilter.$inject = ['$locale']; 14940function numberFilter($locale) { 14941 var formats = $locale.NUMBER_FORMATS; 14942 return function(number, fractionSize) { 14943 return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 14944 fractionSize); 14945 }; 14946} 14947 14948var DECIMAL_SEP = '.'; 14949function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 14950 if (number == null || !isFinite(number) || isObject(number)) return ''; 14951 14952 var isNegative = number < 0; 14953 number = Math.abs(number); 14954 var numStr = number + '', 14955 formatedText = '', 14956 parts = []; 14957 14958 var hasExponent = false; 14959 if (numStr.indexOf('e') !== -1) { 14960 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 14961 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 14962 numStr = '0'; 14963 number = 0; 14964 } else { 14965 formatedText = numStr; 14966 hasExponent = true; 14967 } 14968 } 14969 14970 if (!hasExponent) { 14971 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 14972 14973 // determine fractionSize if it is not specified 14974 if (isUndefined(fractionSize)) { 14975 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 14976 } 14977 14978 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics 14979 // inspired by: 14980 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round 14981 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); 14982 14983 var fraction = ('' + number).split(DECIMAL_SEP); 14984 var whole = fraction[0]; 14985 fraction = fraction[1] || ''; 14986 14987 var i, pos = 0, 14988 lgroup = pattern.lgSize, 14989 group = pattern.gSize; 14990 14991 if (whole.length >= (lgroup + group)) { 14992 pos = whole.length - lgroup; 14993 for (i = 0; i < pos; i++) { 14994 if ((pos - i)%group === 0 && i !== 0) { 14995 formatedText += groupSep; 14996 } 14997 formatedText += whole.charAt(i); 14998 } 14999 } 15000 15001 for (i = pos; i < whole.length; i++) { 15002 if ((whole.length - i)%lgroup === 0 && i !== 0) { 15003 formatedText += groupSep; 15004 } 15005 formatedText += whole.charAt(i); 15006 } 15007 15008 // format fraction part. 15009 while(fraction.length < fractionSize) { 15010 fraction += '0'; 15011 } 15012 15013 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 15014 } else { 15015 15016 if (fractionSize > 0 && number > -1 && number < 1) { 15017 formatedText = number.toFixed(fractionSize); 15018 } 15019 } 15020 15021 parts.push(isNegative ? pattern.negPre : pattern.posPre); 15022 parts.push(formatedText); 15023 parts.push(isNegative ? pattern.negSuf : pattern.posSuf); 15024 return parts.join(''); 15025} 15026 15027function padNumber(num, digits, trim) { 15028 var neg = ''; 15029 if (num < 0) { 15030 neg = '-'; 15031 num = -num; 15032 } 15033 num = '' + num; 15034 while(num.length < digits) num = '0' + num; 15035 if (trim) 15036 num = num.substr(num.length - digits); 15037 return neg + num; 15038} 15039 15040 15041function dateGetter(name, size, offset, trim) { 15042 offset = offset || 0; 15043 return function(date) { 15044 var value = date['get' + name](); 15045 if (offset > 0 || value > -offset) 15046 value += offset; 15047 if (value === 0 && offset == -12 ) value = 12; 15048 return padNumber(value, size, trim); 15049 }; 15050} 15051 15052function dateStrGetter(name, shortForm) { 15053 return function(date, formats) { 15054 var value = date['get' + name](); 15055 var get = uppercase(shortForm ? ('SHORT' + name) : name); 15056 15057 return formats[get][value]; 15058 }; 15059} 15060 15061function timeZoneGetter(date) { 15062 var zone = -1 * date.getTimezoneOffset(); 15063 var paddedZone = (zone >= 0) ? "+" : ""; 15064 15065 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 15066 padNumber(Math.abs(zone % 60), 2); 15067 15068 return paddedZone; 15069} 15070 15071function ampmGetter(date, formats) { 15072 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 15073} 15074 15075var DATE_FORMATS = { 15076 yyyy: dateGetter('FullYear', 4), 15077 yy: dateGetter('FullYear', 2, 0, true), 15078 y: dateGetter('FullYear', 1), 15079 MMMM: dateStrGetter('Month'),
15080 MMM: dateStrGetter('Month', true), 15081 MM: dateGetter('Month', 2, 1), 15082 M: dateGetter('Month', 1, 1), 15083 dd: dateGetter('Date', 2), 15084 d: dateGetter('Date', 1), 15085 HH: dateGetter('Hours', 2), 15086 H: dateGetter('Hours', 1), 15087 hh: dateGetter('Hours', 2, -12), 15088 h: dateGetter('Hours', 1, -12), 15089 mm: dateGetter('Minutes', 2), 15090 m: dateGetter('Minutes', 1), 15091 ss: dateGetter('Seconds', 2), 15092 s: dateGetter('Seconds', 1), 15093 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 15094 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 15095 sss: dateGetter('Milliseconds', 3), 15096 EEEE: dateStrGetter('Day'), 15097 EEE: dateStrGetter('Day', true), 15098 a: ampmGetter, 15099 Z: timeZoneGetter 15100}; 15101 15102var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, 15103 NUMBER_STRING = /^\-?\d+$/; 15104 15105/** 15106 * @ngdoc filter 15107 * @name date 15108 * @kind function 15109 * 15110 * @description 15111 * Formats `date` to a string based on the requested `format`. 15112 * 15113 * `format` string can be composed of the following elements: 15114 * 15115 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 15116 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 15117 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 15118 * * `'MMMM'`: Month in year (January-December) 15119 * * `'MMM'`: Month in year (Jan-Dec) 15120 * * `'MM'`: Month in year, padded (01-12) 15121 * * `'M'`: Month in year (1-12) 15122 * * `'dd'`: Day in month, padded (01-31) 15123 * * `'d'`: Day in month (1-31) 15124 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 15125 * * `'EEE'`: Day in Week, (Sun-Sat) 15126 * * `'HH'`: Hour in day, padded (00-23) 15127 * * `'H'`: Hour in day (0-23) 15128 * * `'hh'`: Hour in am/pm, padded (01-12) 15129 * * `'h'`: Hour in am/pm, (1-12) 15130 * * `'mm'`: Minute in hour, padded (00-59) 15131 * * `'m'`: Minute in hour (0-59) 15132 * * `'ss'`: Second in minute, padded (00-59) 15133 * * `'s'`: Second in minute (0-59) 15134 * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) 15135 * * `'a'`: am/pm marker 15136 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 15137 * 15138 * `format` string can also be one of the following predefined 15139 * {@link guide/i18n localizable formats}: 15140 * 15141 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 15142 * (e.g. Sep 3, 2010 12:05:08 pm) 15143 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) 15144 * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale 15145 * (e.g. Friday, September 3, 2010) 15146 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 15147 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 15148 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 15149 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) 15150 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) 15151 * 15152 * `format` string can contain literal values. These need to be quoted with single quotes (e.g. 15153 * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence 15154 * (e.g. `"h 'o''clock'"`). 15155 * 15156 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 15157 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 15158 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 15159 * specified in the string input, the time is considered to be in the local timezone. 15160 * @param {string=} format Formatting rules (see Description). If not specified, 15161 * `mediumDate` is used. 15162 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 15163 * 15164 * @example 15165 <example> 15166 <file name="index.html"> 15167 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 15168 <span>{{1288323623006 | date:'medium'}}</span><br>
15169 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 15170 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 15171 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 15172 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 15173 </file> 15174 <file name="protractor.js" type="protractor"> 15175 it('should format date', function() { 15176 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 15177 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 15178 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 15179 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 15180 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 15181 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 15182 }); 15183 </file> 15184 </example> 15185 */ 15186dateFilter.$inject = ['$locale']; 15187function dateFilter($locale) { 15188 15189 15190 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 15191 // 1 2 3 4 5 6 7 8 9 10 11 15192 function jsonStringToDate(string) { 15193 var match; 15194 if (match = string.match(R_ISO8601_STR)) { 15195 var date = new Date(0), 15196 tzHour = 0, 15197 tzMin = 0, 15198 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 15199 timeSetter = match[8] ? date.setUTCHours : date.setHours; 15200 15201 if (match[9]) { 15202 tzHour = int(match[9] + match[10]); 15203 tzMin = int(match[9] + match[11]); 15204 } 15205 dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); 15206 var h = int(match[4]||0) - tzHour; 15207 var m = int(match[5]||0) - tzMin; 15208 var s = int(match[6]||0); 15209 var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); 15210 timeSetter.call(date, h, m, s, ms); 15211 return date; 15212 } 15213 return string; 15214 } 15215 15216 15217 return function(date, format) { 15218 var text = '', 15219 parts = [], 15220 fn, match; 15221 15222 format = format || 'mediumDate'; 15223 format = $locale.DATETIME_FORMATS[format] || format; 15224 if (isString(date)) { 15225 date = NUMBER_STRING.test(date) ? int(date) : jsonStringToDate(date); 15226 } 15227 15228 if (isNumber(date)) { 15229 date = new Date(date); 15230 } 15231 15232 if (!isDate(date)) { 15233 return date; 15234 } 15235 15236 while(format) { 15237 match = DATE_FORMATS_SPLIT.exec(format); 15238 if (match) { 15239 parts = concat(parts, match, 1); 15240 format = parts.pop(); 15241 } else { 15242 parts.push(format); 15243 format = null; 15244 } 15245 } 15246 15247 forEach(parts, function(value){ 15248 fn = DATE_FORMATS[value]; 15249 text += fn ? fn(date, $locale.DATETIME_FORMATS) 15250 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 15251 }); 15252 15253 return text; 15254 }; 15255} 15256 15257 15258/** 15259 * @ngdoc filter 15260 * @name json 15261 * @kind function 15262 * 15263 * @description 15264 * Allows you to convert a JavaScript object into JSON string. 15265 * 15266 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 15267 * the binding is automatically converted to JSON. 15268 * 15269 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 15270 * @returns {string} JSON string. 15271 * 15272 * 15273 * @example 15274 <example> 15275 <file name="index.html"> 15276 <pre>{{ {'name':'value'} | json }}</pre> 15277 </file> 15278 <file name="protractor.js" type="protractor"> 15279 it('should jsonify filtered objects', function() { 15280 expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/); 15281 }); 15282 </file> 15283 </example> 15284 * 15285 */ 15286function jsonFilter() { 15287 return function(object) { 15288 return toJson(object, true); 15289 }; 15290} 15291 15292 15293/** 15294 * @ngdoc filter 15295 * @name lowercase 15296 * @kind function 15297 * @description 15298 * Converts string to lowercase. 15299 * @see angular.lowercase 15300 */ 15301var lowercaseFilter = valueFn(lowercase); 15302 15303 15304/** 15305 * @ngdoc filter 15306 * @name uppercase 15307 * @kind function 15308 * @description 15309 * Converts string to uppercase. 15310 * @see angular.uppercase 15311 */ 15312var uppercaseFilter = valueFn(uppercase); 15313 15314/** 15315 * @ngdoc filter 15316 * @name limitTo 15317 * @kind function 15318 * 15319 * @description 15320 * Creates a new array or string containing only a specified number of elements. The elements 15321 * are taken from either the beginning or the end of the source array or string, as specified by 15322 * the value and sign (positive or negative) of `limit`. 15323 * 15324 * @param {Array|string} input Source array or string to be limited. 15325 * @param {string|number} limit The length of the returned array or string. If the `limit` number 15326 * is positive, `limit` number of items from the beginning of the source array/string are copied. 15327 * If the number is negative, `limit` number of items from the end of the source array/string 15328 * are copied. The `limit` will be trimmed if it exceeds `array.length` 15329 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 15330 * had less than `limit` elements. 15331 * 15332 * @example 15333 <example module="limitToExample"> 15334 <file name="index.html"> 15335 <script> 15336 angular.module('limitToExample', [])
15337 .controller('ExampleController', ['$scope', function($scope) { 15338 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 15339 $scope.letters = "abcdefghi"; 15340 $scope.numLimit = 3; 15341 $scope.letterLimit = 3; 15342 }]); 15343 </script> 15344 <div ng-controller="ExampleController"> 15345 Limit {{numbers}} to: <input type="integer" ng-model="numLimit"> 15346 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 15347 Limit {{letters}} to: <input type="integer" ng-model="letterLimit"> 15348 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 15349 </div> 15350 </file> 15351 <file name="protractor.js" type="protractor"> 15352 var numLimitInput = element(by.model('numLimit')); 15353 var letterLimitInput = element(by.model('letterLimit')); 15354 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 15355 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 15356 15357 it('should limit the number array to first three items', function() { 15358 expect(numLimitInput.getAttribute('value')).toBe('3'); 15359 expect(letterLimitInput.getAttribute('value')).toBe('3'); 15360 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 15361 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 15362 }); 15363 15364 it('should update the output when -3 is entered', function() { 15365 numLimitInput.clear(); 15366 numLimitInput.sendKeys('-3'); 15367 letterLimitInput.clear(); 15368 letterLimitInput.sendKeys('-3'); 15369 expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 15370 expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 15371 }); 15372 15373 it('should not exceed the maximum size of input array', function() { 15374 numLimitInput.clear(); 15375 numLimitInput.sendKeys('100'); 15376 letterLimitInput.clear(); 15377 letterLimitInput.sendKeys('100'); 15378 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 15379 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 15380 }); 15381 </file> 15382 </example> 15383 */ 15384function limitToFilter(){ 15385 return function(input, limit) { 15386 if (!isArray(input) && !isString(input)) return input; 15387 15388 if (Math.abs(Number(limit)) === Infinity) { 15389 limit = Number(limit); 15390 } else { 15391 limit = int(limit); 15392 } 15393 15394 if (isString(input)) { 15395 //NaN check on limit 15396 if (limit) { 15397 return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); 15398 } else { 15399 return ""; 15400 } 15401 } 15402 15403 var out = [], 15404 i, n; 15405 15406 // if abs(limit) exceeds maximum length, trim it 15407 if (limit > input.length) 15408 limit = input.length; 15409 else if (limit < -input.length) 15410 limit = -input.length; 15411 15412 if (limit > 0) { 15413 i = 0; 15414 n = limit; 15415 } else { 15416 i = input.length + limit; 15417 n = input.length; 15418 } 15419 15420 for (; i<n; i++) { 15421 out.push(input[i]); 15422 } 15423 15424 return out; 15425 }; 15426} 15427 15428/** 15429 * @ngdoc filter 15430 * @name orderBy 15431 * @kind function 15432 * 15433 * @description 15434 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 15435 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 15436 * correctly, make sure they are actually being saved as numbers and not strings. 15437 * 15438 * @param {Array} array The array to sort. 15439 * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be 15440 * used by the comparator to determine the order of elements. 15441 * 15442 * Can be one of: 15443 * 15444 * - `function`: Getter function. The result of this function will be sorted using the 15445 * `<`, `=`, `>` operator. 15446 * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' 15447 * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control 15448 * ascending or descending sort order (for example, +name or -name). 15449 * - `Array`: An array of function or string predicates. The first predicate in the array 15450 * is used for sorting, but when two items are equivalent, the next predicate is used. 15451 * 15452 * @param {boolean=} reverse Reverse the order of the array. 15453 * @returns {Array} Sorted copy of the source array. 15454 * 15455 * @example 15456 <example module="orderByExample"> 15457 <file name="index.html"> 15458 <script> 15459 angular.module('orderByExample', [])
15460 .controller('ExampleController', ['$scope', function($scope) { 15461 $scope.friends = 15462 [{name:'John', phone:'555-1212', age:10}, 15463 {name:'Mary', phone:'555-9876', age:19}, 15464 {name:'Mike', phone:'555-4321', age:21}, 15465 {name:'Adam', phone:'555-5678', age:35}, 15466 {name:'Julie', phone:'555-8765', age:29}]; 15467 $scope.predicate = '-age'; 15468 }]); 15469 </script> 15470 <div ng-controller="ExampleController"> 15471 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 15472 <hr/> 15473 [ <a href="" ng-click="predicate=''">unsorted</a> ] 15474 <table class="friend"> 15475 <tr> 15476 <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a> 15477 (<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th> 15478 <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> 15479 <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th> 15480 </tr> 15481 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 15482 <td>{{friend.name}}</td> 15483 <td>{{friend.phone}}</td> 15484 <td>{{friend.age}}</td> 15485 </tr> 15486 </table> 15487 </div> 15488 </file> 15489 </example> 15490 * 15491 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the 15492 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 15493 * desired parameters. 15494 * 15495 * Example: 15496 * 15497 * @example 15498 <example module="orderByExample"> 15499 <file name="index.html">
15500 <div ng-controller="ExampleController"> 15501 <table class="friend"> 15502 <tr> 15503 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a> 15504 (<a href="" ng-click="order('-name',false)">^</a>)</th> 15505 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th> 15506 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th> 15507 </tr> 15508 <tr ng-repeat="friend in friends"> 15509 <td>{{friend.name}}</td> 15510 <td>{{friend.phone}}</td> 15511 <td>{{friend.age}}</td> 15512 </tr> 15513 </table> 15514 </div> 15515 </file> 15516 15517 <file name="script.js"> 15518 angular.module('orderByExample', []) 15519 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { 15520 var orderBy = $filter('orderBy'); 15521 $scope.friends = [ 15522 { name: 'John', phone: '555-1212', age: 10 }, 15523 { name: 'Mary', phone: '555-9876', age: 19 }, 15524 { name: 'Mike', phone: '555-4321', age: 21 }, 15525 { name: 'Adam', phone: '555-5678', age: 35 }, 15526 { name: 'Julie', phone: '555-8765', age: 29 } 15527 ]; 15528 $scope.order = function(predicate, reverse) { 15529 $scope.friends = orderBy($scope.friends, predicate, reverse); 15530 }; 15531 $scope.order('-age',false); 15532 }]); 15533 </file> 15534</example> 15535 */ 15536orderByFilter.$inject = ['$parse']; 15537function orderByFilter($parse){ 15538 return function(array, sortPredicate, reverseOrder) { 15539 if (!isArray(array)) return array; 15540 if (!sortPredicate) return array; 15541 sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; 15542 sortPredicate = map(sortPredicate, function(predicate){ 15543 var descending = false, get = predicate || identity; 15544 if (isString(predicate)) { 15545 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 15546 descending = predicate.charAt(0) == '-'; 15547 predicate = predicate.substring(1); 15548 } 15549 get = $parse(predicate); 15550 if (get.constant) { 15551 var key = get(); 15552 return reverseComparator(function(a,b) { 15553 return compare(a[key], b[key]); 15554 }, descending); 15555 } 15556 } 15557 return reverseComparator(function(a,b){ 15558 return compare(get(a),get(b)); 15559 }, descending); 15560 }); 15561 var arrayCopy = []; 15562 for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } 15563 return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); 15564 15565 function comparator(o1, o2){ 15566 for ( var i = 0; i < sortPredicate.length; i++) { 15567 var comp = sortPredicate[i](o1, o2); 15568 if (comp !== 0) return comp; 15569 } 15570 return 0; 15571 } 15572 function reverseComparator(comp, descending) { 15573 return toBoolean(descending) 15574 ? function(a,b){return comp(b,a);} 15575 : comp; 15576 } 15577 function compare(v1, v2){ 15578 var t1 = typeof v1; 15579 var t2 = typeof v2; 15580 if (t1 == t2) { 15581 if (isDate(v1) && isDate(v2)) { 15582 v1 = v1.valueOf(); 15583 v2 = v2.valueOf(); 15584 } 15585 if (t1 == "string") { 15586 v1 = v1.toLowerCase(); 15587 v2 = v2.toLowerCase(); 15588 } 15589 if (v1 === v2) return 0; 15590 return v1 < v2 ? -1 : 1; 15591 } else { 15592 return t1 < t2 ? -1 : 1; 15593 } 15594 } 15595 }; 15596} 15597 15598function ngDirective(directive) { 15599 if (isFunction(directive)) { 15600 directive = { 15601 link: directive 15602 }; 15603 } 15604 directive.restrict = directive.restrict || 'AC'; 15605 return valueFn(directive); 15606} 15607 15608/** 15609 * @ngdoc directive 15610 * @name a 15611 * @restrict E 15612 * 15613 * @description 15614 * Modifies the default behavior of the html A tag so that the default action is prevented when 15615 * the href attribute is empty. 15616 * 15617 * This change permits the easy creation of action links with the `ngClick` directive 15618 * without changing the location or causing page reloads, e.g.: 15619 * `<a href="" ng-click="list.addItem()">Add Item</a>` 15620 */ 15621var htmlAnchorDirective = valueFn({ 15622 restrict: 'E', 15623 compile: function(element, attr) { 15624 15625 if (msie <= 8) { 15626 15627 // turn <a href ng-click="..">link</a> into a stylable link in IE 15628 // but only if it doesn't have name attribute, in which case it's an anchor 15629 if (!attr.href && !attr.name) { 15630 attr.$set('href', ''); 15631 } 15632 15633 // add a comment node to anchors to workaround IE bug that causes element content to be reset 15634 // to new attribute content if attribute is updated with value containing @ and element also 15635 // contains value with @ 15636 // see issue #1949 15637 element.append(document.createComment('IE fix')); 15638 } 15639 15640 if (!attr.href && !attr.xlinkHref && !attr.name) { 15641 return function(scope, element) { 15642 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 15643 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 15644 'xlink:href' : 'href'; 15645 element.on('click', function(event){ 15646 // if we have no href url, then don't navigate anywhere. 15647 if (!element.attr(href)) { 15648 event.preventDefault(); 15649 } 15650 }); 15651 }; 15652 } 15653 } 15654}); 15655 15656/** 15657 * @ngdoc directive 15658 * @name ngHref 15659 * @restrict A 15660 * @priority 99 15661 * 15662 * @description 15663 * Using Angular markup like `{{hash}}` in an href attribute will 15664 * make the link go to the wrong URL if the user clicks it before 15665 * Angular has a chance to replace the `{{hash}}
15665` markup with its 15666 * value. Until Angular replaces the markup the link will be broken 15667 * and will most likely return a 404 error. 15668 * 15669 * The `ngHref` directive solves this problem. 15670 * 15671 * The wrong way to write it: 15672 * ```html 15673 * <a href="http://www.gravatar.com/avatar/{{hash}}"/> 15674 * ``` 15675 * 15676 * The correct way to write it: 15677 * ```html 15678 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/> 15679 * ``` 15680 * 15681 * @element A 15682 * @param {template} ngHref any string which can contain `{{}}` markup. 15683 * 15684 * @example 15685 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 15686 * in links and their different behaviors: 15687 <example> 15688 <file name="index.html"> 15689 <input ng-model="value" /><br /> 15690 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 15691 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 15692 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 15693 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 15694 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 15695 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 15696 </file> 15697 <file name="protractor.js" type="protractor"> 15698 it('should execute ng-click but not reload when href without value', function() { 15699 element(by.id('link-1')).click(); 15700 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 15701 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 15702 }); 15703 15704 it('should execute ng-click but not reload when href empty string', function() { 15705 element(by.id('link-2')).click(); 15706 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 15707 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 15708 }); 15709 15710 it('should execute ng-click and change url when ng-href specified', function() { 15711 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 15712 15713 element(by.id('link-3')).click(); 15714 15715 // At this point, we navigate away from an Angular page, so we need 15716 // to use browser.driver to get the base webdriver. 15717 15718 browser.wait(function() { 15719 return browser.driver.getCurrentUrl().then(function(url) { 15720 return url.match(/\/123$/); 15721 }); 15722 }, 5000, 'page should navigate to /123'); 15723 }); 15724 15725 xit('should execute ng-click but not reload when href empty string and name specified', function() { 15726 element(by.id('link-4')).click(); 15727 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 15728 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 15729 }); 15730 15731 it('should execute ng-click but not reload when no href but name specified', function() { 15732 element(by.id('link-5')).click(); 15733 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 15734 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 15735 }); 15736 15737 it('should only change url when only ng-href', function() { 15738 element(by.model('value')).clear(); 15739 element(by.model('value')).sendKeys('6'); 15740 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 15741 15742 element(by.id('link-6')).click(); 15743 15744 // At this point, we navigate away from an Angular page, so we need 15745 // to use browser.driver to get the base webdriver. 15746 browser.wait(function() { 15747 return browser.driver.getCurrentUrl().then(function(url) { 15748 return url.match(/\/6$/); 15749 }); 15750 }, 5000, 'page should navigate to /6'); 15751 }); 15752 </file> 15753 </example> 15754 */ 15755 15756/** 15757 * @ngdoc directive 15758 * @name ngSrc 15759 * @restrict A 15760 * @priority 99 15761 * 15762 * @description 15763 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 15764 * work right: The browser will fetch from the URL with the literal 15765 * text `{{hash}}` until Angular replaces the expression inside 15766 * `{{hash}}`. The `ngSrc` directive solves this problem. 15767 * 15768 * The buggy way to write it: 15769 * ```html 15770 * <img src="http://www.gravatar.com/avatar/{{hash}}"/> 15771 * ``` 15772 * 15773 * The correct way to write it: 15774 * ```html 15775 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/> 15776 * ``` 15777 * 15778 * @element IMG 15779 * @param {template} ngSrc any string which can contain `{{}}` markup. 15780 */ 15781 15782/** 15783 * @ngdoc directive 15784 * @name ngSrcset 15785 * @restrict A 15786 * @priority 99 15787 * 15788 * @description 15789 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 15790 * work right: The browser will fetch from the URL with the literal 15791 * text `{{hash}}` until Angular replaces the expression inside 15792 * `{{hash}}`. The `ngSrcset` directive solves this problem. 15793 * 15794 * The buggy way to write it: 15795 * ```html 15796 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 15797 * ``` 15798 * 15799 * The correct way to write it: 15800 * ```html 15801 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 15802 * ``` 15803 * 15804 * @element IMG 15805 * @param {template} ngSrcset any string which can contain `{{}}` markup. 15806 */ 15807 15808/** 15809 * @ngdoc directive 15810 * @name ngDisabled 15811 * @restrict A 15812 * @priority 100 15813 * 15814 * @description 15815 *
15816 * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: 15817 * ```html 15818 * <div ng-init="scope = { isDisabled: false }"> 15819 * <button disabled="{{scope.isDisabled}}">Disabled</button> 15820 * </div> 15821 * ``` 15822 * 15823 * The HTML specification does not require browsers to preserve the values of boolean attributes 15824 * such as disabled. (Their presence means true and their absence means false.) 15825 * If we put an Angular interpolation expression into such an attribute then the 15826 * binding information would be lost when the browser removes the attribute. 15827 * The `ngDisabled` directive solves this problem for the `disabled` attribute. 15828 * This complementary directive is not removed by the browser and so provides 15829 * a permanent reliable place to store the binding information. 15830 * 15831 * @example 15832 <example> 15833 <file name="index.html"> 15834 Click me to toggle: <input type="checkbox" ng-model="checked"><br/> 15835 <button ng-model="button" ng-disabled="checked">Button</button> 15836 </file> 15837 <file name="protractor.js" type="protractor"> 15838 it('should toggle button', function() { 15839 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); 15840 element(by.model('checked')).click(); 15841 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 15842 }); 15843 </file> 15844 </example> 15845 * 15846 * @element INPUT 15847 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 15848 * then special attribute "disabled" will be set on the element 15849 */ 15850 15851 15852/** 15853 * @ngdoc directive 15854 * @name ngChecked 15855 * @restrict A 15856 * @priority 100 15857 * 15858 * @description 15859 * The HTML specification does not require browsers to preserve the values of boolean attributes 15860 * such as checked. (Their presence means true and their absence means false.) 15861 * If we put an Angular interpolation expression into such an attribute then the 15862 * binding information would be lost when the browser removes the attribute. 15863 * The `ngChecked` directive solves this problem for the `checked` attribute. 15864 * This complementary directive is not removed by the browser and so provides 15865 * a permanent reliable place to store the binding information. 15866 * @example 15867 <example> 15868 <file name="index.html"> 15869 Check me to check both: <input type="checkbox" ng-model="master"><br/> 15870 <input id="checkSlave" type="checkbox" ng-checked="master"> 15871 </file> 15872 <file name="protractor.js" type="protractor"> 15873 it('should check both checkBoxes', function() { 15874 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 15875 element(by.model('master')).click(); 15876 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 15877 }); 15878 </file> 15879 </example> 15880 * 15881 * @element INPUT 15882 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 15883 * then special attribute "checked" will be set on the element 15884 */ 15885 15886 15887/** 15888 * @ngdoc directive 15889 * @name ngReadonly 15890 * @restrict A 15891 * @priority 100 15892 * 15893 * @description 15894 * The HTML specification does not require browsers to preserve the values of boolean attributes 15895 * such as readonly. (Their presence means true and their absence means false.) 15896 * If we put an Angular interpolation expression into such an attribute then the 15897 * binding information would be lost when the browser removes the attribute. 15898 * The `ngReadonly` directive solves this problem for the `readonly` attribute. 15899 * This complementary directive is not removed by the browser and so provides 15900 * a permanent reliable place to store the binding information. 15901 * @example 15902 <example> 15903 <file name="index.html"> 15904 Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/> 15905 <input type="text" ng-readonly="checked" value="I'm Angular"/> 15906 </file> 15907 <file name="protractor.js" type="protractor"> 15908 it('should toggle readonly attr', function() { 15909 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 15910 element(by.model('checked')).click(); 15911 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 15912 }); 15913 </file> 15914 </example> 15915 * 15916 * @element INPUT 15917 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 15918 * then special attribute "readonly" will be set on the element 15919 */ 15920 15921 15922/** 15923 * @ngdoc directive 15924 * @name ngSelected 15925 * @restrict A 15926 * @priority 100 15927 * 15928 * @description 15929 * The HTML specification does not require browsers to preserve the values of boolean attributes 15930 * such as selected. (Their presence means true and their absence means false.) 15931 * If we put an Angular interpolation expression into such an attribute then the 15932 * binding information would be lost when the browser removes the attribute. 15933 * The `ngSelected` directive solves this problem for the `selected` attribute. 15934 * This complementary directive is not removed by the browser and so provides 15935 * a permanent reliable place to store the binding information. 15936 * 15937 * @example 15938 <example> 15939 <file name="index.html"> 15940 Check me to select: <input type="checkbox" ng-model="selected"><br/> 15941 <select> 15942 <option>Hello!</option> 15943 <option id="greet" ng-selected="selected">Greetings!</option> 15944 </select> 15945 </file> 15946 <file name="protractor.js" type="protractor"> 15947 it('should select Greetings!', function() { 15948 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
15949 element(by.model('selected')).click(); 15950 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 15951 }); 15952 </file> 15953 </example> 15954 * 15955 * @element OPTION 15956 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 15957 * then special attribute "selected" will be set on the element 15958 */ 15959 15960/** 15961 * @ngdoc directive 15962 * @name ngOpen 15963 * @restrict A 15964 * @priority 100 15965 * 15966 * @description 15967 * The HTML specification does not require browsers to preserve the values of boolean attributes 15968 * such as open. (Their presence means true and their absence means false.) 15969 * If we put an Angular interpolation expression into such an attribute then the 15970 * binding information would be lost when the browser removes the attribute. 15971 * The `ngOpen` directive solves this problem for the `open` attribute. 15972 * This complementary directive is not removed by the browser and so provides 15973 * a permanent reliable place to store the binding information. 15974 * @example 15975 <example> 15976 <file name="index.html"> 15977 Check me check multiple: <input type="checkbox" ng-model="open"><br/> 15978 <details id="details" ng-open="open"> 15979 <summary>Show/Hide me</summary> 15980 </details> 15981 </file> 15982 <file name="protractor.js" type="protractor"> 15983 it('should toggle open', function() { 15984 expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); 15985 element(by.model('open')).click(); 15986 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 15987 }); 15988 </file> 15989 </example> 15990 * 15991 * @element DETAILS 15992 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 15993 * then special attribute "open" will be set on the element 15994 */ 15995 15996var ngAttributeAliasDirectives = {}; 15997 15998 15999// boolean attrs are evaluated 16000forEach(BOOLEAN_ATTR, function(propName, attrName) { 16001 // binding to multiple is not supported 16002 if (propName == "multiple") return; 16003 16004 var normalized = directiveNormalize('ng-' + attrName); 16005 ngAttributeAliasDirectives[normalized] = function() { 16006 return { 16007 priority: 100, 16008 link: function(scope, element, attr) { 16009 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 16010 attr.$set(attrName, !!value); 16011 }); 16012 } 16013 }; 16014 }; 16015}); 16016 16017 16018// ng-src, ng-srcset, ng-href are interpolated 16019forEach(['src', 'srcset', 'href'], function(attrName) { 16020 var normalized = directiveNormalize('ng-' + attrName); 16021 ngAttributeAliasDirectives[normalized] = function() { 16022 return { 16023 priority: 99, // it needs to run after the attributes are interpolated 16024 link: function(scope, element, attr) { 16025 var propName = attrName, 16026 name = attrName; 16027 16028 if (attrName === 'href' && 16029 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 16030 name = 'xlinkHref'; 16031 attr.$attr[name] = 'xlink:href'; 16032 propName = null; 16033 } 16034 16035 attr.$observe(normalized, function(value) { 16036 if (!value) 16037 return; 16038 16039 attr.$set(name, value); 16040 16041 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 16042 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 16043 // to set the property as well to achieve the desired effect. 16044 // we use attr[attrName] value since $set can sanitize the url. 16045 if (msie && propName) element.prop(propName, attr[name]); 16046 }); 16047 } 16048 }; 16049 }; 16050}); 16051 16052/* global -nullFormCtrl */ 16053var nullFormCtrl = { 16054 $addControl: noop, 16055 $removeControl: noop, 16056 $setValidity: noop, 16057 $setDirty: noop, 16058 $setPristine: noop 16059}; 16060 16061/** 16062 * @ngdoc type 16063 * @name form.FormController 16064 * 16065 * @property {boolean} $pristine True if user has not interacted with the form yet. 16066 * @property {boolean} $dirty True if user has already interacted with the form. 16067 * @property {boolean} $valid True if all of the containing forms and controls are valid. 16068 * @property {boolean} $invalid True if at least one containing control or form is invali
16068d. 16069 * 16070 * @property {Object} $error Is an object hash, containing references to all invalid controls or 16071 * forms, where: 16072 * 16073 * - keys are validation tokens (error names), 16074 * - values are arrays of controls or forms that are invalid for given error name. 16075 * 16076 * 16077 * Built-in validation tokens: 16078 * 16079 * - `email` 16080 * - `max` 16081 * - `maxlength` 16082 * - `min` 16083 * - `minlength` 16084 * - `number` 16085 * - `pattern` 16086 * - `required` 16087 * - `url` 16088 * 16089 * @description 16090 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 16091 * such as being valid/invalid or dirty/pristine. 16092 * 16093 * Each {@link ng.directive:form form} directive creates an instance 16094 * of `FormController`. 16095 * 16096 */ 16097//asks for $scope to fool the BC controller module 16098FormController.$inject = ['$element', '$attrs', '$scope', '$animate']; 16099function FormController(element, attrs, $scope, $animate) { 16100 var form = this, 16101 parentForm = element.parent().controller('form') || nullFormCtrl, 16102 invalidCount = 0, // used to easily determine if we are valid 16103 errors = form.$error = {}, 16104 controls = []; 16105 16106 // init state 16107 form.$name = attrs.name || attrs.ngForm; 16108 form.$dirty = false; 16109 form.$pristine = true; 16110 form.$valid = true; 16111 form.$invalid = false; 16112 16113 parentForm.$addControl(form); 16114 16115 // Setup initial state of the control 16116 element.addClass(PRISTINE_CLASS); 16117 toggleValidCss(true); 16118 16119 // convenience method for easy toggling of classes 16120 function toggleValidCss(isValid, validationErrorKey) { 16121 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 16122 $animate.removeClass(element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); 16123 $animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 16124 } 16125 16126 /** 16127 * @ngdoc method 16128 * @name form.FormController#$addControl 16129 * 16130 * @description 16131 * Register a control with the form. 16132 * 16133 * Input elements using ngModelController do this automatically when they are linked. 16134 */ 16135 form.$addControl = function(control) { 16136 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 16137 // and not added to the scope. Now we throw an error. 16138 assertNotHasOwnProperty(control.$name, 'input'); 16139 controls.push(control); 16140 16141 if (control.$name) { 16142 form[control.$name] = control; 16143 } 16144 }; 16145 16146 /** 16147 * @ngdoc method 16148 * @name form.FormController#$removeControl 16149 * 16150 * @description 16151 * Deregister a control from the form. 16152 * 16153 * Input elements using ngModelController do this automatically when they are destroyed. 16154 */ 16155 form.$removeControl = function(control) { 16156 if (control.$name && form[control.$name] === control) { 16157 delete form[control.$name]; 16158 } 16159 forEach(errors, function(queue, validationToken) { 16160 form.$setValidity(validationToken, true, control); 16161 }); 16162 16163 arrayRemove(controls, control); 16164 }; 16165 16166 /** 16167 * @ngdoc method 16168 * @name form.FormController#$setValidity 16169 * 16170 * @description 16171 * Sets the validity of a form control. 16172 * 16173 * This method will also propagate to parent forms. 16174 */ 16175 form.$setValidity = function(validationToken, isValid, control) { 16176 var queue = errors[validationToken]; 16177 16178 if (isValid) { 16179 if (queue) { 16180 arrayRemove(queue, control); 16181 if (!queue.length) { 16182 invalidCount--; 16183 if (!invalidCount) { 16184 toggleValidCss(isValid); 16185 form.$valid = true; 16186 form.$invalid = false; 16187 } 16188 errors[validationToken] = false; 16189 toggleValidCss(true, validationToken); 16190 parentForm.$setValidity(validationToken, true, form); 16191 } 16192 } 16193 16194 } else { 16195 if (!invalidCount) { 16196 toggleValidCss(isValid); 16197 } 16198 if (queue) { 16199 if (includes(queue, control)) return; 16200 } else { 16201 errors[validationToken] = queue = []; 16202 invalidCount++; 16203 toggleValidCss(false, validationToken); 16204 parentForm.$setValidity(validationToken, false, form); 16205 } 16206 queue.push(control); 16207 16208 form.$valid = false; 16209 form.$invalid = true; 16210 } 16211 }; 16212 16213 /** 16214 * @ngdoc method
16215 * @name form.FormController#$setDirty 16216 * 16217 * @description 16218 * Sets the form to a dirty state. 16219 * 16220 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 16221 * state (ng-dirty class). This method will also propagate to parent forms. 16222 */ 16223 form.$setDirty = function() { 16224 $animate.removeClass(element, PRISTINE_CLASS); 16225 $animate.addClass(element, DIRTY_CLASS); 16226 form.$dirty = true; 16227 form.$pristine = false; 16228 parentForm.$setDirty(); 16229 }; 16230 16231 /** 16232 * @ngdoc method 16233 * @name form.FormController#$setPristine 16234 * 16235 * @description 16236 * Sets the form to its pristine state. 16237 * 16238 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 16239 * state (ng-pristine class). This method will also propagate to all the controls contained 16240 * in this form. 16241 * 16242 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 16243 * saving or resetting it. 16244 */ 16245 form.$setPristine = function () { 16246 $animate.removeClass(element, DIRTY_CLASS); 16247 $animate.addClass(element, PRISTINE_CLASS); 16248 form.$dirty = false; 16249 form.$pristine = true; 16250 forEach(controls, function(control) { 16251 control.$setPristine(); 16252 }); 16253 }; 16254} 16255 16256 16257/** 16258 * @ngdoc directive 16259 * @name ngForm 16260 * @restrict EAC 16261 * 16262 * @description 16263 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 16264 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 16265 * sub-group of controls needs to be determined. 16266 *
16267 * Note: the purpose of `ngForm` is to group controls, 16268 * but not to be a replacement for the `<form>` tag with all of its capabilities 16269 * (e.g. posting to the server, ...). 16270 * 16271 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 16272 * related scope, under this name. 16273 * 16274 */ 16275 16276 /** 16277 * @ngdoc directive 16278 * @name form 16279 * @restrict E 16280 * 16281 * @description 16282 * Directive that instantiates 16283 * {@link form.FormController FormController}. 16284 * 16285 * If the `name` attribute is specified, the form controller is published onto the current scope under 16286 * this name. 16287 * 16288 * # Alias: {@link ng.directive:ngForm `ngForm`} 16289 * 16290 * In Angular forms can be nested. This means that the outer form is valid when all of the child 16291 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 16292 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to 16293 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when 16294 * using Angular validation directives in forms that are dynamically generated using the 16295 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` 16296 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an 16297 * `ngForm` directive and nest these in an outer `form` element. 16298 * 16299 * 16300 * # CSS classes 16301 * - `ng-valid` is set if the form is valid. 16302 * - `ng-invalid` is set if the form is invalid. 16303 * - `ng-pristine` is set if the form is pristine. 16304 * - `ng-dirty` is set if the form is dirty. 16305 * 16306 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 16307 * 16308 * 16309 * # Submitting a form and preventing the default action 16310 * 16311 * Since the role of forms in client-side Angular applications is different than in classical 16312 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 16313 * page reload that sends the data to the server. Instead some javascript logic should be triggered 16314 * to handle the form submission in an application-specific way. 16315 * 16316 * For this reason, Angular prevents the default action (form submission to the server) unless the 16317 * `<form>` element has an `action` attribute specified. 16318 * 16319 * You can use one of the following two ways to specify what javascript method should be called when 16320 * a form is submitted: 16321 * 16322 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 16323 * - {@link ng.directive:ngClick ngClick} directive on the first 16324 * button or input field of type submit (input[type=submit]) 16325 * 16326 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 16327 * or {@link ng.directive:ngClick ngClick} directives. 16328 * This is because of the following form submission rules in the HTML specification: 16329 * 16330 * - If a form has only one input field then hitting enter in this field triggers form submit 16331 * (`ngSubmit`) 16332 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 16333 * doesn't trigger submit 16334 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 16335 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 16336 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 16337 * 16338 * @param {string=} name Name of the form. If specified, the form controller will be published into 16339 * related scope, under this name. 16340 * 16341 * ## Animation Hooks 16342 * 16343 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 16344 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 16345 * other validations that are performed within the form. Animations in ngForm are similar to how 16346 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 16347 * as JS animations. 16348 * 16349 * The following example shows a simple way to utilize CSS transitions to style a form element
16350 * that has been rendered as invalid after it has been validated: 16351 * 16352 * <pre> 16353 * //be sure to include ngAnimate as a module to hook into more 16354 * //advanced animations 16355 * .my-form { 16356 * transition:0.5s linear all; 16357 * background: white; 16358 * } 16359 * .my-form.ng-invalid { 16360 * background: red; 16361 * color:white; 16362 * } 16363 * </pre> 16364 * 16365 * @example 16366 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 16367 <file name="index.html"> 16368 <script> 16369 angular.module('formExample', []) 16370 .controller('FormController', ['$scope', function($scope) { 16371 $scope.userType = 'guest'; 16372 }]); 16373 </script> 16374 <style> 16375 .my-form { 16376 -webkit-transition:all linear 0.5s; 16377 transition:all linear 0.5s; 16378 background: transparent; 16379 } 16380 .my-form.ng-invalid { 16381 background: red; 16382 } 16383 </style> 16384 <form name="myForm" ng-controller="FormController" class="my-form"> 16385 userType: <input name="input" ng-model="userType" required> 16386 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 16387 <tt>userType = {{userType}}</tt><br> 16388 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br> 16389 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br> 16390 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 16391 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 16392 </form> 16393 </file> 16394 <file name="protractor.js" type="protractor"> 16395 it('should initialize to model', function() { 16396 var userType = element(by.binding('userType')); 16397 var valid = element(by.binding('myForm.input.$valid')); 16398 16399 expect(userType.getText()).toContain('guest'); 16400 expect(valid.getText()).toContain('true'); 16401 }); 16402 16403 it('should be invalid if empty', function() { 16404 var userType = element(by.binding('userType')); 16405 var valid = element(by.binding('myForm.input.$valid')); 16406 var userInput = element(by.model('userType')); 16407 16408 userInput.clear(); 16409 userInput.sendKeys(''); 16410 16411 expect(userType.getText()).toEqual('userType ='); 16412 expect(valid.getText()).toContain('false'); 16413 }); 16414 </file> 16415 </example> 16416 * 16417 */ 16418var formDirectiveFactory = function(isNgForm) { 16419 return ['$timeout', function($timeout) { 16420 var formDirective = { 16421 name: 'form', 16422 restrict: isNgForm ? 'EAC' : 'E', 16423 controller: FormController, 16424 compile: function() { 16425 return { 16426 pre: function(scope, formElement, attr, controller) { 16427 if (!attr.action) { 16428 // we can't use jq events because if a form is destroyed during submission the default 16429 // action is not prevented. see #1238 16430 // 16431 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 16432 // page reload if the form was destroyed by submission of the form via a click handler 16433 // on a button in the form. Looks like an IE9 specific bug. 16434 var preventDefaultListener = function(event) { 16435 event.preventDefault 16436 ? event.preventDefault() 16437 : event.returnValue = false; // IE 16438 }; 16439 16440 addEventListenerFn(formElement[0], 'submit', preventDefaultListener); 16441 16442 // unregister the preventDefault listener so that we don't not leak memory but in a 16443 // way that will achieve the prevention of the default action. 16444 formElement.on('$destroy', function() { 16445 $timeout(function() { 16446 removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); 16447 }, 0, false); 16448 }); 16449 } 16450 16451 var parentFormCtrl = formElement.parent().controller('form'), 16452 alias = attr.name || attr.ngForm; 16453 16454 if (alias) { 16455 setter(scope, alias, controller, alias); 16456 } 16457 if (parentFormCtrl) { 16458 formElement.on('$destroy', function() { 16459 parentFormCtrl.$removeControl(controller); 16460 if (alias) { 16461 setter(scope, alias, undefined, alias); 16462 } 16463 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 16464 }); 16465 } 16466 } 16467 }; 16468 } 16469 }; 16470 16471 return formDirective; 16472 }]; 16473}; 16474 16475var formDirective = formDirectiveFactory(); 16476var ngFormDirective = formDirectiveFactory(true); 16477 16478/* global VALID_CLASS: true, 16479 INVALID_CLASS: true, 16480 PRISTINE_CLASS: true, 16481 DIRTY_CLASS: true 16482*/ 16483 16484var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 16485var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
16486var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; 16487 16488var inputType = { 16489 16490 /** 16491 * @ngdoc input 16492 * @name input[text] 16493 * 16494 * @description 16495 * Standard HTML text input with angular data binding. 16496 * 16497 * @param {string} ngModel Assignable angular expression to data-bind to. 16498 * @param {string=} name Property name of the form under which the control is published. 16499 * @param {string=} required Adds `required` validation error key if the value is not entered. 16500 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16501 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16502 * `required` when you want to data-bind to the `required` attribute. 16503 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16504 * minlength. 16505 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16506 * maxlength. 16507 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16508 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16509 * patterns defined as scope expressions. 16510 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16511 * interaction with the input element. 16512 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 16513 * 16514 * @example 16515 <example name="text-input-directive" module="textInputExample"> 16516 <file name="index.html"> 16517 <script> 16518 angular.module('textInputExample', []) 16519 .controller('ExampleController', ['$scope', function($scope) { 16520 $scope.text = 'guest'; 16521 $scope.word = /^\s*\w*\s*$/; 16522 }]); 16523 </script>
16524 <form name="myForm" ng-controller="ExampleController"> 16525 Single word: <input type="text" name="input" ng-model="text" 16526 ng-pattern="word" required ng-trim="false"> 16527 <span class="error" ng-show="myForm.input.$error.required"> 16528 Required!</span> 16529 <span class="error" ng-show="myForm.input.$error.pattern"> 16530 Single word only!</span> 16531 16532 <tt>text = {{text}}</tt><br/> 16533 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16534 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16535 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16536 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16537 </form> 16538 </file> 16539 <file name="protractor.js" type="protractor"> 16540 var text = element(by.binding('text')); 16541 var valid = element(by.binding('myForm.input.$valid')); 16542 var input = element(by.model('text')); 16543 16544 it('should initialize to model', function() { 16545 expect(text.getText()).toContain('guest'); 16546 expect(valid.getText()).toContain('true'); 16547 }); 16548 16549 it('should be invalid if empty', function() { 16550 input.clear(); 16551 input.sendKeys(''); 16552 16553 expect(text.getText()).toEqual('text ='); 16554 expect(valid.getText()).toContain('false'); 16555 }); 16556 16557 it('should be invalid if multi word', function() { 16558 input.clear(); 16559 input.sendKeys('hello world'); 16560 16561 expect(valid.getText()).toContain('false'); 16562 }); 16563 </file> 16564 </example> 16565 */ 16566 'text': textInputType, 16567 16568 16569 /** 16570 * @ngdoc input 16571 * @name input[number] 16572 * 16573 * @description 16574 * Text input with number validation and transformation. Sets the `number` validation 16575 * error if not a valid number. 16576 * 16577 * @param {string} ngModel Assignable angular expression to data-bind to. 16578 * @param {string=} name Property name of the form under which the control is published. 16579 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 16580 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 16581 * @param {string=} required Sets `required` validation error key if the value is not entered. 16582 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16583 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16584 * `required` when you want to data-bind to the `required` attribute. 16585 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16586 * minlength. 16587 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16588 * maxlength. 16589 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16590 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16591 * patterns defined as scope expressions. 16592 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16593 * interaction with the input element. 16594 * 16595 * @example 16596 <example name="number-input-directive" module="numberExample"> 16597 <file name="index.html"> 16598 <script> 16599 angular.module('numberExample', []) 16600 .controller('ExampleController', ['$scope', function($scope) { 16601 $scope.value = 12; 16602 }]); 16603 </script>
16604 <form name="myForm" ng-controller="ExampleController"> 16605 Number: <input type="number" name="input" ng-model="value" 16606 min="0" max="99" required> 16607 <span class="error" ng-show="myForm.input.$error.required"> 16608 Required!</span> 16609 <span class="error" ng-show="myForm.input.$error.number"> 16610 Not valid number!</span> 16611 <tt>value = {{value}}</tt><br/> 16612 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16613 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16614 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16615 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16616 </form> 16617 </file> 16618 <file name="protractor.js" type="protractor"> 16619 var value = element(by.binding('value')); 16620 var valid = element(by.binding('myForm.input.$valid')); 16621 var input = element(by.model('value')); 16622 16623 it('should initialize to model', function() { 16624 expect(value.getText()).toContain('12'); 16625 expect(valid.getText()).toContain('true'); 16626 }); 16627 16628 it('should be invalid if empty', function() { 16629 input.clear(); 16630 input.sendKeys(''); 16631 expect(value.getText()).toEqual('value ='); 16632 expect(valid.getText()).toContain('false'); 16633 }); 16634 16635 it('should be invalid if over max', function() { 16636 input.clear(); 16637 input.sendKeys('123'); 16638 expect(value.getText()).toEqual('value ='); 16639 expect(valid.getText()).toContain('false'); 16640 }); 16641 </file> 16642 </example> 16643 */ 16644 'number': numberInputType, 16645 16646 16647 /** 16648 * @ngdoc input 16649 * @name input[url] 16650 * 16651 * @description 16652 * Text input with URL validation. Sets the `url` validation error key if the content is not a 16653 * valid URL. 16654 * 16655 * @param {string} ngModel Assignable angular expression to data-bind to. 16656 * @param {string=} name Property name of the form under which the control is published. 16657 * @param {string=} required Sets `required` validation error key if the value is not entered. 16658 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16659 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16660 * `required` when you want to data-bind to the `required` attribute. 16661 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16662 * minlength. 16663 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16664 * maxlength. 16665 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16666 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16667 * patterns defined as scope expressions. 16668 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16669 * interaction with the input element. 16670 * 16671 * @example 16672 <example name="url-input-directive" module="urlExample"> 16673 <file name="index.html"> 16674 <script> 16675 angular.module('urlExample', []) 16676 .controller('ExampleController', ['$scope', function($scope) { 16677 $scope.text = 'http://google.com'; 16678 }]); 16679 </script>
16680 <form name="myForm" ng-controller="ExampleController"> 16681 URL: <input type="url" name="input" ng-model="text" required> 16682 <span class="error" ng-show="myForm.input.$error.required"> 16683 Required!</span> 16684 <span class="error" ng-show="myForm.input.$error.url"> 16685 Not valid url!</span> 16686 <tt>text = {{text}}</tt><br/> 16687 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16688 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16689 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16690 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16691 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 16692 </form> 16693 </file> 16694 <file name="protractor.js" type="protractor"> 16695 var text = element(by.binding('text')); 16696 var valid = element(by.binding('myForm.input.$valid')); 16697 var input = element(by.model('text')); 16698 16699 it('should initialize to model', function() { 16700 expect(text.getText()).toContain('http://google.com'); 16701 expect(valid.getText()).toContain('true'); 16702 }); 16703 16704 it('should be invalid if empty', function() { 16705 input.clear(); 16706 input.sendKeys(''); 16707 16708 expect(text.getText()).toEqual('text ='); 16709 expect(valid.getText()).toContain('false'); 16710 }); 16711 16712 it('should be invalid if not url', function() { 16713 input.clear(); 16714 input.sendKeys('box'); 16715 16716 expect(valid.getText()).toContain('false'); 16717 }); 16718 </file> 16719 </example> 16720 */ 16721 'url': urlInputType, 16722 16723 16724 /** 16725 * @ngdoc input 16726 * @name input[email] 16727 * 16728 * @description 16729 * Text input with email validation. Sets the `email` validation error key if not a valid email 16730 * address. 16731 * 16732 * @param {string} ngModel Assignable angular expression to data-bind to. 16733 * @param {string=} name Property name of the form under which the control is published. 16734 * @param {string=} required Sets `required` validation error key if the value is not entered. 16735 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16736 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16737 * `required` when you want to data-bind to the `required` attribute. 16738 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16739 * minlength. 16740 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16741 * maxlength. 16742 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16743 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16744 * patterns defined as scope expressions. 16745 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16746 * interaction with the input element. 16747 * 16748 * @example 16749 <example name="email-input-directive" module="emailExample"> 16750 <file name="index.html"> 16751 <script> 16752 angular.module('emailExample', []) 16753 .controller('ExampleController', ['$scope', function($scope) { 16754 $scope.text = '[email protected]'; 16755 }]); 16756 </script>
16757 <form name="myForm" ng-controller="ExampleController"> 16758 Email: <input type="email" name="input" ng-model="text" required> 16759 <span class="error" ng-show="myForm.input.$error.required"> 16760 Required!</span> 16761 <span class="error" ng-show="myForm.input.$error.email"> 16762 Not valid email!</span> 16763 <tt>text = {{text}}</tt><br/> 16764 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16765 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16766 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16767 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16768 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 16769 </form> 16770 </file> 16771 <file name="protractor.js" type="protractor"> 16772 var text = element(by.binding('text')); 16773 var valid = element(by.binding('myForm.input.$valid')); 16774 var input = element(by.model('text')); 16775 16776 it('should initialize to model', function() { 16777 expect(text.getText()).toContain('[email protected]'); 16778 expect(valid.getText()).toContain('true'); 16779 }); 16780 16781 it('should be invalid if empty', function() { 16782 input.clear(); 16783 input.sendKeys(''); 16784 expect(text.getText()).toEqual('text ='); 16785 expect(valid.getText()).toContain('false'); 16786 }); 16787 16788 it('should be invalid if not email', function() { 16789 input.clear(); 16790 input.sendKeys('xxx'); 16791 16792 expect(valid.getText()).toContain('false'); 16793 }); 16794 </file> 16795 </example> 16796 */ 16797 'email': emailInputType, 16798 16799 16800 /** 16801 * @ngdoc input 16802 * @name input[radio] 16803 * 16804 * @description 16805 * HTML radio button. 16806 * 16807 * @param {string} ngModel Assignable angular expression to data-bind to. 16808 * @param {string} value The value to which the expression should be set when selected. 16809 * @param {string=} name Property name of the form under which the control is published. 16810 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16811 * interaction with the input element. 16812 * @param {string} ngValue Angular expression which sets the value to which the expression should 16813 * be set when selected. 16814 * 16815 * @example 16816 <example name="radio-input-directive" module="radioExample"> 16817 <file name="index.html"> 16818 <script> 16819 angular.module('radioExample', []) 16820 .controller('ExampleController', ['$scope', function($scope) { 16821 $scope.color = 'blue'; 16822 $scope.specialValue = { 16823 "id": "12345", 16824 "value": "green" 16825 }; 16826 }]); 16827 </script>
16828 <form name="myForm" ng-controller="ExampleController"> 16829 <input type="radio" ng-model="color" value="red"> Red <br/> 16830 <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/> 16831 <input type="radio" ng-model="color" value="blue"> Blue <br/> 16832 <tt>color = {{color | json}}</tt><br/> 16833 </form> 16834 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 16835 </file> 16836 <file name="protractor.js" type="protractor"> 16837 it('should change state', function() { 16838 var color = element(by.binding('color')); 16839 16840 expect(color.getText()).toContain('blue'); 16841 16842 element.all(by.model('color')).get(0).click(); 16843 16844 expect(color.getText()).toContain('red'); 16845 }); 16846 </file> 16847 </example> 16848 */ 16849 'radio': radioInputType, 16850 16851 16852 /** 16853 * @ngdoc input 16854 * @name input[checkbox] 16855 * 16856 * @description 16857 * HTML checkbox. 16858 * 16859 * @param {string} ngModel Assignable angular expression to data-bind to. 16860 * @param {string=} name Property name of the form under which the control is published. 16861 * @param {string=} ngTrueValue The value to which the expression should be set when selected. 16862 * @param {string=} ngFalseValue The value to which the expression should be set when not selected. 16863 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16864 * interaction with the input element. 16865 * 16866 * @example 16867 <example name="checkbox-input-directive" module="checkboxExample"> 16868 <file name="index.html"> 16869 <script> 16870 angular.module('checkboxExample', []) 16871 .controller('ExampleController', ['$scope', function($scope) { 16872 $scope.value1 = true; 16873 $scope.value2 = 'YES' 16874 }]); 16875 </script>
16876 <form name="myForm" ng-controller="ExampleController"> 16877 Value1: <input type="checkbox" ng-model="value1"> <br/> 16878 Value2: <input type="checkbox" ng-model="value2" 16879 ng-true-value="YES" ng-false-value="NO"> <br/> 16880 <tt>value1 = {{value1}}</tt><br/> 16881 <tt>value2 = {{value2}}</tt><br/> 16882 </form> 16883 </file> 16884 <file name="protractor.js" type="protractor"> 16885 it('should change state', function() { 16886 var value1 = element(by.binding('value1')); 16887 var value2 = element(by.binding('value2')); 16888 16889 expect(value1.getText()).toContain('true'); 16890 expect(value2.getText()).toContain('YES'); 16891 16892 element(by.model('value1')).click(); 16893 element(by.model('value2')).click(); 16894 16895 expect(value1.getText()).toContain('false'); 16896 expect(value2.getText()).toContain('NO'); 16897 }); 16898 </file> 16899 </example> 16900 */ 16901 'checkbox': checkboxInputType, 16902 16903 'hidden': noop, 16904 'button': noop, 16905 'submit': noop, 16906 'reset': noop, 16907 'file': noop 16908}; 16909 16910// A helper function to call $setValidity and return the value / undefined, 16911// a pattern that is repeated a lot in the input validation logic. 16912function validate(ctrl, validatorName, validity, value){ 16913 ctrl.$setValidity(validatorName, validity); 16914 return validity ? value : undefined; 16915} 16916 16917function testFlags(validity, flags) { 16918 var i, flag; 16919 if (flags) { 16920 for (i=0; i<flags.length; ++i) { 16921 flag = flags[i]; 16922 if (validity[flag]) { 16923 return true; 16924 } 16925 } 16926 } 16927 return false; 16928} 16929 16930// Pass validity so that behaviour can be mocked easier. 16931function addNativeHtml5Validators(ctrl, validatorName, badFlags, ignoreFlags, validity) { 16932 if (isObject(validity)) { 16933 ctrl.$$hasNativeValidators = true; 16934 var validator = function(value) { 16935 // Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can 16936 // perform the required validation) 16937 if (!ctrl.$error[validatorName] && 16938 !testFlags(validity, ignoreFlags) && 16939 testFlags(validity, badFlags)) { 16940 ctrl.$setValidity(validatorName, false); 16941 return; 16942 } 16943 return value; 16944 }; 16945 ctrl.$parsers.push(validator); 16946 } 16947} 16948 16949function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16950 var validity = element.prop(VALIDITY_STATE_PROPERTY); 16951 var placeholder = element[0].placeholder, noevent = {}; 16952 ctrl.$$validityState = validity; 16953 16954 // In composition mode, users are still inputing intermediate text buffer, 16955 // hold the listener until composition is done. 16956 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 16957 if (!$sniffer.android) { 16958 var composing = false; 16959 16960 element.on('compositionstart', function(data) { 16961 composing = true; 16962 }); 16963 16964 element.on('compositionend', function() { 16965 composing = false; 16966 listener(); 16967 }); 16968 } 16969 16970 var listener = function(ev) { 16971 if (composing) return; 16972 var value = element.val(); 16973 16974 // IE (11 and under) seem to emit an 'input' event if the placeholder value changes. 16975 // We don't want to dirty the value when this happens, so we abort here. Unfortunately, 16976 // IE also sends input events for other non-input-related things, (such as focusing on a 16977 // form control), so this change is not entirely enough to solve this. 16978 if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) { 16979 placeholder = element[0].placeholder; 16980 return; 16981 } 16982 16983 // By default we will trim the value 16984 // If the attribute ng-trim exists we will avoid trimming 16985 // e.g. <input ng-model="foo" ng-trim="false"> 16986 if (toBoolean(attr.ngTrim || 'T')) { 16987 value = trim(value); 16988 } 16989 16990 // If a control is suffering from bad input, browsers discard its value, so it may be 16991 // necessary to revalidate even if the control's value is the same empty value twice in 16992 // a row. 16993 var revalidate = validity && ctrl.$$hasNativeValidators; 16994 if (ctrl.$viewValue !== value || (value === '' && revalidate)) { 16995 if (scope.$$phase) { 16996 ctrl.$setViewValue(value); 16997 } else { 16998 scope.$apply(function() { 16999 ctrl.$setViewValue(value); 17000 }); 17001 } 17002 } 17003 }; 17004 17005 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 17006 // input event on backspace, delete or cut 17007 if ($sniffer.hasEvent('input')) { 17008 element.on('input', listener); 17009 } else { 17010 var timeout; 17011 17012 var deferListener = function() { 17013 if (!timeout) { 17014 timeout = $browser.defer(function() { 17015 listener(); 17016 timeout = null; 17017 }); 17018 } 17019 }; 17020 17021 element.on('keydown', function(event) { 17022 var key = event.keyCode; 17023 17024 // ignore 17025 // command modifiers arrows 17026 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 17027 17028 deferListener(); 17029 }); 17030 17031 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 17032 if ($sniffer.hasEvent('paste')) { 17033 element.on('paste cut', deferListener); 17034 } 17035 } 17036 17037 // if user paste into input using mouse on older browser 17038 // or form autocomplete on newer browser, we need "change" event to catch it 17039 element.on('change', listener); 17040 17041 ctrl.$render = function() { 17042 element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); 17043 }; 17044 17045 // pattern validator 17046 var pattern = attr.ngPattern, 17047 patternValidator, 17048 match; 17049 17050 if (pattern) {
17051 var validateRegex = function(regexp, value) { 17052 return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value); 17053 }; 17054 match = pattern.match(/^\/(.*)\/([gim]*)$/); 17055 if (match) { 17056 pattern = new RegExp(match[1], match[2]); 17057 patternValidator = function(value) { 17058 return validateRegex(pattern, value); 17059 }; 17060 } else { 17061 patternValidator = function(value) { 17062 var patternObj = scope.$eval(pattern); 17063 17064 if (!patternObj || !patternObj.test) { 17065 throw minErr('ngPattern')('noregexp', 17066 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, 17067 patternObj, startingTag(element)); 17068 } 17069 return validateRegex(patternObj, value); 17070 }; 17071 } 17072 17073 ctrl.$formatters.push(patternValidator); 17074 ctrl.$parsers.push(patternValidator); 17075 } 17076 17077 // min length validator 17078 if (attr.ngMinlength) { 17079 var minlength = int(attr.ngMinlength); 17080 var minLengthValidator = function(value) { 17081 return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value); 17082 }; 17083 17084 ctrl.$parsers.push(minLengthValidator); 17085 ctrl.$formatters.push(minLengthValidator); 17086 } 17087 17088 // max length validator 17089 if (attr.ngMaxlength) { 17090 var maxlength = int(attr.ngMaxlength); 17091 var maxLengthValidator = function(value) { 17092 return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value); 17093 }; 17094 17095 ctrl.$parsers.push(maxLengthValidator); 17096 ctrl.$formatters.push(maxLengthValidator); 17097 } 17098} 17099 17100var numberBadFlags = ['badInput']; 17101 17102function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 17103 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 17104 17105 ctrl.$parsers.push(function(value) { 17106 var empty = ctrl.$isEmpty(value); 17107 if (empty || NUMBER_REGEXP.test(value)) { 17108 ctrl.$setValidity('number', true); 17109 return value === '' ? null : (empty ? value : parseFloat(value)); 17110 } else { 17111 ctrl.$setValidity('number', false); 17112 return undefined; 17113 } 17114 }); 17115 17116 addNativeHtml5Validators(ctrl, 'number', numberBadFlags, null, ctrl.$$validityState); 17117 17118 ctrl.$formatters.push(function(value) { 17119 return ctrl.$isEmpty(value) ? '' : '' + value; 17120 }); 17121 17122 if (attr.min) { 17123 var minValidator = function(value) { 17124 var min = parseFloat(attr.min); 17125 return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); 17126 }; 17127 17128 ctrl.$parsers.push(minValidator); 17129 ctrl.$formatters.push(minValidator); 17130 } 17131 17132 if (attr.max) { 17133 var maxValidator = function(value) { 17134 var max = parseFloat(attr.max); 17135 return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); 17136 }; 17137 17138 ctrl.$parsers.push(maxValidator); 17139 ctrl.$formatters.push(maxValidator); 17140 } 17141 17142 ctrl.$formatters.push(function(value) { 17143 return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); 17144 }); 17145} 17146 17147function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 17148 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 17149 17150 var urlValidator = function(value) { 17151 return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); 17152 }; 17153 17154 ctrl.$formatters.push(urlValidator); 17155 ctrl.$parsers.push(urlValidator); 17156} 17157 17158function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 17159 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 17160 17161 var emailValidator = function(value) { 17162 return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); 17163 }; 17164 17165 ctrl.$formatters.push(emailValidator); 17166 ctrl.$parsers.push(emailValidator); 17167} 17168 17169function radioInputType(scope, element, attr, ctrl) { 17170 // make the name unique, if not defined 17171 if (isUndefined(attr.name)) { 17172 element.attr('name', nextUid()); 17173 } 17174 17175 element.on('click', function() { 17176 if (element[0].checked) { 17177 scope.$apply(function() { 17178 ctrl.$setViewValue(attr.value); 17179 }); 17180 } 17181 }); 17182 17183 ctrl.$render = function() { 17184 var value = attr.value; 17185 element[0].checked = (value == ctrl.$viewValue); 17186 }; 17187 17188 attr.$observe('value', ctrl.$render); 17189} 17190 17191function checkboxInputType(scope, element, attr, ctrl) { 17192 var trueValue = attr.ngTrueValue, 17193 falseValue = attr.ngFalseValue; 17194 17195 if (!isString(trueValue)) trueValue = true; 17196 if (!isString(falseValue)) falseValue = false;
17197 17198 element.on('click', function() { 17199 scope.$apply(function() { 17200 ctrl.$setViewValue(element[0].checked); 17201 }); 17202 }); 17203 17204 ctrl.$render = function() { 17205 element[0].checked = ctrl.$viewValue; 17206 }; 17207 17208 // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. 17209 ctrl.$isEmpty = function(value) { 17210 return value !== trueValue; 17211 }; 17212 17213 ctrl.$formatters.push(function(value) { 17214 return value === trueValue; 17215 }); 17216 17217 ctrl.$parsers.push(function(value) { 17218 return value ? trueValue : falseValue; 17219 }); 17220} 17221 17222 17223/** 17224 * @ngdoc directive 17225 * @name textarea 17226 * @restrict E 17227 * 17228 * @description 17229 * HTML textarea element control with angular data-binding. The data-binding and validation 17230 * properties of this element are exactly the same as those of the 17231 * {@link ng.directive:input input element}. 17232 * 17233 * @param {string} ngModel Assignable angular expression to data-bind to. 17234 * @param {string=} name Property name of the form under which the control is published. 17235 * @param {string=} required Sets `required` validation error key if the value is not entered. 17236 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 17237 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 17238 * `required` when you want to data-bind to the `required` attribute. 17239 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 17240 * minlength. 17241 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 17242 * maxlength. 17243 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 17244 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 17245 * patterns defined as scope expressions. 17246 * @param {string=} ngChange Angular expression to be executed when input changes due to user 17247 * interaction with the input element. 17248 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 17249 */ 17250 17251 17252/** 17253 * @ngdoc directive 17254 * @name input 17255 * @restrict E 17256 * 17257 * @description 17258 * HTML input element control with angular data-binding. Input control follows HTML5 input types 17259 * and polyfills the HTML5 validation behavior for older browsers. 17260 * 17261 * @param {string} ngModel Assignable angular expression to data-bind to. 17262 * @param {string=} name Property name of the form under which the control is published. 17263 * @param {string=} required Sets `required` validation error key if the value is not entered. 17264 * @param {boolean=} ngRequired Sets `required` attribute if set to true 17265 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 17266 * minlength. 17267 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 17268 * maxlength. 17269 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 17270 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 17271 * patterns defined as scope expressions. 17272 * @param {string=} ngChange Angular expression to be executed when input changes due to user 17273 * interaction with the input element. 17274 * 17275 * @example 17276 <example name="input-directive" module="inputExample"> 17277 <file name="index.html"> 17278 <script> 17279 angular.module('inputExample', []) 17280 .controller('ExampleController', ['$scope', function($scope) { 17281 $scope.user = {name: 'guest', last: 'visitor'}; 17282 }]); 17283 </script>
17284 <div ng-controller="ExampleController"> 17285 <form name="myForm"> 17286 User name: <input type="text" name="userName" ng-model="user.name" required> 17287 <span class="error" ng-show="myForm.userName.$error.required"> 17288 Required!</span><br> 17289 Last name: <input type="text" name="lastName" ng-model="user.last" 17290 ng-minlength="3" ng-maxlength="10"> 17291 <span class="error" ng-show="myForm.lastName.$error.minlength"> 17292 Too short!</span> 17293 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 17294 Too long!</span><br> 17295 </form> 17296 <hr> 17297 <tt>user = {{user}}</tt><br/> 17298 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br> 17299 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br> 17300 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br> 17301 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br> 17302 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 17303 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 17304 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br> 17305 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br> 17306 </div> 17307 </file> 17308 <file name="protractor.js" type="protractor"> 17309 var user = element(by.binding('{{user}}
vendor: 2,296 bytes, lines 17309-17363
17309')); 17310 var userNameValid = element(by.binding('myForm.userName.$valid')); 17311 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 17312 var lastNameError = element(by.binding('myForm.lastName.$error')); 17313 var formValid = element(by.binding('myForm.$valid')); 17314 var userNameInput = element(by.model('user.name')); 17315 var userLastInput = element(by.model('user.last')); 17316 17317 it('should initialize to model', function() { 17318 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 17319 expect(userNameValid.getText()).toContain('true'); 17320 expect(formValid.getText()).toContain('true'); 17321 }); 17322 17323 it('should be invalid if empty when required', function() { 17324 userNameInput.clear(); 17325 userNameInput.sendKeys(''); 17326 17327 expect(user.getText()).toContain('{"last":"visitor"}'); 17328 expect(userNameValid.getText()).toContain('false'); 17329 expect(formValid.getText()).toContain('false'); 17330 }); 17331 17332 it('should be valid if empty when min length is set', function() { 17333 userLastInput.clear(); 17334 userLastInput.sendKeys(''); 17335 17336 expect(user.getText()).toContain('{"name":"guest","last":""}'); 17337 expect(lastNameValid.getText()).toContain('true'); 17338 expect(formValid.getText()).toContain('true'); 17339 }); 17340 17341 it('should be invalid if less than required min length', function() { 17342 userLastInput.clear(); 17343 userLastInput.sendKeys('xx'); 17344 17345 expect(user.getText()).toContain('{"name":"guest"}'); 17346 expect(lastNameValid.getText()).toContain('false'); 17347 expect(lastNameError.getText()).toContain('minlength'); 17348 expect(formValid.getText()).toContain('false'); 17349 }); 17350 17351 it('should be invalid if longer than max length', function() { 17352 userLastInput.clear(); 17353 userLastInput.sendKeys('some ridiculously long name'); 17354 17355 expect(user.getText()).toContain('{"name":"guest"}'); 17356 expect(lastNameValid.getText()).toContain('false'); 17357 expect(lastNameError.getText()).toContain('maxlength'); 17358 expect(formValid.getText()).toContain('false'); 17359 }); 17360 </file> 17361 </example> 17362 */ 17363var inputDirective = ['$browser', '$sniffer',
17363function($browser, $sniffer) { 17364 return { 17365 restrict: 'E', 17366 require: '?ngModel', 17367 link: function(scope, element, attr, ctrl) { 17368 if (ctrl) { 17369 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, 17370 $browser); 17371 } 17372 } 17373 }; 17374}]; 17375 17376var VALID_CLASS = 'ng-valid', 17377 INVALID_CLASS = 'ng-invalid', 17378 PRISTINE_CLASS = 'ng-pristine', 17379 DIRTY_CLASS = 'ng-dirty'; 17380 17381/** 17382 * @ngdoc type 17383 * @name ngModel.NgModelController 17384 * 17385 * @property {string} $viewValue Actual string value in the view. 17386 * @property {*} $modelValue The value in the model, that the control is bound to. 17387 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 17388 the control reads value from the DOM. Each function is called, in turn, passing the value 17389 through to the next. The last return value is used to populate the model. 17390 Used to sanitize / convert the value as well as validation. For validation, 17391 the parsers should update the validity state using 17392 {@link ngModel.NgModelController#$setValidity $setValidity()}, 17393 and return `undefined` for invalid values. 17394 17395 * 17396 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 17397 the model value changes. Each function is called, in turn, passing the value through to the 17398 next. Used to format / convert values for display in the control and validation. 17399 * ```js 17400 * function formatter(value) { 17401 * if (value) { 17402 * return value.toUpperCase(); 17403 * } 17404 * } 17405 * ngModel.$formatters.push(formatter); 17406 * ``` 17407 * 17408 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 17409 * view value has changed. It is called with no arguments, and its return value is ignored. 17410 * This can be used in place of additional $watches against the model value. 17411 * 17412 * @property {Object} $error An object hash with all errors as keys. 17413 * 17414 * @property {boolean} $pristine True if user has not interacted with the control yet. 17415 * @property {boolean} $dirty True if user has already interacted with the control. 17416 * @property {boolean} $valid True if there is no error. 17417 * @property {boolean} $invalid True if at least one error on the control. 17418 * 17419 * @description 17420 * 17421 * `NgModelController` provides API for the `ng-model` directive. The controller contains 17422 * services for data-binding, validation, CSS updates, and value formatting and parsing. It 17423 * purposefully does not contain any logic which deals with DOM rendering or listening to 17424 * DOM events. Such DOM related logic should be provided by other directives which make use of 17425 * `NgModelController` for data-binding. 17426 * 17427 * ## Custom Control Example 17428 * This example shows how to use `NgModelController` with a custom control to achieve 17429 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 17430 * collaborate together to achieve the desired result. 17431 * 17432 * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element 17433 * contents be edited in place by the user. This will not work on older browsers. 17434 * 17435 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 17436 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 17437 * However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks 17438 * that content using the `$sce` service. 17439 * 17440 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 17441 <file name="style.css"> 17442 [contenteditable] { 17443 border: 1px solid black; 17444 background-color: white; 17445 min-height: 20px; 17446 } 17447 17448 .ng-invalid { 17449 border: 1px solid red; 17450 } 17451 17452 </file> 17453 <file name="script.js"> 17454 angular.module('customControl', ['ngSanitize']). 17455 directive('contenteditable', ['$sce', function($sce) { 17456 return { 17457 restrict: 'A', // only activate on element attribute 17458 require: '?ngModel', // get a hold of NgModelController 17459 link: function(scope, element, attrs, ngModel) { 17460 if(!ngModel) return; // do nothing if no ng-model 17461 17462 // Specify how UI should be updated 17463 ngModel.$render = function() { 17464 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 17465 }; 17466 17467 // Listen for change events to enable binding 17468 element.on('blur keyup change', function() { 17469 scope.$apply(read); 17470 }); 17471 read(); // initialize 17472 17473 // Write data to the model 17474 function read() { 17475 var html = element.html(); 17476 // When we clear the content editable the browser leaves a <br> behind
17477 // If strip-br attribute is provided then we strip this out 17478 if( attrs.stripBr && html == '<br>' ) { 17479 html = ''; 17480 } 17481 ngModel.$setViewValue(html); 17482 } 17483 } 17484 }; 17485 }]); 17486 </file> 17487 <file name="index.html"> 17488 <form name="myForm"> 17489 <div contenteditable 17490 name="myWidget" ng-model="userContent" 17491 strip-br="true" 17492 required>Change me!</div> 17493 <span ng-show="myForm.myWidget.$error.required">Required!</span> 17494 <hr> 17495 <textarea ng-model="userContent"></textarea> 17496 </form> 17497 </file> 17498 <file name="protractor.js" type="protractor"> 17499 it('should data-bind and become invalid', function() { 17500 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 17501 // SafariDriver can't handle contenteditable 17502 // and Firefox driver can't clear contenteditables very well 17503 return; 17504 } 17505 var contentEditable = element(by.css('[contenteditable]')); 17506 var content = 'Change me!'; 17507 17508 expect(contentEditable.getText()).toEqual(content); 17509 17510 contentEditable.clear(); 17511 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 17512 expect(contentEditable.getText()).toEqual(''); 17513 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 17514 }); 17515 </file> 17516 * </example> 17517 * 17518 * 17519 */ 17520var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', 17521 function($scope, $exceptionHandler, $attr, $element, $parse, $animate) { 17522 this.$viewValue = Number.NaN; 17523 this.$modelValue = Number.NaN; 17524 this.$parsers = []; 17525 this.$formatters = []; 17526 this.$viewChangeListeners = []; 17527 this.$pristine = true; 17528 this.$dirty = false; 17529 this.$valid = true; 17530 this.$invalid = false; 17531 this.$name = $attr.name; 17532 17533 var ngModelGet = $parse($attr.ngModel), 17534 ngModelSet = ngModelGet.assign; 17535 17536 if (!ngModelSet) { 17537 throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 17538 $attr.ngModel, startingTag($element)); 17539 } 17540 17541 /** 17542 * @ngdoc method 17543 * @name ngModel.NgModelController#$render 17544 * 17545 * @description 17546 * Called when the view needs to be updated. It is expected that the user of the ng-model 17547 * directive will implement this method. 17548 */ 17549 this.$render = noop; 17550 17551 /** 17552 * @ngdoc method 17553 * @name ngModel.NgModelController#$isEmpty 17554 * 17555 * @description 17556 * This is called when we need to determine if the value of the input is empty. 17557 * 17558 * For instance, the required directive does this to work out if the input has data or not. 17559 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 17560 * 17561 * You can override this for input directives whose concept of being empty is different to the 17562 * default. The `checkboxInputType` directive does this because in its case a value of `false` 17563 * implies empty. 17564 * 17565 * @param {*} value Reference to check. 17566 * @returns {boolean} True if `value` is empty. 17567 */ 17568 this.$isEmpty = function(value) { 17569 return isUndefined(value) || value === '' || value === null || value !== value; 17570 }; 17571 17572 var parentForm = $element.inheritedData('$formController') || nullFormCtrl, 17573 invalidCount = 0, // used to easily determine if we are valid 17574 $error = this.$error = {}; // keep invalid keys here 17575 17576 17577 // Setup initial state of the control 17578 $element.addClass(PRISTINE_CLASS); 17579 toggleValidCss(true); 17580 17581 // convenience method for easy toggling of classes 17582 function toggleValidCss(isValid, validationErrorKey) { 17583 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 17584 $animate.removeClass($element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); 17585 $animate.addClass($element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 17586 } 17587 17588 /** 17589 * @ngdoc method 17590 * @name ngModel.NgModelController#$setValidity 17591 * 17592 * @description 17593 * Change the validity state, and notifies the form when the control changes validity. (i.e. it 17594 * does not notify form if given validator is already marked as invali
17594d). 17595 * 17596 * This method should be called by validators - i.e. the parser or formatter functions. 17597 * 17598 * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign 17599 * to `$error[validationErrorKey]=!isValid` so that it is available for data-binding. 17600 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 17601 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 17602 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 17603 * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). 17604 */ 17605 this.$setValidity = function(validationErrorKey, isValid) { 17606 // Purposeful use of ! here to cast isValid to boolean in case it is undefined 17607 // jshint -W018 17608 if ($error[validationErrorKey] === !isValid) return; 17609 // jshint +W018 17610 17611 if (isValid) { 17612 if ($error[validationErrorKey]) invalidCount--; 17613 if (!invalidCount) { 17614 toggleValidCss(true); 17615 this.$valid = true; 17616 this.$invalid = false; 17617 } 17618 } else { 17619 toggleValidCss(false); 17620 this.$invalid = true; 17621 this.$valid = false; 17622 invalidCount++; 17623 } 17624 17625 $error[validationErrorKey] = !isValid; 17626 toggleValidCss(isValid, validationErrorKey); 17627 17628 parentForm.$setValidity(validationErrorKey, isValid, this); 17629 }; 17630 17631 /** 17632 * @ngdoc method 17633 * @name ngModel.NgModelController#$setPristine 17634 * 17635 * @description 17636 * Sets the control to its pristine state. 17637 * 17638 * This method can be called to remove the 'ng-dirty' class and set the control to its pristine 17639 * state (ng-pristine class). 17640 */ 17641 this.$setPristine = function () { 17642 this.$dirty = false; 17643 this.$pristine = true; 17644 $animate.removeClass($element, DIRTY_CLASS); 17645 $animate.addClass($element, PRISTINE_CLASS); 17646 }; 17647 17648 /** 17649 * @ngdoc method 17650 * @name ngModel.NgModelController#$setViewValue 17651 * 17652 * @description 17653 * Update the view value. 17654 * 17655 * This method should be called when the view value changes, typically from within a DOM event handler. 17656 * For example {@link ng.directive:input input} and 17657 * {@link ng.directive:select select} directives call it. 17658 * 17659 * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, 17660 * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to 17661 * `$modelValue` and the **expression** specified in the `ng-model` attribute. 17662 * 17663 * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. 17664 * 17665 * Note that calling this function does not trigger a `$digest`. 17666 * 17667 * @param {string} value Value from the view. 17668 */ 17669 this.$setViewValue = function(value) { 17670 this.$viewValue = value; 17671 17672 // change to dirty 17673 if (this.$pristine) { 17674 this.$dirty = true; 17675 this.$pristine = false; 17676 $animate.removeClass($element, PRISTINE_CLASS); 17677 $animate.addClass($element, DIRTY_CLASS); 17678 parentForm.$setDirty(); 17679 } 17680 17681 forEach(this.$parsers, function(fn) { 17682 value = fn(value); 17683 }); 17684 17685 if (this.$modelValue !== value) { 17686 this.$modelValue = value; 17687 ngModelSet($scope, value); 17688 forEach(this.$viewChangeListeners, function(listener) { 17689 try { 17690 listener(); 17691 } catch(e) { 17692 $exceptionHandler(e); 17693 } 17694 }); 17695 } 17696 }; 17697 17698 // model -> value 17699 var ctrl = this; 17700 17701 $scope.$watch(function ngModelWatch() { 17702 var value = ngModelGet($scope); 17703 17704 // if scope model value and ngModel value are out of sync 17705 if (ctrl.$modelValue !== value) { 17706 17707 var formatters = ctrl.$formatters, 17708 idx = formatters.length; 17709 17710 ctrl.$modelValue = value; 17711 while(idx--) { 17712 value = formatters[idx](value); 17713 } 17714 17715 if (ctrl.$viewValue !== value) { 17716 ctrl.$viewValue = value; 17717 ctrl.$render(); 17718 } 17719 } 17720 17721 return value; 17722 }); 17723}]; 17724 17725 17726/** 17727 * @ngdoc directive 17728 * @name ngModel 17729 * 17730 * @element input 17731 * 17732 * @description 17733 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 17734 * property on the scope using {@link ngModel.NgModelController NgModelController}, 17735 * which is created and exposed by this directive. 17736 * 17737 * `ngModel` is responsible for: 17738 * 17739 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 17740 * require. 17741 * - Providing validation behavior (i.e. required, number, email, url). 17742 * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). 17743 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`) including animations. 17744 * - Registering the control with its parent {@link ng.directive:form form}. 17745 *
17746 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 17747 * current scope. If the property doesn't already exist on this scope, it will be created 17748 * implicitly and added to the scope. 17749 * 17750 * For best practices on using `ngModel`, see: 17751 * 17752 * - [https://github.com/angular/angular.js/wiki/Understanding-Scopes] 17753 * 17754 * For basic examples, how to use `ngModel`, see: 17755 * 17756 * - {@link ng.directive:input input} 17757 * - {@link input[text] text} 17758 * - {@link input[checkbox] checkbox} 17759 * - {@link input[radio] radio} 17760 * - {@link input[number] number} 17761 * - {@link input[email] email} 17762 * - {@link input[url] url} 17763 * - {@link ng.directive:select select} 17764 * - {@link ng.directive:textarea textarea} 17765 * 17766 * # CSS classes 17767 * The following CSS classes are added and removed on the associated input/select/textarea element 17768 * depending on the validity of the model. 17769 * 17770 * - `ng-valid` is set if the model is valid. 17771 * - `ng-invalid` is set if the model is invalid. 17772 * - `ng-pristine` is set if the model is pristine. 17773 * - `ng-dirty` is set if the model is dirty. 17774 * 17775 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 17776 * 17777 * ## Animation Hooks 17778 * 17779 * Animations within models are triggered when any of the associated CSS classes are added and removed 17780 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, 17781 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 17782 * The animations that are triggered within ngModel are similar to how they work in ngClass and 17783 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 17784 * 17785 * The following example shows a simple way to utilize CSS transitions to style an input element 17786 * that has been rendered as invalid after it has been validated: 17787 * 17788 * <pre> 17789 * //be sure to include ngAnimate as a module to hook into more 17790 * //advanced animations 17791 * .my-input { 17792 * transition:0.5s linear all; 17793 * background: white; 17794 * } 17795 * .my-input.ng-invalid { 17796 * background: red; 17797 * color:white; 17798 * } 17799 * </pre> 17800 * 17801 * @example 17802 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> 17803 <file name="index.html"> 17804 <script> 17805 angular.module('inputExample', []) 17806 .controller('ExampleController', ['$scope', function($scope) { 17807 $scope.val = '1'; 17808 }]); 17809 </script> 17810 <style> 17811 .my-input { 17812 -webkit-transition:all linear 0.5s; 17813 transition:all linear 0.5s; 17814 background: transparent; 17815 } 17816 .my-input.ng-invalid { 17817 color:white; 17818 background: red; 17819 } 17820 </style> 17821 Update input to see transitions when valid/invalid. 17822 Integer is a valid value.
17823 <form name="testForm" ng-controller="ExampleController"> 17824 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" /> 17825 </form> 17826 </file> 17827 * </example> 17828 */ 17829var ngModelDirective = function() { 17830 return { 17831 require: ['ngModel', '^?form'], 17832 controller: NgModelController, 17833 link: function(scope, element, attr, ctrls) { 17834 // notify others, especially parent forms 17835 17836 var modelCtrl = ctrls[0], 17837 formCtrl = ctrls[1] || nullFormCtrl; 17838 17839 formCtrl.$addControl(modelCtrl); 17840 17841 scope.$on('$destroy', function() { 17842 formCtrl.$removeControl(modelCtrl); 17843 }); 17844 } 17845 }; 17846}; 17847 17848 17849/** 17850 * @ngdoc directive 17851 * @name ngChange 17852 * 17853 * @description 17854 * Evaluate the given expression when the user changes the input. 17855 * The expression is evaluated immediately, unlike the JavaScript onchange event 17856 * which only triggers at the end of a change (usually, when the user leaves the 17857 * form element or presses the return key). 17858 * The expression is not evaluated when the value change is coming from the model. 17859 * 17860 * Note, this directive requires `ngModel` to be present. 17861 * 17862 * @element input 17863 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 17864 * in input value. 17865 * 17866 * @example 17867 * <example name="ngChange-directive" module="changeExample"> 17868 * <file name="index.html"> 17869 * <script> 17870 * angular.module('changeExample', []) 17871 * .controller('ExampleController', ['$scope', function($scope) { 17872 * $scope.counter = 0; 17873 * $scope.change = function() { 17874 * $scope.counter++; 17875 * }; 17876 * }]); 17877 * </script>
17878 * <div ng-controller="ExampleController"> 17879 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 17880 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 17881 * <label for="ng-change-example2">Confirmed</label><br /> 17882 * <tt>debug = {{confirmed}}</tt><br/> 17883 * <tt>counter = {{counter}}</tt><br/> 17884 * </div> 17885 * </file> 17886 * <file name="protractor.js" type="protractor"> 17887 * var counter = element(by.binding('counter')); 17888 * var debug = element(by.binding('confirmed')); 17889 * 17890 * it('should evaluate the expression if changing from view', function() { 17891 * expect(counter.getText()).toContain('0'); 17892 * 17893 * element(by.id('ng-change-example1')).click(); 17894 * 17895 * expect(counter.getText()).toContain('1'); 17896 * expect(debug.getText()).toContain('true'); 17897 * }); 17898 * 17899 * it('should not evaluate the expression if changing from model', function() { 17900 * element(by.id('ng-change-example2')).click(); 17901 17902 * expect(counter.getText()).toContain('0'); 17903 * expect(debug.getText()).toContain('true'); 17904 * }); 17905 * </file> 17906 * </example> 17907 */ 17908var ngChangeDirective = valueFn({ 17909 require: 'ngModel', 17910 link: function(scope, element, attr, ctrl) { 17911 ctrl.$viewChangeListeners.push(function() { 17912 scope.$eval(attr.ngChange); 17913 }); 17914 } 17915}); 17916 17917 17918var requiredDirective = function() { 17919 return { 17920 require: '?ngModel', 17921 link: function(scope, elm, attr, ctrl) { 17922 if (!ctrl) return; 17923 attr.required = true; // force truthy in case we are on non input element 17924 17925 var validator = function(value) { 17926 if (attr.required && ctrl.$isEmpty(value)) { 17927 ctrl.$setValidity('required', false); 17928 return; 17929 } else { 17930 ctrl.$setValidity('required', true); 17931 return value; 17932 } 17933 }; 17934 17935 ctrl.$formatters.push(validator); 17936 ctrl.$parsers.unshift(validator); 17937 17938 attr.$observe('required', function() { 17939 validator(ctrl.$viewValue); 17940 }); 17941 } 17942 }; 17943}; 17944 17945 17946/** 17947 * @ngdoc directive 17948 * @name ngList 17949 * 17950 * @description 17951 * Text input that converts between a delimited string and an array of strings. The delimiter 17952 * can be a fixed string (by default a comma) or a regular expression. 17953 * 17954 * @element input 17955 * @param {string=} ngList optional delimiter that should be used to split the value. If 17956 * specified in form `/something/` then the value will be converted into a regular expression. 17957 * 17958 * @example 17959 <example name="ngList-directive" module="listExample"> 17960 <file name="index.html"> 17961 <script> 17962 angular.module('listExample', []) 17963 .controller('ExampleController', ['$scope', function($scope) { 17964 $scope.names = ['igor', 'misko', 'vojta']; 17965 }]); 17966 </script>
17967 <form name="myForm" ng-controller="ExampleController"> 17968 List: <input name="namesInput" ng-model="names" ng-list required> 17969 <span class="error" ng-show="myForm.namesInput.$error.required"> 17970 Required!</span> 17971 <br> 17972 <tt>names = {{names}}</tt><br/> 17973 <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 17974 <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 17975 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 17976 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 17977 </form> 17978 </file> 17979 <file name="protractor.js" type="protractor"> 17980 var listInput = element(by.model('names')); 17981 var names = element(by.binding('{{names}}')); 17982 var valid = element(by.binding('myForm.namesInput.$valid')); 17983 var error = element(by.css('span.error')); 17984 17985 it('should initialize to model', function() { 17986 expect(names.getText()).toContain('["igor","misko","vojta"]'); 17987 expect(valid.getText()).toContain('true'); 17988 expect(error.getCssValue('display')).toBe('none'); 17989 }); 17990 17991 it('should be invalid if empty', function() { 17992 listInput.clear(); 17993 listInput.sendKeys(''); 17994 17995 expect(names.getText()).toContain(''); 17996 expect(valid.getText()).toContain('false'); 17997 expect(error.getCssValue('display')).not.toBe('none'); }); 17998 </file> 17999 </example> 18000 */ 18001var ngListDirective = function() { 18002 return { 18003 require: 'ngModel', 18004 link: function(scope, element, attr, ctrl) { 18005 var match = /\/(.*)\//.exec(attr.ngList), 18006 separator = match && new RegExp(match[1]) || attr.ngList || ','; 18007 18008 var parse = function(viewValue) { 18009 // If the viewValue is invalid (say required but empty) it will be `undefined` 18010 if (isUndefined(viewValue)) return; 18011 18012 var list = []; 18013 18014 if (viewValue) { 18015 forEach(viewValue.split(separator), function(value) { 18016 if (value) list.push(trim(value)); 18017 }); 18018 } 18019 18020 return list; 18021 }; 18022 18023 ctrl.$parsers.push(parse); 18024 ctrl.$formatters.push(function(value) { 18025 if (isArray(value)) { 18026 return value.join(', '); 18027 } 18028 18029 return undefined; 18030 }); 18031 18032 // Override the standard $isEmpty because an empty array means the input is empty. 18033 ctrl.$isEmpty = function(value) { 18034 return !value || !value.length; 18035 }; 18036 } 18037 }; 18038}; 18039 18040 18041var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 18042/** 18043 * @ngdoc directive 18044 * @name ngValue 18045 * 18046 * @description 18047 * Binds the given expression to the value of `input[select]` or `input[radio]`, so 18048 * that when the element is selected, the `ngModel` of that element is set to the 18049 * bound value. 18050 * 18051 * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as 18052 * shown below. 18053 * 18054 * @element input 18055 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 18056 * of the `input` element 18057 * 18058 * @example 18059 <example name="ngValue-directive" module="valueExample"> 18060 <file name="index.html"> 18061 <script> 18062 angular.module('valueExample', []) 18063 .controller('ExampleController', ['$scope', function($scope) { 18064 $scope.names = ['pizza', 'unicorns', 'robots']; 18065 $scope.my = { favorite: 'unicorns' }; 18066 }]); 18067 </script>
18068 <form ng-controller="ExampleController"> 18069 <h2>Which is your favorite?</h2> 18070 <label ng-repeat="name in names" for="{{name}}"> 18071 {{name}} 18072 <input type="radio" 18073 ng-model="my.favorite" 18074 ng-value="name" 18075 id="{{name}}" 18076 name="favorite"> 18077 </label> 18078 <div>You chose {{my.favorite}}</div> 18079 </form> 18080 </file> 18081 <file name="protractor.js" type="protractor"> 18082 var favorite = element(by.binding('my.favorite')); 18083 18084 it('should initialize to model', function() { 18085 expect(favorite.getText()).toContain('unicorns'); 18086 }); 18087 it('should bind the values to the inputs', function() { 18088 element.all(by.model('my.favorite')).get(0).click(); 18089 expect(favorite.getText()).toContain('pizza'); 18090 }); 18091 </file> 18092 </example> 18093 */ 18094var ngValueDirective = function() { 18095 return { 18096 priority: 100, 18097 compile: function(tpl, tplAttr) { 18098 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 18099 return function ngValueConstantLink(scope, elm, attr) { 18100 attr.$set('value', scope.$eval(attr.ngValue)); 18101 }; 18102 } else { 18103 return function ngValueLink(scope, elm, attr) { 18104 scope.$watch(attr.ngValue, function valueWatchAction(value) { 18105 attr.$set('value', value); 18106 }); 18107 }; 18108 } 18109 } 18110 }; 18111}; 18112 18113/** 18114 * @ngdoc directive 18115 * @name ngBind 18116 * @restrict AC 18117 * 18118 * @description 18119 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 18120 * with the value of a given expression, and to update the text content when the value of that 18121 * expression changes. 18122 * 18123 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 18124 * `{{ expression }}` which is similar but less verbose. 18125 * 18126 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 18127 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 18128 * element attribute, it makes the bindings invisible to the user while the page is loading. 18129 * 18130 * An alternative solution to this problem would be using the 18131 * {@link ng.directive:ngCloak ngCloak} directive. 18132 * 18133 * 18134 * @element ANY 18135 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 18136 * 18137 * @example 18138 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 18139 <example module="bindExample"> 18140 <file name="index.html"> 18141 <script> 18142 angular.module('bindExample', []) 18143 .controller('ExampleController', ['$scope', function($scope) { 18144 $scope.name = 'Whirled'; 18145 }]); 18146 </script>
18147 <div ng-controller="ExampleController"> 18148 Enter name: <input type="text" ng-model="name"><br> 18149 Hello <span ng-bind="name"></span>! 18150 </div> 18151 </file> 18152 <file name="protractor.js" type="protractor"> 18153 it('should check ng-bind', function() { 18154 var nameInput = element(by.model('name')); 18155 18156 expect(element(by.binding('name')).getText()).toBe('Whirled'); 18157 nameInput.clear(); 18158 nameInput.sendKeys('world'); 18159 expect(element(by.binding('name')).getText()).toBe('world'); 18160 }); 18161 </file> 18162 </example> 18163 */ 18164var ngBindDirective = ngDirective({ 18165 compile: function(templateElement) { 18166 templateElement.addClass('ng-binding'); 18167 return function (scope, element, attr) { 18168 element.data('$binding', attr.ngBind); 18169 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 18170 // We are purposefully using == here rather than === because we want to 18171 // catch when value is "null or undefined" 18172 // jshint -W041 18173 element.text(value == undefined ? '' : value); 18174 }); 18175 }; 18176 } 18177}); 18178 18179 18180/** 18181 * @ngdoc directive 18182 * @name ngBindTemplate 18183 * 18184 * @description 18185 * The `ngBindTemplate` directive specifies that the element 18186 * text content should be replaced with the interpolation of the template 18187 * in the `ngBindTemplate` attribute. 18188 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 18189 * expressions. This directive is needed since some HTML elements 18190 * (such as TITLE and OPTION) cannot contain SPAN elements. 18191 * 18192 * @element ANY 18193 * @param {string} ngBindTemplate template of form 18194 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 18195 * 18196 * @example 18197 * Try it here: enter text in text box and watch the greeting change. 18198 <example module="bindExample"> 18199 <file name="index.html"> 18200 <script> 18201 angular.module('bindExample', []) 18202 .controller('ExampleController', ['$scope', function ($scope) { 18203 $scope.salutation = 'Hello'; 18204 $scope.name = 'World'; 18205 }]); 18206 </script>
18207 <div ng-controller="ExampleController"> 18208 Salutation: <input type="text" ng-model="salutation"><br> 18209 Name: <input type="text" ng-model="name"><br> 18210 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 18211 </div> 18212 </file> 18213 <file name="protractor.js" type="protractor"> 18214 it('should check ng-bind', function() { 18215 var salutationElem = element(by.binding('salutation')); 18216 var salutationInput = element(by.model('salutation')); 18217 var nameInput = element(by.model('name')); 18218 18219 expect(salutationElem.getText()).toBe('Hello World!'); 18220 18221 salutationInput.clear(); 18222 salutationInput.sendKeys('Greetings'); 18223 nameInput.clear(); 18224 nameInput.sendKeys('user'); 18225 18226 expect(salutationElem.getText()).toBe('Greetings user!'); 18227 }); 18228 </file> 18229 </example> 18230 */ 18231var ngBindTemplateDirective = ['$interpolate', function($interpolate) { 18232 return function(scope, element, attr) { 18233 // TODO: move this to scenario runner 18234 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 18235 element.addClass('ng-binding').data('$binding', interpolateFn); 18236 attr.$observe('ngBindTemplate', function(value) { 18237 element.text(value); 18238 }); 18239 }; 18240}]; 18241 18242 18243/** 18244 * @ngdoc directive 18245 * @name ngBindHtml 18246 * 18247 * @description 18248 * Creates a binding that will innerHTML the result of evaluating the `expression` into the current 18249 * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link 18250 * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` 18251 * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in 18252 * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to 18253 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 18254 * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. 18255 * 18256 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 18257 * will have an exception (instead of an exploit.) 18258 * 18259 * @element ANY 18260 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 18261 * 18262 * @example 18263 Try it here: enter text in text box and watch the greeting change. 18264 18265 <example module="bindHtmlExample" deps="angular-sanitize.js"> 18266 <file name="index.html">
18267 <div ng-controller="ExampleController"> 18268 <p ng-bind-html="myHTML"></p> 18269 </div> 18270 </file> 18271 18272 <file name="script.js"> 18273 angular.module('bindHtmlExample', ['ngSanitize']) 18274 .controller('ExampleController', ['$scope', function($scope) { 18275 $scope.myHTML = 18276 'I am an <code>HTML</code>string with ' + 18277 '<a href="#">links!</a> and other <em>stuff</em>'; 18278 }]); 18279 </file> 18280 18281 <file name="protractor.js" type="protractor"> 18282 it('should check ng-bind-html', function() { 18283 expect(element(by.binding('myHTML')).getText()).toBe( 18284 'I am an HTMLstring with links! and other stuff'); 18285 }); 18286 </file> 18287 </example> 18288 */ 18289var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { 18290 return { 18291 compile: function (tElement) { 18292 tElement.addClass('ng-binding'); 18293 18294 return function (scope, element, attr) { 18295 element.data('$binding', attr.ngBindHtml); 18296 18297 var parsed = $parse(attr.ngBindHtml); 18298 18299 function getStringValue() { 18300 return (parsed(scope) || '').toString(); 18301 } 18302 18303 scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { 18304 element.html($sce.getTrustedHtml(parsed(scope)) || ''); 18305 }); 18306 }; 18307 } 18308 }; 18309}]; 18310 18311function classDirective(name, selector) { 18312 name = 'ngClass' + name; 18313 return ['$animate', function($animate) { 18314 return { 18315 restrict: 'AC', 18316 link: function(scope, element, attr) { 18317 var oldVal; 18318 18319 scope.$watch(attr[name], ngClassWatchAction, true); 18320 18321 attr.$observe('class', function(value) { 18322 ngClassWatchAction(scope.$eval(attr[name])); 18323 }); 18324 18325 18326 if (name !== 'ngClass') { 18327 scope.$watch('$index', function($index, old$index) { 18328 // jshint bitwise: false 18329 var mod = $index & 1; 18330 if (mod !== (old$index & 1)) { 18331 var classes = arrayClasses(scope.$eval(attr[name])); 18332 mod === selector ? 18333 addClasses(classes) : 18334 removeClasses(classes); 18335 } 18336 }); 18337 } 18338 18339 function addClasses(classes) { 18340 var newClasses = digestClassCounts(classes, 1); 18341 attr.$addClass(newClasses); 18342 } 18343 18344 function removeClasses(classes) { 18345 var newClasses = digestClassCounts(classes, -1); 18346 attr.$removeClass(newClasses); 18347 } 18348 18349 function digestClassCounts (classes, count) { 18350 var classCounts = element.data('$classCounts') || {}; 18351 var classesToUpdate = []; 18352 forEach(classes, function (className) { 18353 if (count > 0 || classCounts[className]) { 18354 classCounts[className] = (classCounts[className] || 0) + count; 18355 if (classCounts[className] === +(count > 0)) { 18356 classesToUpdate.push(className); 18357 } 18358 } 18359 }); 18360 element.data('$classCounts', classCounts); 18361 return classesToUpdate.join(' '); 18362 } 18363 18364 function updateClasses (oldClasses, newClasses) { 18365 var toAdd = arrayDifference(newClasses, oldClasses); 18366 var toRemove = arrayDifference(oldClasses, newClasses); 18367 toRemove = digestClassCounts(toRemove, -1); 18368 toAdd = digestClassCounts(toAdd, 1); 18369 18370 if (toAdd.length === 0) { 18371 $animate.removeClass(element, toRemove); 18372 } else if (toRemove.length === 0) { 18373 $animate.addClass(element, toAdd); 18374 } else { 18375 $animate.setClass(element, toAdd, toRemove); 18376 } 18377 } 18378 18379 function ngClassWatchAction(newVal) { 18380 if (selector === true || scope.$index % 2 === selector) { 18381 var newClasses = arrayClasses(newVal || []); 18382 if (!oldVal) { 18383 addClasses(newClasses); 18384 } else if (!equals(newVal,oldVal)) { 18385 var oldClasses = arrayClasses(oldVal); 18386 updateClasses(oldClasses, newClasses); 18387 } 18388 } 18389 oldVal = shallowCopy(newVal); 18390 } 18391 } 18392 }; 18393 18394 function arrayDifference(tokens1, tokens2) { 18395 var values = []; 18396 18397 outer: 18398 for(var i = 0; i < tokens1.length; i++) { 18399 var token = tokens1[i]; 18400 for(var j = 0; j < tokens2.length; j++) { 18401 if(token == tokens2[j]) continue outer; 18402 } 18403 values.push(token); 18404 } 18405 return values; 18406 } 18407
18408 function arrayClasses (classVal) { 18409 if (isArray(classVal)) { 18410 return classVal; 18411 } else if (isString(classVal)) { 18412 return classVal.split(' '); 18413 } else if (isObject(classVal)) { 18414 var classes = [], i = 0; 18415 forEach(classVal, function(v, k) { 18416 if (v) { 18417 classes = classes.concat(k.split(' ')); 18418 } 18419 }); 18420 return classes; 18421 } 18422 return classVal; 18423 } 18424 }]; 18425} 18426 18427/** 18428 * @ngdoc directive 18429 * @name ngClass 18430 * @restrict AC 18431 * 18432 * @description 18433 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 18434 * an expression that represents all classes to be added. 18435 * 18436 * The directive operates in three different ways, depending on which of three types the expression 18437 * evaluates to: 18438 * 18439 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 18440 * names. 18441 * 18442 * 2. If the expression evaluates to an array, each element of the array should be a string that is 18443 * one or more space-delimited class names. 18444 * 18445 * 3. If the expression evaluates to an object, then for each key-value pair of the 18446 * object with a truthy value the corresponding key is used as a class name. 18447 * 18448 * The directive won't add duplicate classes if a particular class was already set. 18449 * 18450 * When the expression changes, the previously added classes are removed and only then the 18451 * new classes are added. 18452 * 18453 * @animations 18454 * add - happens just before the class is applied to the element 18455 * remove - happens just before the class is removed from the element 18456 * 18457 * @element ANY 18458 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 18459 * of the evaluation can be a string representing space delimited class 18460 * names, an array, or a map of class names to boolean values. In the case of a map, the 18461 * names of the properties whose values are truthy will be added as css classes to the 18462 * element. 18463 * 18464 * @example Example that demonstrates basic bindings via ngClass directive. 18465 <example> 18466 <file name="index.html"> 18467 <p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p> 18468 <input type="checkbox" ng-model="deleted"> deleted (apply "strike" class)<br> 18469 <input type="checkbox" ng-model="important"> important (apply "bold" class)<br> 18470 <input type="checkbox" ng-model="error"> error (apply "red" class) 18471 <hr> 18472 <p ng-class="style">Using String Syntax</p> 18473 <input type="text" ng-model="style" placeholder="Type: bold strike red"> 18474 <hr> 18475 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 18476 <input ng-model="style1" placeholder="Type: bold, strike or red"><br> 18477 <input ng-model="style2" placeholder="Type: bold, strike or red"><br> 18478 <input ng-model="style3" placeholder="Type: bold, strike or red"><br> 18479 </file> 18480 <file name="style.css"> 18481 .strike { 18482 text-decoration: line-through; 18483 } 18484 .bold { 18485 font-weight: bold; 18486 } 18487 .red { 18488 color: red; 18489 } 18490 </file> 18491 <file name="protractor.js" type="protractor"> 18492 var ps = element.all(by.css('p')); 18493 18494 it('should let you toggle the class', function() { 18495 18496 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 18497 expect(ps.first().getAttribute('class')).not.toMatch(/red/); 18498 18499 element(by.model('important')).click(); 18500 expect(ps.first().getAttribute('class')).toMatch(/bold/); 18501 18502 element(by.model('error')).click(); 18503 expect(ps.first().getAttribute('class')).toMatch(/red/); 18504 }); 18505 18506 it('should let you toggle string example', function() { 18507 expect(ps.get(1).getAttribute('class')).toBe(''); 18508 element(by.model('style')).clear(); 18509 element(by.model('style')).sendKeys('red'); 18510 expect(ps.get(1).getAttribute('class')).toBe('red'); 18511 }); 18512 18513 it('array example should have 3 classes', function() { 18514 expect(ps.last().getAttribute('class')).toBe(''); 18515 element(by.model('style1')).sendKeys('bold');
18516 element(by.model('style2')).sendKeys('strike'); 18517 element(by.model('style3')).sendKeys('red'); 18518 expect(ps.last().getAttribute('class')).toBe('bold strike red'); 18519 }); 18520 </file> 18521 </example> 18522 18523 ## Animations 18524 18525 The example below demonstrates how to perform animations using ngClass. 18526 18527 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 18528 <file name="index.html"> 18529 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 18530 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 18531 <br> 18532 <span class="base-class" ng-class="myVar">Sample Text</span> 18533 </file> 18534 <file name="style.css"> 18535 .base-class { 18536 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18537 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18538 } 18539 18540 .base-class.my-class { 18541 color: red; 18542 font-size:3em; 18543 } 18544 </file> 18545 <file name="protractor.js" type="protractor"> 18546 it('should check ng-class', function() { 18547 expect(element(by.css('.base-class')).getAttribute('class')).not. 18548 toMatch(/my-class/); 18549 18550 element(by.id('setbtn')).click(); 18551 18552 expect(element(by.css('.base-class')).getAttribute('class')). 18553 toMatch(/my-class/); 18554 18555 element(by.id('clearbtn')).click(); 18556 18557 expect(element(by.css('.base-class')).getAttribute('class')).not. 18558 toMatch(/my-class/); 18559 }); 18560 </file> 18561 </example> 18562 18563 18564 ## ngClass and pre-existing CSS3 Transitions/Animations 18565 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 18566 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 18567 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 18568 to view the step by step details of {@link ngAnimate.$animate#addclass $animate.addClass} and 18569 {@link ngAnimate.$animate#removeclass $animate.removeClass}. 18570 */ 18571var ngClassDirective = classDirective('', true); 18572 18573/** 18574 * @ngdoc directive 18575 * @name ngClassOdd 18576 * @restrict AC 18577 * 18578 * @description 18579 * The `ngClassOdd` and `ngClassEven` directives work exactly as 18580 * {@link ng.directive:ngClass ngClass}, except they work in 18581 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 18582 * 18583 * This directive can be applied only within the scope of an 18584 * {@link ng.directive:ngRepeat ngRepeat}. 18585 * 18586 * @element ANY 18587 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 18588 * of the evaluation can be a string representing space delimited class names or an array. 18589 * 18590 * @example 18591 <example> 18592 <file name="index.html"> 18593 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 18594 <li ng-repeat="name in names"> 18595 <span ng-class-odd="'odd'" ng-class-even="'even'"> 18596 {{name}} 18597 </span> 18598 </li> 18599 </ol> 18600 </file> 18601 <file name="style.css"> 18602 .odd { 18603 color: red; 18604 } 18605 .even { 18606 color: blue; 18607 } 18608 </file> 18609 <file name="protractor.js" type="protractor"> 18610 it('should check ng-class-odd and ng-class-even', function() { 18611 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 18612 toMatch(/odd/); 18613 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 18614 toMatch(/even/); 18615 }); 18616 </file> 18617 </example> 18618 */ 18619var ngClassOddDirective = classDirective('Odd', 0); 18620 18621/** 18622 * @ngdoc directive 18623 * @name ngClassEven 18624 * @restrict AC 18625 * 18626 * @description 18627 * The `ngClassOdd` and `ngClassEven` directives work exactly as 18628 * {@link ng.directive:ngClass ngClass}, except they work in 18629 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 18630 * 18631 * This directive can be applied only within the scope of an 18632 * {@link ng.directive:ngRepeat ngRepeat}. 18633 * 18634 * @element ANY 18635 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 18636 * result of the evaluation can be a string representing space delimited class names or an array.
18637 * 18638 * @example 18639 <example> 18640 <file name="index.html"> 18641 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 18642 <li ng-repeat="name in names"> 18643 <span ng-class-odd="'odd'" ng-class-even="'even'"> 18644 {{name}} 18645 </span> 18646 </li> 18647 </ol> 18648 </file> 18649 <file name="style.css"> 18650 .odd { 18651 color: red; 18652 } 18653 .even { 18654 color: blue; 18655 } 18656 </file> 18657 <file name="protractor.js" type="protractor"> 18658 it('should check ng-class-odd and ng-class-even', function() { 18659 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 18660 toMatch(/odd/); 18661 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 18662 toMatch(/even/); 18663 }); 18664 </file> 18665 </example> 18666 */ 18667var ngClassEvenDirective = classDirective('Even', 1); 18668 18669/** 18670 * @ngdoc directive 18671 * @name ngCloak 18672 * @restrict AC 18673 * 18674 * @description 18675 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 18676 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 18677 * directive to avoid the undesirable flicker effect caused by the html template display. 18678 * 18679 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 18680 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 18681 * of the browser view. 18682 * 18683 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 18684 * `angular.min.js`. 18685 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 18686 * 18687 * ```css 18688 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 18689 * display: none !important; 18690 * } 18691 * ``` 18692 * 18693 * When this css rule is loaded by the browser, all html elements (including their children) that 18694 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 18695 * during the compilation of the template it deletes the `ngCloak` element attribute, making 18696 * the compiled element visible. 18697 * 18698 * For the best result, the `angular.js` script must be loaded in the head section of the html 18699 * document; alternatively, the css rule above must be included in the external stylesheet of the 18700 * application. 18701 * 18702 * Legacy browsers, like IE7, do not provide attribute selector support (a
18702dded in CSS 2.1) so they 18703 * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css 18704 * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. 18705 * 18706 * @element ANY 18707 * 18708 * @example 18709 <example> 18710 <file name="index.html"> 18711 <div id="template1" ng-cloak>{{ 'hello' }}</div> 18712 <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div> 18713 </file> 18714 <file name="protractor.js" type="protractor"> 18715 it('should remove the template directive and css class', function() { 18716 expect($('#template1').getAttribute('ng-cloak')). 18717 toBeNull(); 18718 expect($('#template2').getAttribute('ng-cloak')). 18719 toBeNull(); 18720 }); 18721 </file> 18722 </example> 18723 * 18724 */ 18725var ngCloakDirective = ngDirective({ 18726 compile: function(element, attr) { 18727 attr.$set('ngCloak', undefined); 18728 element.removeClass('ng-cloak'); 18729 } 18730}); 18731 18732/** 18733 * @ngdoc directive 18734 * @name ngController 18735 * 18736 * @description 18737 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 18738 * supports the principles behind the Model-View-Controller design pattern. 18739 * 18740 * MVC components in angular: 18741 * 18742 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 18743 * are accessed through bindings. 18744 * * View â The template (HTML with data bindings) that is rendered into the View. 18745 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 18746 * logic behind the application to decorate the scope with functions and values 18747 * 18748 * Note that you can also attach controllers to the DOM by declaring it in a route definition 18749 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 18750 * again using `ng-controller` in the template itself. This will cause the controller to be attached 18751 * and executed twice. 18752 * 18753 * @element ANY 18754 * @scope 18755 * @param {expression} ngController Name of a globally accessible constructor function or an 18756 * {@link guide/expression expression} that on the current scope evaluates to a 18757 * constructor function. The controller instance can be published into a scope property 18758 * by specifying `as propertyName`. 18759 * 18760 * @example 18761 * Here is a simple form for editing user contact information. Adding, removing, clearing, and 18762 * greeting are methods declared on the controller (see source tab). These methods can 18763 * easily be called from the angular markup. Any changes to the data are automatically reflected 18764 * in the View without the need for a manual update. 18765 * 18766 * Two different declaration styles are included below: 18767 * 18768 * * one binds methods and properties directly onto the controller using `this`: 18769 * `ng-controller="SettingsController1 as settings"` 18770 * * one injects `$scope` into the controller: 18771 * `ng-controller="SettingsController2"` 18772 * 18773 * The second option is more common in the Angular community, and is generally used in boilerplates 18774 * and in this guide. However, there are advantages to binding properties directly to the controller 18775 * and avoiding scope. 18776 * 18777 * * Using `controller as` makes it obvious which controller you are accessing in the template when 18778 * multiple controllers apply to an element. 18779 * * If you are writing your controllers as classes you have easier access to the properties and 18780 * methods, which will appear on the scope, from inside the controller code. 18781 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 18782 * inheritance masking primitives. 18783 * 18784 * This example demonstrates the `controller as` syntax. 18785 * 18786 * <example name="ngControllerAs" module="controllerAsExample"> 18787 * <file name="index.html"> 18788 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 18789 * Name: <input type="text" ng-model="settings.name"/> 18790 * [ <a href="" ng-click="settings.greet()">greet</a> ]<br/> 18791 * Contact: 18792 * <ul> 18793 * <li ng-repeat="contact in settings.contacts"> 18794 * <select ng-model="contact.type"> 18795 * <option>phone</option> 18796 * <option>email</option> 18797 * </select> 18798 * <input type="text" ng-model="contact.value"/> 18799 * [ <a href="" ng-click="settings.clearContact(contact)">clear</a> 18800 * | <a href="" ng-click="settings.removeContact(contact)">X</a> ] 18801 * </li> 18802 * <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li> 18803 * </ul> 18804 * </div> 18805 * </file> 18806 * <file name="app.js">
18807 * angular.module('controllerAsExample', []) 18808 * .controller('SettingsController1', SettingsController1); 18809 * 18810 * function SettingsController1() { 18811 * this.name = "John Smith"; 18812 * this.contacts = [ 18813 * {type: 'phone', value: '408 555 1212'}, 18814 * {type: 'email', value: '[email protected]'} ]; 18815 * } 18816 * 18817 * SettingsController1.prototype.greet = function() { 18818 * alert(this.name); 18819 * }; 18820 * 18821 * SettingsController1.prototype.addContact = function() { 18822 * this.contacts.push({type: 'email', value: '[email protected]'}); 18823 * }; 18824 * 18825 * SettingsController1.prototype.removeContact = function(contactToRemove) { 18826 * var index = this.contacts.indexOf(contactToRemove); 18827 * this.contacts.splice(index, 1); 18828 * }; 18829 * 18830 * SettingsController1.prototype.clearContact = function(contact) { 18831 * contact.type = 'phone'; 18832 * contact.value = ''; 18833 * }; 18834 * </file> 18835 * <file name="protractor.js" type="protractor"> 18836 * it('should check controller as', function() { 18837 * var container = element(by.id('ctrl-as-exmpl')); 18838 * expect(container.element(by.model('settings.name')) 18839 * .getAttribute('value')).toBe('John Smith'); 18840 * 18841 * var firstRepeat = 18842 * container.element(by.repeater('contact in settings.contacts').row(0)); 18843 * var secondRepeat = 18844 * container.element(by.repeater('contact in settings.contacts').row(1)); 18845 * 18846 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 18847 * .toBe('408 555 1212'); 18848 * 18849 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 18850 * .toBe('[email protected]'); 18851 * 18852 * firstRepeat.element(by.linkText('clear')).click(); 18853 * 18854 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 18855 * .toBe(''); 18856 * 18857 * container.element(by.linkText('add')).click(); 18858 * 18859 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 18860 * .element(by.model('contact.value')) 18861 * .getAttribute('value')) 18862 * .toBe('[email protected]'); 18863 * }); 18864 * </file> 18865 * </example> 18866 * 18867 * This example demonstrates the "attach to `$scope`" style of controller. 18868 * 18869 * <example name="ngController" module="controllerExample"> 18870 * <file name="index.html"> 18871 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 18872 * Name: <input type="text" ng-model="name"/> 18873 * [ <a href="" ng-click="greet()">greet</a> ]<br/> 18874 * Contact: 18875 * <ul> 18876 * <li ng-repeat="contact in contacts"> 18877 * <select ng-model="contact.type"> 18878 * <option>phone</option> 18879 * <option>email</option> 18880 * </select> 18881 * <input type="text" ng-model="contact.value"/> 18882 * [ <a href="" ng-click="clearContact(contact)">clear</a> 18883 * | <a href="" ng-click="removeContact(contact)">X</a> ] 18884 * </li> 18885 * <li>[ <a href="" ng-click="addContact()">add</a> ]</li> 18886 * </ul> 18887 * </div> 18888 * </file> 18889 * <file name="app.js"> 18890 * angular.module('controllerExample', []) 18891 * .controller('SettingsController2', ['$scope', SettingsController2]); 18892 * 18893 * function SettingsController2($scope) { 18894 * $scope.name = "John Smith"; 18895 * $scope.contacts = [ 18896 * {type:'phone', value:'408 555 1212'}, 18897 * {type:'email', value:'[email protected]'} ]; 18898 * 18899 * $scope.greet = function() { 18900 * alert($scope.name); 18901 * }; 18902 * 18903 * $scope.addContact = function() { 18904 * $scope.contacts.push({type:'email', value:'[email protected]'}); 18905 * }; 18906 * 18907 * $scope.removeContact = function(contactToRemove) { 18908 * var index = $scope.contacts.indexOf(contactToRemove); 18909 * $scope.contacts.splice(index, 1); 18910 * }; 18911 * 18912 * $scope.clearContact = function(contact) { 18913 * contact.type = 'phone'; 18914 * contact.value = ''; 18915 * }; 18916 * } 18917 * </file> 18918 * <file name="protractor.js" type="protractor"> 18919 * it('should check controller', function() { 18920 * var container = element(by.id('ctrl-exmpl')); 18921 * 18922 * expect(container.element(by.model('name')) 18923 * .getAttribute('value')).toBe('John Smith'); 18924 * 18925 * var firstRepeat = 18926 * container.element(by.repeater('contact in contacts').row(0)); 18927 * var secondRepeat = 18928 * container.element(by.repeater('contact in contacts').row(1)); 18929 * 18930 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 18931 * .toBe('408 555 1212'); 18932 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 18933 * .toBe('[email protected]'); 18934 * 18935 * firstRepeat.element(by.linkText('clear')).click(); 18936 * 18937 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 18938 * .toBe(''); 18939 * 18940 * container.element(by.linkText('add')).click(); 18941 * 18942 * expect(container.element(by.repeater('contact in contacts').row(2)) 18943 * .element(by.model('contact.value')) 18944 * .getAttribute('value')) 18945 * .toBe('[email protected]'); 18946 * }); 18947 * </file> 18948 *</example> 18949 18950 */ 18951var ngControllerDirective = [function() { 18952 return { 18953 scope: true, 18954 controller: '@', 18955 priority: 500 18956 }; 18957}]; 18958 18959/** 18960 * @ngdoc directive 18961 * @name ngCsp 18962 * 18963 * @element html 18964 * @description 18965 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. 18966 * 18967 * This is necessary when developing things like Google Chrome Extensions. 18968 * 18969 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). 18970 * For Angular to be CSP compatible there are only two things that we need to do differently: 18971 * 18972 * - don't use `Function` constructor to generate optimized value getters 18973 * - don't inject custom stylesheet into the document 18974 * 18975 * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` 18976 * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will
18977 * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will 18978 * be raised. 18979 * 18980 * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically 18981 * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). 18982 * To make those directives work in CSP mode, include the `angular-csp.css` manually. 18983 * 18984 * Angular tries to autodetect if CSP is active and automatically turn on the CSP-safe mode. This 18985 * autodetection however triggers a CSP error to be logged in the console: 18986 * 18987 * ``` 18988 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 18989 * script in the following Content Security Policy directive: "default-src 'self'". Note that 18990 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 18991 * ``` 18992 * 18993 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 18994 * directive on the root element of the application or on the `angular.js` script tag, whichever 18995 * appears first in the html document. 18996 * 18997 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 18998 * 18999 * @example 19000 * This example shows how to apply the `ngCsp` directive to the `html` tag. 19001 ```html 19002 <!doctype html> 19003 <html ng-app ng-csp> 19004 ... 19005 ... 19006 </html> 19007 ``` 19008 */ 19009 19010// ngCsp is not implemented as a proper directive any more, because we need it be processed while we 19011// bootstrap the system (before $parse is instantiated), for this reason we just have 19012// the csp.isActive() fn that looks for ng-csp attribute anywhere in the current doc 19013 19014/** 19015 * @ngdoc directive 19016 * @name ngClick 19017 * 19018 * @description 19019 * The ngClick directive allows you to specify custom behavior when 19020 * an element is clicked. 19021 * 19022 * @element ANY 19023 * @priority 0 19024 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 19025 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 19026 * 19027 * @example 19028 <example> 19029 <file name="index.html"> 19030 <button ng-click="count = count + 1" ng-init="count=0"> 19031 Increment 19032 </button> 19033 count: {{count}} 19034 </file> 19035 <file name="protractor.js" type="protractor"> 19036 it('should check ng-click', function() { 19037 expect(element(by.binding('count')).getText()).toMatch('0'); 19038 element(by.css('button')).click(); 19039 expect(element(by.binding('count')).getText()).toMatch('1'); 19040 }); 19041 </file> 19042 </example> 19043 */ 19044/* 19045 * A directive that allows creation of custom onclick handlers that are defined as angular 19046 * expressions and are compiled and executed within the current scope. 19047 * 19048 * Events that are handled via these handler are always configured not to propagate further. 19049 */ 19050var ngEventDirectives = {}; 19051forEach( 19052 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 19053 function(name) { 19054 var directiveName = directiveNormalize('ng-' + name); 19055 ngEventDirectives[directiveName] = ['$parse', function($parse) { 19056 return { 19057 compile: function($element, attr) { 19058 var fn = $parse(attr[directiveName]); 19059 return function ngEventHandler(scope, element) { 19060 element.on(lowercase(name), function(event) { 19061 scope.$apply(function() { 19062 fn(scope, {$event:event}); 19063 }); 19064 }); 19065 }; 19066 } 19067 }; 19068 }]; 19069 } 19070); 19071 19072/** 19073 * @ngdoc directive 19074 * @name ngDblclick 19075 * 19076 * @description 19077 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 19078 * 19079 * @element ANY 19080 * @priority 0 19081 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 19082 * a dblclick. (The Event object is available as `$event`) 19083 * 19084 * @example 19085 <example> 19086 <file name="index.html"> 19087 <button ng-dblclick="count = count + 1" ng-init="count=0"> 19088 Increment (on double click) 19089 </button> 19090 count: {{count}} 19091 </file> 19092 </example> 19093 */ 19094 19095 19096/** 19097 * @ngdoc directive 19098 * @name ngMousedown 19099 * 19100 * @description 19101 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 19102 * 19103 * @element ANY 19104 * @priority 0 19105 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 19106 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 19107 * 19108 * @example 19109 <example> 19110 <file name="index.html"> 19111 <button ng-mousedown="count = count + 1" ng-init="count=0"> 19112 Increment (on mouse down) 19113 </button> 19114 count: {{count}} 19115 </file> 19116 </example> 19117 */ 19118 19119 19120/** 19121 * @ngdoc directive 19122 * @name ngMouseup 19123 * 19124 * @description 19125 * Specify custom behavior on mouseup event. 19126 * 19127 * @element ANY 19128 * @priority 0 19129 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 19130 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 19131 * 19132 * @example 19133 <example> 19134 <file name="index.html"> 19135 <button ng-mouseup="count = count + 1" ng-init="count=0"> 19136 Increment (on mouse up) 19137 </button> 19138 count: {{count}} 19139 </file> 19140 </example> 19141 */ 19142 19143/** 19144 * @ngdoc directive 19145 * @name ngMouseover 19146 * 19147 * @description 19148 * Specify custom behavior on mouseover event. 19149 * 19150 * @element ANY 19151 * @priority 0 19152 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 19153 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 19154 * 19155 * @example 19156 <example> 19157 <file name="index.html"> 19158 <button ng-mouseover="count = count + 1" ng-init="count=0"> 19159 Increment (when mouse is over) 19160 </button> 19161 count: {{count}} 19162 </file> 19163 </example> 19164 */ 19165 19166 19167/** 19168 * @ngdoc directive 19169 * @name ngMouseenter 19170 * 19171 * @description 19172 * Specify custom behavior on mouseenter event. 19173 * 19174 * @element ANY 19175 * @priority 0 19176 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 19177 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 19178 * 19179 * @example 19180 <example> 19181 <file name="index.html"> 19182 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 19183 Increment (when mouse enters) 19184 </button> 19185 count: {{count}} 19186 </file> 19187 </example> 19188 */ 19189 19190 19191/** 19192 * @ngdoc directive 19193 * @name ngMouseleave 19194 * 19195 * @description 19196 * Specify custom behavior on mouseleave event. 19197 * 19198 * @element ANY 19199 * @priority 0 19200 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 19201 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 19202 * 19203 * @example 19204 <example> 19205 <file name="index.html"> 19206 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 19207 Increment (when mouse leaves) 19208 </button> 19209 count: {{count}} 19210 </file> 19211 </example> 19212 */ 19213 19214 19215/** 19216 * @ngdoc directive 19217 * @name ngMousemove 19218 * 19219 * @description 19220 * Specify custom behavior on mousemove event. 19221 * 19222 * @element ANY 19223 * @priority 0 19224 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 19225 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 19226 * 19227 * @example 19228 <example> 19229 <file name="index.html"> 19230 <button ng-mousemove="count = count + 1" ng-init="count=0"> 19231 Increment (when mouse moves) 19232 </button> 19233 count: {{count}} 19234 </file> 19235 </example> 19236 */ 19237 19238 19239/** 19240 * @ngdoc directive 19241 * @name ngKeydown 19242 * 19243 * @description 19244 * Specify custom behavior on keydown event. 19245 * 19246 * @element ANY 19247 * @priority 0 19248 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 19249 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 19250 * 19251 * @example 19252 <example> 19253 <file name="index.html"> 19254 <input ng-keydown="count = count + 1" ng-init="count=0"> 19255 key down count: {{count}} 19256 </file> 19257 </example> 19258 */ 19259 19260 19261/** 19262 * @ngdoc directive 19263 * @name ngKeyup 19264 * 19265 * @description 19266 * Specify custom behavior on keyup event. 19267 * 19268 * @element ANY 19269 * @priority 0 19270 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 19271 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 19272 * 19273 * @example 19274 <example> 19275 <file name="index.html"> 19276 <p>Typing in the input box below updates the key count</p> 19277 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 19278 19279 <p>Typing in the input box below updates the keycode</p> 19280 <input ng-keyup="event=$event"> 19281 <p>event keyCode: {{ event.keyCode }}</p> 19282 <p>event altKey: {{ event.altKey }}</p> 19283 </file> 19284 </example> 19285 */ 19286 19287 19288/** 19289 * @ngdoc directive 19290 * @name ngKeypress 19291 * 19292 * @description 19293 * Specify custom behavior on keypress event. 19294 * 19295 * @element ANY 19296 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 19297 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 19298 * and can be interrogated for keyCode, altKey, etc.) 19299 * 19300 * @example 19301 <example> 19302 <file name="index.html"> 19303 <input ng-keypress="count = count + 1" ng-init="count=0"> 19304 key press count: {{count}} 19305 </file> 19306 </example> 19307 */ 19308 19309 19310/** 19311 * @ngdoc directive 19312 * @name ngSubmit 19313 * 19314 * @description 19315 * Enables binding angular expressions to onsubmit events. 19316 * 19317 * Additionally it prevents the default action (which for form means
19317sending the request to the 19318 * server and reloading the current page), but only if the form does not contain `action`, 19319 * `data-action`, or `x-action` attributes. 19320 * 19321 * <div class="alert alert-warning"> 19322 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 19323 * `ngSubmit` handlers together. See the 19324 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 19325 * for a detailed discussion of when `ngSubmit` may be triggered. 19326 * </div> 19327 * 19328 * @element form 19329 * @priority 0 19330 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 19331 * ({@link guide/expression#-event- Event object is available as `$event`}) 19332 * 19333 * @example 19334 <example module="submitExample"> 19335 <file name="index.html"> 19336 <script> 19337 angular.module('submitExample', []) 19338 .controller('ExampleController', ['$scope', function($scope) { 19339 $scope.list = []; 19340 $scope.text = 'hello'; 19341 $scope.submit = function() { 19342 if ($scope.text) { 19343 $scope.list.push(this.text); 19344 $scope.text = ''; 19345 } 19346 }; 19347 }]); 19348 </script>
19349 <form ng-submit="submit()" ng-controller="ExampleController"> 19350 Enter text and hit enter: 19351 <input type="text" ng-model="text" name="text" /> 19352 <input type="submit" id="submit" value="Submit" /> 19353 <pre>list={{list}}</pre> 19354 </form> 19355 </file> 19356 <file name="protractor.js" type="protractor"> 19357 it('should check ng-submit', function() { 19358 expect(element(by.binding('list')).getText()).toBe('list=[]'); 19359 element(by.css('#submit')).click(); 19360 expect(element(by.binding('list')).getText()).toContain('hello'); 19361 expect(element(by.model('text')).getAttribute('value')).toBe(''); 19362 }); 19363 it('should ignore empty strings', function() { 19364 expect(element(by.binding('list')).getText()).toBe('list=[]'); 19365 element(by.css('#submit')).click(); 19366 element(by.css('#submit')).click(); 19367 expect(element(by.binding('list')).getText()).toContain('hello'); 19368 }); 19369 </file> 19370 </example> 19371 */ 19372 19373/** 19374 * @ngdoc directive 19375 * @name ngFocus 19376 * 19377 * @description 19378 * Specify custom behavior on focus event. 19379 * 19380 * @element window, input, select, textarea, a 19381 * @priority 0 19382 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 19383 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 19384 * 19385 * @example 19386 * See {@link ng.directive:ngClick ngClick} 19387 */ 19388 19389/** 19390 * @ngdoc directive 19391 * @name ngBlur 19392 * 19393 * @description 19394 * Specify custom behavior on blur event. 19395 * 19396 * @element window, input, select, textarea, a 19397 * @priority 0 19398 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 19399 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 19400 * 19401 * @example 19402 * See {@link ng.directive:ngClick ngClick} 19403 */ 19404 19405/** 19406 * @ngdoc directive 19407 * @name ngCopy 19408 * 19409 * @description 19410 * Specify custom behavior on copy event. 19411 * 19412 * @element window, input, select, textarea, a 19413 * @priority 0 19414 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 19415 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 19416 * 19417 * @example 19418 <example> 19419 <file name="index.html"> 19420 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 19421 copied: {{copied}} 19422 </file> 19423 </example> 19424 */ 19425 19426/** 19427 * @ngdoc directive 19428 * @name ngCut 19429 * 19430 * @description 19431 * Specify custom behavior on cut event. 19432 * 19433 * @element window, input, select, textarea, a 19434 * @priority 0 19435 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 19436 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 19437 * 19438 * @example 19439 <example> 19440 <file name="index.html"> 19441 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 19442 cut: {{cut}} 19443 </file> 19444 </example> 19445 */ 19446 19447/** 19448 * @ngdoc directive 19449 * @name ngPaste 19450 * 19451 * @description 19452 * Specify custom behavior on paste event. 19453 * 19454 * @element window, input, select, textarea, a 19455 * @priority 0 19456 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 19457 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 19458 * 19459 * @example 19460 <example> 19461 <file name="index.html"> 19462 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 19463 pasted: {{paste}} 19464 </file> 19465 </example> 19466 */ 19467 19468/** 19469 * @ngdoc directive 19470 * @name ngIf 19471 * @restrict A 19472 * 19473 * @description 19474 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 19475 * {expression}. If the expression assigned to `ngIf` evaluates to a false 19476 * value then the element is removed from the DOM, otherwise a clone of the 19477 * element is reinserted into the DOM. 19478 * 19479 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 19480 * element in the DOM rather than changing its visibility via the `display` css property. A common 19481 * case when this difference is significant is when using css selectors that rely on an element's 19482 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
19482s. 19483 * 19484 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 19485 * is created when the element is restored. The scope created within `ngIf` inherits from 19486 * its parent scope using 19487 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance). 19488 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 19489 * a javascript primitive defined in the parent scope. In this case any modifications made to the 19490 * variable within the child scope will override (hide) the value in the parent scope. 19491 * 19492 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 19493 * is if an element's class attribute is directly modified after it's compiled, using something like 19494 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 19495 * the added class will be lost because the original compiled state is used to regenerate the element. 19496 * 19497 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 19498 * and `leave` effects. 19499 * 19500 * @animations 19501 * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container 19502 * leave - happens just before the ngIf contents are removed from the DOM 19503 * 19504 * @element ANY 19505 * @scope 19506 * @priority 600 19507 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 19508 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 19509 * element is added to the DOM tree. 19510 * 19511 * @example 19512 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 19513 <file name="index.html"> 19514 Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/> 19515 Show when checked: 19516 <span ng-if="checked" class="animate-if"> 19517 I'm removed when the checkbox is unchecked. 19518 </span> 19519 </file> 19520 <file name="animations.css"> 19521 .animate-if { 19522 background:white; 19523 border:1px solid black; 19524 padding:10px; 19525 } 19526 19527 .animate-if.ng-enter, .animate-if.ng-leave { 19528 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19529 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19530 } 19531 19532 .animate-if.ng-enter, 19533 .animate-if.ng-leave.ng-leave-active { 19534 opacity:0; 19535 } 19536 19537 .animate-if.ng-leave, 19538 .animate-if.ng-enter.ng-enter-active { 19539 opacity:1; 19540 } 19541 </file> 19542 </example> 19543 */ 19544var ngIfDirective = ['$animate', function($animate) { 19545 return { 19546 transclude: 'element', 19547 priority: 600, 19548 terminal: true, 19549 restrict: 'A', 19550 $$tlb: true, 19551 link: function ($scope, $element, $attr, ctrl, $transclude) { 19552 var block, childScope, previousElements; 19553 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 19554 19555 if (toBoolean(value)) { 19556 if (!childScope) { 19557 childScope = $scope.$new(); 19558 $transclude(childScope, function (clone) { 19559 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 19560 // Note: We only need the first/last node of the cloned nodes. 19561 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 19562 // by a directive with templateUrl when its template arrives. 19563 block = { 19564 clone: clone 19565 }; 19566 $animate.enter(clone, $element.parent(), $element); 19567 }); 19568 } 19569 } else { 19570 if(previousElements) { 19571 previousElements.remove(); 19572 previousElements = null; 19573 } 19574 if(childScope) { 19575 childScope.$destroy(); 19576 childScope = null; 19577 } 19578 if(block) { 19579 previousElements = getBlockElements(block.clone); 19580 $animate.leave(previousElements, function() { 19581 previousElements = null; 19582 }); 19583 block = null; 19584 } 19585 } 19586 }); 19587 } 19588 }; 19589}]; 19590 19591/** 19592 * @ngdoc directive 19593 * @name ngInclude 19594 * @restrict ECA 19595 * 19596 * @description 19597 * Fetches, compiles and includes an external HTML fragment. 19598 * 19599 * By default, the template URL is restricted to the same domain and protocol as the 19600 * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 19601 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 19602 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 19603 * [wrap them](ng.$sce#trustAsResourceUrl) as trusted values. Refer to Angular's {@link 19604 * ng.$sce Strict Contextual Escaping}. 19605 * 19606 * In addition, the browser's 19607 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 19608 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 19609 * policy may further restrict whether the template is successfully loaded. 19610 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 19611 * access on some browsers. 19612 * 19613 * @animations 19614 * enter - animation is used to bring new content into the browser. 19615 * leave - animation is used to animate existing content away. 19616 * 19617 * The enter and leave animation occur concurrently. 19618 * 19619 * @scope 19620 * @priority 400 19621 * 19622 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 19623 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 19624 * @param {string=} onload Expression to evaluate when a new partial is loaded. 19625 * 19626 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 19627 * $anchorScroll} to scroll the viewport after the content is loaded. 19628 * 19629 * - If the attribute is not set, disable scrolling. 19630 * - If the attribute is set without value, enable scrolling. 19631 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 19632 * 19633 * @example 19634 <example module="includeExample" deps="angular-animate.js" animations="true"> 19635 <file name="index.html">
19636 <div ng-controller="ExampleController"> 19637 <select ng-model="template" ng-options="t.name for t in templates"> 19638 <option value="">(blank)</option> 19639 </select> 19640 url of the template: <tt>{{template.url}}</tt> 19641 <hr/> 19642 <div class="slide-animate-container"> 19643 <div class="slide-animate" ng-include="template.url"></div> 19644 </div> 19645 </div> 19646 </file> 19647 <file name="script.js"> 19648 angular.module('includeExample', ['ngAnimate']) 19649 .controller('ExampleController', ['$scope', function($scope) { 19650 $scope.templates = 19651 [ { name: 'template1.html', url: 'template1.html'}, 19652 { name: 'template2.html', url: 'template2.html'} ]; 19653 $scope.template = $scope.templates[0]; 19654 }]); 19655 </file> 19656 <file name="template1.html"> 19657 Content of template1.html 19658 </file> 19659 <file name="template2.html"> 19660 Content of template2.html 19661 </file> 19662 <file name="animations.css"> 19663 .slide-animate-container { 19664 position:relative; 19665 background:white; 19666 border:1px solid black; 19667 height:40px; 19668 overflow:hidden; 19669 } 19670 19671 .slide-animate { 19672 padding:10px; 19673 } 19674 19675 .slide-animate.ng-enter, .slide-animate.ng-leave { 19676 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19677 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19678 19679 position:absolute; 19680 top:0; 19681 left:0; 19682 right:0; 19683 bottom:0; 19684 display:block; 19685 padding:10px; 19686 } 19687 19688 .slide-animate.ng-enter { 19689 top:-50px; 19690 } 19691 .slide-animate.ng-enter.ng-enter-active { 19692 top:0; 19693 } 19694 19695 .slide-animate.ng-leave { 19696 top:0; 19697 } 19698 .slide-animate.ng-leave.ng-leave-active { 19699 top:50px; 19700 } 19701 </file> 19702 <file name="protractor.js" type="protractor"> 19703 var templateSelect = element(by.model('template')); 19704 var includeElem = element(by.css('[ng-include]')); 19705 19706 it('should load template1.html', function() { 19707 expect(includeElem.getText()).toMatch(/Content of template1.html/); 19708 }); 19709 19710 it('should load template2.html', function() { 19711 if (browser.params.browser == 'firefox') { 19712 // Firefox can't handle using selects 19713 // See https://github.com/angular/protractor/issues/480 19714 return; 19715 } 19716 templateSelect.click(); 19717 templateSelect.all(by.css('option')).get(2).click(); 19718 expect(includeElem.getText()).toMatch(/Content of template2.html/); 19719 }); 19720 19721 it('should change to blank', function() { 19722 if (browser.params.browser == 'firefox') { 19723 // Firefox can't handle using selects 19724 return; 19725 } 19726 templateSelect.click(); 19727 templateSelect.all(by.css('option')).get(0).click(); 19728 expect(includeElem.isPresent()).toBe(false); 19729 }); 19730 </file> 19731 </example> 19732 */ 19733 19734 19735/** 19736 * @ngdoc event 19737 * @name ngInclude#$includeContentRequested 19738 * @eventType emit on the scope ngInclude was declared in 19739 * @description 19740 * Emitted every time the ngInclude content is requested. 19741 */ 19742 19743 19744/** 19745 * @ngdoc event 19746 * @name ngInclude#$includeContentLoaded 19747 * @eventType emit on the current ngInclude scope 19748 * @description 19749 * Emitted every time the ngInclude content is reloaded. 19750 */ 19751var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce', 19752 function($http, $templateCache, $anchorScroll, $animate, $sce) { 19753 return { 19754 restrict: 'ECA', 19755 priority: 400, 19756 terminal: true, 19757 transclude: 'element', 19758 controller: angular.noop, 19759 compile: function(element, attr) { 19760 var srcExp = attr.ngInclude || attr.src, 19761 onloadExp = attr.onload || '', 19762 autoScrollExp = attr.autoscroll; 19763 19764 return function(scope, $element, $attr, ctrl, $transclude) { 19765 var changeCounter = 0, 19766 currentScope, 19767 previousElement, 19768 currentElement; 19769 19770 var cleanupLastIncludeContent = function() { 19771 if(previousElement) { 19772 previousElement.remove(); 19773 previousElement = null; 19774 } 19775 if(currentScope) { 19776 currentScope.$destroy(); 19777 currentScope = null; 19778 } 19779 if(currentElement) { 19780 $animate.leave(currentElement, function() { 19781 previousElement = null; 19782 }); 19783 previousElement = currentElement; 19784 currentElement = null; 19785 } 19786 }; 19787 19788 scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { 19789 var afterAnimation = function() { 19790 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 19791 $anchorScroll(); 19792 } 19793 }; 19794 var thisChangeId = ++changeCounter; 19795 19796 if (src) { 19797 $http.get(src, {cache: $templateCache}).success(function(response) { 19798 if (thisChangeId !== changeCounter) return; 19799 var newScope = scope.$new(); 19800 ctrl.template = response; 19801 19802 // Note: This will also link all children of ng-include that were contained in the original 19803 // html. If that content contains controllers, ... they could pollute/change the scope. 19804 // However, using ng-include on an element with additional content does not make sense... 19805 // Note: We can't remove them in the cloneAttchFn of $transclude as that 19806 // function is called before linking the content, which would apply child 19807 // directives to non existing elements. 19808 var clone = $transclude(newScope, function(clone) { 19809 cleanupLastIncludeContent(); 19810 $animate.enter(clone, null, $element, afterAnimation); 19811 }); 19812 19813 currentScope = newScope; 19814 currentElement = clone; 19815 19816 currentScope.$emit('$includeContentLoaded'); 19817 scope.$eval(onloadExp); 19818 }).error(function() { 19819 if (thisChangeId === changeCounter) cleanupLastIncludeContent(); 19820 }); 19821 scope.$emit('$includeContentRequested'); 19822 } else { 19823 cleanupLastIncludeContent(); 19824 ctrl.template = null; 19825 } 19826 }); 19827 }; 19828 } 19829 }; 19830}]; 19831 19832// This directive is called during the $transclude call of the first `ngInclude` directive. 19833// It will replace and compile the content of the element with the loaded template. 19834// We need this directive so that the element content is already filled when 19835// the link function of another directive on the same element as ngInclude 19836// is called. 19837var ngIncludeFillContentDirective = ['$compile', 19838 function($compile) { 19839 return { 19840 restrict: 'ECA', 19841 priority: -400, 19842 require: 'ngInclude', 19843 link: function(scope, $element, $attr, ctrl) { 19844 $element.html(ctrl.template); 19845 $compile($element.contents())(scope); 19846 } 19847 }; 19848 }]; 19849 19850/** 19851 * @ngdoc directive 19852 * @name ngInit 19853 * @restrict AC 19854 * 19855 * @description 19856 * The `ngInit` directive allows you to evaluate an expression in the 19857 * current scope. 19858 * 19859 * <div class="alert alert-error"> 19860 * The only appropriate use of `ngInit` is for aliasing special properties of 19861 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you 19862 * should use {@link guide/controller controllers} rather than `ngInit` 19863 * to initialize values on a scope. 19864 * </div> 19865 * <div class="alert alert-warning"> 19866 * **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
19867 * sure you have parenthesis for correct precedence: 19868 * <pre class="prettyprint"> 19869 * <div ng-init="test1 = (data | orderBy:'name')"></div> 19870 * </pre> 19871 * </div> 19872 * 19873 * @priority 450 19874 * 19875 * @element ANY 19876 * @param {expression} ngInit {@link guide/expression Expression} to eval. 19877 * 19878 * @example 19879 <example module="initExample"> 19880 <file name="index.html"> 19881 <script> 19882 angular.module('initExample', []) 19883 .controller('ExampleController', ['$scope', function($scope) { 19884 $scope.list = [['a', 'b'], ['c', 'd']]; 19885 }]); 19886 </script> 19887 <div ng-controller="ExampleController"> 19888 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 19889 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 19890 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 19891 </div> 19892 </div> 19893 </div> 19894 </file> 19895 <file name="protractor.js" type="protractor"> 19896 it('should alias index positions', function() { 19897 var elements = element.all(by.css('.example-init')); 19898 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 19899 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 19900 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 19901 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 19902 }); 19903 </file> 19904 </example> 19905 */ 19906var ngInitDirective = ngDirective({ 19907 priority: 450, 19908 compile: function() { 19909 return { 19910 pre: function(scope, element, attrs) { 19911 scope.$eval(attrs.ngInit); 19912 } 19913 }; 19914 } 19915}); 19916 19917/** 19918 * @ngdoc directive 19919 * @name ngNonBindable 19920 * @restrict AC 19921 * @priority 1000 19922 * 19923 * @description 19924 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 19925 * DOM element. This is useful if the element contains what appears to be Angular directives and 19926 * bindings but which should be ignored by Angular. This could be the case if you have a site that 19927 * displays snippets of code, for instance. 19928 * 19929 * @element ANY 19930 * 19931 * @example 19932 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 19933 * but the one wrapped in `ngNonBindable` is left alone. 19934 * 19935 * @example 19936 <example> 19937 <file name="index.html"> 19938 <div>Normal: {{1 + 2}}</div> 19939 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 19940 </file> 19941 <file name="protractor.js" type="protractor"> 19942 it('should check ng-non-bindable', function() { 19943 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 19944 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 19945 }); 19946 </file> 19947 </example> 19948 */ 19949var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 19950 19951/** 19952 * @ngdoc directive 19953 * @name ngPluralize 19954 * @restrict EA 19955 * 19956 * @description 19957 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 19958 * These rules are bundled with angular.js, but can be overridden 19959 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 19960 * by specifying the mappings between 19961 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 19962 * and the strings to be displayed. 19963 * 19964 * # Plural categories and explicit number rules 19965 * There are two 19966 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 19967 * in Angular's default en-US locale: "one" and "other". 19968 * 19969 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 19970 * any number that is not 1), an explicit number rule can only match one number. For example, the 19971 * explicit number rule for "3" matches the number 3. There are examples of plural categories 19972 * and explicit number rules throughout the rest of this documentation. 19973 * 19974 * # Configuring ngPluralize 19975 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 19976 * You can also provide an optional attribute, `offset`. 19977 * 19978 * The value of the `count` attribute can be either a string or an {@link guide/expression 19979 * Angular expression}; these are evaluated on the current scope for its bound value. 19980 * 19981 * The `when` attribute specifies the mappings between plural categories and the actual 19982 * string to be displayed. The value of the attribute should be a JSON object. 19983 * 19984 * The following example shows how to configure ngPluralize: 19985 * 19986 * ```html 19987 * <ng-pluralize count="personCount"
19988 when="{'0': 'Nobody is viewing.', 19989 * 'one': '1 person is viewing.', 19990 * 'other': '{} people are viewing.'}"> 19991 * </ng-pluralize> 19992 *``` 19993 * 19994 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 19995 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 19996 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 19997 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 19998 * show "a dozen people are viewing". 19999 * 20000 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 20001 * into pluralized strings. In the previous example, Angular will replace `{}` with 20002 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 20003 * for <span ng-non-bindable>{{numberExpression}}</span>. 20004 * 20005 * # Configuring ngPluralize with offset 20006 * The `offset` attribute allows further customization of pluralized text, which can result in 20007 * a better user experience. For example, instead of the message "4 people are viewing this document", 20008 * you might display "John, Kate and 2 others are viewing this document". 20009 * The offset attribute allows you to offset a number by any desired value. 20010 * Let's take a look at an example: 20011 * 20012 * ```html 20013 * <ng-pluralize count="personCount" offset=2 20014 * when="{'0': 'Nobody is viewing.', 20015 * '1': '{{person1}} is viewing.', 20016 * '2': '{{person1}} and {{person2}} are viewing.', 20017 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 20018 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 20019 * </ng-pluralize> 20020 * ``` 20021 * 20022 * Notice that we are still using two plural categories(one, other), but we added 20023 * three explicit number rules 0, 1 and 2. 20024 * When one person, perhaps John, views the document, "John is viewing" will be shown. 20025 * When three people view the document, no explicit number rule is found, so 20026 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 20027 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 20028 * is shown. 20029 * 20030 * Note that when you specify offsets, you must provide explicit number rules for 20031 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 20032 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 20033 * plural categories "one" and "other". 20034 * 20035 * @param {string|expression} count The variable to be bound to. 20036 * @param {string} when The mapping between plural category to its corresponding strings. 20037 * @param {number=} offset Offset to deduct from the total number. 20038 * 20039 * @example 20040 <example module="pluralizeExample"> 20041 <file name="index.html"> 20042 <script> 20043 angular.module('pluralizeExample', []) 20044 .controller('ExampleController', ['$scope', function($scope) { 20045 $scope.person1 = 'Igor'; 20046 $scope.person2 = 'Misko'; 20047 $scope.personCount = 1; 20048 }]); 20049 </script>
20050 <div ng-controller="ExampleController"> 20051 Person 1:<input type="text" ng-model="person1" value="Igor" /><br/> 20052 Person 2:<input type="text" ng-model="person2" value="Misko" /><br/> 20053 Number of People:<input type="text" ng-model="personCount" value="1" /><br/> 20054 20055 <!--- Example with simple pluralization rules for en locale ---> 20056 Without Offset: 20057 <ng-pluralize count="personCount" 20058 when="{'0': 'Nobody is viewing.', 20059 'one': '1 person is viewing.', 20060 'other': '{} people are viewing.'}"> 20061 </ng-pluralize><br> 20062 20063 <!--- Example with offset ---> 20064 With Offset(2): 20065 <ng-pluralize count="personCount" offset=2 20066 when="{'0': 'Nobody is viewing.', 20067 '1': '{{person1}} is viewing.', 20068 '2': '{{person1}} and {{person2}} are viewing.', 20069 'one': '{{person1}}, {{person2}} and one other person are viewing.', 20070 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 20071 </ng-pluralize> 20072 </div> 20073 </file> 20074 <file name="protractor.js" type="protractor"> 20075 it('should show correct pluralized string', function() { 20076 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 20077 var withOffset = element.all(by.css('ng-pluralize')).get(1); 20078 var countInput = element(by.model('personCount')); 20079 20080 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 20081 expect(withOffset.getText()).toEqual('Igor is viewing.'); 20082 20083 countInput.clear(); 20084 countInput.sendKeys('0'); 20085 20086 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 20087 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 20088 20089 countInput.clear(); 20090 countInput.sendKeys('2'); 20091 20092 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 20093 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 20094 20095 countInput.clear(); 20096 countInput.sendKeys('3'); 20097 20098 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 20099 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 20100 20101 countInput.clear(); 20102 countInput.sendKeys('4'); 20103 20104 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 20105 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 20106 }); 20107 it('should show data-bound names', function() { 20108 var withOffset = element.all(by.css('ng-pluralize')).get(1); 20109 var personCount = element(by.model('personCount')); 20110 var person1 = element(by.model('person1')); 20111 var person2 = element(by.model('person2')); 20112 personCount.clear(); 20113 personCount.sendKeys('4'); 20114 person1.clear(); 20115 person1.sendKeys('Di'); 20116 person2.clear(); 20117 person2.sendKeys('Vojta'); 20118 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 20119 }); 20120 </file> 20121 </example> 20122 */ 20123var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { 20124 var BRACE = /{}/g; 20125 return { 20126 restrict: 'EA', 20127 link: function(scope, element, attr) { 20128 var numberExp = attr.count, 20129 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 20130 offset = attr.offset || 0, 20131 whens = scope.$eval(whenExp) || {}, 20132 whensExpFns = {}, 20133 startSymbol = $interpolate.startSymbol(), 20134 endSymbol = $interpolate.endSymbol(), 20135 isWhen = /^when(Minus)?(.+)$/; 20136 20137 forEach(attr, function(expression, attributeName) { 20138 if (isWhen.test(attributeName)) { 20139 whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = 20140 element.attr(attr.$attr[attributeName]); 20141 } 20142 }); 20143 forEach(whens, function(expression, key) { 20144 whensExpFns[key] = 20145 $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + 20146 offset + endSymbol)); 20147 }); 20148 20149 scope.$watch(function ngPluralizeWatch() {
20150 var value = parseFloat(scope.$eval(numberExp)); 20151 20152 if (!isNaN(value)) { 20153 //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, 20154 //check it against pluralization rules in $locale service 20155 if (!(value in whens)) value = $locale.pluralCat(value - offset); 20156 return whensExpFns[value](scope, element, true); 20157 } else { 20158 return ''; 20159 } 20160 }, function ngPluralizeWatchAction(newVal) { 20161 element.text(newVal); 20162 }); 20163 } 20164 }; 20165}]; 20166 20167/** 20168 * @ngdoc directive 20169 * @name ngRepeat 20170 * 20171 * @description 20172 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 20173 * instance gets its own scope, where the given loop variable is set to the current collection item, 20174 * and `$index` is set to the item index or key. 20175 * 20176 * Special properties are exposed on the local scope of each template instance, including: 20177 * 20178 * | Variable | Type | Details | 20179 * |-----------|-----------------|-----------------------------------------------------------------------------| 20180 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 20181 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 20182 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 20183 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 20184 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 20185 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 20186 * 20187 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 20188 * This may be useful when, for instance, nesting ngRepeats. 20189 * 20190 * # Special repeat start and end points 20191 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 20192 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 20193 * 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) 20194 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 20195 * 20196 * The example below makes use of this feature: 20197 * ```html 20198 * <header ng-repeat-start="item in items"> 20199 * Header {{ item }} 20200 * </header> 20201 * <div class="body"> 20202 * Body {{ item }} 20203 * </div> 20204 * <footer ng-repeat-end> 20205 * Footer {{ item }} 20206 * </footer> 20207 * ``` 20208 * 20209 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 20210 * ```html 20211 * <header> 20212 * Header A 20213 * </header> 20214 * <div class="body"> 20215 * Body A 20216 * </div> 20217 * <footer> 20218 * Footer A 20219 * </footer> 20220 * <header> 20221 * Header B 20222 * </header> 20223 * <div class="body"> 20224 * Body B 20225 * </div> 20226 * <footer> 20227 * Footer B 20228 * </footer> 20229 * ``` 20230 * 20231 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 20232 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 20233 * 20234 * @animations 20235 * **.enter** - when a new item is added to the list or when an item is revealed after a filter 20236 * 20237 * **.leave** - when an item is removed from the list or when an item is filtered out 20238 * 20239 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 20240 * 20241 * @element ANY 20242 * @scope 20243 * @priority 1000 20244 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 20245 * formats are currently supported: 20246 * 20247 * * `variable in expression` â where variable is the user defined loop variable and `expression` 20248 * is a scope expression giving the collection to enumerate. 20249 * 20250 * For example: `album in artist.albums`. 20251 * 20252 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 20253 * and `expression` is the scope expression giving the collection to enumerate. 20254 * 20255 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 20256 * 20257 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking function 20258 * which can be used to associate the objects in the collection with the DOM elements. If no tracking function 20259 * is specified the ng-repeat associates elements by identity in the collection. It is an error to have 20260 * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are 20261 * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, 20262 * before specifying a tracking expression. 20263 * 20264 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 20265 * will be associated by item identity in the array. 20266 * 20267 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 20268 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 20269 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 20270 * element in the same way in the DOM. 20271 * 20272 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 20273 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 20274 * property is same. 20275 * 20276 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 20277 * to items in conjunction with a tracking expression. 20278 * 20279 * @example 20280 * This example initializes the scope to a list of names and 20281 * then uses `ngRepeat` to display every person: 20282 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20283 <file name="index.html"> 20284 <div ng-init="friends = [ 20285 {name:'John', age:25, gender:'boy'}, 20286 {name:'Jessie', age:30, gender:'girl'}, 20287 {name:'Johanna', age:28, gender:'girl'}, 20288 {name:'Joy', age:15, gender:'girl'}, 20289 {name:'Mary', age:28, gender:'girl'}, 20290 {name:'Peter', age:95, gender:'boy'}, 20291 {name:'Sebastian', age:50, gender:'boy'}, 20292 {name:'Erika', age:27, gender:'girl'}, 20293 {name:'Patrick', age:40, gender:'boy'}, 20294 {name:'Samantha', age:60, gender:'girl'} 20295 ]"> 20296 I have {{friends.length}} friends. They are: 20297 <input type="search" ng-model="q" placeholder="filter friends..." /> 20298 <ul class="example-animate-container"> 20299 <li class="animate-repeat" ng-repeat="friend in friends | filter:q"> 20300 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 20301 </li> 20302 </ul> 20303 </div> 20304 </file> 20305 <file name="animations.css"> 20306 .example-animate-container { 20307 background:white; 20308 border:1px solid black; 20309 list-style:none; 20310 margin:0; 20311 padding:0 10px; 20312 } 20313 20314 .animate-repeat { 20315 line-height:40px; 20316 list-style:none; 20317 box-sizing:border-box; 20318 } 20319 20320 .animate-repeat.ng-move, 20321 .animate-repeat.ng-enter, 20322 .animate-repeat.ng-leave { 20323 -webkit-transition:all linear 0.5s; 20324 transition:all linear 0.5s; 20325 } 20326 20327 .animate-repeat.ng-leave.ng-leave-active, 20328 .animate-repeat.ng-move, 20329 .animate-repeat.ng-enter { 20330 opacity:0; 20331 max-height:0; 20332 } 20333 20334 .animate-repeat.ng-leave,
20335 .animate-repeat.ng-move.ng-move-active, 20336 .animate-repeat.ng-enter.ng-enter-active { 20337 opacity:1; 20338 max-height:40px; 20339 } 20340 </file> 20341 <file name="protractor.js" type="protractor"> 20342 var friends = element.all(by.repeater('friend in friends')); 20343 20344 it('should render initial data set', function() { 20345 expect(friends.count()).toBe(10); 20346 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 20347 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 20348 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 20349 expect(element(by.binding('friends.length')).getText()) 20350 .toMatch("I have 10 friends. They are:"); 20351 }); 20352 20353 it('should update repeater when filter predicate changes', function() { 20354 expect(friends.count()).toBe(10); 20355 20356 element(by.model('q')).sendKeys('ma'); 20357 20358 expect(friends.count()).toBe(2); 20359 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 20360 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 20361 }); 20362 </file> 20363 </example> 20364 */ 20365var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 20366 var NG_REMOVED = '$$NG_REMOVED'; 20367 var ngRepeatMinErr = minErr('ngRepeat'); 20368 return { 20369 transclude: 'element', 20370 priority: 1000, 20371 terminal: true, 20372 $$tlb: true, 20373 link: function($scope, $element, $attr, ctrl, $transclude){ 20374 var expression = $attr.ngRepeat; 20375 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), 20376 trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, 20377 lhs, rhs, valueIdentifier, keyIdentifier, 20378 hashFnLocals = {$id: hashKey}; 20379 20380 if (!match) { 20381 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 20382 expression); 20383 } 20384 20385 lhs = match[1]; 20386 rhs = match[2]; 20387 trackByExp = match[3]; 20388 20389 if (trackByExp) { 20390 trackByExpGetter = $parse(trackByExp); 20391 trackByIdExpFn = function(key, value, index) { 20392 // assign key, value, and $index to the locals so that they can be used in hash functions 20393 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 20394 hashFnLocals[valueIdentifier] = value; 20395 hashFnLocals.$index = index; 20396 return trackByExpGetter($scope, hashFnLocals); 20397 }; 20398 } else { 20399 trackByIdArrayFn = function(key, value) { 20400 return hashKey(value); 20401 }; 20402 trackByIdObjFn = function(key) { 20403 return key; 20404 }; 20405 } 20406 20407 match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); 20408 if (!match) { 20409 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 20410 lhs); 20411 } 20412 valueIdentifier = match[3] || match[1]; 20413 keyIdentifier = match[2]; 20414 20415 // Store a list of elements from previous run. This is a hash where key is the item from the 20416 // iterator, and the value is objects with following properties. 20417 // - scope: bound scope 20418 // - element: previous element. 20419 // - index: position 20420 var lastBlockMap = {}; 20421 20422 //watch props 20423 $scope.$watchCollection(rhs, function ngRepeatAction(collection){ 20424 var index, length, 20425 previousNode = $element[0], // current position of the node 20426 nextNode, 20427 // Same as lastBlockMap but it has the current state. It will become the 20428 // lastBlockMap on the next iteration. 20429 nextBlockMap = {}, 20430 arrayLength, 20431 childScope, 20432 key, value, // key/value of iteration 20433 trackById, 20434 trackByIdFn, 20435 collectionKeys, 20436 block, // last object information {scope, element, id} 20437 nextBlockOrder = [], 20438 elementsToRemove; 20439 20440 20441 if (isArrayLike(collection)) { 20442 collectionKeys = collection; 20443 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 20444 } else { 20445 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 20446 // if object, extract keys, sort them and use to determine order of iteration over obj props 20447 collectionKeys = []; 20448 for (key in collection) { 20449 if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { 20450 collectionKeys.push(key); 20451 } 20452 } 20453 collectionKeys.sort(); 20454 } 20455 20456 arrayLength = collectionKeys.length; 20457 20458 // locate existing items 20459 length = nextBlockOrder.length = collectionKeys.length; 20460 for(index = 0; index < length; index++) { 20461 key = (collection === collectionKeys) ? index : collectionKeys[index]; 20462 value = collection[key]; 20463 trackById = trackByIdFn(key, value, index); 20464 assertNotHasOwnProperty(trackById, '`track by` id'); 20465 if(lastBlockMap.hasOwnProperty(trackById)) { 20466 block = lastBlockMap[trackById]; 20467 delete lastBlockMap[trackById]; 20468 nextBlockMap[trackById] = block; 20469 nextBlockOrder[index] = block; 20470 } else if (nextBlockMap.hasOwnProperty(trackById)) { 20471 // restore lastBlockMap
20472 forEach(nextBlockOrder, function(block) { 20473 if (block && block.scope) lastBlockMap[block.id] = block; 20474 }); 20475 // This is a duplicate and we need to throw an error 20476 throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}", 20477 expression, trackById); 20478 } else { 20479 // new never before seen block 20480 nextBlockOrder[index] = { id: trackById }; 20481 nextBlockMap[trackById] = false; 20482 } 20483 } 20484 20485 // remove existing items 20486 for (key in lastBlockMap) { 20487 // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn 20488 if (lastBlockMap.hasOwnProperty(key)) { 20489 block = lastBlockMap[key]; 20490 elementsToRemove = getBlockElements(block.clone); 20491 $animate.leave(elementsToRemove); 20492 forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); 20493 block.scope.$destroy(); 20494 } 20495 } 20496 20497 // we are not using forEach for perf reasons (trying to avoid #call) 20498 for (index = 0, length = collectionKeys.length; index < length; index++) { 20499 key = (collection === collectionKeys) ? index : collectionKeys[index]; 20500 value = collection[key]; 20501 block = nextBlockOrder[index]; 20502 if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]); 20503 20504 if (block.scope) { 20505 // if we have already seen this object, then we need to reuse the 20506 // associated scope/element 20507 childScope = block.scope; 20508 20509 nextNode = previousNode; 20510 do { 20511 nextNode = nextNode.nextSibling; 20512 } while(nextNode && nextNode[NG_REMOVED]); 20513 20514 if (getBlockStart(block) != nextNode) { 20515 // existing item which got moved 20516 $animate.move(getBlockElements(block.clone), null, jqLite(previousNode)); 20517 } 20518 previousNode = getBlockEnd(block); 20519 } else { 20520 // new item which we don't know about 20521 childScope = $scope.$new(); 20522 } 20523 20524 childScope[valueIdentifier] = value; 20525 if (keyIdentifier) childScope[keyIdentifier] = key; 20526 childScope.$index = index; 20527 childScope.$first = (index === 0); 20528 childScope.$last = (index === (arrayLength - 1)); 20529 childScope.$middle = !(childScope.$first || childScope.$last); 20530 // jshint bitwise: false 20531 childScope.$odd = !(childScope.$even = (index&1) === 0); 20532 // jshint bitwise: true 20533 20534 if (!block.scope) { 20535 $transclude(childScope, function(clone) { 20536 clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); 20537 $animate.enter(clone, null, jqLite(previousNode)); 20538 previousNode = clone; 20539 block.scope = childScope; 20540 // Note: We only need the first/last node of the cloned nodes. 20541 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 20542 // by a directive with templateUrl when its template arrives. 20543 block.clone = clone; 20544 nextBlockMap[block.id] = block; 20545 }); 20546 } 20547 } 20548 lastBlockMap = nextBlockMap; 20549 }); 20550 } 20551 }; 20552 20553 function getBlockStart(block) { 20554 return block.clone[0]; 20555 } 20556 20557 function getBlockEnd(block) { 20558 return block.clone[block.clone.length - 1]; 20559 } 20560}]; 20561 20562/** 20563 * @ngdoc directive 20564 * @name ngShow 20565 * 20566 * @description 20567 * The `ngShow` directive shows or hides the given HTML element based on the expression 20568 * provided to the ngShow attribute. The element is shown or hidden by removing or adding 20569 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 20570 * in AngularJS and sets the display style to none (using an !important flag). 20571 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 20572 * 20573 * ```html 20574 * <!-- when $scope.myValue is truthy (element is visible) --> 20575 * <div ng-show="myValue"></div> 20576 * 20577 * <!-- when $scope.myValue is falsy (element is hidden) --> 20578 * <div ng-show="myValue" class="ng-hide"></div> 20579 * ``` 20580 * 20581 * When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute 20582 * on the element causing it to become hidden. When true, the ng-hide CSS class is removed 20583 * from the element causing the element not to appear hidden. 20584 * 20585 * <div class="alert alert-warning"> 20586 * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):<br /> 20587 * "f" / "0" / "false" / "no" / "n" / "[]" 20588 * </div> 20589 * 20590 * ## Why is !important used? 20591 * 20592 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 20593 * can be easily overridden by heavier selectors. For example, something as simple 20594 * as changing the display style on a HTML list item would make hidden elements appear visible.
20595 * This also becomes a bigger issue when dealing with CSS frameworks. 20596 * 20597 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 20598 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 20599 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 20600 * 20601 * ### Overriding .ng-hide 20602 * 20603 * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change 20604 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 20605 * class in CSS: 20606 * 20607 * ```css 20608 * .ng-hide { 20609 * //this is just another form of hiding an element 20610 * display:block!important; 20611 * position:absolute; 20612 * top:-9999px; 20613 * left:-9999px; 20614 * } 20615 * ``` 20616 * 20617 * By default you don't need to override in CSS anything and the animations will work around the display style. 20618 * 20619 * ## A note about animations with ngShow 20620 * 20621 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 20622 * is true and false. This system works like the animation system present with ngClass except that 20623 * you must also include the !important flag to override the display property 20624 * so that you can perform an animation when the element is hidden during the time of the animation. 20625 * 20626 * ```css 20627 * // 20628 * //a working example can be found at the bottom of this page 20629 * // 20630 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 20631 * transition:0.5s linear all; 20632 * } 20633 * 20634 * .my-element.ng-hide-add { ... } 20635 * .my-element.ng-hide-add.ng-hide-add-active { ... } 20636 * .my-element.ng-hide-remove { ... } 20637 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 20638 * ``` 20639 * 20640 * Keep in mind that, as of AngularJS version 1.2.17 (and 1.3.0-beta.11), there is no need to change the display 20641 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 20642 * 20643 * @animations 20644 * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible 20645 * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden 20646 * 20647 * @element ANY 20648 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 20649 * then the element is shown or hidden respectively. 20650 * 20651 * @example 20652 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20653 <file name="index.html"> 20654 Click me: <input type="checkbox" ng-model="checked"><br/> 20655 <div> 20656 Show: 20657 <div class="check-element animate-show" ng-show="checked"> 20658 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 20659 </div> 20660 </div> 20661 <div> 20662 Hide: 20663 <div class="check-element animate-show" ng-hide="checked"> 20664 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 20665 </div> 20666 </div> 20667 </file> 20668 <file name="glyphicons.css"> 20669 @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); 20670 </file> 20671 <file name="animations.css"> 20672 .animate-show { 20673 -webkit-transition:all linear 0.5s; 20674 transition:all linear 0.5s; 20675 line-height:20px; 20676 opacity:1; 20677 padding:10px; 20678 border:1px solid black; 20679 background:white; 20680 } 20681 20682 .animate-show.ng-hide { 20683 line-height:0; 20684 opacity:0; 20685 padding:0 10px; 20686 } 20687 20688 .check-element { 20689 padding:10px; 20690 border:1px solid black; 20691 background:white; 20692 } 20693 </file> 20694 <file name="protractor.js" type="protractor"> 20695 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 20696 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 20697 20698 it('should check ng-show / ng-hide', function() { 20699 expect(thumbsUp.isDisplayed()).toBeFalsy(); 20700 expect(thumbsDown.isDisplayed()).toBeTruthy(); 20701
20702 element(by.model('checked')).click(); 20703 20704 expect(thumbsUp.isDisplayed()).toBeTruthy(); 20705 expect(thumbsDown.isDisplayed()).toBeFalsy(); 20706 }); 20707 </file> 20708 </example> 20709 */ 20710var ngShowDirective = ['$animate', function($animate) { 20711 return function(scope, element, attr) { 20712 scope.$watch(attr.ngShow, function ngShowWatchAction(value){ 20713 $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); 20714 }); 20715 }; 20716}]; 20717 20718 20719/** 20720 * @ngdoc directive 20721 * @name ngHide 20722 * 20723 * @description 20724 * The `ngHide` directive shows or hides the given HTML element based on the expression 20725 * provided to the ngHide attribute. The element is shown or hidden by removing or adding 20726 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 20727 * in AngularJS and sets the display style to none (using an !important flag). 20728 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 20729 * 20730 * ```html 20731 * <!-- when $scope.myValue is truthy (element is hidden) --> 20732 * <div ng-hide="myValue" class="ng-hide"></div> 20733 * 20734 * <!-- when $scope.myValue is falsy (element is visible) --> 20735 * <div ng-hide="myValue"></div> 20736 * ``` 20737 * 20738 * When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute 20739 * on the element causing it to become hidden. When false, the ng-hide CSS class is removed 20740 * from the element causing the element not to appear hidden. 20741 * 20742 * <div class="alert alert-warning"> 20743 * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):<br /> 20744 * "f" / "0" / "false" / "no" / "n" / "[]" 20745 * </div> 20746 * 20747 * ## Why is !important used? 20748 * 20749 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 20750 * can be easily overridden by heavier selectors. For example, something as simple 20751 * as changing the display style on a HTML list item would make hidden elements appear visible. 20752 * This also becomes a bigger issue when dealing with CSS frameworks. 20753 * 20754 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 20755 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 20756 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 20757 * 20758 * ### Overriding .ng-hide 20759 * 20760 * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change 20761 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 20762 * class in CSS: 20763 * 20764 * ```css 20765 * .ng-hide { 20766 * //this is just another form of hiding an element 20767 * display:block!important; 20768 * position:absolute; 20769 * top:-9999px; 20770 * left:-9999px; 20771 * } 20772 * ``` 20773 * 20774 * By default you don't need to override in CSS anything and the animations will work around the display style. 20775 * 20776 * ## A note about animations with ngHide 20777 * 20778 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 20779 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 20780 * CSS class is added and removed for you instead of your own CSS class. 20781 * 20782 * ```css 20783 * // 20784 * //a working example can be found at the bottom of this page 20785 * // 20786 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 20787 * transition:0.5s linear all; 20788 * } 20789 * 20790 * .my-element.ng-hide-add { ... } 20791 * .my-element.ng-hide-add.ng-hide-add-active { ... } 20792 * .my-element.ng-hide-remove { ... } 20793 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 20794 * ``` 20795 * 20796 * Keep in mind that, as of AngularJS version 1.2.17 (and 1.3.0-beta.11), there is no need to change the display 20797 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 20798 * 20799 * @animations 20800 * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden 20801 * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible 20802 * 20803 * @element ANY 20804 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 20805 * the element is shown or hidden respectively. 20806 * 20807 * @example 20808 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20809 <file name="index.html"> 20810 Click me: <input type="checkbox" ng-model="checked"><br/> 20811 <div> 20812 Show: 20813 <div class="check-element animate-hide" ng-show="checked">
20814 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 20815 </div> 20816 </div> 20817 <div> 20818 Hide: 20819 <div class="check-element animate-hide" ng-hide="checked"> 20820 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 20821 </div> 20822 </div> 20823 </file> 20824 <file name="glyphicons.css"> 20825 @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); 20826 </file> 20827 <file name="animations.css"> 20828 .animate-hide { 20829 -webkit-transition:all linear 0.5s; 20830 transition:all linear 0.5s; 20831 line-height:20px; 20832 opacity:1; 20833 padding:10px; 20834 border:1px solid black; 20835 background:white; 20836 } 20837 20838 .animate-hide.ng-hide { 20839 line-height:0; 20840 opacity:0; 20841 padding:0 10px; 20842 } 20843 20844 .check-element { 20845 padding:10px; 20846 border:1px solid black; 20847 background:white; 20848 } 20849 </file> 20850 <file name="protractor.js" type="protractor"> 20851 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 20852 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 20853 20854 it('should check ng-show / ng-hide', function() { 20855 expect(thumbsUp.isDisplayed()).toBeFalsy(); 20856 expect(thumbsDown.isDisplayed()).toBeTruthy(); 20857 20858 element(by.model('checked')).click(); 20859 20860 expect(thumbsUp.isDisplayed()).toBeTruthy(); 20861 expect(thumbsDown.isDisplayed()).toBeFalsy(); 20862 }); 20863 </file> 20864 </example> 20865 */ 20866var ngHideDirective = ['$animate', function($animate) { 20867 return function(scope, element, attr) { 20868 scope.$watch(attr.ngHide, function ngHideWatchAction(value){ 20869 $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); 20870 }); 20871 }; 20872}]; 20873 20874/** 20875 * @ngdoc directive 20876 * @name ngStyle 20877 * @restrict AC 20878 * 20879 * @description 20880 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 20881 * 20882 * @element ANY 20883 * @param {expression} ngStyle 20884 * 20885 * {@link guide/expression Expression} which evals to an 20886 * object whose keys are CSS style names and values are corresponding values for those CSS 20887 * keys. 20888 * 20889 * Since some CSS style names are not valid keys for an object, they must be quoted. 20890 * See the 'background-color' style in the example below. 20891 * 20892 * @example 20893 <example> 20894 <file name="index.html"> 20895 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 20896 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 20897 <input type="button" value="clear" ng-click="myStyle={}"> 20898 <br/> 20899 <span ng-style="myStyle">Sample Text</span> 20900 <pre>myStyle={{myStyle}}</pre> 20901 </file> 20902 <file name="style.css"> 20903 span { 20904 color: black; 20905 } 20906 </file> 20907 <file name="protractor.js" type="protractor"> 20908 var colorSpan = element(by.css('span')); 20909 20910 it('should check ng-style', function() { 20911 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 20912 element(by.css('input[value=\'set color\']')).click(); 20913 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 20914 element(by.css('input[value=clear]')).click(); 20915 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 20916 }); 20917 </file> 20918 </example> 20919 */ 20920var ngStyleDirective = ngDirective(function(scope, element, attr) { 20921 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 20922 if (oldStyles && (newStyles !== oldStyles)) { 20923 forEach(oldStyles, function(val, style) { element.css(style, '');}); 20924 } 20925 if (newStyles) element.css(newStyles); 20926 }, true); 20927}); 20928 20929/** 20930 * @ngdoc directive 20931 * @name ngSwitch 20932 * @restrict EA 20933 * 20934 * @description 20935 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 20936 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 20937 * as specified in the template. 20938 * 20939 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 20940 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 20941 * matches the value obtained from the evaluated expression. In other words, you define a container element 20942 * (where you place the directive), place an expression on the **`on="..."` attribute** 20943 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 20944 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 20945 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 20946 * attribute is displayed. 20947 * 20948 * <div class="alert alert-info"> 20949 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 20950 * as literal string values to match against. 20951 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
20951omeVal"` not against the 20952 * value of the expression `$scope.someVal`. 20953 * </div> 20954 20955 * @animations 20956 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 20957 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 20958 * 20959 * @usage 20960 * 20961 * ``` 20962 * <ANY ng-switch="expression"> 20963 * <ANY ng-switch-when="matchValue1">...</ANY> 20964 * <ANY ng-switch-when="matchValue2">...</ANY> 20965 * <ANY ng-switch-default>...</ANY> 20966 * </ANY> 20967 * ``` 20968 * 20969 * 20970 * @scope 20971 * @priority 800 20972 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>. 20973 * On child elements add: 20974 * 20975 * * `ngSwitchWhen`: the case statement to match against. If match then this 20976 * case will be displayed. If the same match appears multiple times, all the 20977 * elements will be displayed. 20978 * * `ngSwitchDefault`: the default case when no other case match. If there 20979 * are multiple default cases, all of them will be displayed when no other 20980 * case match. 20981 * 20982 * 20983 * @example 20984 <example module="switchExample" deps="angular-animate.js" animations="true"> 20985 <file name="index.html">
20986 <div ng-controller="ExampleController"> 20987 <select ng-model="selection" ng-options="item for item in items"> 20988 </select> 20989 <tt>selection={{selection}}</tt> 20990 <hr/> 20991 <div class="animate-switch-container" 20992 ng-switch on="selection"> 20993 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 20994 <div class="animate-switch" ng-switch-when="home">Home Span</div> 20995 <div class="animate-switch" ng-switch-default>default</div> 20996 </div> 20997 </div> 20998 </file> 20999 <file name="script.js"> 21000 angular.module('switchExample', ['ngAnimate']) 21001 .controller('ExampleController', ['$scope', function($scope) { 21002 $scope.items = ['settings', 'home', 'other']; 21003 $scope.selection = $scope.items[0]; 21004 }]); 21005 </file> 21006 <file name="animations.css"> 21007 .animate-switch-container { 21008 position:relative; 21009 background:white; 21010 border:1px solid black; 21011 height:40px; 21012 overflow:hidden; 21013 } 21014 21015 .animate-switch { 21016 padding:10px; 21017 } 21018 21019 .animate-switch.ng-animate { 21020 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 21021 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 21022 21023 position:absolute; 21024 top:0; 21025 left:0; 21026 right:0; 21027 bottom:0; 21028 } 21029 21030 .animate-switch.ng-leave.ng-leave-active, 21031 .animate-switch.ng-enter { 21032 top:-50px; 21033 } 21034 .animate-switch.ng-leave, 21035 .animate-switch.ng-enter.ng-enter-active { 21036 top:0; 21037 } 21038 </file> 21039 <file name="protractor.js" type="protractor"> 21040 var switchElem = element(by.css('[ng-switch]')); 21041 var select = element(by.model('selection')); 21042 21043 it('should start in settings', function() { 21044 expect(switchElem.getText()).toMatch(/Settings Div/); 21045 }); 21046 it('should change to home', function() { 21047 select.all(by.css('option')).get(1).click(); 21048 expect(switchElem.getText()).toMatch(/Home Span/); 21049 }); 21050 it('should select default', function() { 21051 select.all(by.css('option')).get(2).click(); 21052 expect(switchElem.getText()).toMatch(/default/); 21053 }); 21054 </file> 21055 </example> 21056 */ 21057var ngSwitchDirective = ['$animate', function($animate) { 21058 return { 21059 restrict: 'EA', 21060 require: 'ngSwitch', 21061 21062 // asks for $scope to fool the BC controller module 21063 controller: ['$scope', function ngSwitchController() { 21064 this.cases = {}; 21065 }], 21066 link: function(scope, element, attr, ngSwitchController) { 21067 var watchExpr = attr.ngSwitch || attr.on, 21068 selectedTranscludes = [], 21069 selectedElements = [], 21070 previousElements = [], 21071 selectedScopes = []; 21072 21073 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 21074 var i, ii; 21075 for (i = 0, ii = previousElements.length; i < ii; ++i) { 21076 previousElements[i].remove(); 21077 } 21078 previousElements.length = 0; 21079 21080 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 21081 var selected = selectedElements[i]; 21082 selectedScopes[i].$destroy(); 21083 previousElements[i] = selected; 21084 $animate.leave(selected, function() { 21085 previousElements.splice(i, 1); 21086 }); 21087 } 21088 21089 selectedElements.length = 0; 21090 selectedScopes.length = 0; 21091 21092 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 21093 scope.$eval(attr.change); 21094 forEach(selectedTranscludes, function(selectedTransclude) { 21095 var selectedScope = scope.$new(); 21096 selectedScopes.push(selectedScope); 21097 selectedTransclude.transclude(selectedScope, function(caseElement) { 21098 var anchor = selectedTransclude.element; 21099 21100 selectedElements.push(caseElement); 21101 $animate.enter(caseElement, anchor.parent(), anchor); 21102 }); 21103 }); 21104 } 21105 }); 21106 } 21107 }; 21108}]; 21109 21110var ngSwitchWhenDirective = ngDirective({ 21111 transclude: 'element', 21112 priority: 800, 21113 require: '^ngSwitch', 21114 link: function(scope, element, attrs, ctrl, $transclude) { 21115 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 21116 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 21117 } 21118}); 21119 21120var ngSwitchDefaultDirective = ngDirective({ 21121 transclude: 'element', 21122 priority: 800, 21123 require: '^ngSwitch', 21124 link: function(scope, element, attr, ctrl, $transclude) { 21125 ctrl.cases['?'] = (ctrl.cases['?'] || []); 21126 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 21127 } 21128}); 21129 21130/** 21131 * @ngdoc directive 21132 * @name ngTransclude 21133 * @restrict AC 21134 * 21135 * @description 21136 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 21137 * 21138 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 21139 * 21140 * @element ANY 21141 * 21142 * @example 21143 <example module="transcludeExample"> 21144 <file name="index.html"> 21145 <script> 21146 angular.module('transcludeExample', []) 21147 .directive('pane', function(){ 21148 return { 21149 restrict: 'E', 21150 transclude: true, 21151 scope: { title:'@' }, 21152 template: '<div style="border: 1px solid black;">
21152' + 21153 '<div style="background-color: gray">{{title}}</div>' + 21154 '<div ng-transclude></div>' + 21155 '</div>' 21156 }; 21157 }) 21158 .controller('ExampleController', ['$scope', function($scope) { 21159 $scope.title = 'Lorem Ipsum'; 21160 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 21161 }]); 21162 </script> 21163 <div ng-controller="ExampleController"> 21164 <input ng-model="title"><br> 21165 <textarea ng-model="text"></textarea> <br/> 21166 <pane title="{{title}}">{{text}}</pane> 21167 </div> 21168 </file> 21169 <file name="protractor.js" type="protractor"> 21170 it('should have transcluded', function() { 21171 var titleElement = element(by.model('title')); 21172 titleElement.clear(); 21173 titleElement.sendKeys('TITLE'); 21174 var textElement = element(by.model('text')); 21175 textElement.clear(); 21176 textElement.sendKeys('TEXT'); 21177 expect(element(by.binding('title')).getText()).toEqual('TITLE'); 21178 expect(element(by.binding('text')).getText()).toEqual('TEXT'); 21179 }); 21180 </file> 21181 </example> 21182 * 21183 */ 21184var ngTranscludeDirective = ngDirective({ 21185 link: function($scope, $element, $attrs, controller, $transclude) { 21186 if (!$transclude) { 21187 throw minErr('ngTransclude')('orphan', 21188 'Illegal use of ngTransclude directive in the template! ' + 21189 'No parent directive that requires a transclusion found. ' + 21190 'Element: {0}', 21191 startingTag($element)); 21192 } 21193 21194 $transclude(function(clone) { 21195 $element.empty(); 21196 $element.append(clone); 21197 }); 21198 } 21199}); 21200 21201/** 21202 * @ngdoc directive 21203 * @name script 21204 * @restrict E 21205 * 21206 * @description 21207 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 21208 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 21209 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 21210 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 21211 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 21212 * 21213 * @param {string} type Must be set to `'text/ng-template'`. 21214 * @param {string} id Cache name of the template. 21215 * 21216 * @example 21217 <example> 21218 <file name="index.html"> 21219 <script type="text/ng-template" id="/tpl.html"> 21220 Content of the template. 21221 </script> 21222 21223 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 21224 <div id="tpl-content" ng-include src="currentTpl"></div> 21225 </file> 21226 <file name="protractor.js" type="protractor"> 21227 it('should load template defined inside script tag', function() { 21228 element(by.css('#tpl-link')).click(); 21229 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 21230 }); 21231 </file> 21232 </example> 21233 */ 21234var scriptDirective = ['$templateCache', function($templateCache) { 21235 return { 21236 restrict: 'E', 21237 terminal: true, 21238 compile: function(element, attr) { 21239 if (attr.type == 'text/ng-template') { 21240 var templateUrl = attr.id, 21241 // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent 21242 text = element[0].text; 21243 21244 $templateCache.put(templateUrl, text); 21245 } 21246 } 21247 }; 21248}]; 21249 21250var ngOptionsMinErr = minErr('ngOptions'); 21251/** 21252 * @ngdoc directive 21253 * @name select 21254 * @restrict E 21255 * 21256 * @description 21257 * HTML `SELECT` element with angular data-binding. 21258 * 21259 * # `ngOptions` 21260 * 21261 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 21262 * elements for the `<select>` element using the array or object obtained by evaluating the 21263 * `ngOptions` comprehension_expression. 21264 * 21265 * When an item in the `<select>` menu is selected, the array element or object property 21266 * represented by the selected option will be bound to the model identified by the `ngModel` 21267 * directive. 21268 * 21269 * <div class="alert alert-warning"> 21270 * **Note:** `ngModel` compares by reference, not value. This is important when binding to an 21271 * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). 21272 * </div> 21273 * 21274 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 21275 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 21276 * option. See example below for demonstration. 21277 * 21278 * <div class="alert alert-warning"> 21279 * **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead 21280 * of {@link ng.directive:ngRepeat ngRepeat} when you want the 21281 * `select` model to be bound to a non-string value. This is because an option element can only 21282 * be bound to string values at present. 21283 * </div> 21284 * 21285 * @param {string} ngModel Assignable angular expression to data-bind to. 21286 * @param {string=} name Property name of the form under which the control is published. 21287 * @param {string=} required The control is considered valid only if value is entered. 21288 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21289 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21290 * `required` when you want to data-bind to the `required` attribute. 21291 * @param {comprehension_expression=} ngOptions in one of the following forms: 21292 * 21293 * * for array data sources: 21294 * * `label` **`for`** `value` **`in`** `array` 21295 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 21296 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 21297 * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 21298 * * for object data sources:
21299 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 21300 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 21301 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 21302 * * `select` **`as`** `label` **`group by`** `group` 21303 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 21304 * 21305 * Where: 21306 * 21307 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 21308 * * `value`: local variable which will refer to each item in the `array` or each property value 21309 * of `object` during iteration. 21310 * * `key`: local variable which will refer to a property name in `object` during iteration. 21311 * * `label`: The result of this expression will be the label for `<option>` element. The 21312 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 21313 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 21314 * element. If not specified, `select` expression will default to `value`. 21315 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 21316 * DOM element. 21317 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 21318 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 21319 * `value` variable (e.g. `value.propertyName`). 21320 * 21321 * @example 21322 <example module="selectExample"> 21323 <file name="index.html"> 21324 <script> 21325 angular.module('selectExample', []) 21326 .controller('ExampleController', ['$scope', function($scope) { 21327 $scope.colors = [ 21328 {name:'black', shade:'dark'}, 21329 {name:'white', shade:'light'}, 21330 {name:'red', shade:'dark'}, 21331 {name:'blue', shade:'dark'}, 21332 {name:'yellow', shade:'light'} 21333 ]; 21334 $scope.myColor = $scope.colors[2]; // red 21335 }]); 21336 </script>
21337 <div ng-controller="ExampleController"> 21338 <ul> 21339 <li ng-repeat="color in colors"> 21340 Name: <input ng-model="color.name"> 21341 [<a href ng-click="colors.splice($index, 1)">X</a>] 21342 </li> 21343 <li> 21344 [<a href ng-click="colors.push({})">add</a>] 21345 </li> 21346 </ul> 21347 <hr/> 21348 Color (null not allowed): 21349 <select ng-model="myColor" ng-options="color.name for color in colors"></select><br> 21350 21351 Color (null allowed): 21352 <span class="nullable"> 21353 <select ng-model="myColor" ng-options="color.name for color in colors"> 21354 <option value="">-- choose color --</option> 21355 </select> 21356 </span><br/> 21357 21358 Color grouped by shade: 21359 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 21360 </select><br/> 21361 21362 21363 Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br> 21364 <hr/> 21365 Currently selected: {{ {selected_color:myColor} }} 21366 <div style="border:solid 1px black; height:20px" 21367 ng-style="{'background-color':myColor.name}"> 21368 </div> 21369 </div> 21370 </file> 21371 <file name="protractor.js" type="protractor"> 21372 it('should check ng-options', function() { 21373 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 21374 element.all(by.model('myColor')).first().click(); 21375 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 21376 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 21377 element(by.css('.nullable select[ng-model="myColor"]')).click(); 21378 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 21379 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 21380 }); 21381 </file> 21382 </example> 21383 */ 21384 21385var ngOptionsDirective = valueFn({ terminal: true }); 21386// jshint maxlen: false 21387var selectDirective = ['$compile', '$parse', function($compile, $parse) { 21388 //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 21389 var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\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]+?))?$/, 21390 nullModelCtrl = {$setViewValue: noop}; 21391// jshint maxlen: 100 21392 21393 return { 21394 restrict: 'E', 21395 require: ['select', '?ngModel'], 21396 controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 21397 var self = this, 21398 optionsMap = {}, 21399 ngModelCtrl = nullModelCtrl, 21400 nullOption, 21401 unknownOption; 21402 21403 21404 self.databound = $attrs.ngModel; 21405 21406 21407 self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { 21408 ngModelCtrl = ngModelCtrl_; 21409 nullOption = nullOption_; 21410 unknownOption = unknownOption_; 21411 }; 21412 21413 21414 self.addOption = function(value) { 21415 assertNotHasOwnProperty(value, '"option value"'); 21416 optionsMap[value] = true; 21417 21418 if (ngModelCtrl.$viewValue == value) { 21419 $element.val(value); 21420 if (unknownOption.parent()) unknownOption.remove(); 21421 } 21422 }; 21423 21424 21425 self.removeOption = function(value) { 21426 if (this.hasOption(value)) { 21427 delete optionsMap[value]; 21428 if (ngModelCtrl.$viewValue == value) { 21429 this.renderUnknownOption(value); 21430 } 21431 } 21432 }; 21433 21434 21435 self.renderUnknownOption = function(val) { 21436 var unknownVal = '? ' + hashKey(val) + ' ?'; 21437 unknownOption.val(unknownVal); 21438 $element.prepend(unknownOption); 21439 $element.val(unknownVal); 21440 unknownOption.prop('selected', true); // needed for IE 21441 }; 21442 21443 21444 self.hasOption = function(value) { 21445 return optionsMap.hasOwnProperty(value); 21446 }; 21447 21448 $scope.$on('$destroy', function() { 21449 // disable unknown option so that we don't do work when the whole select is being destroyed 21450 self.renderUnknownOption = noop; 21451 }); 21452 }], 21453 21454 link: function(scope, element, attr, ctrls) { 21455 // if ngModel is not defined, we don't need to do anything 21456 if (!ctrls[1]) return; 21457 21458 var selectCtrl = ctrls[0], 21459 ngModelCtrl = ctrls[1], 21460 multiple = attr.multiple, 21461 optionsExp = attr.ngOptions, 21462 nullOption = false, // if false, user will not be able to select it (used by ngOptions) 21463 emptyOption, 21464 // we can't just jqLite('<option>') since jqLite is not smart enough 21465 // to create it in <select> and IE barfs otherwise. 21466 optionTemplate = jqLite(document.createElement('option')), 21467 optGroupTemplate =jqLite(document.createElement('optgroup')), 21468 unknownOption = optionTemplate.clone(); 21469 21470 // find "null" option 21471 for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) { 21472 if (children[i].value === '') { 21473 emptyOption = nullOption = children.eq(i); 21474 break; 21475 } 21476 } 21477 21478 selectCtrl.init(ngModelCtrl, nullOption, unknownOption); 21479 21480 // required validator 21481 if (multiple) { 21482 ngModelCtrl.$isEmpty = function(value) { 21483 return !value || value.length === 0; 21484 }; 21485 } 21486 21487 if (optionsExp) setupAsOptions(scope, element, ngModelCtrl); 21488 else if (multiple) setupAsMultiple(scope, element, ngModelCtrl); 21489 else setupAsSingle(scope, element, ngModelCtrl, selectCtrl); 21490 21491
21492 //////////////////////////// 21493 21494 21495 21496 function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) { 21497 ngModelCtrl.$render = function() { 21498 var viewValue = ngModelCtrl.$viewValue; 21499 21500 if (selectCtrl.hasOption(viewValue)) { 21501 if (unknownOption.parent()) unknownOption.remove(); 21502 selectElement.val(viewValue); 21503 if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy 21504 } else { 21505 if (isUndefined(viewValue) && emptyOption) { 21506 selectElement.val(''); 21507 } else { 21508 selectCtrl.renderUnknownOption(viewValue); 21509 } 21510 } 21511 }; 21512 21513 selectElement.on('change', function() { 21514 scope.$apply(function() { 21515 if (unknownOption.parent()) unknownOption.remove(); 21516 ngModelCtrl.$setViewValue(selectElement.val()); 21517 }); 21518 }); 21519 } 21520 21521 function setupAsMultiple(scope, selectElement, ctrl) { 21522 var lastView; 21523 ctrl.$render = function() { 21524 var items = new HashMap(ctrl.$viewValue); 21525 forEach(selectElement.find('option'), function(option) { 21526 option.selected = isDefined(items.get(option.value)); 21527 }); 21528 }; 21529 21530 // we have to do it on each watch since ngModel watches reference, but 21531 // we need to work of an array, so we need to see if anything was inserted/removed 21532 scope.$watch(function selectMultipleWatch() { 21533 if (!equals(lastView, ctrl.$viewValue)) { 21534 lastView = shallowCopy(ctrl.$viewValue); 21535 ctrl.$render(); 21536 } 21537 }); 21538 21539 selectElement.on('change', function() { 21540 scope.$apply(function() { 21541 var array = []; 21542 forEach(selectElement.find('option'), function(option) { 21543 if (option.selected) { 21544 array.push(option.value); 21545 } 21546 }); 21547 ctrl.$setViewValue(array); 21548 }); 21549 }); 21550 } 21551 21552 function setupAsOptions(scope, selectElement, ctrl) { 21553 var match; 21554 21555 if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) { 21556 throw ngOptionsMinErr('iexp', 21557 "Expected expression in form of " + 21558 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 21559 " but got '{0}'. Element: {1}", 21560 optionsExp, startingTag(selectElement)); 21561 } 21562 21563 var displayFn = $parse(match[2] || match[1]), 21564 valueName = match[4] || match[6], 21565 keyName = match[5], 21566 groupByFn = $parse(match[3] || ''), 21567 valueFn = $parse(match[2] ? match[1] : valueName), 21568 valuesFn = $parse(match[7]), 21569 track = match[8], 21570 trackFn = track ? $parse(match[8]) : null, 21571 // This is an array of array of existing option groups in DOM. 21572 // We try to reuse these if possible 21573 // - optionGroupsCache[0] is the options with no option group 21574 // - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element 21575 optionGroupsCache = [[{element: selectElement, label:''}]]; 21576 21577 if (nullOption) { 21578 // compile the element since there might be bindings in it 21579 $compile(nullOption)(scope); 21580 21581 // remove the class, which is added automatically because we recompile the element and it 21582 // becomes the compilation root 21583 nullOption.removeClass('ng-scope'); 21584 21585 // we need to remove it before calling selectElement.empty() because otherwise IE will 21586 // remove the label from the element. wtf? 21587 nullOption.remove(); 21588 } 21589 21590 // clear contents, we'll add what's needed based on the model 21591 selectElement.empty(); 21592 21593 selectElement.on('change', function() { 21594 scope.$apply(function() { 21595 var optionGroup, 21596 collection = valuesFn(scope) || [], 21597 locals = {}, 21598 key, value, optionElement, index, groupIndex, length, groupLength, trackIndex; 21599 21600 if (multiple) { 21601 value = []; 21602 for (groupIndex = 0, groupLength = optionGroupsCache.length; 21603 groupIndex < groupLength; 21604 groupIndex++) { 21605 // list of options for that group. (first item has the parent) 21606 optionGroup = optionGroupsCache[groupIndex]; 21607 21608 for(index = 1, length = optionGroup.length; index < length; index++) { 21609 if ((optionElement = optionGroup[index].element)[0].selected) { 21610 key = optionElement.val(); 21611 if (keyName) locals[keyName] = key; 21612 if (trackFn) { 21613 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 21614 locals[valueName] = collection[trackIndex]; 21615 if (trackFn(scope, locals) == key) break; 21616 } 21617 } else { 21618 locals[valueName] = collection[key]; 21619 } 21620 value.push(valueFn(scope, locals)); 21621 } 21622 } 21623 } 21624 } else { 21625 key = selectElement.val(); 21626 if (key == '?') { 21627 value = undefined; 21628 } else if (key === ''){ 21629 value = null; 21630 } else { 21631 if (trackFn) { 21632 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 21633 locals[valueName] = collection[trackIndex]; 21634 if (trackFn(scope, locals) == key) { 21635 value = valueFn(scope, locals); 21636 break; 21637 } 21638 } 21639 } else { 21640 locals[valueName] = collection[key]; 21641 if (keyName) locals[keyName] = key; 21642 value = valueFn(scope, locals); 21643 } 21644 } 21645 } 21646 ctrl.$setViewValue(value); 21647 render(); 21648 }); 21649 }); 21650 21651 ctrl.$render = render; 21652 21653 scope.$watchCollection(valuesFn, render); 21654 if ( multiple ) { 21655 scope.$watchCollection(function() { return ctrl.$modelValue; }, render); 21656 } 21657 21658 function getSelectedSet() { 21659 var selectedSet = false;
21660 if (multiple) { 21661 var modelValue = ctrl.$modelValue; 21662 if (trackFn && isArray(modelValue)) { 21663 selectedSet = new HashMap([]); 21664 var locals = {}; 21665 for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) { 21666 locals[valueName] = modelValue[trackIndex]; 21667 selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]); 21668 } 21669 } else { 21670 selectedSet = new HashMap(modelValue); 21671 } 21672 } 21673 return selectedSet; 21674 } 21675 21676 21677 function render() { 21678 // Temporary location for the option groups before we render them 21679 var optionGroups = {'':[]}, 21680 optionGroupNames = [''], 21681 optionGroupName, 21682 optionGroup, 21683 option, 21684 existingParent, existingOptions, existingOption, 21685 modelValue = ctrl.$modelValue, 21686 values = valuesFn(scope) || [], 21687 keys = keyName ? sortedKeys(values) : values, 21688 key, 21689 groupLength, length, 21690 groupIndex, index, 21691 locals = {}, 21692 selected, 21693 selectedSet = getSelectedSet(), 21694 lastElement, 21695 element, 21696 label; 21697 21698 21699 // We now build up the list of options we need (we merge later) 21700 for (index = 0; length = keys.length, index < length; index++) { 21701 21702 key = index; 21703 if (keyName) { 21704 key = keys[index]; 21705 if ( key.charAt(0) === '$' ) continue; 21706 locals[keyName] = key; 21707 } 21708 21709 locals[valueName] = values[key]; 21710 21711 optionGroupName = groupByFn(scope, locals) || ''; 21712 if (!(optionGroup = optionGroups[optionGroupName])) { 21713 optionGroup = optionGroups[optionGroupName] = []; 21714 optionGroupNames.push(optionGroupName); 21715 } 21716 if (multiple) { 21717 selected = isDefined( 21718 selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals)) 21719 ); 21720 } else { 21721 if (trackFn) { 21722 var modelCast = {}; 21723 modelCast[valueName] = modelValue; 21724 selected = trackFn(scope, modelCast) === trackFn(scope, locals); 21725 } else { 21726 selected = modelValue === valueFn(scope, locals); 21727 } 21728 selectedSet = selectedSet || selected; // see if at least one item is selected 21729 } 21730 label = displayFn(scope, locals); // what will be seen by the user 21731 21732 // doing displayFn(scope, locals) || '' overwrites zero values 21733 label = isDefined(label) ? label : ''; 21734 optionGroup.push({ 21735 // either the index into array or key from object 21736 id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index), 21737 label: label, 21738 selected: selected // determine if we should be selected 21739 }); 21740 } 21741 if (!multiple) { 21742 if (nullOption || modelValue === null) { 21743 // insert null option if we have a placeholder, or the model is null 21744 optionGroups[''].unshift({id:'', label:'', selected:!selectedSet}); 21745 } else if (!selectedSet) { 21746 // option could not be found, we have to insert the undefined item 21747 optionGroups[''].unshift({id:'?', label:'', selected:true}); 21748 } 21749 } 21750 21751 // Now we need to update the list of DOM nodes to match the optionGroups we computed above 21752 for (groupIndex = 0, groupLength = optionGroupNames.length; 21753 groupIndex < groupLength; 21754 groupIndex++) { 21755 // current option group name or '' if no group 21756 optionGroupName = optionGroupNames[groupIndex]; 21757 21758 // list of options for that group. (first item has the parent) 21759 optionGroup = optionGroups[optionGroupName]; 21760 21761 if (optionGroupsCache.length <= groupIndex) { 21762 // we need to grow the optionGroups 21763 existingParent = { 21764 element: optGroupTemplate.clone().attr('label', optionGroupName), 21765 label: optionGroup.label 21766 }; 21767 existingOptions = [existingParent]; 21768 optionGroupsCache.push(existingOptions); 21769 selectElement.append(existingParent.element); 21770 } else { 21771 existingOptions = optionGroupsCache[groupIndex]; 21772 existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element 21773 21774 // update the OPTGROUP label if not the same. 21775 if (existingParent.label != optionGroupName) { 21776 existingParent.element.attr('label', existingParent.label = optionGroupName); 21777 } 21778 } 21779 21780 lastElement = null; // start at the beginning 21781 for(index = 0, length = optionGroup.length; index < length; index++) { 21782 option = optionGroup[index]; 21783 if ((existingOption = existingOptions[index+1])) { 21784 // reuse elements 21785 lastElement = existingOption.element; 21786 if (existingOption.label !== option.label) { 21787 lastElement.text(existingOption.label = option.label); 21788 } 21789 if (existingOption.id !== option.id) { 21790 lastElement.val(existingOption.id = option.id); 21791 } 21792 // lastElement.prop('selected') provided by jQuery has side-effects 21793 if (lastElement[0].selected !== option.selected) {
21794 lastElement.prop('selected', (existingOption.selected = option.selected)); 21795 if (msie) { 21796 // See #7692 21797 // The selected item wouldn't visually update on IE without this. 21798 // Tested on Win7: IE9, IE10 and IE11. Future IEs should be tested as well 21799 lastElement.prop('selected', existingOption.selected); 21800 } 21801 } 21802 } else { 21803 // grow elements 21804 21805 // if it's a null option 21806 if (option.id === '' && nullOption) { 21807 // put back the pre-compiled element 21808 element = nullOption; 21809 } else { 21810 // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but 21811 // in this version of jQuery on some browser the .text() returns a string 21812 // rather then the element. 21813 (element = optionTemplate.clone()) 21814 .val(option.id) 21815 .prop('selected', option.selected) 21816 .attr('selected', option.selected) 21817 .text(option.label); 21818 } 21819 21820 existingOptions.push(existingOption = { 21821 element: element, 21822 label: option.label, 21823 id: option.id, 21824 selected: option.selected 21825 }); 21826 if (lastElement) { 21827 lastElement.after(element); 21828 } else { 21829 existingParent.element.append(element); 21830 } 21831 lastElement = element; 21832 } 21833 } 21834 // remove any excessive OPTIONs in a group 21835 index++; // increment since the existingOptions[0] is parent element not OPTION 21836 while(existingOptions.length > index) { 21837 existingOptions.pop().element.remove(); 21838 } 21839 } 21840 // remove any excessive OPTGROUPs from select 21841 while(optionGroupsCache.length > groupIndex) { 21842 optionGroupsCache.pop()[0].element.remove(); 21843 } 21844 } 21845 } 21846 } 21847 }; 21848}]; 21849 21850var optionDirective = ['$interpolate', function($interpolate) { 21851 var nullSelectCtrl = { 21852 addOption: noop, 21853 removeOption: noop 21854 }; 21855 21856 return { 21857 restrict: 'E', 21858 priority: 100, 21859 compile: function(element, attr) { 21860 if (isUndefined(attr.value)) { 21861 var interpolateFn = $interpolate(element.text(), true); 21862 if (!interpolateFn) { 21863 attr.$set('value', element.text()); 21864 } 21865 } 21866 21867 return function (scope, element, attr) { 21868 var selectCtrlName = '$selectController', 21869 parent = element.parent(), 21870 selectCtrl = parent.data(selectCtrlName) || 21871 parent.parent().data(selectCtrlName); // in case we are in optgroup 21872 21873 if (selectCtrl && selectCtrl.databound) { 21874 // For some reason Opera defaults to true and if not overridden this messes up the repeater. 21875 // We don't want the view to drive the initialization of the model anyway. 21876 element.prop('selected', false); 21877 } else { 21878 selectCtrl = nullSelectCtrl; 21879 } 21880 21881 if (interpolateFn) { 21882 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 21883 attr.$set('value', newVal); 21884 if (newVal !== oldVal) selectCtrl.removeOption(oldVal); 21885 selectCtrl.addOption(newVal); 21886 }); 21887 } else { 21888 selectCtrl.addOption(attr.value); 21889 } 21890 21891 element.on('$destroy', function() { 21892 selectCtrl.removeOption(attr.value); 21893 }); 21894 }; 21895 } 21896 }; 21897}]; 21898 21899var styleDirective = valueFn({ 21900 restrict: 'E', 21901 terminal: true 21902}); 21903 21904 if (window.angular.bootstrap) { 21905 //AngularJS is already loaded, so we can return here... 21906 console.log('WARNING: Tried to load angular more than once.'); 21907 return; 21908 } 21909 21910 //try to bind to jquery now so that one can write angular.element().read() 21911 //but we will rebind on bootstrap again. 21912 bindJQuery(); 21913 21914 publishExternalAPI(angular); 21915 21916 jqLite(document).ready(function() { 21917 angularInit(document, bootstrap); 21918 }); 21919 21920})(window, document); 21921 21922!window.angular.$$csp() && window.angular.element(document).find('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{display:none !important;}ng\\:form{display:block;}.ng-animate-block-transitions{transition:0s all!important;-webkit-transition:0s all!important;}.ng-hide-add-active,.ng-hide-remove{display:block!important;}</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.