1/** 2 * @license AngularJS v1.2.32 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.32/' + 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 238 msie, // holds major version number for IE, or NaN if UA is not IE. 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 own enumerable 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 * @param {*} value to be returned. 486 * @returns {*} the value passed in. 487 */ 488function identity($) {return $;} 489identity.$inject = []; 490 491 492function valueFn(value) {return function() {return value;};} 493 494/** 495 * @ngdoc function 496 * @name angular.isUndefined 497 * @module ng 498 * @kind function 499 * 500 * @description 501 * Determines if a reference is undefined. 502 * 503 * @param {*} value Reference to check. 504 * @returns {boolean} True if `value` is undefined. 505 */ 506function isUndefined(value){return typeof value === 'undefined';} 507 508 509/** 510 * @ngdoc function 511 * @name angular.isDefined 512 * @module ng 513 * @kind function 514 * 515 * @description 516 * Determines if a reference is defined. 517 * 518 * @param {*} value Reference to check. 519 * @returns {boolean} True if `value` is defined. 520 */ 521function isDefined(value){return typeof value !== 'undefined';} 522 523 524/** 525 * @ngdoc function 526 * @name angular.isObject 527 * @module ng 528 * @kind function 529 * 530 * @description 531 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 532 * considered to be objects. Note that JavaScript arrays are objects. 533 * 534 * @param {*} value Reference to check. 535 * @returns {boolean} True if `value` is an `Object` but not `null`. 536 */ 537function isObject(value){return value != null && typeof value === 'object';} 538 539 540/** 541 * @ngdoc function 542 * @name angular.isString 543 * @module ng 544 * @kind function 545 * 546 * @description 547 * Determines if a reference is a `String`. 548 * 549 * @param {*} value Reference to check. 550 * @returns {boolean} True if `value` is a `String`. 551 */ 552function isString(value){return typeof value === 'string';} 553 554 555/** 556 * @ngdoc function 557 * @name angular.isNumber 558 * @module ng 559 * @kind function 560 * 561 * @description 562 * Determines if a reference is a `Number`. 563 * 564 * @param {*} value Reference to check. 565 * @returns {boolean} True if `value` is a `Number`. 566 */ 567function isNumber(value){return typeof value === 'number';} 568 569 570/** 571 * @ngdoc function 572 * @name angular.isDate 573 * @module ng 574 * @kind function 575 * 576 * @description 577 * Determines if a value is a date. 578 * 579 * @param {*} value Reference to check. 580 * @returns {boolean} True if `value` is a `Date`. 581 */ 582function isDate(value) { 583 return toString.call(value) === '[object Date]'; 584} 585 586 587/** 588 * @ngdoc function 589 * @name angular.isArray 590 * @module ng 591 * @kind function 592 * 593 * @description 594 * Determines if a reference is an `Array`. 595 * 596 * @param {*} value Reference to check. 597 * @returns {boolean} True if `value` is an `Array`. 598 */ 599var isArray = (function() { 600 if (!isFunction(Array.isArray)) { 601 return function(value) { 602 return toString.call(value) === '[object Array]'; 603 }; 604 } 605 return Array.isArray; 606})(); 607 608/** 609 * @ngdoc function 610 * @name angular.isFunction 611 * @module ng 612 * @kind function 613 * 614 * @description 615 * Determines if a reference is a `Function`. 616 * 617 * @param {*} value Reference to check. 618 * @returns {boolean} True if `value` is a `Function`. 619 */ 620function isFunction(value){return typeof value === 'function';} 621 622 623/** 624 * Determines if a value is a regular expression object. 625 * 626 * @private 627 * @param {*} value Reference to check. 628 * @returns {boolean} True if `value` is a `RegExp`. 629 */ 630function isRegExp(value) { 631 return toString.call(value) === '[object RegExp]'; 632} 633 634 635/** 636 * Checks if `obj` is a window object. 637 * 638 * @private 639 * @param {*} obj Object to check 640 * @returns {boolean} True if `obj` is a window obj. 641 */ 642function isWindow(obj) { 643 return obj && obj.document && obj.location && obj.alert && obj.setInterval; 644} 645 646 647function isScope(obj) { 648 return obj && obj.$evalAsync && obj.$watch; 649} 650 651 652function isFile(obj) { 653 return toString.call(obj) === '[object File]'; 654} 655 656 657function isBlob(obj) { 658 return toString.call(obj) === '[object Blob]'; 659} 660 661 662function isBoolean(value) { 663 return typeof value === 'boolean'; 664} 665 666 667function isPromiseLike(obj) { 668 return obj && isFunction(obj.then); 669} 670 671 672var trim = (function() { 673 // native trim is way faster: http://jsperf.com/angular-trim-test 674 // but IE doesn't have it... :-( 675 // TODO: we should move this into IE/ES5 polyfill 676 if (!String.prototype.trim) { 677 return function(value) { 678 return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value; 679 }; 680 } 681 return function(value) { 682 return isString(value) ? value.trim() : value; 683 }; 684})(); 685 686 687/** 688 * @ngdoc function 689 * @name angular.isElement 690 * @module ng 691 * @kind function 692 * 693 * @description 694 * Determines if a reference is a DOM element (or wrapped jQuery element). 695 * 696 * @param {*} value Reference to check. 697 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 698 */ 699function isElement(node) { 700 return !!(node && 701 (node.nodeName // we are a direct element 702 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 703} 704 705/** 706 * @param str 'key1,key2,...' 707 * @returns {object} in the form of {key1:true, key2:true, ...} 708 */ 709function makeMap(str) { 710 var obj = {}, items = str.split(","), i; 711 for ( i = 0; i < items.length; i++ ) 712 obj[ items[i] ] = true; 713 return obj; 714} 715 716 717if (msie < 9) { 718 nodeName_ = function(element) { 719 element = element.nodeName ? element : element[0]; 720 return (element.scopeName && element.scopeName != 'HTML') 721 ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; 722 }; 723} else { 724 nodeName_ = function(element) { 725 return element.nodeName ? element.nodeName : element[0].nodeName; 726 }; 727} 728 729 730function map(obj, iterator, context) { 731 var results = []; 732 forEach(obj, function(value, index, list) { 733 results.push(iterator.call(context, value, index, list)); 734 }); 735 return results; 736} 737 738 739/** 740 * @description 741 * Determines the number of elements in an array, the number of properties an object has, or 742 * the length of a string. 743 *
744 * Note: This function is used to augment the Object type in Angular expressions. See 745 * {@link angular.Object} for more information about Angular arrays. 746 * 747 * @param {Object|Array|string} obj Object, array, or string to inspect. 748 * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object 749 * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. 750 */ 751function size(obj, ownPropsOnly) { 752 var count = 0, key; 753 754 if (isArray(obj) || isString(obj)) { 755 return obj.length; 756 } else if (isObject(obj)) { 757 for (key in obj) 758 if (!ownPropsOnly || obj.hasOwnProperty(key)) 759 count++; 760 } 761 762 return count; 763} 764 765 766function includes(array, obj) { 767 return indexOf(array, obj) != -1; 768} 769 770function indexOf(array, obj) { 771 if (array.indexOf) return array.indexOf(obj); 772 773 for (var i = 0; i < array.length; i++) { 774 if (obj === array[i]) return i; 775 } 776 return -1; 777} 778 779function arrayRemove(array, value) { 780 var index = indexOf(array, value); 781 if (index >=0) 782 array.splice(index, 1); 783 return value; 784} 785 786function isLeafNode (node) { 787 if (node) { 788 switch (node.nodeName) { 789 case "OPTION": 790 case "PRE": 791 case "TITLE": 792 return true; 793 } 794 } 795 return false; 796} 797 798/** 799 * @ngdoc function 800 * @name angular.copy 801 * @module ng 802 * @kind function 803 * 804 * @description 805 * Creates a deep copy of `source`, which should be an object or an array. 806 * 807 * * If no destination is supplied, a copy of the object or array is created. 808 * * If a destination is provided, all of its elements (for array) or properties (for objects) 809 * are deleted and then all elements/properties from the source are copied to it. 810 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 811 * * If `source` is identical to 'destination' an exception will be thrown. 812 * 813 * @param {*} source The source that will be used to make a copy. 814 * Can be any type, including primitives, `null`, and `undefined`. 815 * @param {(Object|Array)=} destination Destination into which the source is copied. If 816 * provided, must be of the same type as `source`. 817 * @returns {*} The copy or updated `destination`, if `destination` was specified. 818 * 819 * @example 820 <example module="copyExample"> 821 <file name="index.html">
822 <div ng-controller="ExampleController"> 823 <form novalidate class="simple-form"> 824 Name: <input type="text" ng-model="user.name" /><br /> 825 E-mail: <input type="email" ng-model="user.email" /><br /> 826 Gender: <input type="radio" ng-model="user.gender" value="male" />male 827 <input type="radio" ng-model="user.gender" value="female" />female<br /> 828 <button ng-click="reset()">RESET</button> 829 <button ng-click="update(user)">SAVE</button> 830 </form> 831 <pre>form = {{user | json}}</pre> 832 <pre>master = {{master | json}}</pre> 833 </div> 834 835 <script> 836 angular.module('copyExample', []) 837 .controller('ExampleController', ['$scope', function($scope) { 838 $scope.master= {}; 839 840 $scope.update = function(user) { 841 // Example with 1 argument 842 $scope.master= angular.copy(user); 843 }; 844 845 $scope.reset = function() { 846 // Example with 2 arguments 847 angular.copy($scope.master, $scope.user); 848 }; 849 850 $scope.reset(); 851 }]); 852 </script> 853 </file> 854 </example> 855 */ 856function copy(source, destination, stackSource, stackDest) { 857 if (isWindow(source) || isScope(source)) { 858 throw ngMinErr('cpws', 859 "Can't copy! Making copies of Window or Scope instances is not supported."); 860 } 861 862 if (!destination) { 863 destination = source; 864 if (source) { 865 if (isArray(source)) { 866 destination = copy(source, [], stackSource, stackDest); 867 } else if (isDate(source)) { 868 destination = new Date(source.getTime()); 869 } else if (isRegExp(source)) { 870 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); 871 destination.lastIndex = source.lastIndex; 872 } else if (isObject(source)) { 873 destination = copy(source, {}, stackSource, stackDest); 874 } 875 } 876 } else { 877 if (source === destination) throw ngMinErr('cpi', 878 "Can't copy! Source and destination are identical."); 879 880 stackSource = stackSource || []; 881 stackDest = stackDest || []; 882 883 if (isObject(source)) { 884 var index = indexOf(stackSource, source); 885 if (index !== -1) return stackDest[index]; 886 887 stackSource.push(source); 888 stackDest.push(destination); 889 } 890 891 var result; 892 if (isArray(source)) { 893 destination.length = 0; 894 for ( var i = 0; i < source.length; i++) { 895 result = copy(source[i], null, stackSource, stackDest); 896 if (isObject(source[i])) { 897 stackSource.push(source[i]); 898 stackDest.push(result); 899 } 900 destination.push(result); 901 } 902 } else { 903 var h = destination.$$hashKey; 904 if (isArray(destination)) { 905 destination.length = 0; 906 } else { 907 forEach(destination, function(value, key) { 908 delete destination[key]; 909 }); 910 } 911 for ( var key in source) { 912 result = copy(source[key], null, stackSource, stackDest); 913 if (isObject(source[key])) { 914 stackSource.push(source[key]); 915 stackDest.push(result); 916 } 917 destination[key] = result; 918 } 919 setHashKey(destination,h); 920 } 921 922 } 923 return destination; 924} 925 926/** 927 * Creates a shallow copy of an object, an array or a primitive 928 */ 929function shallowCopy(src, dst) { 930 if (isArray(src)) { 931 dst = dst || []; 932 933 for ( var i = 0; i < src.length; i++) { 934 dst[i] = src[i]; 935 } 936 } else if (isObject(src)) { 937 dst = dst || {}; 938 939 for (var key in src) { 940 if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { 941 dst[key] = src[key]; 942 } 943 } 944 } 945 946 return dst || src; 947} 948 949 950/** 951 * @ngdoc function 952 * @name angular.equals 953 * @module ng 954 * @kind function 955 * 956 * @description 957 * Determines if two objects or two values are equivalent. Supports value types, regular 958 * expressions, arrays and objects. 959 * 960 * Two objects or values are considered equivalent if at least one of the following is true: 961 * 962 * * Both objects or values pass `===` comparison. 963 * * Both objects or values are of the same type and all of their properties are equal by 964 * comparing them with `angular.equals`. 965 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 966 * * Both values represent the same regular expression (In JavaScript,
967 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 968 * representation matches). 969 * 970 * During a property comparison, properties of `function` type and properties with names 971 * that begin with `$` are ignored. 972 * 973 * Scope and DOMWindow objects are being compared only by identify (`===`). 974 * 975 * @param {*} o1 Object or value to compare. 976 * @param {*} o2 Object or value to compare. 977 * @returns {boolean} True if arguments are equal. 978 */ 979function equals(o1, o2) { 980 if (o1 === o2) return true; 981 if (o1 === null || o2 === null) return false; 982 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 983 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 984 if (t1 == t2) { 985 if (t1 == 'object') { 986 if (isArray(o1)) { 987 if (!isArray(o2)) return false; 988 if ((length = o1.length) == o2.length) { 989 for(key=0; key<length; key++) { 990 if (!equals(o1[key], o2[key])) return false; 991 } 992 return true; 993 } 994 } else if (isDate(o1)) { 995 if (!isDate(o2)) return false; 996 return (isNaN(o1.getTime()) && isNaN(o2.getTime())) || (o1.getTime() === o2.getTime()); 997 } else if (isRegExp(o1) && isRegExp(o2)) { 998 return o1.toString() == o2.toString(); 999 } else { 1000 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false; 1001 keySet = {}; 1002 for(key in o1) { 1003 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 1004 if (!equals(o1[key], o2[key])) return false; 1005 keySet[key] = true; 1006 } 1007 for(key in o2) { 1008 if (!keySet.hasOwnProperty(key) && 1009 key.charAt(0) !== '$' && 1010 o2[key] !== undefined && 1011 !isFunction(o2[key])) return false; 1012 } 1013 return true; 1014 } 1015 } 1016 } 1017 return false; 1018} 1019 1020var csp = function() { 1021 if (isDefined(csp.isActive_)) return csp.isActive_; 1022 1023 var active = !!(document.querySelector('[ng-csp]') || 1024 document.querySelector('[data-ng-csp]')); 1025 1026 if (!active) { 1027 try { 1028 /* jshint -W031, -W054 */ 1029 new Function(''); 1030 /* jshint +W031, +W054 */ 1031 } catch (e) { 1032 active = true; 1033 } 1034 } 1035 1036 return (csp.isActive_ = active); 1037}; 1038 1039 1040 1041function concat(array1, array2, index) { 1042 return array1.concat(slice.call(array2, index)); 1043} 1044 1045function sliceArgs(args, startIndex) { 1046 return slice.call(args, startIndex || 0); 1047} 1048 1049 1050/* jshint -W101 */ 1051/** 1052 * @ngdoc function 1053 * @name angular.bind 1054 * @module ng 1055 * @kind function 1056 * 1057 * @description 1058 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1059 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1060 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1061 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1062 * 1063 * @param {Object} self Context which `fn` should be evaluated in. 1064 * @param {function()} fn Function to be bound. 1065 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1066 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1067 */ 1068/* jshint +W101 */ 1069function bind(self, fn) { 1070 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1071 if (isFunction(fn) && !(fn instanceof RegExp)) { 1072 return curryArgs.length 1073 ? function() { 1074 return arguments.length 1075 ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) 1076 : fn.apply(self, curryArgs); 1077 } 1078 : function() { 1079 return arguments.length 1080 ? fn.apply(self, arguments) 1081 : fn.call(self); 1082 }; 1083 } else { 1084 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1085 return fn; 1086 } 1087} 1088 1089 1090function toJsonReplacer(key, value) { 1091 var val = value; 1092 1093 if (typeof key === 'string' && key.charAt(0) === '$') { 1094 val = undefined; 1095 } else if (isWindow(value)) { 1096 val = '$WINDOW'; 1097 } else if (value && document === value) { 1098 val = '$DOCUMENT'; 1099 } else if (isScope(value)) { 1100 val = '$SCOPE'; 1101 } 1102 1103 return val; 1104} 1105 1106 1107/** 1108 * @ngdoc function 1109 * @name angular.toJson 1110 * @module ng 1111 * @kind function 1112 * 1113 * @description 1114 * Serializes input into a JSON-formatted string. Properties with leading $ characters will be 1115 * stripped since angular uses this notation internally. 1116 * 1117 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1118 * @param {boolean=}
1118 pretty If set to true, the JSON output will contain newlines and whitespace. 1119 * @returns {string|undefined} JSON-ified string representing `obj`. 1120 */ 1121function toJson(obj, pretty) { 1122 if (typeof obj === 'undefined') return undefined; 1123 return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); 1124} 1125 1126 1127/** 1128 * @ngdoc function 1129 * @name angular.fromJson 1130 * @module ng 1131 * @kind function 1132 * 1133 * @description 1134 * Deserializes a JSON string. 1135 * 1136 * @param {string} json JSON string to deserialize. 1137 * @returns {Object|Array|string|number} Deserialized thingy. 1138 */ 1139function fromJson(json) { 1140 return isString(json) 1141 ? JSON.parse(json) 1142 : json; 1143} 1144 1145 1146function toBoolean(value) { 1147 if (typeof value === 'function') { 1148 value = true; 1149 } else if (value && value.length !== 0) { 1150 var v = lowercase("" + value); 1151 value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); 1152 } else { 1153 value = false; 1154 } 1155 return value; 1156} 1157 1158/** 1159 * @returns {string} Returns the string representation of the element. 1160 */ 1161function startingTag(element) { 1162 element = jqLite(element).clone(); 1163 try { 1164 // turns out IE does not let you set .html() on elements which 1165 // are not allowed to have children. So we just ignore it. 1166 element.empty(); 1167 } catch(e) {} 1168 // As Per DOM Standards 1169 var TEXT_NODE = 3; 1170 var elemHtml = jqLite('<div>').append(element).html(); 1171 try { 1172 return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : 1173 elemHtml. 1174 match(/^(<[^>]+>)/)[1]. 1175 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1176 } catch(e) { 1177 return lowercase(elemHtml); 1178 } 1179 1180} 1181 1182 1183///////////////////////////////////////////////// 1184 1185/** 1186 * Tries to decode the URI component without throwing an exception. 1187 * 1188 * @private 1189 * @param str value potential URI component to check. 1190 * @returns {boolean} True if `value` can be decoded 1191 * with the decodeURIComponent function. 1192 */ 1193function tryDecodeURIComponent(value) { 1194 try { 1195 return decodeURIComponent(value); 1196 } catch(e) { 1197 // Ignore any invalid uri component 1198 } 1199} 1200 1201 1202/** 1203 * Parses an escaped url query string into key-value pairs. 1204 * @returns {Object.<string,boolean|Array>} 1205 */ 1206function parseKeyValue(/**string*/keyValue) { 1207 var obj = {}, key_value, key; 1208 forEach((keyValue || "").split('&'), function(keyValue) { 1209 if ( keyValue ) { 1210 key_value = keyValue.replace(/\+/g,'%20').split('='); 1211 key = tryDecodeURIComponent(key_value[0]); 1212 if ( isDefined(key) ) { 1213 var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; 1214 if (!hasOwnProperty.call(obj, key)) { 1215 obj[key] = val; 1216 } else if(isArray(obj[key])) { 1217 obj[key].push(val); 1218 } else { 1219 obj[key] = [obj[key],val]; 1220 } 1221 } 1222 } 1223 }); 1224 return obj; 1225} 1226 1227function toKeyValue(obj) { 1228 var parts = []; 1229 forEach(obj, function(value, key) { 1230 if (isArray(value)) { 1231 forEach(value, function(arrayValue) { 1232 parts.push(encodeUriQuery(key, true) + 1233 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1234 }); 1235 } else { 1236 parts.push(encodeUriQuery(key, true) + 1237 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1238 } 1239 }); 1240 return parts.length ? parts.join('&') : ''; 1241} 1242 1243 1244/** 1245 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1246 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1247 * segments: 1248 * segment = *pchar 1249 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1250 * pct-encoded = "%" HEXDIG HEXDIG 1251 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1252 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1253 * / "*" / "+" / "," / ";" / "=" 1254 */ 1255function encodeUriSegment(val) { 1256 return encodeUriQuery(val, true). 1257 replace(/%26/gi, '&'). 1258 replace(/%3D/gi, '='). 1259 replace(/%2B/gi, '+'); 1260} 1261 1262 1263/** 1264 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1265 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1266 * encoded per http://tools.ietf.org/html/rfc3986: 1267 * query = *( pchar / "/" / "?" ) 1268 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1269 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1270 * pct-encoded = "%" HEXDIG HEXDIG 1271 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1272 * / "*" / "+" / "," / ";" / "=" 1273 */ 1274function encodeUriQuery(val, pctEncodeSpaces) { 1275 return encodeURIComponent(val). 1276 replace(/%40/gi, '@'). 1277 replace(/%3A/gi, ':'). 1278 replace(/%24/g, '$'). 1279 replace(/%2C/gi, ','). 1280 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1281} 1282 1283 1284/** 1285 * @ngdoc directive 1286 * @name ngApp 1287 * @module ng 1288 * 1289 * @element ANY 1290 * @param {angular.Module} ngApp an optional application 1291 * {@link angular.module module} name to load. 1292 * 1293 * @description 1294 * 1295 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1296 * designates the **root element** of the application and is typically placed near the root element 1297 * of the page - e.g. on the `<body>` or `<html>` tags. 1298 * 1299 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngAp
1299p` 1300 * found in the document will be used to define the root element to auto-bootstrap as an 1301 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1302 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1303 * 1304 * You can specify an **AngularJS module** to be used as the root module for the application. This 1305 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and 1306 * should contain the application code needed or have dependencies on other modules that will 1307 * contain the code. See {@link angular.module} for more information. 1308 * 1309 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1310 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1311 * would not be resolved to `3`. 1312 * 1313 * `ngApp` is the easiest, and most common, way to bootstrap an application. 1314 * 1315 <example module="ngAppDemo"> 1316 <file name="index.html"> 1317 <div ng-controller="ngAppDemoController"> 1318 I can add: {{a}} + {{b}} = {{ a+b }} 1319 </div> 1320 </file> 1321 <file name="script.js"> 1322 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1323 $scope.a = 1; 1324 $scope.b = 2; 1325 }); 1326 </file> 1327 </example> 1328 * 1329 */ 1330function angularInit(element, bootstrap) { 1331 var elements = [element], 1332 appElement, 1333 module, 1334 names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], 1335 NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; 1336 1337 function append(element) { 1338 element && elements.push(element); 1339 } 1340 1341 forEach(names, function(name) { 1342 names[name] = true; 1343 append(document.getElementById(name)); 1344 name = name.replace(':', '\\:'); 1345 if (element.querySelectorAll) { 1346 forEach(element.querySelectorAll('.' + name), append); 1347 forEach(element.querySelectorAll('.' + name + '\\:'), append); 1348 forEach(element.querySelectorAll('[' + name + ']'), append); 1349 } 1350 }); 1351 1352 forEach(elements, function(element) { 1353 if (!appElement) { 1354 var className = ' ' + element.className + ' '; 1355 var match = NG_APP_CLASS_REGEXP.exec(className); 1356 if (match) { 1357 appElement = element; 1358 module = (match[2] || '').replace(/\s+/g, ','); 1359 } else { 1360 forEach(element.attributes, function(attr) { 1361 if (!appElement && names[attr.name]) { 1362 appElement = element; 1363 module = attr.value; 1364 } 1365 }); 1366 } 1367 } 1368 }); 1369 if (appElement) { 1370 bootstrap(appElement, module ? [module] : []); 1371 } 1372} 1373 1374/** 1375 * @ngdoc function 1376 * @name angular.bootstrap 1377 * @module ng 1378 * @description 1379 * Use this function to manually start up angular application. 1380 * 1381 * See: {@link guide/bootstrap Bootstrap} 1382 * 1383 * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. 1384 * They must use {@link ng.directive:ngApp ngApp}. 1385 * 1386 * Angular will detect if it has been loaded into the browser more than once and only allow the 1387 * first loaded script to be bootstrapped and will report a warning to the browser console for 1388 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1389 * multiple instances of Angular try to work on the DOM. 1390 * 1391 * <example name="multi-bootstrap" module="multi-bootstrap"> 1392 * <file name="index.html"> 1393 * <script src="../../../angular.js"></script> 1394 * <div ng-controller="BrokenTable"> 1395 * <table> 1396 * <tr> 1397 * <th ng-repeat="heading in headings">{{heading}}</th> 1398 * </tr> 1399 * <tr ng-repeat="filling in fillings"> 1400 * <td ng-repeat="fill in filling">{{fill}}</td> 1401 * </tr> 1402 * </table> 1403 * </div> 1404 * </file> 1405 * <file name="controller.js"> 1406 * var app = angular.module('multi-bootstrap', []) 1407 * 1408 * .controller('BrokenTable', function($scope) { 1409 * $scope.headings = ['One', 'Two', 'Three']; 1410 * $scope.fillings = [[1, 2, 3], ['A', 'B', 'C'], [7, 8, 9]]; 1411 * }); 1412 * </file> 1413 * <file name="protractor.js" type="protractor"> 1414 * it('should only insert one table cell for each item in $scope.fillings', function() { 1415 * expect(element.all(by.css('td')).count()) 1416 * .toBe(9); 1417 * }); 1418 * </file> 1419 * </example> 1420 * 1421 * @param {DOMElement} element DOM element which is the root of angular application. 1422 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1423 * Each item in the array should be the name of a predefined module or a (DI annotated) 1424 * function that will be invoked by the injector as a run block. 1425 * See: {@link angular.module modules} 1426 * @returns {auto.$injector} Returns the newly created injector for this app. 1427 */ 1428function bootstrap(element, modules) { 1429 var doBootstrap = function() { 1430 element = jqLite(element); 1431 1432 if (element.injector()) { 1433 var tag = (element[0] === document) ? 'document' : startingTag(element); 1434 //Encode angle brackets to prevent input from being sanitized to empty string #8683 1435 throw ngMinErr( 1436 'btstrpd', 1437 "App Already Bootstrapped with this Element '{0}'", 1438 tag.replace(/</,'<').replace(/>/,'>')); 1439 } 1440 1441 modules = modules || []; 1442 modules.unshift(['$provide', function($provide) { 1443 $provide.value('$rootElement', element); 1444 }]); 1445 modules.unshift('ng'); 1446 var injector = createInjector(modules); 1447 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', 1448 function(scope, element, compile, injector, animate) { 1449 scope.$apply(function() { 1450 element.data('$injector', injector); 1451 compile(element)(scope); 1452 }); 1453 }] 1454 ); 1455 return injector; 1456 }; 1457 1458 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1459 1460 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1461 return doBootstrap(); 1462 } 1463 1464 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1465 angular.resumeBootstrap = function(extraModules) {
vendor: 2,977 bytes, lines 1466-1567
1466 forEach(extraModules, function(module) { 1467 modules.push(module); 1468 }); 1469 doBootstrap(); 1470 }; 1471} 1472 1473var SNAKE_CASE_REGEXP = /[A-Z]/g; 1474function snake_case(name, separator) { 1475 separator = separator || '_'; 1476 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1477 return (pos ? separator : '') + letter.toLowerCase(); 1478 }); 1479} 1480 1481function bindJQuery() { 1482 // bind to jQuery if present; 1483 jQuery = window.jQuery; 1484 // Use jQuery if it exists with proper functionality, otherwise default to us. 1485 // Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support. 1486 if (jQuery && jQuery.fn.on) { 1487 jqLite = jQuery; 1488 extend(jQuery.fn, { 1489 scope: JQLitePrototype.scope, 1490 isolateScope: JQLitePrototype.isolateScope, 1491 controller: JQLitePrototype.controller, 1492 injector: JQLitePrototype.injector, 1493 inheritedData: JQLitePrototype.inheritedData 1494 }); 1495 // Method signature: 1496 // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) 1497 jqLitePatchJQueryRemove('remove', true, true, false); 1498 jqLitePatchJQueryRemove('empty', false, false, false); 1499 jqLitePatchJQueryRemove('html', false, false, true); 1500 } else { 1501 jqLite = JQLite; 1502 } 1503 angular.element = jqLite; 1504} 1505 1506/** 1507 * throw error if the argument is falsy. 1508 */ 1509function assertArg(arg, name, reason) { 1510 if (!arg) { 1511 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1512 } 1513 return arg; 1514} 1515 1516function assertArgFn(arg, name, acceptArrayAnnotation) { 1517 if (acceptArrayAnnotation && isArray(arg)) { 1518 arg = arg[arg.length - 1]; 1519 } 1520 1521 assertArg(isFunction(arg), name, 'not a function, got ' + 1522 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1523 return arg; 1524} 1525 1526/** 1527 * throw error if the name given is hasOwnProperty 1528 * @param {String} name the name to test 1529 * @param {String} context the context in which the name is used, such as module or directive 1530 */ 1531function assertNotHasOwnProperty(name, context) { 1532 if (name === 'hasOwnProperty') { 1533 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1534 } 1535} 1536 1537/** 1538 * Return the value accessible from the object by path. Any undefined traversals are ignored 1539 * @param {Object} obj starting object 1540 * @param {String} path path to traverse 1541 * @param {boolean} [bindFnToScope=true] 1542 * @returns {Object} value as accessible by path 1543 */ 1544//TODO(misko): this function needs to be removed 1545function getter(obj, path, bindFnToScope) { 1546 if (!path) return obj; 1547 var keys = path.split('.'); 1548 var key; 1549 var lastInstance = obj; 1550 var len = keys.length; 1551 1552 for (var i = 0; i < len; i++) { 1553 key = keys[i]; 1554 if (obj) { 1555 obj = (lastInstance = obj)[key]; 1556 } 1557 } 1558 if (!bindFnToScope && isFunction(obj)) { 1559 return bind(lastInstance, obj); 1560 } 1561 return obj; 1562} 1563 1564/** 1565 * Return the DOM siblings between the first and last node in the given array. 1566 * @param {Array} array like object 1567 * @returns {DOMElement}
1567 object containing the elements 1568 */ 1569function getBlockElements(nodes) { 1570 var startNode = nodes[0], 1571 endNode = nodes[nodes.length - 1]; 1572 if (startNode === endNode) { 1573 return jqLite(startNode); 1574 } 1575 1576 var element = startNode; 1577 var elements = [element]; 1578 1579 do { 1580 element = element.nextSibling; 1581 if (!element) break; 1582 elements.push(element); 1583 } while (element !== endNode); 1584 1585 return jqLite(elements); 1586} 1587 1588/** 1589 * @ngdoc type 1590 * @name angular.Module 1591 * @module ng 1592 * @description 1593 * 1594 * Interface for configuring angular {@link angular.module modules}. 1595 */ 1596 1597function setupModuleLoader(window) { 1598 1599 var $injectorMinErr = minErr('$injector'); 1600 var ngMinErr = minErr('ng'); 1601 1602 function ensure(obj, name, factory) { 1603 return obj[name] || (obj[name] = factory()); 1604 } 1605 1606 var angular = ensure(window, 'angular', Object); 1607 1608 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1609 angular.$$minErr = angular.$$minErr || minErr; 1610 1611 return ensure(angular, 'module', function() { 1612 /** @type {Object.<string, angular.Module>} */ 1613 var modules = {}; 1614 1615 /** 1616 * @ngdoc function 1617 * @name angular.module 1618 * @module ng 1619 * @description 1620 * 1621 * The `angular.module` is a global place for creating, registering and retrieving Angular 1622 * modules. 1623 * All modules (angular core or 3rd party) that should be available to an application must be 1624 * registered using this mechanism. 1625 * 1626 * When passed two or more arguments, a new module is created. If passed only one argument, an 1627 * existing module (the name passed as the first argument to `module`) is retrieved. 1628 * 1629 * 1630 * # Module 1631 * 1632 * A module is a collection of services, directives, controllers, filters, and configuration information. 1633 * `angular.module` is used to configure the {@link auto.$injector $injector}. 1634 * 1635 * ```js 1636 * // Create a new module 1637 * var myModule = angular.module('myModule', []); 1638 * 1639 * // register a new service 1640 * myModule.value('appName', 'MyCoolApp'); 1641 * 1642 * // configure existing services inside initialization blocks. 1643 * myModule.config(['$locationProvider', function($locationProvider) { 1644 * // Configure existing providers 1645 * $locationProvider.hashPrefix('!'); 1646 * }]); 1647 * ``` 1648 * 1649 * Then you can create an injector and load your modules like this: 1650 * 1651 * ```js 1652 * var injector = angular.injector(['ng', 'myModule']) 1653 * ``` 1654 * 1655 * However it's more likely that you'll just use 1656 * {@link ng.directive:ngApp ngApp} or 1657 * {@link angular.bootstrap} to simplify this process for you. 1658 * 1659 * @param {!string} name The name of the module to create or retrieve. 1660 * @param {!Array.<string>=} requires If specified then new module is being created. If 1661 * unspecified then the module is being retrieved for further configuration. 1662 * @param {Function=} configFn Optional configuration function for the module. Same as 1663 * {@link angular.Module#config Module#config()}. 1664 * @returns {module} new module with the {@link angular.Module} api. 1665 */ 1666 return function module(name, requires, configFn) { 1667 var assertNotHasOwnProperty = function(name, context) { 1668 if (name === 'hasOwnProperty') { 1669 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 1670 } 1671 }; 1672 1673 assertNotHasOwnProperty(name, 'module'); 1674 if (requires && modules.hasOwnProperty(name)) { 1675 modules[name] = null; 1676 } 1677 return ensure(modules, name, function() { 1678 if (!requires) { 1679 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 1680 "the module name or forgot to load it. If registering a module ensure that you " + 1681 "specify the dependencies as the second argument.", name); 1682 } 1683 1684 /** @type {!Array.<Array.<*>>} */ 1685 var invokeQueue = []; 1686 1687 /** @type {!Array.<Function>} */ 1688 var runBlocks = []; 1689 1690 var config = invokeLater('$injector', 'invoke'); 1691 1692 /** @type {angular.Module} */ 1693 var moduleInstance = { 1694 // Private state 1695 _invokeQueue: invokeQueue, 1696 _runBlocks: runBlocks, 1697 1698 /** 1699 * @ngdoc property 1700 * @name angular.Module#requires 1701 * @module ng 1702 *
1703 * @description 1704 * Holds the list of modules which the injector will load before the current module is 1705 * loaded. 1706 */ 1707 requires: requires, 1708 1709 /** 1710 * @ngdoc property 1711 * @name angular.Module#name 1712 * @module ng 1713 * 1714 * @description 1715 * Name of the module. 1716 */ 1717 name: name, 1718 1719 1720 /** 1721 * @ngdoc method 1722 * @name angular.Module#provider 1723 * @module ng 1724 * @param {string} name service name 1725 * @param {Function} providerType Construction function for creating new instance of the 1726 * service. 1727 * @description 1728 * See {@link auto.$provide#provider $provide.provider()}. 1729 */ 1730 provider: invokeLater('$provide', 'provider'), 1731 1732 /** 1733 * @ngdoc method 1734 * @name angular.Module#factory 1735 * @module ng 1736 * @param {string} name service name 1737 * @param {Function} providerFunction Function for creating new instance of the service. 1738 * @description 1739 * See {@link auto.$provide#factory $provide.factory()}. 1740 */ 1741 factory: invokeLater('$provide', 'factory'), 1742 1743 /** 1744 * @ngdoc method 1745 * @name angular.Module#service 1746 * @module ng 1747 * @param {string} name service name 1748 * @param {Function} constructor A constructor function that will be instantiated. 1749 * @description 1750 * See {@link auto.$provide#service $provide.service()}. 1751 */ 1752 service: invokeLater('$provide', 'service'), 1753 1754 /** 1755 * @ngdoc method 1756 * @name angular.Module#value 1757 * @module ng 1758 * @param {string} name service name 1759 * @param {*} object Service instance object. 1760 * @description 1761 * See {@link auto.$provide#value $provide.value()}. 1762 */ 1763 value: invokeLater('$provide', 'value'), 1764 1765 /** 1766 * @ngdoc method 1767 * @name angular.Module#constant 1768 * @module ng 1769 * @param {string} name constant name 1770 * @param {*} object Constant value. 1771 * @description 1772 * Because the constant are fixed, they get applied before other provide methods. 1773 * See {@link auto.$provide#constant $provide.constant()}. 1774 */ 1775 constant: invokeLater('$provide', 'constant', 'unshift'), 1776 1777 /** 1778 * @ngdoc method 1779 * @name angular.Module#animation 1780 * @module ng 1781 * @param {string} name animation name 1782 * @param {Function} animationFactory Factory function for creating new instance of an 1783 * animation. 1784 * @description 1785 * 1786 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 1787 * 1788 * 1789 * Defines an animation hook that can be later used with 1790 * {@link ngAnimate.$animate $animate} service and directives that use this service. 1791 * 1792 * ```js 1793 * module.animation('.animation-name', function($inject1, $inject2) { 1794 * return { 1795 * eventName : function(element, done) { 1796 * //code to run the animation 1797 * //once complete, then run done() 1798 * return function cancellationFunction(element) { 1799 * //code to cancel the animation 1800 * } 1801 * } 1802 * } 1803 * }) 1804 * ``` 1805 * 1806 * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and 1807 * {@link ngAnimate ngAnimate module} for more information. 1808 */ 1809 animation: invokeLater('$animateProvider', 'register'), 1810 1811 /** 1812 * @ngdoc method 1813 * @name angular.Module#filter 1814 * @module ng 1815 * @param {string} name Filter name. 1816 * @param {Function} filterFactory Factory function for creating new instance of filter.
1817 * @description 1818 * See {@link ng.$filterProvider#register $filterProvider.register()}. 1819 */ 1820 filter: invokeLater('$filterProvider', 'register'), 1821 1822 /** 1823 * @ngdoc method 1824 * @name angular.Module#controller 1825 * @module ng 1826 * @param {string|Object} name Controller name, or an object map of controllers where the 1827 * keys are the names and the values are the constructors. 1828 * @param {Function} constructor Controller constructor function. 1829 * @description 1830 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 1831 */ 1832 controller: invokeLater('$controllerProvider', 'register'), 1833 1834 /** 1835 * @ngdoc method 1836 * @name angular.Module#directive 1837 * @module ng 1838 * @param {string|Object} name Directive name, or an object map of directives where the 1839 * keys are the names and the values are the factories. 1840 * @param {Function} directiveFactory Factory function for creating new instance of 1841 * directives. 1842 * @description 1843 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 1844 */ 1845 directive: invokeLater('$compileProvider', 'directive'), 1846 1847 /** 1848 * @ngdoc method 1849 * @name angular.Module#config 1850 * @module ng 1851 * @param {Function} configFn Execute this function on module load. Useful for service 1852 * configuration. 1853 * @description 1854 * Use this method to register work which needs to be performed on module loading. 1855 * For more about how to configure services, see 1856 * {@link providers#providers_provider-recipe Provider Recipe}. 1857 */ 1858 config: config, 1859 1860 /** 1861 * @ngdoc method 1862 * @name angular.Module#run 1863 * @module ng 1864 * @param {Function} initializationFn Execute this function after injector creation. 1865 * Useful for application initialization. 1866 * @description 1867 * Use this method to register work which should be performed when the injector is done 1868 * loading all modules. 1869 */ 1870 run: function(block) { 1871 runBlocks.push(block); 1872 return this; 1873 } 1874 }; 1875 1876 if (configFn) { 1877 config(configFn); 1878 } 1879 1880 return moduleInstance; 1881 1882 /** 1883 * @param {string} provider 1884 * @param {string} method 1885 * @param {String=} insertMethod 1886 * @returns {angular.Module} 1887 */ 1888 function invokeLater(provider, method, insertMethod) { 1889 return function() { 1890 invokeQueue[insertMethod || 'push']([provider, method, arguments]); 1891 return moduleInstance; 1892 }; 1893 } 1894 }); 1895 }; 1896 }); 1897 1898} 1899 1900/* global angularModule: true, 1901 version: true, 1902 1903 $LocaleProvider, 1904 $CompileProvider, 1905 1906 htmlAnchorDirective, 1907 inputDirective, 1908 inputDirective, 1909 formDirective, 1910 scriptDirective, 1911 selectDirective, 1912 styleDirective, 1913 optionDirective, 1914 ngBindDirective, 1915 ngBindHtmlDirective, 1916 ngBindTemplateDirective, 1917 ngClassDirective, 1918 ngClassEvenDirective, 1919 ngClassOddDirective, 1920 ngCspDirective, 1921 ngCloakDirective, 1922 ngControllerDirective, 1923 ngFormDirective, 1924 ngHideDirective, 1925 ngIfDirective, 1926 ngIncludeDirective, 1927 ngIncludeFillContentDirective, 1928 ngInitDirective, 1929 ngNonBindableDirective, 1930 ngPluralizeDirective, 1931 ngRepeatDirective, 1932 ngShowDirective, 1933 ngStyleDirective, 1934 ngSwitchDirective, 1935 ngSwitchWhenDirective, 1936 ngSwitchDefaultDirective, 1937 ngOptionsDirective, 1938 ngTranscludeDirective, 1939 ngModelDirective, 1940 ngListDirective, 1941 ngChangeDirective, 1942 requiredDirective, 1943 requiredDirective, 1944 ngValueDirective, 1945 ngAttributeAliasDirectives, 1946 ngEventDirectives, 1947 1948 $AnchorScrollProvider, 1949 $AnimateProvider, 1950 $BrowserProvider, 1951 $CacheFactoryProvider, 1952 $ControllerProvider, 1953 $DocumentProvider, 1954 $ExceptionHandlerProvider, 1955 $FilterProvider, 1956 $InterpolateProvider, 1957 $IntervalProvider, 1958 $HttpProvider, 1959 $HttpBackendProvider, 1960 $LocationProvider, 1961 $LogProvider, 1962 $ParseProvider, 1963 $RootScopeProvider, 1964 $QProvider,
1965 $$SanitizeUriProvider, 1966 $SceProvider, 1967 $SceDelegateProvider, 1968 $SnifferProvider, 1969 $TemplateCacheProvider, 1970 $TimeoutProvider, 1971 $$RAFProvider, 1972 $$AsyncCallbackProvider, 1973 $WindowProvider 1974*/ 1975 1976 1977/** 1978 * @ngdoc object 1979 * @name angular.version 1980 * @module ng 1981 * @description 1982 * An object that contains information about the current AngularJS version. This object has the 1983 * following properties: 1984 * 1985 * - `full` â `{string}` â Full version string, such as "0.9.18". 1986 * - `major` â `{number}` â Major version number, such as "0". 1987 * - `minor` â `{number}` â Minor version number, such as "9". 1988 * - `dot` â `{number}` â Dot version number, such as "18". 1989 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 1990 */ 1991var version = { 1992 full: '1.2.32', // all of these placeholder strings will be replaced by grunt's 1993 major: 1, // package task 1994 minor: 2, 1995 dot: 32, 1996 codeName: 'alternation-intention' 1997}; 1998 1999 2000function publishExternalAPI(angular){ 2001 extend(angular, { 2002 'bootstrap': bootstrap, 2003 'copy': copy, 2004 'extend': extend, 2005 'equals': equals, 2006 'element': jqLite, 2007 'forEach': forEach, 2008 'injector': createInjector, 2009 'noop': noop, 2010 'bind': bind, 2011 'toJson': toJson, 2012 'fromJson': fromJson, 2013 'identity': identity, 2014 'isUndefined': isUndefined, 2015 'isDefined': isDefined, 2016 'isString': isString, 2017 'isFunction': isFunction, 2018 'isObject': isObject, 2019 'isNumber': isNumber, 2020 'isElement': isElement, 2021 'isArray': isArray, 2022 'version': version, 2023 'isDate': isDate, 2024 'lowercase': lowercase, 2025 'uppercase': uppercase, 2026 'callbacks': {counter: 0}, 2027 '$$minErr': minErr, 2028 '$$csp': csp 2029 }); 2030 2031 angularModule = setupModuleLoader(window); 2032 try { 2033 angularModule('ngLocale'); 2034 } catch (e) { 2035 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 2036 } 2037 2038 angularModule('ng', ['ngLocale'], ['$provide', 2039 function ngModule($provide) { 2040 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2041 $provide.provider({ 2042 $$sanitizeUri: $$SanitizeUriProvider 2043 }); 2044 $provide.provider('$compile', $CompileProvider). 2045 directive({ 2046 a: htmlAnchorDirective, 2047 input: inputDirective, 2048 textarea: inputDirective, 2049 form: formDirective, 2050 script: scriptDirective, 2051 select: selectDirective, 2052 style: styleDirective, 2053 option: optionDirective, 2054 ngBind: ngBindDirective, 2055 ngBindHtml: ngBindHtmlDirective, 2056 ngBindTemplate: ngBindTemplateDirective, 2057 ngClass: ngClassDirective, 2058 ngClassEven: ngClassEvenDirective, 2059 ngClassOdd: ngClassOddDirective, 2060 ngCloak: ngCloakDirective, 2061 ngController: ngControllerDirective, 2062 ngForm: ngFormDirective, 2063 ngHide: ngHideDirective, 2064 ngIf: ngIfDirective, 2065 ngInclude: ngIncludeDirective, 2066 ngInit: ngInitDirective, 2067 ngNonBindable: ngNonBindableDirective, 2068 ngPluralize: ngPluralizeDirective, 2069 ngRepeat: ngRepeatDirective, 2070 ngShow: ngShowDirective, 2071 ngStyle: ngStyleDirective, 2072 ngSwitch: ngSwitchDirective, 2073 ngSwitchWhen: ngSwitchWhenDirective, 2074 ngSwitchDefault: ngSwitchDefaultDirective, 2075 ngOptions: ngOptionsDirective, 2076 ngTransclude: ngTranscludeDirective, 2077 ngModel: ngModelDirective, 2078 ngList: ngListDirective, 2079 ngChange: ngChangeDirective, 2080 required: requiredDirective, 2081 ngRequired: requiredDirective, 2082 ngValue: ngValueDirective 2083 }). 2084 directive({ 2085 ngInclude: ngIncludeFillContentDirective 2086 }). 2087 directive(ngAttributeAliasDirectives). 2088 directive(ngEventDirectives); 2089 $provide.provider({ 2090 $anchorScroll: $AnchorScrollProvider, 2091 $animate: $AnimateProvider, 2092 $browser: $BrowserProvider,
2093 $cacheFactory: $CacheFactoryProvider, 2094 $controller: $ControllerProvider, 2095 $document: $DocumentProvider, 2096 $exceptionHandler: $ExceptionHandlerProvider, 2097 $filter: $FilterProvider, 2098 $interpolate: $InterpolateProvider, 2099 $interval: $IntervalProvider, 2100 $http: $HttpProvider, 2101 $httpBackend: $HttpBackendProvider, 2102 $location: $LocationProvider, 2103 $log: $LogProvider, 2104 $parse: $ParseProvider, 2105 $rootScope: $RootScopeProvider, 2106 $q: $QProvider, 2107 $sce: $SceProvider, 2108 $sceDelegate: $SceDelegateProvider, 2109 $sniffer: $SnifferProvider, 2110 $templateCache: $TemplateCacheProvider, 2111 $timeout: $TimeoutProvider, 2112 $window: $WindowProvider, 2113 $$rAF: $$RAFProvider, 2114 $$asyncCallback : $$AsyncCallbackProvider 2115 }); 2116 } 2117 ]); 2118} 2119 2120/* global JQLitePrototype: true, 2121 addEventListenerFn: true, 2122 removeEventListenerFn: true, 2123 BOOLEAN_ATTR: true 2124*/ 2125 2126////////////////////////////////// 2127//JQLite 2128////////////////////////////////// 2129 2130/** 2131 * @ngdoc function 2132 * @name angular.element 2133 * @module ng 2134 * @kind function 2135 * 2136 * @description 2137 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2138 * 2139 * If jQuery is available, `angular.element` is an alias for the 2140 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2141 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." 2142 * 2143 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows 2144 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most 2145 * commonly needed functionality with the goal of having a very small footprint.</div> 2146 * 2147 * To use jQuery, simply load it before `DOMContentLoaded` event fired. 2148 * 2149 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or 2150 * jqLite; they are never raw DOM references.</div> 2151 * 2152 * ## Angular's jqLite 2153 * jqLite provides only the following jQuery methods: 2154 * 2155 * - [`addClass()`](http://api.jquery.com/addClass/) 2156 * - [`after()`](http://api.jquery.com/after/) 2157 * - [`append()`](http://api.jquery.com/append/) 2158 * - [`attr()`](http://api.jquery.com/attr/) 2159 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2160 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2161 * - [`clone()`](http://api.jquery.com/clone/) 2162 * - [`contents()`](http://api.jquery.com/contents/) 2163 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()` 2164 * - [`data()`](http://api.jquery.com/data/) 2165 * - [`empty()`](http://api.jquery.com/empty/) 2166 * - [`eq()`](http://api.jquery.com/eq/) 2167 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2168 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2169 * - [`html()`](http://api.jquery.com/html/) 2170 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2171 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2172 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors 2173 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2174 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2175 * - [`prepend()`](http://api.jquery.com/prepend/) 2176 * - [`prop()`](http://api.jquery.com/prop/) 2177 * - [`ready()`](http://api.jquery.com/ready/) 2178 * - [`remove()`](http://api.jquery.com/remove/) 2179 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2180 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2181 * - [`removeData()`](http://api.jquery.com/removeData/) 2182 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2183 * - [`text()`](http://api.jquery.com/text/) 2184 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2185 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2186 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces 2187 * - [`val()`](http://api.jquery.com/val/) 2188 * - [`wrap()`](http://api.jquery.com/wrap/) 2189 * 2190 * ## jQuery/jqLite Extras 2191 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2192 * 2193 * ### Events 2194 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
2194pis and fires this event 2195 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2196 * element before it is removed. 2197 * 2198 * ### Methods 2199 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2200 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2201 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2202 * `'ngModel'`). 2203 * - `injector()` - retrieves the injector of the current element or its parent. 2204 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2205 * element or its parent. 2206 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2207 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 2208 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2209 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2210 * parent element is reached. 2211 * 2212 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2213 * @returns {Object} jQuery object. 2214 */ 2215 2216JQLite.expando = 'ng339'; 2217 2218var jqCache = JQLite.cache = {}, 2219 jqId = 1, 2220 addEventListenerFn = (window.document.addEventListener 2221 ? function(element, type, fn) {element.addEventListener(type, fn, false);} 2222 : function(element, type, fn) {element.attachEvent('on' + type, fn);}), 2223 removeEventListenerFn = (window.document.removeEventListener 2224 ? function(element, type, fn) {element.removeEventListener(type, fn, false); } 2225 : function(element, type, fn) {element.detachEvent('on' + type, fn); }); 2226 2227/* 2228 * !!! This is an undocumented "private" function !!! 2229 */ 2230var jqData = JQLite._data = function(node) { 2231 //jQuery always returns an object on cache miss 2232 return this.cache[node[this.expando]] || {}; 2233}; 2234 2235function jqNextId() { return ++jqId; } 2236 2237 2238var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2239var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2240var jqLiteMinErr = minErr('jqLite'); 2241 2242/** 2243 * Converts snake_case to camelCase. 2244 * Also there is special case for Moz prefix starting with upper case letter. 2245 * @param name Name to normalize 2246 */ 2247function camelCase(name) { 2248 return name. 2249 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2250 return offset ? letter.toUpperCase() : letter; 2251 }). 2252 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2253} 2254 2255///////////////////////////////////////////// 2256// jQuery mutation patch 2257// 2258// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a 2259// $destroy event on all DOM nodes being removed. 2260// 2261///////////////////////////////////////////// 2262 2263function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { 2264 var originalJqFn = jQuery.fn[name]; 2265 originalJqFn = originalJqFn.$original || originalJqFn; 2266 removePatch.$original = originalJqFn; 2267 jQuery.fn[name] = removePatch; 2268 2269 function removePatch(param) { 2270 // jshint -W040 2271 var list = filterElems && param ? [this.filter(param)] : [this], 2272 fireEvent = dispatchThis, 2273 set, setIndex, setLength, 2274 element, childIndex, childLength, children; 2275 2276 if (!getterIfNoArguments || param != null) { 2277 while(list.length) { 2278 set = list.shift(); 2279 for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { 2280 element = jqLite(set[setIndex]); 2281 if (fireEvent) { 2282 element.triggerHandler('$destroy'); 2283 } else { 2284 fireEvent = !fireEvent; 2285 } 2286 for(childIndex = 0, childLength = (children = element.children()).length; 2287 childIndex < childLength; 2288 childIndex++) { 2289 list.push(jQuery(children[childIndex])); 2290 } 2291 } 2292 } 2293 } 2294 return originalJqFn.apply(this, arguments); 2295 } 2296} 2297 2298var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; 2299var HTML_REGEXP = /<|&#?\w+;/; 2300var TAG_NAME_REGEXP = /<([\w:]+)/; 2301var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; 2302 2303var wrapMap = { 2304 'option': [1, '<select multiple="multiple">', '</select>'], 2305 2306 'thead': [1, '<table>', '</table>'], 2307 'col': [2, '<table><colgroup>', '</colgroup></table>'],
2308 'tr': [2, '<table><tbody>', '</tbody></table>'], 2309 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2310 '_default': [0, "", ""] 2311}; 2312 2313wrapMap.optgroup = wrapMap.option; 2314wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2315wrapMap.th = wrapMap.td; 2316 2317function jqLiteIsTextNode(html) { 2318 return !HTML_REGEXP.test(html); 2319} 2320 2321function jqLiteBuildFragment(html, context) { 2322 var elem, tmp, tag, wrap, 2323 fragment = context.createDocumentFragment(), 2324 nodes = [], i, j, jj; 2325 2326 if (jqLiteIsTextNode(html)) { 2327 // Convert non-html into a text node 2328 nodes.push(context.createTextNode(html)); 2329 } else { 2330 tmp = fragment.appendChild(context.createElement('div')); 2331 // Convert html into DOM nodes 2332 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2333 wrap = wrapMap[tag] || wrapMap._default; 2334 tmp.innerHTML = '<div> </div>' + 2335 wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2336 tmp.removeChild(tmp.firstChild); 2337 2338 // Descend through wrappers to the right content 2339 i = wrap[0]; 2340 while (i--) { 2341 tmp = tmp.lastChild; 2342 } 2343 2344 for (j=0, jj=tmp.childNodes.length; j<jj; ++j) nodes.push(tmp.childNodes[j]); 2345 2346 tmp = fragment.firstChild; 2347 tmp.textContent = ""; 2348 } 2349 2350 // Remove wrapper from fragment 2351 fragment.textContent = ""; 2352 fragment.innerHTML = ""; // Clear inner HTML 2353 return nodes; 2354} 2355 2356function jqLiteParseHTML(html, context) { 2357 context = context || document; 2358 var parsed; 2359 2360 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2361 return [context.createElement(parsed[1])]; 2362 } 2363 2364 return jqLiteBuildFragment(html, context); 2365} 2366 2367///////////////////////////////////////////// 2368function JQLite(element) { 2369 if (element instanceof JQLite) { 2370 return element; 2371 } 2372 if (isString(element)) { 2373 element = trim(element); 2374 } 2375 if (!(this instanceof JQLite)) { 2376 if (isString(element) && element.charAt(0) != '<') { 2377 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2378 } 2379 return new JQLite(element); 2380 } 2381 2382 if (isString(element)) { 2383 jqLiteAddNodes(this, jqLiteParseHTML(element)); 2384 var fragment = jqLite(document.createDocumentFragment()); 2385 fragment.append(this); 2386 } else { 2387 jqLiteAddNodes(this, element); 2388 } 2389} 2390 2391function jqLiteClone(element) { 2392 return element.cloneNode(true); 2393} 2394 2395function jqLiteDealoc(element){ 2396 jqLiteRemoveData(element); 2397 for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { 2398 jqLiteDealoc(children[i]); 2399 } 2400} 2401 2402function jqLiteOff(element, type, fn, unsupported) {
2403 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2404 2405 var events = jqLiteExpandoStore(element, 'events'), 2406 handle = jqLiteExpandoStore(element, 'handle'); 2407 2408 if (!handle) return; //no listeners registered 2409 2410 if (isUndefined(type)) { 2411 forEach(events, function(eventHandler, type) { 2412 removeEventListenerFn(element, type, eventHandler); 2413 delete events[type]; 2414 }); 2415 } else { 2416 forEach(type.split(' '), function(type) { 2417 if (isUndefined(fn)) { 2418 removeEventListenerFn(element, type, events[type]); 2419 delete events[type]; 2420 } else { 2421 arrayRemove(events[type] || [], fn); 2422 } 2423 }); 2424 } 2425} 2426 2427function jqLiteRemoveData(element, name) { 2428 var expandoId = element.ng339, 2429 expandoStore = jqCache[expandoId]; 2430 2431 if (expandoStore) { 2432 if (name) { 2433 delete jqCache[expandoId].data[name]; 2434 return; 2435 } 2436 2437 if (expandoStore.handle) { 2438 expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); 2439 jqLiteOff(element); 2440 } 2441 delete jqCache[expandoId]; 2442 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 2443 } 2444} 2445 2446function jqLiteExpandoStore(element, key, value) { 2447 var expandoId = element.ng339, 2448 expandoStore = jqCache[expandoId || -1]; 2449 2450 if (isDefined(value)) { 2451 if (!expandoStore) { 2452 element.ng339 = expandoId = jqNextId(); 2453 expandoStore = jqCache[expandoId] = {}; 2454 } 2455 expandoStore[key] = value; 2456 } else { 2457 return expandoStore && expandoStore[key]; 2458 } 2459} 2460 2461function jqLiteData(element, key, value) { 2462 var data = jqLiteExpandoStore(element, 'data'), 2463 isSetter = isDefined(value), 2464 keyDefined = !isSetter && isDefined(key), 2465 isSimpleGetter = keyDefined && !isObject(key); 2466 2467 if (!data && !isSimpleGetter) { 2468 jqLiteExpandoStore(element, 'data', data = {}); 2469 } 2470 2471 if (isSetter) { 2472 data[key] = value; 2473 } else { 2474 if (keyDefined) { 2475 if (isSimpleGetter) { 2476 // don't create data in this case. 2477 return data && data[key]; 2478 } else { 2479 extend(data, key); 2480 } 2481 } else { 2482 return data; 2483 } 2484 } 2485} 2486 2487function jqLiteHasClass(element, selector) { 2488 if (!element.getAttribute) return false; 2489 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2490 indexOf( " " + selector + " " ) > -1); 2491} 2492 2493function jqLiteRemoveClass(element, cssClasses) { 2494 if (cssClasses && element.setAttribute) { 2495 forEach(cssClasses.split(' '), function(cssClass) { 2496 element.setAttribute('class', trim( 2497 (" " + (element.getAttribute('class') || '') + " ") 2498 .replace(/[\n\t]/g, " ") 2499 .replace(" " + trim(cssClass) + " ", " ")) 2500 ); 2501 }); 2502 } 2503} 2504 2505function jqLiteAddClass(element, cssClasses) { 2506 if (cssClasses && element.setAttribute) { 2507 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 2508 .replace(/[\n\t]/g, " "); 2509 2510 forEach(cssClasses.split(' '), function(cssClass) { 2511 cssClass = trim(cssClass); 2512 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 2513 existingClasses += cssClass + ' '; 2514 } 2515 }); 2516 2517 element.setAttribute('class', trim(existingClasses)); 2518 } 2519} 2520 2521function jqLiteAddNodes(root, elements) { 2522 if (elements) { 2523 elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) 2524 ? elements 2525 : [ elements ]; 2526 for(var i=0; i < elements.length; i++) { 2527 root.push(elements[i]); 2528 } 2529 } 2530} 2531 2532function jqLiteController(element, name) { 2533 return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); 2534} 2535 2536function jqLiteInheritedData(element, name, value) { 2537 // if element is the document object work with the html element instead 2538 // this makes $(document).scope() possible 2539 if(element.nodeType == 9) { 2540 element = element.documentElement; 2541 } 2542 var names = isArray(name) ? name : [name]; 2543 2544 while (element) { 2545 for (var i = 0, ii = names.length; i < ii; i++) { 2546 if ((value = jqLite.data(element, names[i])) !== undefined) return value; 2547 } 2548 2549 // If dealing with a document fragment node with a host element, and no parent, use the host 2550 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 2551 // to lookup parent controllers. 2552 element = element.parentNode || (element.nodeType === 11 && element.host); 2553 } 2554} 2555 2556function jqLiteEmpty(element) { 2557 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2558 jqLiteDealoc(childNodes[i]); 2559 } 2560 while (element.firstChild) { 2561 element.removeChild(element.firstChild); 2562 } 2563} 2564
2565////////////////////////////////////////// 2566// Functions which are declared directly. 2567////////////////////////////////////////// 2568var JQLitePrototype = JQLite.prototype = { 2569 ready: function(fn) { 2570 var fired = false; 2571 2572 function trigger() { 2573 if (fired) return; 2574 fired = true; 2575 fn(); 2576 } 2577 2578 // check if document already is loaded 2579 if (document.readyState === 'complete'){ 2580 setTimeout(trigger); 2581 } else { 2582 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 2583 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 2584 // jshint -W064 2585 JQLite(window).on('load', trigger); // fallback to window.onload for others 2586 // jshint +W064 2587 } 2588 }, 2589 toString: function() { 2590 var value = []; 2591 forEach(this, function(e){ value.push('' + e);}); 2592 return '[' + value.join(', ') + ']'; 2593 }, 2594 2595 eq: function(index) { 2596 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 2597 }, 2598 2599 length: 0, 2600 push: push, 2601 sort: [].sort, 2602 splice: [].splice 2603}; 2604 2605////////////////////////////////////////// 2606// Functions iterating getter/setters. 2607// these functions return self on setter and 2608// value on get. 2609////////////////////////////////////////// 2610var BOOLEAN_ATTR = {}; 2611forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 2612 BOOLEAN_ATTR[lowercase(value)] = value; 2613}); 2614var BOOLEAN_ELEMENTS = {}; 2615forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 2616 BOOLEAN_ELEMENTS[uppercase(value)] = true; 2617}); 2618 2619function getBooleanAttrName(element, name) { 2620 // check dom last since we will most likely fail on name 2621 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 2622 2623 // booleanAttr is here twice to minimize DOM access 2624 return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; 2625} 2626 2627forEach({ 2628 data: jqLiteData, 2629 removeData: jqLiteRemoveData 2630}, function(fn, name) { 2631 JQLite[name] = fn; 2632}); 2633 2634forEach({ 2635 data: jqLiteData, 2636 inheritedData: jqLiteInheritedData, 2637 2638 scope: function(element) { 2639 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2640 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 2641 }, 2642 2643 isolateScope: function(element) { 2644 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2645 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 2646 }, 2647 2648 controller: jqLiteController, 2649 2650 injector: function(element) { 2651 return jqLiteInheritedData(element, '$injector'); 2652 }, 2653 2654 removeAttr: function(element,name) { 2655 element.removeAttribute(name); 2656 }, 2657 2658 hasClass: jqLiteHasClass, 2659 2660 css: function(element, name, value) { 2661 name = camelCase(name); 2662 2663 if (isDefined(value)) { 2664 element.style[name] = value; 2665 } else { 2666 var val; 2667 2668 if (msie <= 8) { 2669 // this is some IE specific weirdness that jQuery 1.6.4 does not sure why 2670 val = element.currentStyle && element.currentStyle[name]; 2671 if (val === '') val = 'auto'; 2672 } 2673 2674 val = val || element.style[name]; 2675 2676 if (msie <= 8) { 2677 // jquery weirdness :-/ 2678 val = (val === '') ? undefined : val; 2679 } 2680 2681 return val; 2682 } 2683 }, 2684 2685 attr: function(element, name, value){ 2686 var lowercasedName = lowercase(name); 2687 if (BOOLEAN_ATTR[lowercasedName]) { 2688 if (isDefined(value)) { 2689 if (!!value) { 2690 element[name] = true; 2691 element.setAttribute(name, lowercasedName); 2692 } else { 2693 element[name] = false; 2694 element.removeAttribute(lowercasedName); 2695 } 2696 } else { 2697 return (element[name] || 2698 (element.attributes.getNamedItem(name)|| noop).specified) 2699 ? lowercasedName 2700 : undefined; 2701 } 2702 } else if (isDefined(value)) { 2703 element.setAttribute(name, value); 2704 } else if (element.getAttribute) { 2705 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 2706 // some elements (e.g. Document) don't have get attribute, so return undefined 2707 var ret = element.getAttribute(name, 2); 2708 // normalize non-existing attributes to undefined (as jQuery) 2709 return ret === null ? undefined : ret; 2710 } 2711 }, 2712 2713 prop: function(element, name, value) { 2714 if (isDefined(value)) { 2715 element[name] = value; 2716 } else { 2717 return element[name]; 2718 } 2719 }, 2720 2721 text: (function() { 2722 var NODE_TYPE_TEXT_PROPERTY = []; 2723 if (msie < 9) { 2724 NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ 2725 NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ 2726 } else { 2727 NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ 2728 NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ 2729 } 2730 getText.$dv = ''; 2731 return getText; 2732 2733 function getText(element, value) { 2734 var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; 2735 if (isUndefined(value)) { 2736 return textProp ? element[textProp] : ''; 2737 } 2738 element[textProp] = value; 2739 } 2740 })(), 2741 2742 val: function(element, value) { 2743 if (isUndefined(value)) { 2744 if (nodeName_(element) === 'SELECT' && element.multiple) { 2745 var result = [];
2746 forEach(element.options, function (option) { 2747 if (option.selected) { 2748 result.push(option.value || option.text); 2749 } 2750 }); 2751 return result.length === 0 ? null : result; 2752 } 2753 return element.value; 2754 } 2755 element.value = value; 2756 }, 2757 2758 html: function(element, value) { 2759 if (isUndefined(value)) { 2760 return element.innerHTML; 2761 } 2762 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2763 jqLiteDealoc(childNodes[i]); 2764 } 2765 element.innerHTML = value; 2766 }, 2767 2768 empty: jqLiteEmpty 2769}, function(fn, name){ 2770 /** 2771 * Properties: writes return selection, reads return first value 2772 */ 2773 JQLite.prototype[name] = function(arg1, arg2) { 2774 var i, key; 2775 var nodeCount = this.length; 2776 2777 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 2778 // in a way that survives minification. 2779 // jqLiteEmpty takes no arguments but is a setter. 2780 if (fn !== jqLiteEmpty && 2781 (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { 2782 if (isObject(arg1)) { 2783 2784 // we are a write, but the object properties are the key/values 2785 for (i = 0; i < nodeCount; i++) { 2786 if (fn === jqLiteData) { 2787 // data() takes the whole object in jQuery 2788 fn(this[i], arg1); 2789 } else { 2790 for (key in arg1) { 2791 fn(this[i], key, arg1[key]); 2792 } 2793 } 2794 } 2795 // return self for chaining 2796 return this; 2797 } else { 2798 // we are a read, so read the first child. 2799 // TODO: do we still need this? 2800 var value = fn.$dv; 2801 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 2802 var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount; 2803 for (var j = 0; j < jj; j++) { 2804 var nodeValue = fn(this[j], arg1, arg2); 2805 value = value ? value + nodeValue : nodeValue; 2806 } 2807 return value; 2808 } 2809 } else { 2810 // we are a write, so apply to all children 2811 for (i = 0; i < nodeCount; i++) { 2812 fn(this[i], arg1, arg2); 2813 } 2814 // return self for chaining 2815 return this; 2816 } 2817 }; 2818}); 2819 2820function createEventHandler(element, events) { 2821 var eventHandler = function (event, type) { 2822 if (!event.preventDefault) { 2823 event.preventDefault = function() { 2824 event.returnValue = false; //ie 2825 }; 2826 } 2827 2828 if (!event.stopPropagation) { 2829 event.stopPropagation = function() { 2830 event.cancelBubble = true; //ie 2831 }; 2832 } 2833 2834 if (!event.target) { 2835 event.target = event.srcElement || document; 2836 } 2837 2838 if (isUndefined(event.defaultPrevented)) { 2839 var prevent = event.preventDefault; 2840 event.preventDefault = function() { 2841 event.defaultPrevented = true; 2842 prevent.call(event); 2843 }; 2844 event.defaultPrevented = false; 2845 } 2846 2847 event.isDefaultPrevented = function() { 2848 return event.defaultPrevented || event.returnValue === false; 2849 }; 2850 2851 // Copy event handlers in case event handlers array is modified during execution. 2852 var eventHandlersCopy = shallowCopy(events[type || event.type] || []); 2853 2854 forEach(eventHandlersCopy, function(fn) { 2855 fn.call(element, event); 2856 }); 2857 2858 // Remove monkey-patched methods (IE), 2859 // as they would cause memory leaks in IE8. 2860 if (msie <= 8) { 2861 // IE7/8 does not allow to delete property on native object 2862 event.preventDefault = null; 2863 event.stopPropagation = null; 2864 event.isDefaultPrevented = null; 2865 } else { 2866 // It shouldn't affect normal browsers (native methods are defined on prototype). 2867 delete event.preventDefault; 2868 delete event.stopPropagation; 2869 delete event.isDefaultPrevented; 2870 } 2871 }; 2872 eventHandler.elem = element; 2873 return eventHandler; 2874} 2875 2876////////////////////////////////////////// 2877// Functions iterating traversal. 2878// These functions chain results into a single 2879// selector. 2880////////////////////////////////////////// 2881forEach({ 2882 removeData: jqLiteRemoveData, 2883 2884 dealoc: jqLiteDealoc, 2885 2886 on: function onFn(element, type, fn, unsupported){
2887 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 2888 2889 var events = jqLiteExpandoStore(element, 'events'), 2890 handle = jqLiteExpandoStore(element, 'handle'); 2891 2892 if (!events) jqLiteExpandoStore(element, 'events', events = {}); 2893 if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); 2894 2895 forEach(type.split(' '), function(type){ 2896 var eventFns = events[type]; 2897 2898 if (!eventFns) { 2899 if (type == 'mouseenter' || type == 'mouseleave') { 2900 var contains = document.body.contains || document.body.compareDocumentPosition ? 2901 function( a, b ) { 2902 // jshint bitwise: false 2903 var adown = a.nodeType === 9 ? a.documentElement : a, 2904 bup = b && b.parentNode; 2905 return a === bup || !!( bup && bup.nodeType === 1 && ( 2906 adown.contains ? 2907 adown.contains( bup ) : 2908 a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 2909 )); 2910 } : 2911 function( a, b ) { 2912 if ( b ) { 2913 while ( (b = b.parentNode) ) { 2914 if ( b === a ) { 2915 return true; 2916 } 2917 } 2918 } 2919 return false; 2920 }; 2921 2922 events[type] = []; 2923 2924 // Refer to jQuery's implementation of mouseenter & mouseleave 2925 // Read about mouseenter and mouseleave: 2926 // http://www.quirksmode.org/js/events_mouse.html#link8 2927 var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; 2928 2929 onFn(element, eventmap[type], function(event) { 2930 var target = this, related = event.relatedTarget; 2931 // For mousenter/leave call the handler if related is outside the target. 2932 // NB: No relatedTarget if the mouse left/entered the browser window 2933 if ( !related || (related !== target && !contains(target, related)) ){ 2934 handle(event, type); 2935 } 2936 }); 2937 2938 } else { 2939 addEventListenerFn(element, type, handle); 2940 events[type] = []; 2941 } 2942 eventFns = events[type]; 2943 } 2944 eventFns.push(fn); 2945 }); 2946 }, 2947 2948 off: jqLiteOff, 2949 2950 one: function(element, type, fn) { 2951 element = jqLite(element); 2952 2953 //add the listener twice so that when it is called 2954 //you can remove the original function and still be 2955 //able to call element.off(ev, fn) normally 2956 element.on(type, function onFn() { 2957 element.off(type, fn); 2958 element.off(type, onFn); 2959 }); 2960 element.on(type, fn); 2961 }, 2962 2963 replaceWith: function(element, replaceNode) { 2964 var index, parent = element.parentNode; 2965 jqLiteDealoc(element); 2966 forEach(new JQLite(replaceNode), function(node){ 2967 if (index) { 2968 parent.insertBefore(node, index.nextSibling); 2969 } else { 2970 parent.replaceChild(node, element); 2971 } 2972 index = node; 2973 }); 2974 }, 2975 2976 children: function(element) { 2977 var children = []; 2978 forEach(element.childNodes, function(element){ 2979 if (element.nodeType === 1) 2980 children.push(element); 2981 }); 2982 return children; 2983 }, 2984 2985 contents: function(element) { 2986 return element.contentDocument || element.childNodes || []; 2987 }, 2988 2989 append: function(element, node) { 2990 forEach(new JQLite(node), function(child){ 2991 if (element.nodeType === 1 || element.nodeType === 11) { 2992 element.appendChild(child); 2993 } 2994 }); 2995 }, 2996 2997 prepend: function(element, node) { 2998 if (element.nodeType === 1) { 2999 var index = element.firstChild; 3000 forEach(new JQLite(node), function(child){ 3001 element.insertBefore(child, index); 3002 }); 3003 } 3004 }, 3005 3006 wrap: function(element, wrapNode) { 3007 wrapNode = jqLite(wrapNode)[0]; 3008 var parent = element.parentNode; 3009 if (parent) { 3010 parent.replaceChild(wrapNode, element); 3011 } 3012 wrapNode.appendChild(element); 3013 }, 3014 3015 remove: function(element) { 3016 jqLiteDealoc(element); 3017 var parent = element.parentNode; 3018 if (parent) parent.removeChild(element); 3019 }, 3020 3021 after: function(element, newElement) { 3022 var index = element, parent = element.parentNode;
3023 forEach(new JQLite(newElement), function(node){ 3024 parent.insertBefore(node, index.nextSibling); 3025 index = node; 3026 }); 3027 }, 3028 3029 addClass: jqLiteAddClass, 3030 removeClass: jqLiteRemoveClass, 3031 3032 toggleClass: function(element, selector, condition) { 3033 if (selector) { 3034 forEach(selector.split(' '), function(className){ 3035 var classCondition = condition; 3036 if (isUndefined(classCondition)) { 3037 classCondition = !jqLiteHasClass(element, className); 3038 } 3039 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 3040 }); 3041 } 3042 }, 3043 3044 parent: function(element) { 3045 var parent = element.parentNode; 3046 return parent && parent.nodeType !== 11 ? parent : null; 3047 }, 3048 3049 next: function(element) { 3050 if (element.nextElementSibling) { 3051 return element.nextElementSibling; 3052 } 3053 3054 // IE8 doesn't have nextElementSibling 3055 var elm = element.nextSibling; 3056 while (elm != null && elm.nodeType !== 1) { 3057 elm = elm.nextSibling; 3058 } 3059 return elm; 3060 }, 3061 3062 find: function(element, selector) { 3063 if (element.getElementsByTagName) { 3064 return element.getElementsByTagName(selector); 3065 } else { 3066 return []; 3067 } 3068 }, 3069 3070 clone: jqLiteClone, 3071 3072 triggerHandler: function(element, event, extraParameters) { 3073 3074 var dummyEvent, eventFnsCopy, handlerArgs; 3075 var eventName = event.type || event; 3076 var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; 3077 3078 if (eventFns) { 3079 3080 // Create a dummy event to pass to the handlers 3081 dummyEvent = { 3082 preventDefault: function() { this.defaultPrevented = true; }, 3083 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 3084 stopPropagation: noop, 3085 type: eventName, 3086 target: element 3087 }; 3088 3089 // If a custom event was provided then extend our dummy event with it 3090 if (event.type) { 3091 dummyEvent = extend(dummyEvent, event); 3092 } 3093 3094 // Copy event handlers in case event handlers array is modified during execution. 3095 eventFnsCopy = shallowCopy(eventFns); 3096 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 3097 3098 forEach(eventFnsCopy, function(fn) { 3099 fn.apply(element, handlerArgs); 3100 }); 3101 3102 } 3103 } 3104}, function(fn, name){ 3105 /** 3106 * chaining functions 3107 */ 3108 JQLite.prototype[name] = function(arg1, arg2, arg3) { 3109 var value; 3110 for(var i=0; i < this.length; i++) { 3111 if (isUndefined(value)) { 3112 value = fn(this[i], arg1, arg2, arg3); 3113 if (isDefined(value)) { 3114 // any function which returns a value needs to be wrapped 3115 value = jqLite(value); 3116 } 3117 } else { 3118 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3119 } 3120 } 3121 return isDefined(value) ? value : this; 3122 }; 3123 3124 // bind legacy bind/unbind to on/off 3125 JQLite.prototype.bind = JQLite.prototype.on; 3126 JQLite.prototype.unbind = JQLite.prototype.off; 3127}); 3128 3129/** 3130 * Computes a hash of an 'obj'. 3131 * Hash of a: 3132 * string is string 3133 * number is number as string 3134 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3135 * that is also assigned to the $$hashKey property of the object. 3136 * 3137 * @param obj 3138 * @returns {string} hash string such that the same input will have the same hash string. 3139 * The resulting string key is in 'type:hashKey' format. 3140 */ 3141function hashKey(obj, nextUidFn) { 3142 var objType = typeof obj, 3143 key; 3144 3145 if (objType == 'function' || (objType == 'object' && obj !== null)) { 3146 if (typeof (key = obj.$$hashKey) == 'function') { 3147 // must invoke on object to keep the right this 3148 key = obj.$$hashKey(); 3149 } else if (key === undefined) { 3150 key = obj.$$hashKey = (nextUidFn || nextUid)(); 3151 } 3152 } else { 3153 key = obj; 3154 } 3155 3156 return objType + ':' + key; 3157} 3158 3159/** 3160 * HashMap which can use objects as keys 3161 */ 3162function HashMap(array, isolatedUid) { 3163 if (isolatedUid) { 3164 var uid = 0; 3165 this.nextUid = function() { 3166 return ++uid; 3167 }; 3168 } 3169 forEach(array, this.put, this); 3170} 3171HashMap.prototype = { 3172 /** 3173 * Store key value pair 3174 * @param key key to store can be any type 3175 * @param value value to store can be any type 3176 */ 3177 put: function(key, value) { 3178 this[hashKey(key, this.nextUid)] = value; 3179 }, 3180 3181 /** 3182 * @param key 3183 * @returns {Object} the value for the key 3184 */ 3185 get: function(key) { 3186 return this[hashKey(key, this.nextUid)]; 3187 }, 3188 3189 /** 3190 * Remove the key/value pair 3191 * @param key 3192 */ 3193 remove: function(key) { 3194 var value = this[key = hashKey(key, this.nextUid)]; 3195 delete this[key]; 3196 return value; 3197 } 3198}; 3199 3200/** 3201 * @ngdoc function 3202 * @module ng 3203 * @name angular.injector 3204 * @kind function 3205 * 3206 * @description 3207 * Creates an injector object that can be used for retrieving services as well as for 3208 * dependency injection (see {@link guide/di dependency injection}). 3209 * 3210 3211 * @param {Array.<string|Function>}
3211 modules A list of module functions or their aliases. See 3212 * {@link angular.module}. The `ng` module must be explicitly added. 3213 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 3214 * 3215 * @example 3216 * Typical usage 3217 * ```js 3218 * // create an injector 3219 * var $injector = angular.injector(['ng']); 3220 * 3221 * // use the injector to kick off your application 3222 * // use the type inference to auto inject arguments, or use implicit injection 3223 * $injector.invoke(function($rootScope, $compile, $document){ 3224 * $compile($document)($rootScope); 3225 * $rootScope.$digest(); 3226 * }); 3227 * ``` 3228 * 3229 * Sometimes you want to get access to the injector of a currently running Angular app 3230 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3231 * application has been bootstrapped. You can do this using the extra `injector()` added 3232 * to JQuery/jqLite elements. See {@link angular.element}. 3233 * 3234 * *This is fairly rare but could be the case if a third party library is injecting the 3235 * markup.* 3236 * 3237 * In the following example a new block of HTML containing a `ng-controller` 3238 * directive is added to the end of the document body by JQuery. We then compile and link 3239 * it into the current AngularJS scope. 3240 * 3241 * ```js 3242 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3243 * $(document.body).append($div); 3244 * 3245 * angular.element(document).injector().invoke(function($compile) { 3246 * var scope = angular.element($div).scope(); 3247 * $compile($div)(scope); 3248 * }); 3249 * ``` 3250 */ 3251 3252 3253/** 3254 * @ngdoc module 3255 * @name auto 3256 * @description 3257 * 3258 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3259 */ 3260 3261var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; 3262var FN_ARG_SPLIT = /,/; 3263var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3264var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3265var $injectorMinErr = minErr('$injector'); 3266function annotate(fn) { 3267 var $inject, 3268 fnText, 3269 argDecl, 3270 last; 3271 3272 if (typeof fn === 'function') { 3273 if (!($inject = fn.$inject)) { 3274 $inject = []; 3275 if (fn.length) { 3276 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3277 argDecl = fnText.match(FN_ARGS); 3278 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ 3279 arg.replace(FN_ARG, function(all, underscore, name){ 3280 $inject.push(name); 3281 }); 3282 }); 3283 } 3284 fn.$inject = $inject; 3285 } 3286 } else if (isArray(fn)) { 3287 last = fn.length - 1; 3288 assertArgFn(fn[last], 'fn'); 3289 $inject = fn.slice(0, last); 3290 } else { 3291 assertArgFn(fn, 'fn', true); 3292 } 3293 return $inject; 3294} 3295 3296/////////////////////////////////////// 3297 3298/** 3299 * @ngdoc service 3300 * @name $injector 3301 * 3302 * @description 3303 * 3304 * `$injector` is used to retrieve object instances as defined by 3305 * {@link auto.$provide provider}, instantiate types, invoke methods, 3306 * and load modules. 3307 * 3308 * The following always holds true: 3309 * 3310 * ```js 3311 * var $injector = angular.injector(); 3312 * expect($injector.get('$injector')).toBe($injector); 3313 * expect($injector.invoke(function($injector){ 3314 * return $injector; 3315 * })).toBe($injector); 3316 * ``` 3317 * 3318 * # Injection Function Annotation 3319 * 3320 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3321 * following are all valid ways of annotating function with injection arguments and are equivalent. 3322 * 3323 * ```js 3324 * // inferred (only works if code not minified/obfuscated) 3325 * $injector.invoke(function(serviceA){}); 3326 * 3327 * // annotated 3328 * function explicit(serviceA) {}; 3329 * explicit.$inject = ['serviceA']; 3330 * $injector.invoke(explicit); 3331 * 3332 * // inline 3333 * $injector.invoke(['serviceA', function(serviceA){}]); 3334 * ``` 3335 * 3336 * ## Inference 3337 * 3338 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3339 * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with 3340 * minification, and obfuscation tools since these tools change the argument names. 3341 * 3342 * ## `$inject` Annotation 3343 * By adding an `$inject` property onto a function the injection parameters can be specified. 3344 * 3345 * ## Inline 3346 * As an array of injection names, where the last item in the array is the function to call. 3347 */ 3348 3349/** 3350 * @ngdoc method 3351 * @name $injector#get 3352 * 3353 * @description 3354 * Return an instance of the service. 3355 * 3356 * @param {string} name The name of the instance to retrieve. 3357 * @return {*} The instance. 3358 */ 3359 3360/** 3361 * @ngdoc method 3362 * @name $injector#invoke 3363 * 3364 * @description 3365 * Invoke the method and supply the method arguments from the `$injector`. 3366 * 3367 * @param {!Function} fn The function to invoke. Function parameters are injected according to the 3368 * {@link guide/di $inject Annotation} rules. 3369 * @param {Object=} self The `this` for the invoked method. 3370 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3371 * object first, before the `$injector` is consulted. 3372 * @returns {*} the value returned by the invoked `fn` function. 3373 */ 3374 3375/** 3376 * @ngdoc method 3377 * @name $injector#has 3378 * 3379 * @description 3380 * Allows the user to query if the particular service exists. 3381 * 3382 * @param {string} name Name of the service to query. 3383 * @returns {boolean} `true` if injector has given service. 3384 */ 3385 3386/** 3387 * @ngdoc method 3388 * @name $injector#instantiate 3389 * @description 3390 * Create a new instance of JS type. The method takes a constructor function, invokes the new 3391 * operator, and supplies all of the arguments to the constructor function as specified by the 3392 * constructor annotation. 3393 * 3394 * @param {Function} Type Annotated constructor function. 3395 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3396 * object first, before the `$injector` is consulted. 3397 * @returns {Object} new instance of `Type`. 3398 */ 3399 3400/** 3401 * @ngdoc method 3402 * @name $injector#annotate 3403 * 3404 * @description 3405 * Returns an array of service names which the function is requesting for injection. This API is 3406 * used by the injector to determine which services need to be injected into the function when the 3407 * function is invoked. There are three ways in which the function can be annotated with the needed 3408 * dependencies. 3409 * 3410 * # Argument names 3411 * 3412 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3413 * by converting the function into a string using `toString()` method and extracting the argument 3414 * names. 3415 * ```js 3416 * // Given 3417 * function MyController($scope, $route) { 3418 * // ... 3419 * } 3420 * 3421 * // Then 3422 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3423 * ``` 3424 * 3425 * This method does not work with code minification / obfuscation. For this reason the following 3426 * annotation strategies are supported. 3427 * 3428 * # The `$inject` property 3429 * 3430 * If a function has an `$inject` property and its value is an array of strings, then the strings 3431 * represent names of services to be injected into the function. 3432 * ```js 3433 * // Given 3434 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 3435 * // ... 3436 * } 3437 * // Define function dependencies 3438 * MyController['$inject'] = ['$scope', '$route']; 3439 * 3440 * // Then 3441 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3442 * ``` 3443 * 3444 * # The array notation 3445 * 3446 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 3447 * is very inconvenient. In these situations using the array notation to specify the dependencies in 3448 * a way that survives minification is a better choice: 3449 * 3450 * ```js 3451 * // We wish to write this (not minification / obfuscation safe) 3452 * injector.invoke(function($compile, $rootScope) { 3453 * // ... 3454 * }); 3455 * 3456 * // We are forced to write break inlining 3457 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 3458 * // ... 3459 * }; 3460 * tmpFn.$inject = ['$compile', '$rootScope']; 3461 * injector.invoke(tmpFn); 3462 * 3463 * // To better support inline function the inline annotation is supported 3464 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 3465 * // ... 3466 * }]); 3467 * 3468 * // Therefore 3469 * expect(injector.annotate( 3470 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 3471 * ).toEqual(['$compile', '$rootScope']); 3472 * ``` 3473 * 3474 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 3475 * be retrieved as described above. 3476 * 3477 * @returns {Array.<string>} The names of the services which the function requires. 3478 */ 3479 3480 3481 3482 3483/** 3484 * @ngdoc service 3485 * @name $provide 3486 * 3487 * @description 3488 * 3489 * The {@link auto.$provide $provide} service has a number of methods for registering components 3490 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 3491 * {@link angular.Module}. 3492 * 3493 * An Angular **service** is a singleton object created by a **service factory**. These **service 3494 * factories** are functions which, in turn, are created by a **service provider**. 3495 * The **service providers** are constructor functions. When instantiated they must contain a 3496 * property called `$get`, which holds the **service factory** function. 3497 * 3498 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 3499 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 3500 * function to get the instance of the **service**. 3501 * 3502 * Often services have no configuration options and there is no need to add methods to the service 3503 * provider. The provider will be no more than a constructor function with a `$get` property. For 3504 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 3505 * services without specifying a provider. 3506 * 3507 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 3508 * {@link auto.$injector $injector} 3509 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 3510 * providers and services. 3511 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 3512 * services, not providers. 3513 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 3514 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 3515 * given factory function. 3516 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 3517 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 3518 * a new object using the given constructor function. 3519 * 3520 * See the individual methods for more information and examples. 3521 */ 3522 3523/** 3524 * @ngdoc method 3525 * @name $provide#provider 3526 * @description 3527 * 3528 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 3529 * are constructor functions, whose instances are responsible for "providing" a factory for a 3530 * service. 3531 * 3532 * Service provider names start with the name of the service they provide followed by `Provider`. 3533 * For example, the {@link ng.$log $log} service has a provider called 3534 * {@link ng.$logProvider $logProvider}. 3535 * 3536 * Service provider objects can have additional methods which allow configuration of the provider 3537 * and its service. Importantly, you can configure what kind of service is created by the `$get` 3538 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 3539 * method {@link ng.$logProvider#debugEnabled debugEnabled} 3540 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 3541 * console or not. 3542 * 3543 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 3544 'Provider'` key. 3545 * @param {(Object|function())} provider If the provider is: 3546 * 3547 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 3548 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 3549 * - `Constructor`: a new instance of the provider will be created using 3550 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 3551 * 3552 * @returns {Object} registered provider instance 3553 3554 * @example 3555 * 3556 * The following example shows how to create a simple event tracking service and register it using 3557 * {@link auto.$provide#provider $provide.provider()}. 3558 * 3559 * ```js 3560 * // Define the eventTracker provider 3561 * function EventTrackerProvider() { 3562 * var trackingUrl = '/track'; 3563 * 3564 * // A provider method for configuring where the tracked events should been saved 3565 * this.setTrackingUrl = function(url) { 3566 * trackingUrl = url; 3567 * }; 3568 * 3569 * // The service factory function 3570 * this.$get = ['$http', function($http) { 3571 * var trackedEvents = {}; 3572 * return { 3573 * // Call this to track an event 3574 * event: function(event) { 3575 * var count = trackedEvents[event] || 0; 3576 * count += 1; 3577 * trackedEvents[event] = count; 3578 * return count; 3579 * }, 3580 * // Call this to save the tracked events to the trackingUrl 3581 * save: function() { 3582 * $http.post(trackingUrl, trackedEvents); 3583 * } 3584 * }; 3585 * }]; 3586 * } 3587 * 3588 * describe('eventTracker', function() { 3589 * var postSpy; 3590 * 3591 * beforeEach(module(function($provide) { 3592 * // Register the eventTracker provider 3593 * $provide.provider('eventTracker', EventTrackerProvider); 3594 * })); 3595 * 3596 * beforeEach(module(function(eventTrackerProvider) { 3597 * // Configure eventTracker provider 3598 * eventTrackerProvider.setTrackingUrl('/custom-track'); 3599 * })); 3600 * 3601 * it('tracks events', inject(function(eventTracker) { 3602 * expect(eventTracker.event('login')).toEqual(1); 3603 * expect(eventTracker.event('login')).toEqual(2); 3604 * })); 3605 * 3606 * it('saves to the tracking url', inject(function(eventTracker, $http) { 3607 * postSpy = spyOn($http, 'post'); 3608 * eventTracker.event('login'); 3609 * eventTracker.save(); 3610 * expect(postSpy).toHaveBeenCalled(); 3611 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 3612 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 3613 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 3614 * })); 3615 * }); 3616 * ``` 3617 */ 3618 3619/** 3620 * @ngdoc method 3621 * @name $provide#factory 3622 * @description 3623 * 3624 * Register a **service factory**, which will be called to return the service instance. 3625 * This is short for registering a service where its provider consists of only a `$get` property, 3626 * which is the given service factory function. 3627 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 3628 * configure your service in a provider. 3629 * 3630 * @param {string} name The name of the instance. 3631 * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand 3632 * for `$provide.provider(name, {$get: $getFn})`. 3633 * @returns {Object} registered provider instance 3634 * 3635 * @example 3636 * Here is an example of registering a service 3637 * ```js 3638 * $provide.factory('ping', ['$http', function($http) { 3639 * return function ping() { 3640 * return $http.send('/ping'); 3641 * }; 3642 * }]); 3643 * ``` 3644 * You would then inject and use this service like this: 3645 * ```js 3646 * someModule.controller('Ctrl', ['ping', function(ping) { 3647 * ping(); 3648 * }]); 3649 * ``` 3650 */ 3651 3652 3653/** 3654 * @ngdoc method 3655 * @name $provide#service 3656 * @description 3657 * 3658 * Register a **service constructor**, which will be invoked with `new` to create the service 3659 * instance. 3660 * This is short for registering a service where its provider's `$get` property is the service 3661 * constructor function that will be used to instantiate the service instance. 3662 * 3663 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 3664 * as a type/class. 3665 * 3666 * @param {string} name The name of the instance. 3667 * @param {Function} constructor A class (constructor function) that will be instantiated. 3668 * @returns {Object} registered provider instance 3669 * 3670 * @example 3671 * Here is an example of registering a service using 3672 * {@link auto.$provide#service $provide.service(class)}. 3673 * ```js 3674 * var Ping = function($http) { 3675 * this.$http = $http; 3676 * }; 3677 * 3678 * Ping.$inject = ['$http']; 3679 * 3680 * Ping.prototype.send = function() { 3681 * return this.$http.get('/ping'); 3682 * }; 3683 * $provide.service('ping', Ping); 3684 * ``` 3685 * You would then inject and use this service like this: 3686 * ```js 3687 * someModule.controller('Ctrl', ['ping', function(ping) { 3688 * ping.send(); 3689 * }]); 3690 * ``` 3691 */ 3692 3693 3694/** 3695 * @ngdoc method 3696 * @name $provide#value 3697 * @description 3698 * 3699 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 3700 * number, an array, an object or a function. This is short for registering a service where its 3701 * provider's `$get` property is a factory function that takes no arguments and returns the **value 3702 * service**. 3703 * 3704 * Value services are similar to constant services, except that they cannot be injected into a 3705 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 3706 * an Angular 3707 * {@link auto.$provide#decorator decorator}. 3708 * 3709 * @param {string} name The name of the instance. 3710 * @param {*} value The value. 3711 * @returns {Object} registered provider instance 3712 * 3713 * @example 3714 * Here are some examples of creating value services. 3715 * ```js 3716 * $provide.value('ADMIN_USER', 'admin'); 3717 *
3718 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 3719 * 3720 * $provide.value('halfOf', function(value) { 3721 * return value / 2; 3722 * }); 3723 * ``` 3724 */ 3725 3726 3727/** 3728 * @ngdoc method 3729 * @name $provide#constant 3730 * @description 3731 * 3732 * Register a **constant service**, such as a string, a number, an array, an object or a function, 3733 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be 3734 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 3735 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 3736 * 3737 * @param {string} name The name of the constant. 3738 * @param {*} value The constant value. 3739 * @returns {Object} registered instance 3740 * 3741 * @example 3742 * Here a some examples of creating constants: 3743 * ```js 3744 * $provide.constant('SHARD_HEIGHT', 306); 3745 * 3746 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 3747 * 3748 * $provide.constant('double', function(value) { 3749 * return value * 2; 3750 * }); 3751 * ``` 3752 */ 3753 3754 3755/** 3756 * @ngdoc method 3757 * @name $provide#decorator 3758 * @description 3759 * 3760 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 3761 * intercepts the creation of a service, allowing it to override or modify the behaviour of the 3762 * service. The object returned by the decorator may be the original service, or a new service 3763 * object which replaces or wraps and delegates to the original service. 3764 * 3765 * @param {string} name The name of the service to decorate. 3766 * @param {function()} decorator This function will be invoked when the service needs to be 3767 * instantiated and should return the decorated service instance. The function is called using 3768 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 3769 * Local injection arguments: 3770 * 3771 * * `$delegate` - The original service instance, which can be monkey patched, configured, 3772 * decorated or delegated to. 3773 * 3774 * @example 3775 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 3776 * calls to {@link ng.$log#error $log.warn()}. 3777 * ```js 3778 * $provide.decorator('$log', ['$delegate', function($delegate) { 3779 * $delegate.warn = $delegate.error; 3780 * return $delegate; 3781 * }]); 3782 * ``` 3783 */ 3784 3785 3786function createInjector(modulesToLoad) { 3787 var INSTANTIATING = {}, 3788 providerSuffix = 'Provider', 3789 path = [], 3790 loadedModules = new HashMap([], true), 3791 providerCache = { 3792 $provide: { 3793 provider: supportObject(provider), 3794 factory: supportObject(factory), 3795 service: supportObject(service), 3796 value: supportObject(value), 3797 constant: supportObject(constant), 3798 decorator: decorator 3799 } 3800 }, 3801 providerInjector = (providerCache.$injector = 3802 createInternalInjector(providerCache, function() { 3803 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 3804 })), 3805 instanceCache = {}, 3806 instanceInjector = (instanceCache.$injector = 3807 createInternalInjector(instanceCache, function(servicename) { 3808 var provider = providerInjector.get(servicename + providerSuffix); 3809 return instanceInjector.invoke(provider.$get, provider); 3810 })); 3811 3812 3813 forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); 3814 3815 return instanceInjector; 3816 3817 //////////////////////////////////// 3818 // $provider 3819 //////////////////////////////////// 3820 3821 function supportObject(delegate) { 3822 return function(key, value) { 3823 if (isObject(key)) { 3824 forEach(key, reverseParams(delegate)); 3825 } else { 3826 return delegate(key, value); 3827 } 3828 }; 3829 } 3830 3831 function provider(name, provider_) { 3832 assertNotHasOwnProperty(name, 'service'); 3833 if (isFunction(provider_) || isArray(provider_)) { 3834 provider_ = providerInjector.instantiate(provider_); 3835 } 3836 if (!provider_.$get) { 3837 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 3838 } 3839 return providerCache[name + providerSuffix] = provider_; 3840 } 3841 3842 function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } 3843 3844 function service(name, constructor) { 3845 return factory(name, ['$injector', function($injector) { 3846 return $injector.instantiate(constructor); 3847 }]); 3848 } 3849 3850 function value(name, val) { return factory(name, valueFn(val)); } 3851 3852 function constant(name, value) { 3853 assertNotHasOwnProperty(name, 'constant'); 3854 providerCache[name] = value; 3855 instanceCache[name] = value; 3856 } 3857 3858 function decorator(serviceName, decorFn) { 3859 var origProvider = providerInjector.get(serviceName + providerSuffix), 3860 orig$get = origProvider.$get; 3861 3862 origProvider.$get = function() { 3863 var origInstance = instanceInjector.invoke(orig$get, origProvider); 3864 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 3865 }; 3866 } 3867 3868 //////////////////////////////////// 3869 // Module Loading 3870 //////////////////////////////////// 3871 function loadModules(modulesToLoad){ 3872 var runBlocks = [], moduleFn, invokeQueue, i, ii;
3873 forEach(modulesToLoad, function(module) { 3874 if (loadedModules.get(module)) return; 3875 loadedModules.put(module, true); 3876 3877 try { 3878 if (isString(module)) { 3879 moduleFn = angularModule(module); 3880 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 3881 3882 for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { 3883 var invokeArgs = invokeQueue[i], 3884 provider = providerInjector.get(invokeArgs[0]); 3885 3886 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 3887 } 3888 } else if (isFunction(module)) { 3889 runBlocks.push(providerInjector.invoke(module)); 3890 } else if (isArray(module)) { 3891 runBlocks.push(providerInjector.invoke(module)); 3892 } else { 3893 assertArgFn(module, 'module'); 3894 } 3895 } catch (e) { 3896 if (isArray(module)) { 3897 module = module[module.length - 1]; 3898 } 3899 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 3900 // Safari & FF's stack traces don't contain error.message content 3901 // unlike those of Chrome and IE 3902 // So if stack doesn't contain message, we create a new string that contains both. 3903 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 3904 /* jshint -W022 */ 3905 e = e.message + '\n' + e.stack; 3906 } 3907 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 3908 module, e.stack || e.message || e); 3909 } 3910 }); 3911 return runBlocks; 3912 } 3913 3914 //////////////////////////////////// 3915 // internal Injector 3916 //////////////////////////////////// 3917 3918 function createInternalInjector(cache, factory) { 3919 3920 function getService(serviceName) { 3921 if (cache.hasOwnProperty(serviceName)) { 3922 if (cache[serviceName] === INSTANTIATING) { 3923 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 3924 serviceName + ' <- ' + path.join(' <- ')); 3925 } 3926 return cache[serviceName]; 3927 } else { 3928 try { 3929 path.unshift(serviceName); 3930 cache[serviceName] = INSTANTIATING; 3931 return cache[serviceName] = factory(serviceName); 3932 } catch (err) { 3933 if (cache[serviceName] === INSTANTIATING) { 3934 delete cache[serviceName]; 3935 } 3936 throw err; 3937 } finally { 3938 path.shift(); 3939 } 3940 } 3941 } 3942 3943 function invoke(fn, self, locals){ 3944 var args = [], 3945 $inject = annotate(fn), 3946 length, i, 3947 key; 3948 3949 for(i = 0, length = $inject.length; i < length; i++) { 3950 key = $inject[i]; 3951 if (typeof key !== 'string') { 3952 throw $injectorMinErr('itkn', 3953 'Incorrect injection token! Expected service name as string, got {0}', key); 3954 } 3955 args.push( 3956 locals && locals.hasOwnProperty(key) 3957 ? locals[key] 3958 : getService(key) 3959 ); 3960 } 3961 if (isArray(fn)) { 3962 fn = fn[length]; 3963 } 3964 3965 // http://jsperf.com/angularjs-invoke-apply-vs-switch 3966 // #5388 3967 return fn.apply(self, args); 3968 } 3969 3970 function instantiate(Type, locals) { 3971 var Constructor = function() {}, 3972 instance, returnedValue; 3973 3974 // Check if Type is annotated and use just the given function at n-1 as parameter 3975 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 3976 Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; 3977 instance = new Constructor(); 3978 returnedValue = invoke(Type, instance, locals); 3979 3980 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 3981 } 3982 3983 return { 3984 invoke: invoke, 3985 instantiate: instantiate, 3986 get: getService, 3987 annotate: annotate, 3988 has: function(name) { 3989 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 3990 } 3991 }; 3992 } 3993} 3994 3995/** 3996 * @ngdoc service 3997 * @name $anchorScroll 3998 * @kind function 3999 * @requires $window 4000 * @requires $location 4001 * @requires $rootScope 4002 * 4003 * @description 4004 * When called, it checks current value of `$location.hash()` and scrolls to the related element, 4005 * according to rules specified in 4006 * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). 4007 * 4008 * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. 4009 * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. 4010 * 4011 * @example 4012 <example> 4013 <file name="index.html"> 4014 <div id="scrollArea" ng-controller="ScrollCtrl"> 4015 <a ng-click="gotoBottom()">Go to bottom</a> 4016 <a id="bottom"></a> You're at the bottom! 4017 </div> 4018 </file> 4019 <file name="script.js"> 4020 function ScrollCtrl($scope, $location, $anchorScroll) { 4021 $scope.gotoBottom = function (){ 4022 // set the location.hash to the id of
4023 // the element you wish to scroll to. 4024 $location.hash('bottom'); 4025 4026 // call $anchorScroll() 4027 $anchorScroll(); 4028 }; 4029 } 4030 </file> 4031 <file name="style.css"> 4032 #scrollArea { 4033 height: 350px; 4034 overflow: auto; 4035 } 4036 4037 #bottom { 4038 display: block; 4039 margin-top: 2000px; 4040 } 4041 </file> 4042 </example> 4043 */ 4044function $AnchorScrollProvider() { 4045 4046 var autoScrollingEnabled = true; 4047 4048 /** 4049 * @ngdoc method 4050 * @name $anchorScrollProvider#disableAutoScrolling 4051 * 4052 * @description 4053 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 4054 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 4055 * Use this method to disable automatic scrolling. 4056 * 4057 * If automatic scrolling is disabled, one must explicitly call 4058 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 4059 * current hash. 4060 */ 4061 this.disableAutoScrolling = function() { 4062 autoScrollingEnabled = false; 4063 }; 4064 4065 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 4066 var document = $window.document; 4067 4068 // helper function to get first anchor from a NodeList 4069 // can't use filter.filter, as it accepts only instances of Array 4070 // and IE can't convert NodeList to an array using [].slice 4071 // TODO(vojta): use filter if we change it to accept lists as well 4072 function getFirstAnchor(list) { 4073 var result = null; 4074 forEach(list, function(element) { 4075 if (!result && lowercase(element.nodeName) === 'a') result = element; 4076 }); 4077 return result; 4078 } 4079 4080 function scroll() { 4081 var hash = $location.hash(), elm; 4082 4083 // empty hash, scroll to the top of the page 4084 if (!hash) $window.scrollTo(0, 0); 4085 4086 // element with given id 4087 else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); 4088 4089 // first anchor with given name :-D 4090 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); 4091 4092 // no element and hash == 'top', scroll to the top of the page 4093 else if (hash === 'top') $window.scrollTo(0, 0); 4094 } 4095 4096 // does not scroll when user clicks on anchor link that is currently on 4097 // (no url change, no $location.hash() change), browser native does scroll 4098 if (autoScrollingEnabled) { 4099 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 4100 function autoScrollWatchAction() { 4101 $rootScope.$evalAsync(scroll); 4102 }); 4103 } 4104 4105 return scroll; 4106 }]; 4107} 4108 4109var $animateMinErr = minErr('$animate'); 4110 4111/** 4112 * @ngdoc provider 4113 * @name $animateProvider 4114 * 4115 * @description 4116 * Default implementation of $animate that doesn't perform any animations, instead just 4117 * synchronously performs DOM 4118 * updates and calls done() callbacks. 4119 * 4120 * In order to enable animations the ngAnimate module has to be loaded. 4121 * 4122 * To see the functional implementation check out src/ngAnimate/animate.js 4123 */ 4124var $AnimateProvider = ['$provide', function($provide) { 4125 4126 4127 this.$$selectors = {}; 4128 4129 4130 /** 4131 * @ngdoc method 4132 * @name $animateProvider#register 4133 * 4134 * @description 4135 * Registers a new injectable animation factory function. The factory function produces the 4136 * animation object which contains callback functions for each event that is expected to be 4137 * animated. 4138 * 4139 * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` 4140 * must be called once the element animation is complete. If a function is returned then the 4141 * animation service will use this function to cancel the animation whenever a cancel event is 4142 * triggered. 4143 * 4144 * 4145 * ```js 4146 * return { 4147 * eventFn : function(element, done) { 4148 * //code to run the animation 4149 * //once complete, then run done() 4150 * return function cancellationFunction() { 4151 * //code to cancel the animation 4152 * } 4153 * } 4154 * } 4155 * ``` 4156 * 4157 * @param {string} name The name of the animation. 4158 * @param {Function} factory The factory function that will be executed to return the animation 4159 * object. 4160 */ 4161 this.register = function(name, factory) { 4162 var key = name + '-animation'; 4163 if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', 4164 "Expecting class selector starting with '.' got '{0}'.", name); 4165 this.$$selectors[name.substr(1)] = key; 4166 $provide.factory(key, factory); 4167 }; 4168 4169 /** 4170 * @ngdoc method 4171 * @name $animateProvider#classNameFilter 4172 * 4173 * @description 4174 * Sets and/or returns the CSS class regular expression that is checked when performing 4175 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 4176 * therefore enable $animate to attempt to perform an animation on any element. 4177 * When setting the classNameFilter value, animations will only be performed on elements 4178 * that successfully match the filter expression. This in turn can boost performance 4179 * for low-powered devices as well as applications containing a lot of structural operations. 4180 * @param {RegExp=} expression The className expression which will be checked against all animations 4181 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 4182 */ 4183 this.classNameFilter = function(expression) { 4184 if(arguments.length === 1) { 4185 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 4186 } 4187 return this.$$classNameFilter; 4188 }; 4189 4190 this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) { 4191 4192 function async(fn) { 4193 fn && $$asyncCallback(fn); 4194 } 4195 4196 /** 4197 * 4198 * @ngdoc service 4199 * @name $animate
4200 * @description The $animate service provides rudimentary DOM manipulation functions to 4201 * insert, remove and move elements within the DOM, as well as adding and removing classes. 4202 * This service is the core service used by the ngAnimate $animator service which provides 4203 * high-level animation hooks for CSS and JavaScript. 4204 * 4205 * $animate is available in the AngularJS core, however, the ngAnimate module must be included 4206 * to enable full out animation support. Otherwise, $animate will only perform simple DOM 4207 * manipulation operations. 4208 * 4209 * To learn more about enabling animation support, click here to visit the {@link ngAnimate 4210 * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service 4211 * page}. 4212 */ 4213 return { 4214 4215 /** 4216 * 4217 * @ngdoc method 4218 * @name $animate#enter 4219 * @kind function 4220 * @description Inserts the element into the DOM either after the `after` element or within 4221 * the `parent` element. Once complete, the done() callback will be fired (if provided). 4222 * @param {DOMElement} element the element which will be inserted into the DOM 4223 * @param {DOMElement} parent the parent element which will append the element as 4224 * a child (if the after element is not present) 4225 * @param {DOMElement} after the sibling element which will append the element 4226 * after itself 4227 * @param {Function=} done callback function that will be called after the element has been 4228 * inserted into the DOM 4229 */ 4230 enter : function(element, parent, after, done) { 4231 if (after) { 4232 after.after(element); 4233 } else { 4234 if (!parent || !parent[0]) { 4235 parent = after.parent(); 4236 } 4237 parent.append(element); 4238 } 4239 async(done); 4240 }, 4241 4242 /** 4243 * 4244 * @ngdoc method 4245 * @name $animate#leave 4246 * @kind function 4247 * @description Removes the element from the DOM. Once complete, the done() callback will be 4248 * fired (if provided). 4249 * @param {DOMElement} element the element which will be removed from the DOM 4250 * @param {Function=} done callback function that will be called after the element has been 4251 * removed from the DOM 4252 */ 4253 leave : function(element, done) { 4254 element.remove(); 4255 async(done); 4256 }, 4257 4258 /** 4259 * 4260 * @ngdoc method 4261 * @name $animate#move 4262 * @kind function 4263 * @description Moves the position of the provided element within the DOM to be placed 4264 * either after the `after` element or inside of the `parent` element. Once complete, the 4265 * done() callback will be fired (if provided). 4266 * 4267 * @param {DOMElement} element the element which will be moved around within the 4268 * DOM 4269 * @param {DOMElement} parent the parent element where the element will be 4270 * inserted into (if the after element is not present) 4271 * @param {DOMElement} after the sibling element where the element will be 4272 * positioned next to 4273 * @param {Function=} done the callback function (if provided) that will be fired after the 4274 * element has been moved to its new position 4275 */ 4276 move : function(element, parent, after, done) { 4277 // Do not remove element before insert. Removing will cause data associated with the 4278 // element to be dropped. Insert will implicitly do the remove. 4279 this.enter(element, parent, after, done); 4280 }, 4281 4282 /** 4283 * 4284 * @ngdoc method 4285 * @name $animate#addClass 4286 * @kind function 4287 * @description Adds the provided className CSS class value to the provided element. Once 4288 * complete, the done() callback will be fired (if provided). 4289 * @param {DOMElement} element the element which will have the className value 4290 * added to it 4291 * @param {string} className the CSS class which will be added to the element 4292 * @param {Function=} done the callback function (if provided) that will be fired after the 4293 * className value has been added to the element 4294 */ 4295 addClass : function(element, className, done) { 4296 className = isString(className) ? 4297 className : 4298 isArray(className) ? className.join(' ') : '';
4299 forEach(element, function (element) { 4300 jqLiteAddClass(element, className); 4301 }); 4302 async(done); 4303 }, 4304 4305 /** 4306 * 4307 * @ngdoc method 4308 * @name $animate#removeClass 4309 * @kind function 4310 * @description Removes the provided className CSS class value from the provided element. 4311 * Once complete, the done() callback will be fired (if provided). 4312 * @param {DOMElement} element the element which will have the className value 4313 * removed from it 4314 * @param {string} className 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 * className value has been removed from the element 4317 */ 4318 removeClass : function(element, className, done) { 4319 className = isString(className) ? 4320 className : 4321 isArray(className) ? className.join(' ') : ''; 4322 forEach(element, function (element) { 4323 jqLiteRemoveClass(element, className); 4324 }); 4325 async(done); 4326 }, 4327 4328 /** 4329 * 4330 * @ngdoc method 4331 * @name $animate#setClass 4332 * @kind function 4333 * @description Adds and/or removes the given CSS classes to and from the element. 4334 * Once complete, the done() callback will be fired (if provided). 4335 * @param {DOMElement} element the element which will have its CSS classes changed 4336 * removed from it 4337 * @param {string} add the CSS classes which will be added to the element 4338 * @param {string} remove the CSS class which will be removed from the element 4339 * @param {Function=} done the callback function (if provided) that will be fired after the 4340 * CSS classes have been set on the element 4341 */ 4342 setClass : function(element, add, remove, done) { 4343 forEach(element, function (element) { 4344 jqLiteAddClass(element, add); 4345 jqLiteRemoveClass(element, remove); 4346 }); 4347 async(done); 4348 }, 4349 4350 enabled : noop 4351 }; 4352 }]; 4353}]; 4354 4355function $$AsyncCallbackProvider(){ 4356 this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { 4357 return $$rAF.supported 4358 ? function(fn) { return $$rAF(fn); } 4359 : function(fn) { 4360 return $timeout(fn, 0, false); 4361 }; 4362 }]; 4363} 4364 4365/* global stripHash: true */ 4366 4367/** 4368 * ! This is a private undocumented service ! 4369 * 4370 * @name $browser 4371 * @requires $log 4372 * @description 4373 * This object has two goals: 4374 * 4375 * - hide all the global state in the browser caused by the window object 4376 * - abstract away all the browser specific features and inconsistencies 4377 * 4378 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 4379 * service, which can be used for convenient testing of the application without the interaction with 4380 * the real browser apis. 4381 */ 4382/** 4383 * @param {object} window The global window object. 4384 * @param {object} document jQuery wrapped document. 4385 * @param {function()} XHR XMLHttpRequest constructor. 4386 * @param {object} $log console.log or an object with the same interface. 4387 * @param {object} $sniffer $sniffer service 4388 */ 4389function Browser(window, document, $log, $sniffer) { 4390 var self = this, 4391 rawDocument = document[0], 4392 location = window.location, 4393 history = window.history, 4394 setTimeout = window.setTimeout, 4395 clearTimeout = window.clearTimeout, 4396 pendingDeferIds = {}; 4397 4398 self.isMock = false; 4399 4400 var outstandingRequestCount = 0; 4401 var outstandingRequestCallbacks = []; 4402 4403 // TODO(vojta): remove this temporary api 4404 self.$$completeOutstandingRequest = completeOutstandingRequest; 4405 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 4406 4407 /** 4408 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 4409 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 4410 */ 4411 function completeOutstandingRequest(fn) { 4412 try { 4413 fn.apply(null, sliceArgs(arguments, 1)); 4414 } finally { 4415 outstandingRequestCount--; 4416 if (outstandingRequestCount === 0) { 4417 while(outstandingRequestCallbacks.length) { 4418 try { 4419 outstandingRequestCallbacks.pop()(); 4420 } catch (e) { 4421 $log.error(e); 4422 } 4423 } 4424 } 4425 } 4426 } 4427 4428 function getHash(url) { 4429 var index = url.indexOf('#'); 4430 return index === -1 ? '' : url.substr(index + 1); 4431 } 4432 4433 /** 4434 * @private 4435 * Note: this method is used only by scenario runner 4436 * TODO(vojta): prefix this method with $$ ? 4437 * @param {function()} callback Function that will be called when no outstanding request 4438 */ 4439 self.notifyWhenNoOutstandingRequests = function(callback) { 4440 // force browser to execute all pollFns - this is needed so that cookies and other pollers fire 4441 // at some deterministic time in respect to the test runner's actions. Leaving things up to the 4442 // regular poller would result in flaky tests.
4443 forEach(pollFns, function(pollFn){ pollFn(); }); 4444 4445 if (outstandingRequestCount === 0) { 4446 callback(); 4447 } else { 4448 outstandingRequestCallbacks.push(callback); 4449 } 4450 }; 4451 4452 ////////////////////////////////////////////////////////////// 4453 // Poll Watcher API 4454 ////////////////////////////////////////////////////////////// 4455 var pollFns = [], 4456 pollTimeout; 4457 4458 /** 4459 * @name $browser#addPollFn 4460 * 4461 * @param {function()} fn Poll function to add 4462 * 4463 * @description 4464 * Adds a function to the list of functions that poller periodically executes, 4465 * and starts polling if not started yet. 4466 * 4467 * @returns {function()} the added function 4468 */ 4469 self.addPollFn = function(fn) { 4470 if (isUndefined(pollTimeout)) startPoller(100, setTimeout); 4471 pollFns.push(fn); 4472 return fn; 4473 }; 4474 4475 /** 4476 * @param {number} interval How often should browser call poll functions (ms) 4477 * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. 4478 * 4479 * @description 4480 * Configures the poller to run in the specified intervals, using the specified 4481 * setTimeout fn and kicks it off. 4482 */ 4483 function startPoller(interval, setTimeout) { 4484 (function check() { 4485 forEach(pollFns, function(pollFn){ pollFn(); }); 4486 pollTimeout = setTimeout(check, interval); 4487 })(); 4488 } 4489 4490 ////////////////////////////////////////////////////////////// 4491 // URL API 4492 ////////////////////////////////////////////////////////////// 4493 4494 var lastBrowserUrl = location.href, 4495 baseElement = document.find('base'), 4496 reloadLocation = null; 4497 4498 /** 4499 * @name $browser#url 4500 * 4501 * @description 4502 * GETTER: 4503 * Without any argument, this method just returns current value of location.href. 4504 * 4505 * SETTER: 4506 * With at least one argument, this method sets url to new value. 4507 * If html5 history api supported, pushState/replaceState is used, otherwise 4508 * location.href/location.replace is used. 4509 * Returns its own instance to allow chaining 4510 * 4511 * NOTE: this api is intended for use only by the $location service. Please use the 4512 * {@link ng.$location $location service} to change url. 4513 * 4514 * @param {string} url New url (when used as setter) 4515 * @param {boolean=} replace Should new url replace current history record ? 4516 */ 4517 self.url = function(url, replace) { 4518 // Android Browser BFCache causes location, history reference to become stale. 4519 if (location !== window.location) location = window.location; 4520 if (history !== window.history) history = window.history; 4521 4522 // setter 4523 if (url) { 4524 if (lastBrowserUrl == url) return; 4525 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 4526 lastBrowserUrl = url; 4527 // Don't use history API if only the hash changed 4528 // due to a bug in IE10/IE11 which leads 4529 // to not firing a `hashchange` nor `popstate` event 4530 // in some cases (see #9143). 4531 if (!sameBase && $sniffer.history) { 4532 if (replace) history.replaceState(null, '', url); 4533 else { 4534 history.pushState(null, '', url); 4535 // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 4536 baseElement.attr('href', baseElement.attr('href')); 4537 } 4538 } else { 4539 if (!sameBase) { 4540 reloadLocation = url; 4541 } 4542 if (replace) { 4543 location.replace(url); 4544 } else if (!sameBase) { 4545 location.href = url; 4546 } else { 4547 location.hash = getHash(url); 4548 } 4549 } 4550 return self; 4551 // getter 4552 } else { 4553 // - reloadLocation is needed as browsers don't allow to read out 4554 // the new location.href if a reload happened. 4555 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 4556 return reloadLocation || location.href.replace(/%27/g,"'"); 4557 } 4558 }; 4559 4560 var urlChangeListeners = [], 4561 urlChangeInit = false; 4562 4563 function fireUrlChange() { 4564 if (lastBrowserUrl == self.url()) return; 4565 4566 lastBrowserUrl = self.url(); 4567 forEach(urlChangeListeners, function(listener) { 4568 listener(self.url()); 4569 }); 4570 } 4571 4572 /** 4573 * @name $browser#onUrlChange 4574 * 4575 * @description 4576 * Register callback function that will be called, when url changes. 4577 * 4578 * It's only called when the url is changed from outside of angular: 4579 * - user types different url into address bar 4580 * - user clicks on history (forward/back) button 4581 * - user clicks on a link 4582 * 4583 * It's not called when url is changed by $browser.url() method 4584 * 4585 * The listener gets called with new url as parameter. 4586 * 4587 * NOTE: this api is intended for use only by the $location service. Please use the 4588 * {@link ng.$location $location service} to monitor url changes in angular apps. 4589 * 4590 * @param {function(string)} listener Listener function to be called when url changes. 4591 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 4592 */ 4593 self.onUrlChange = function(callback) { 4594 // TODO(vojta): refactor to use node's syntax for events 4595 if (!urlChangeInit) { 4596 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 4597 // don't fire popstate when user change the address bar and don't fire hashchange when url 4598 // changed by push/replaceState 4599 4600 // html5 history api - popstate event 4601 if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); 4602 // hashchange event 4603 if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); 4604 // polling 4605 else self.addPollFn(fireUrlChange); 4606 4607 urlChangeInit = true; 4608 } 4609 4610 urlChangeListeners.push(callback); 4611 return callback; 4612 }; 4613 4614 /** 4615 * Checks whether the url has changed outside of Angular. 4616 * Needs to be exported to be able to check for changes that have been done in sync,
4617 * as hashchange/popstate events fire in async. 4618 */ 4619 self.$$checkUrlChange = fireUrlChange; 4620 4621 ////////////////////////////////////////////////////////////// 4622 // Misc API 4623 ////////////////////////////////////////////////////////////// 4624 4625 /** 4626 * @name $browser#baseHref 4627 * 4628 * @description 4629 * Returns current <base href> 4630 * (always relative - without domain) 4631 * 4632 * @returns {string} The current base href 4633 */ 4634 self.baseHref = function() { 4635 var href = baseElement.attr('href'); 4636 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 4637 }; 4638 4639 ////////////////////////////////////////////////////////////// 4640 // Cookies API 4641 ////////////////////////////////////////////////////////////// 4642 var lastCookies = {}; 4643 var lastCookieString = ''; 4644 var cookiePath = self.baseHref(); 4645 4646 /** 4647 * @name $browser#cookies 4648 * 4649 * @param {string=} name Cookie name 4650 * @param {string=} value Cookie value 4651 * 4652 * @description 4653 * The cookies method provides a 'private' low level access to browser cookies. 4654 * It is not meant to be used directly, use the $cookie service instead. 4655 * 4656 * The return values vary depending on the arguments that the method was called with as follows: 4657 * 4658 * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify 4659 * it 4660 * - cookies(name, value) -> set name to value, if value is undefined delete the cookie 4661 * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that 4662 * way) 4663 * 4664 * @returns {Object} Hash of all cookies (if called without any parameter) 4665 */ 4666 self.cookies = function(name, value) { 4667 /* global escape: false, unescape: false */ 4668 var cookieLength, cookieArray, cookie, i, index; 4669 4670 if (name) { 4671 if (value === undefined) { 4672 rawDocument.cookie = escape(name) + "=;path=" + cookiePath + 4673 ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; 4674 } else { 4675 if (isString(value)) { 4676 cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + 4677 ';path=' + cookiePath).length + 1; 4678 4679 // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: 4680 // - 300 cookies 4681 // - 20 cookies per unique domain 4682 // - 4096 bytes per cookie 4683 if (cookieLength > 4096) { 4684 $log.warn("Cookie '"+ name + 4685 "' possibly not set or overflowed because it was too large ("+ 4686 cookieLength + " > 4096 bytes)!"); 4687 } 4688 } 4689 } 4690 } else { 4691 if (rawDocument.cookie !== lastCookieString) { 4692 lastCookieString = rawDocument.cookie; 4693 cookieArray = lastCookieString.split("; "); 4694 lastCookies = {}; 4695 4696 for (i = 0; i < cookieArray.length; i++) { 4697 cookie = cookieArray[i]; 4698 index = cookie.indexOf('='); 4699 if (index > 0) { //ignore nameless cookies 4700 name = unescape(cookie.substring(0, index)); 4701 // the first value that is seen for a cookie is the most 4702 // specific one. values for the same cookie name that 4703 // follow are for less specific paths. 4704 if (lastCookies[name] === undefined) { 4705 lastCookies[name] = unescape(cookie.substring(index + 1)); 4706 } 4707 } 4708 } 4709 } 4710 return lastCookies; 4711 } 4712 }; 4713 4714 4715 /** 4716 * @name $browser#defer 4717 * @param {function()} fn A function, who's execution should be deferred. 4718 * @param {number=} [delay=0] of milliseconds to defer the function execution. 4719 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 4720 * 4721 * @description 4722 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 4723 * 4724 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 4725 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 4726 * via `$browser.defer.flush()`. 4727 * 4728 */ 4729 self.defer = function(fn, delay) { 4730 var timeoutId; 4731 outstandingRequestCount++; 4732 timeoutId = setTimeout(function() {
4733 delete pendingDeferIds[timeoutId]; 4734 completeOutstandingRequest(fn); 4735 }, delay || 0); 4736 pendingDeferIds[timeoutId] = true; 4737 return timeoutId; 4738 }; 4739 4740 4741 /** 4742 * @name $browser#defer.cancel 4743 * 4744 * @description 4745 * Cancels a deferred task identified with `deferId`. 4746 * 4747 * @param {*} deferId Token returned by the `$browser.defer` function. 4748 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 4749 * canceled. 4750 */ 4751 self.defer.cancel = function(deferId) { 4752 if (pendingDeferIds[deferId]) { 4753 delete pendingDeferIds[deferId]; 4754 clearTimeout(deferId); 4755 completeOutstandingRequest(noop); 4756 return true; 4757 } 4758 return false; 4759 }; 4760 4761} 4762 4763function $BrowserProvider(){ 4764 this.$get = ['$window', '$log', '$sniffer', '$document', 4765 function( $window, $log, $sniffer, $document){ 4766 return new Browser($window, $document, $log, $sniffer); 4767 }]; 4768} 4769 4770/** 4771 * @ngdoc service 4772 * @name $cacheFactory 4773 * 4774 * @description 4775 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 4776 * them. 4777 * 4778 * ```js 4779 * 4780 * var cache = $cacheFactory('cacheId'); 4781 * expect($cacheFactory.get('cacheId')).toBe(cache); 4782 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 4783 * 4784 * cache.put("key", "value"); 4785 * cache.put("another key", "another value"); 4786 * 4787 * // We've specified no options on creation 4788 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 4789 * 4790 * ``` 4791 * 4792 * 4793 * @param {string} cacheId Name or id of the newly created cache. 4794 * @param {object=} options Options object that specifies the cache behavior. Properties: 4795 * 4796 * - `{number=}` `capacity` â turns the cache into LRU cache. 4797 * 4798 * @returns {object} Newly created cache object with the following set of methods: 4799 * 4800 * - `{object}` `info()` â Returns id, size, and options of cache. 4801 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 4802 * it. 4803 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 4804 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 4805 * - `{void}` `removeAll()` â Removes all cached values. 4806 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 4807 * 4808 * @example 4809 <example module="cacheExampleApp"> 4810 <file name="index.html"> 4811 <div ng-controller="CacheController"> 4812 <input ng-model="newCacheKey" placeholder="Key"> 4813 <input ng-model="newCacheValue" placeholder="Value"> 4814 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 4815 4816 <p ng-if="keys.length">Cached Values</p> 4817 <div ng-repeat="key in keys"> 4818 <span ng-bind="key"></span> 4819 <span>: </span> 4820 <b ng-bind="cache.get(key)"></b> 4821 </div> 4822 4823 <p>Cache Info</p> 4824 <div ng-repeat="(key, value) in cache.info()"> 4825 <span ng-bind="key"></span> 4826 <span>: </span> 4827 <b ng-bind="value"></b> 4828 </div> 4829 </div> 4830 </file> 4831 <file name="script.js"> 4832 angular.module('cacheExampleApp', []). 4833 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 4834 $scope.keys = []; 4835 $scope.cache = $cacheFactory('cacheId'); 4836 $scope.put = function(key, value) { 4837 if ($scope.cache.get(key) === undefined) { 4838 $scope.keys.push(key); 4839 } 4840 $scope.cache.put(key, value === undefined ? null : value); 4841 }; 4842 }]); 4843 </file> 4844 <file name="style.css"> 4845 p { 4846 margin: 10px 0 3px; 4847 } 4848 </file> 4849 </example> 4850 */ 4851function $CacheFactoryProvider() { 4852 4853 this.$get = function() { 4854 var caches = {}; 4855 4856 function cacheFactory(cacheId, options) { 4857 if (cacheId in caches) { 4858 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 4859 } 4860 4861 var size = 0, 4862 stats = extend({}, options, {id: cacheId}), 4863 data = {}, 4864 capacity = (options && options.capacity) || Number.MAX_VALUE, 4865 lruHash = {}, 4866 freshEnd = null, 4867 staleEnd = null; 4868 4869 /** 4870 * @ngdoc type 4871 * @name $cacheFactory.Cache 4872 *
4873 * @description 4874 * A cache object used to store and retrieve data, primarily used by 4875 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 4876 * templates and other data. 4877 * 4878 * ```js 4879 * angular.module('superCache') 4880 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 4881 * return $cacheFactory('super-cache'); 4882 * }]); 4883 * ``` 4884 * 4885 * Example test: 4886 * 4887 * ```js 4888 * it('should behave like a cache', inject(function(superCache) { 4889 * superCache.put('key', 'value'); 4890 * superCache.put('another key', 'another value'); 4891 * 4892 * expect(superCache.info()).toEqual({ 4893 * id: 'super-cache', 4894 * size: 2 4895 * }); 4896 * 4897 * superCache.remove('another key'); 4898 * expect(superCache.get('another key')).toBeUndefined(); 4899 * 4900 * superCache.removeAll(); 4901 * expect(superCache.info()).toEqual({ 4902 * id: 'super-cache', 4903 * size: 0 4904 * }); 4905 * })); 4906 * ``` 4907 */ 4908 return caches[cacheId] = { 4909 4910 /** 4911 * @ngdoc method 4912 * @name $cacheFactory.Cache#put 4913 * @kind function 4914 * 4915 * @description 4916 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 4917 * retrieved later, and incrementing the size of the cache if the key was not already 4918 * present in the cache. If behaving like an LRU cache, it will also remove stale 4919 * entries from the set. 4920 * 4921 * It will not insert undefined values into the cache. 4922 * 4923 * @param {string} key the key under which the cached data is stored. 4924 * @param {*} value the value to store alongside the key. If it is undefined, the key 4925 * will not be stored. 4926 * @returns {*} the value stored. 4927 */ 4928 put: function(key, value) { 4929 if (capacity < Number.MAX_VALUE) { 4930 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 4931 4932 refresh(lruEntry); 4933 } 4934 4935 if (isUndefined(value)) return; 4936 if (!(key in data)) size++; 4937 data[key] = value; 4938 4939 if (size > capacity) { 4940 this.remove(staleEnd.key); 4941 } 4942 4943 return value; 4944 }, 4945 4946 /** 4947 * @ngdoc method 4948 * @name $cacheFactory.Cache#get 4949 * @kind function 4950 * 4951 * @description 4952 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 4953 * 4954 * @param {string} key the key of the data to be retrieved 4955 * @returns {*} the value stored. 4956 */ 4957 get: function(key) { 4958 if (capacity < Number.MAX_VALUE) { 4959 var lruEntry = lruHash[key]; 4960 4961 if (!lruEntry) return; 4962 4963 refresh(lruEntry); 4964 } 4965 4966 return data[key]; 4967 }, 4968 4969 4970 /** 4971 * @ngdoc method 4972 * @name $cacheFactory.Cache#remove 4973 * @kind function 4974 * 4975 * @description 4976 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 4977 * 4978 * @param {string} key the key of the entry to be removed 4979 */ 4980 remove: function(key) { 4981 if (capacity < Number.MAX_VALUE) { 4982 var lruEntry = lruHash[key]; 4983 4984 if (!lruEntry) return; 4985 4986 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 4987 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 4988 link(lruEntry.n,lruEntry.p); 4989 4990 delete lruHash[key]; 4991 } 4992 4993 delete data[key]; 4994 size--; 4995 }, 4996 4997 4998 /** 4999 * @ngdoc method 5000 * @name $cacheFactory.Cache#removeAll 5001 * @kind function 5002 * 5003 * @description 5004 * Clears the cache object of any entries. 5005 */ 5006 removeAll: function() { 5007 data = {}; 5008 size = 0; 5009 lruHash = {}; 5010 freshEnd = staleEnd = null; 5011 }, 5012 5013 5014 /** 5015 * @ngdoc method 5016 * @name $cacheFactory.Cache#destroy 5017 * @kind function 5018 *
vendor: 6,188 bytes, lines 5019-5226
5019 * @description 5020 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 5021 * removing it from the {@link $cacheFactory $cacheFactory} set. 5022 */ 5023 destroy: function() { 5024 data = null; 5025 stats = null; 5026 lruHash = null; 5027 delete caches[cacheId]; 5028 }, 5029 5030 5031 /** 5032 * @ngdoc method 5033 * @name $cacheFactory.Cache#info 5034 * @kind function 5035 * 5036 * @description 5037 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 5038 * 5039 * @returns {object} an object with the following properties: 5040 * <ul> 5041 * <li>**id**: the id of the cache instance</li> 5042 * <li>**size**: the number of entries kept in the cache instance</li> 5043 * <li>**...**: any additional properties from the options object when creating the 5044 * cache.</li> 5045 * </ul> 5046 */ 5047 info: function() { 5048 return extend({}, stats, {size: size}); 5049 } 5050 }; 5051 5052 5053 /** 5054 * makes the `entry` the freshEnd of the LRU linked list 5055 */ 5056 function refresh(entry) { 5057 if (entry != freshEnd) { 5058 if (!staleEnd) { 5059 staleEnd = entry; 5060 } else if (staleEnd == entry) { 5061 staleEnd = entry.n; 5062 } 5063 5064 link(entry.n, entry.p); 5065 link(entry, freshEnd); 5066 freshEnd = entry; 5067 freshEnd.n = null; 5068 } 5069 } 5070 5071 5072 /** 5073 * bidirectionally links two entries of the LRU linked list 5074 */ 5075 function link(nextEntry, prevEntry) { 5076 if (nextEntry != prevEntry) { 5077 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 5078 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 5079 } 5080 } 5081 } 5082 5083 5084 /** 5085 * @ngdoc method 5086 * @name $cacheFactory#info 5087 * 5088 * @description 5089 * Get information about all the caches that have been created 5090 * 5091 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 5092 */ 5093 cacheFactory.info = function() { 5094 var info = {}; 5095 forEach(caches, function(cache, cacheId) { 5096 info[cacheId] = cache.info(); 5097 }); 5098 return info; 5099 }; 5100 5101 5102 /** 5103 * @ngdoc method 5104 * @name $cacheFactory#get 5105 * 5106 * @description 5107 * Get access to a cache object by the `cacheId` used when it was created. 5108 * 5109 * @param {string} cacheId Name or id of a cache to access. 5110 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 5111 */ 5112 cacheFactory.get = function(cacheId) { 5113 return caches[cacheId]; 5114 }; 5115 5116 5117 return cacheFactory; 5118 }; 5119} 5120 5121/** 5122 * @ngdoc service 5123 * @name $templateCache 5124 * 5125 * @description 5126 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 5127 * can load templates directly into the cache in a `script` tag, or by consuming the 5128 * `$templateCache` service directly. 5129 * 5130 * Adding via the `script` tag: 5131 * 5132 * ```html 5133 * <script type="text/ng-template" id="templateId.html"> 5134 * <p>This is the content of the template</p> 5135 * </script> 5136 * ``` 5137 * 5138 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 5139 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, 5140 * element with ng-app attribute), otherwise the template will be ignored. 5141 * 5142 * Adding via the $templateCache service: 5143 * 5144 * ```js 5145 * var myApp = angular.module('myApp', []); 5146 * myApp.run(function($templateCache) { 5147 * $templateCache.put('templateId.html', 'This is the content of the template'); 5148 * }); 5149 * ``` 5150 * 5151 * To retrieve the template later, simply use it in your HTML: 5152 * ```html 5153 * <div ng-include=" 'templateId.html' "></div> 5154 * ``` 5155 * 5156 * or get it via Javascript: 5157 * ```js 5158 * $templateCache.get('templateId.html') 5159 * ``` 5160 * 5161 * See {@link ng.$cacheFactory $cacheFactory}. 5162 * 5163 */ 5164function $TemplateCacheProvider() { 5165 this.$get = ['$cacheFactory', function($cacheFactory) { 5166 return $cacheFactory('templates'); 5167 }]; 5168} 5169 5170/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 5171 * 5172 * DOM-related variables: 5173 * 5174 * - "node" - DOM Node 5175 * - "element" - DOM Element or Node 5176 * - "$node" or "$element" - jqLite-wrapped node or element 5177 * 5178 * 5179 * Compiler related stuff: 5180 * 5181 * - "linkFn" - linking fn of a single directive 5182 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 5183 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 5184 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 5185 */ 5186 5187 5188/** 5189 * @ngdoc service 5190 * @name $compile 5191 * @kind function 5192 * 5193 * @description 5194 * Compiles an HTML string or DOM into a template and produces a template function, which 5195 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 5196 * 5197 * The compilation is a process of walking the DOM tree and matching DOM elements to 5198 * {@link ng.$compileProvider#directive directives}. 5199 * 5200 * <div class="alert alert-warning"> 5201 * **Note:** This document is an in-depth reference of all directive options. 5202 * For a gentle introduction to directives with examples of common use cases, 5203 * see the {@link guide/directive directive guide}. 5204 * </div> 5205 * 5206 * ## Comprehensive Directive API 5207 * 5208 * There are many different options for a directive. 5209 * 5210 * The difference resides in the return value of the factory function. 5211 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 5212 * or just the `postLink` function (all other properties will have the default values). 5213 * 5214 * <div class="alert alert-success"> 5215 * **Best Practice:** It's recommended to use the "directive definition object" form. 5216 * </div> 5217 * 5218 * Here's an example directive declared with a Directive Definition Object: 5219 * 5220 * ```js 5221 * var myModule = angular.module(...); 5222 * 5223 * myModule.directive('directiveName', function factory(injectables) { 5224 * var directiveDefinitionObject = { 5225 * priority: 0, 5226 * template: '<div></div>
5226', // or // function(tElement, tAttrs) { ... }, 5227 * // or 5228 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 5229 * transclude: false, 5230 * restrict: 'A', 5231 * scope: false, 5232 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 5233 * controllerAs: 'stringAlias', 5234 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 5235 * compile: function compile(tElement, tAttrs, transclude) { 5236 * return { 5237 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 5238 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 5239 * } 5240 * // or 5241 * // return function postLink( ... ) { ... } 5242 * }, 5243 * // or 5244 * // link: { 5245 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 5246 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 5247 * // } 5248 * // or 5249 * // link: function postLink( ... ) { ... } 5250 * }; 5251 * return directiveDefinitionObject; 5252 * }); 5253 * ``` 5254 * 5255 * <div class="alert alert-warning"> 5256 * **Note:** Any unspecified options will use the default value. You can see the default values below. 5257 * </div> 5258 * 5259 * Therefore the above can be simplified as: 5260 * 5261 * ```js 5262 * var myModule = angular.module(...); 5263 * 5264 * myModule.directive('directiveName', function factory(injectables) { 5265 * var directiveDefinitionObject = { 5266 * link: function postLink(scope, iElement, iAttrs) { ... } 5267 * }; 5268 * return directiveDefinitionObject; 5269 * // or 5270 * // return function postLink(scope, iElement, iAttrs) { ... } 5271 * }); 5272 * ``` 5273 * 5274 * 5275 * 5276 * ### Directive Definition Object 5277 * 5278 * The directive definition object provides instructions to the {@link ng.$compile 5279 * compiler}. The attributes are: 5280 * 5281 * #### `priority` 5282 * When there are multiple directives defined on a single DOM element, sometimes it 5283 * is necessary to specify the order in which the directives are applied. The `priority` is used 5284 * to sort the directives before their `compile` functions get called. Priority is defined as a 5285 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 5286 * are also run in priority order, but post-link functions are run in reverse order. The order 5287 * of directives with the same priority is undefined. The default priority is `0`. 5288 * 5289 * #### `terminal` 5290 * If set to true then the current `priority` will be the last set of directives 5291 * which will execute (any directives at the current priority will still execute 5292 * as the order of execution on same `priority` is undefined). 5293 * 5294 * #### `scope` 5295 * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the 5296 * same element request a new scope, only one new scope is created. The new scope rule does not 5297 * apply for the root of the template since the root of the template always gets a new scope. 5298 * 5299 * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from 5300 * normal scope in that it does not prototypically inherit from the parent scope. This is useful 5301 * when creating reusable components, which should not accidentally read or modify data in the 5302 * parent scope. 5303 * 5304 * The 'isolate' scope takes an object hash which defines a set of local scope properties 5305 * derived from the parent scope. These local properties are useful for aliasing values for 5306 * templates. Locals definition is a hash of local scope property to its source: 5307 * 5308 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is 5309 * always a string since DOM attributes are strings. If no `attr` name is specified then the 5310 * attribute name is assumed to be the same as the local name. 5311 * Given `<widget my-attr="hello {{name}}">` and widget definition 5312 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 5313 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 5314 * `localName` property on the widget scope. The `name` is read from the parent scope (not 5315 * component scope). 5316 * 5317 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 5318 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 5319 * name is specified then the attribute name is assumed to be the same as the local name. 5320 * Given `<widget my-attr="parentModel">` and widget definition of 5321 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 5322 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 5323 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 5324 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 5325 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. 5326 * 5327 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 5328 * If no `attr` name is specified then the attribute name is assumed to be the same as the 5329 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 5330 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 5331 * a function wrapper for the `count = count + value` expression. Often it's desirable to 5332 * pass data from the isolated scope via an expression to the parent scope, this can be 5333 * done by passing a map of local variable names and values into the expression wrapper fn. 5334 * For example, if the expression is `increment(amount)` then we can specify the amount value 5335 * by calling the `localFn` as `localFn({amount: 22})`. 5336 * 5337 * 5338 * 5339 * #### `controller` 5340 * Controller constructor function. The controller is instantiated before the 5341 * pre-linking phase and it is shared with other directives (see 5342 * `require` attribute). This allows the directives to communicate with each other and augment 5343 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 5344 * 5345 * * `$scope` - Current scope associated with the element 5346 * * `$element` - Current element 5347 * * `$attrs` - Current attributes object for the element 5348 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. 5349 * The scope can be overridden by an optional first argument. 5350 * `function([scope], cloneLinkingFn)`. 5351 * 5352 * 5353 * #### `require` 5354 * Require another directive and inject its controller as the fourth argument to the linking function. The
5355 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 5356 * injected argument will be an array in corresponding order. If no such directive can be 5357 * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: 5358 * 5359 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 5360 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 5361 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 5362 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 5363 * `null` to the `link` fn if not found. 5364 * 5365 * 5366 * #### `controllerAs` 5367 * Controller alias at the directive scope. An alias for the controller so it 5368 * can be referenced at the directive template. The directive needs to define a scope for this 5369 * configuration to be used. Useful in the case when directive is used as component. 5370 * 5371 * 5372 * #### `restrict` 5373 * String of subset of `EACM` which restricts the directive to a specific directive 5374 * declaration style. If omitted, the default (attributes only) is used. 5375 * 5376 * * `E` - Element name: `<my-directive></my-directive>` 5377 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 5378 * * `C` - Class: `<div class="my-directive: exp;"></div>` 5379 * * `M` - Comment: `<!-- directive: my-directive exp -->` 5380 * 5381 * 5382 * #### `template` 5383 * HTML markup that may: 5384 * * Replace the contents of the directive's element (default). 5385 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 5386 * * Wrap the contents of the directive's element (if `transclude` is true). 5387 * 5388 * Value may be: 5389 * 5390 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 5391 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` 5392 * function api below) and returns a string value. 5393 * 5394 * 5395 * #### `templateUrl` 5396 * Same as `template` but the template is loaded from the specified URL. Because 5397 * the template loading is asynchronous the compilation/linking is suspended until the template 5398 * is loaded. 5399 * 5400 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 5401 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 5402 * a string value representing the url. In either case, the template URL is passed through {@link 5403 * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 5404 * 5405 * 5406 * #### `replace` ([*DEPRECATED*!], will be removed in next major release) 5407 * specify what the template should replace. Defaults to `false`. 5408 * 5409 * * `true` - the template will replace the directive's element. 5410 * * `false` - the template will replace the contents of the directive's element. 5411 * 5412 * The replacement process migrates all of the attributes / classes from the old element to the new 5413 * one. See the {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive 5414 * Directives Guide} for an example. 5415 * 5416 * #### `transclude` 5417 * compile the content of the element and make it available to the directive. 5418 * Typically used with {@link ng.directive:ngTransclude 5419 * ngTransclude}. The advantage of transclusion is that the linking function receives a 5420 * transclusion function which is pre-bound to the correct scope. In a typical setup the widget 5421 * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` 5422 * scope. This makes it possible for the widget to have private state, and the transclusion to 5423 * be bound to the parent (pre-`isolate`) scope. 5424 * 5425 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the 5426 * directive's element or the entire element: 5427 * 5428 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 5429 * * `'element'` - transclude the whole of the directive's element including any directives on this 5430 * element that defined at a lower priority than this directive. When used, the `template` 5431 * property is ignored. 5432 * 5433 * <div class="alert alert-warning"> 5434 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 5435 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 5436 * Testing Transclusion Directives}. 5437 * </div> 5438 * 5439 * #### `compile` 5440 * 5441 * ```js 5442 * function compile(tElement, tAttrs, transclude) { ... } 5443 * ``` 5444 * 5445 * The compile function deals with transforming the template DOM. Since most directives do not do 5446 * template transformation, it is not used often. The compile function takes the following arguments: 5447 * 5448 * * `tElement` - template element - The element where the directive has been declared. It is 5449 * safe to do template transformation on the element and child elements only. 5450 * 5451 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 5452 * between all directive compile functions. 5453 * 5454 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 5455 * 5456 * <div class="alert alert-warning"> 5457 * **Note:** The template instance and the link instance may be different objects if the template has 5458 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 5459 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
5460 * should be done in a linking function rather than in a compile function. 5461 * </div> 5462 5463 * <div class="alert alert-warning"> 5464 * **Note:** The compile function cannot handle directives that recursively use themselves in their 5465 * own templates or compile functions. Compiling these directives results in an infinite loop and a 5466 * stack overflow errors. 5467 * 5468 * This can be avoided by manually using $compile in the postLink function to imperatively compile 5469 * a directive's template instead of relying on automatic template compilation via `template` or 5470 * `templateUrl` declaration or manual compilation inside the compile function. 5471 * </div> 5472 * 5473 * <div class="alert alert-error"> 5474 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 5475 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 5476 * to the link function instead. 5477 * </div> 5478 5479 * A compile function can have a return value which can be either a function or an object. 5480 * 5481 * * returning a (post-link) function - is equivalent to registering the linking function via the 5482 * `link` property of the config object when the compile function is empty. 5483 * 5484 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 5485 * control when a linking function should be called during the linking phase. See info about 5486 * pre-linking and post-linking functions below. 5487 * 5488 * 5489 * #### `link` 5490 * This property is used only if the `compile` property is not defined. 5491 * 5492 * ```js 5493 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 5494 * ``` 5495 * 5496 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 5497 * executed after the template has been cloned. This is where most of the directive logic will be 5498 * put. 5499 * 5500 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 5501 * directive for registering {@link ng.$rootScope.Scope#$watch watches}. 5502 * 5503 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 5504 * manipulate the children of the element only in `postLink` function since the children have 5505 * already been linked. 5506 * 5507 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 5508 * between all directive linking functions. 5509 * 5510 * * `controller` - a controller instance - A controller instance if at least one directive on the 5511 * element defines a controller. The controller is shared among all the directives, which allows 5512 * the directives to use the controllers as a communication channel. 5513 * 5514 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 5515 * The scope can be overridden by an optional first argument. This is the same as the `$transclude` 5516 * parameter of directive controllers. 5517 * `function([scope], cloneLinkingFn)`. 5518 * 5519 * 5520 * #### Pre-linking function 5521 * 5522 * Executed before the child elements are linked. Not safe to do DOM transformation since the 5523 * compiler linking function will fail to locate the correct elements for linking. 5524 * 5525 * #### Post-linking function 5526 * 5527 * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. 5528 * 5529 * <a name="Attributes"></a> 5530 * ### Attributes 5531 * 5532 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 5533 * `link()` or `compile()` functions. It has a variety of uses. 5534 * 5535 * accessing *Normalized attribute names:* 5536 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 5537 * the attributes object allows for normalized access to 5538 * the attributes. 5539 * 5540 * * *Directive inter-communication:* All directives share the same instance of the attributes 5541 * object which allows the directives to use the attributes object as inter directive 5542 * communication. 5543 * 5544 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 5545 * allowing other directives to read the interpolated value. 5546 * 5547 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 5548 * that contain interpolation (e.g. `src="{{bar}}
5548"`). Not only is this very efficient but it's also 5549 * the only way to easily get the actual value because during the linking phase the interpolation 5550 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 5551 * 5552 * ```js 5553 * function linkingFn(scope, elm, attrs, ctrl) { 5554 * // get the attribute value 5555 * console.log(attrs.ngModel); 5556 * 5557 * // change the attribute 5558 * attrs.$set('ngModel', 'new value'); 5559 * 5560 * // observe changes to interpolated attribute 5561 * attrs.$observe('ngModel', function(value) { 5562 * console.log('ngModel has changed value to ' + value); 5563 * }); 5564 * } 5565 * ``` 5566 * 5567 * ## Example 5568 * 5569 * <div class="alert alert-warning"> 5570 * **Note**: Typically directives are registered with `module.directive`. The example below is 5571 * to illustrate how `$compile` works. 5572 * </div> 5573 * 5574 <example module="compileExample"> 5575 <file name="index.html"> 5576 <script> 5577 angular.module('compileExample', [], function($compileProvider) { 5578 // configure new 'compile' directive by passing a directive 5579 // factory function. The factory function injects the '$compile' 5580 $compileProvider.directive('compile', function($compile) { 5581 // directive factory creates a link function 5582 return function(scope, element, attrs) { 5583 scope.$watch( 5584 function(scope) { 5585 // watch the 'compile' expression for changes 5586 return scope.$eval(attrs.compile); 5587 }, 5588 function(value) { 5589 // when the 'compile' expression changes 5590 // assign it into the current DOM 5591 element.html(value); 5592 5593 // compile the new DOM and link it to the current 5594 // scope. 5595 // NOTE: we only compile .childNodes so that 5596 // we don't get into infinite loop compiling ourselves 5597 $compile(element.contents())(scope); 5598 } 5599 ); 5600 }; 5601 }); 5602 }) 5603 .controller('GreeterController', ['$scope', function($scope) { 5604 $scope.name = 'Angular'; 5605 $scope.html = 'Hello {{name}}'; 5606 }]); 5607 </script> 5608 <div ng-controller="GreeterController"> 5609 <input ng-model="name"> <br> 5610 <textarea ng-model="html"></textarea> <br> 5611 <div compile="html"></div> 5612 </div> 5613 </file> 5614 <file name="protractor.js" type="protractor"> 5615 it('should auto compile', function() { 5616 var textarea = $('textarea'); 5617 var output = $('div[compile]'); 5618 // The initial state reads 'Hello Angular'. 5619 expect(output.getText()).toBe('Hello Angular'); 5620 textarea.clear(); 5621 textarea.sendKeys('{{name}}!'); 5622 expect(output.getText()).toBe('Angular!'); 5623 }); 5624 </file> 5625 </example> 5626 5627 * 5628 * 5629 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 5630 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. 5631 * @param {number} maxPriority only apply directives lower than given priority (Only effects the 5632 * root element(s), not their children) 5633 * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template 5634 * (a DOM element/tree) to a scope. Where: 5635 * 5636 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 5637 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 5638 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 5639 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 5640 * called as: <br> `cloneAttachFn(clonedElement, scope)` where: 5641 * 5642 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 5643 * * `scope` - is the current scope with which the linking function is working with. 5644 * 5645 * Calling the linking function returns the element of the template. It is either the original 5646 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 5647 * 5648 * After linking the view is not updated until after a call to $digest which typically is done by 5649 * Angular automatically. 5650 * 5651 * If you need access to the bound view, there are two ways to do it: 5652 * 5653 * - If you are not asking the linking function to clone the template, create the DOM element(s) 5654 * before you send them to the compiler and keep this reference around. 5655 * ```js 5656 * var element = $compile('<p>{{total}}</p>')(scope); 5657 * ``` 5658 * 5659 * - if on the other hand, you need the element to be cloned, the view reference from the original 5660 * example would not point to the clone, but rather to the original template that was cloned. In 5661 * this case, you can access the clone via the cloneAttachFn: 5662 * ```js 5663 * var templateElement = angular.element('<p>{{total}}</p>'), 5664 * scope = ....; 5665 * 5666 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 5667 * //attach the clone to DOM document at the right place 5668 * }); 5669 * 5670 * //now we have reference to the cloned DOM via `clonedElement` 5671 * ``` 5672 * 5673 * 5674 * For information on how the compiler works, see the 5675 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 5676 */ 5677 5678var $compileMinErr = minErr('$compile'); 5679 5680/** 5681 * @ngdoc provider 5682 * @name $compileProvider 5683 * @kind function 5684 * 5685 * @description 5686 */
vendor: 4,560 bytes, lines 5687-5795
5687$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 5688function $CompileProvider($provide, $$sanitizeUriProvider) { 5689 var hasDirectives = {}, 5690 Suffix = 'Directive', 5691 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/, 5692 CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/; 5693 5694 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 5695 // The assumption is that future DOM event attribute names will begin with 5696 // 'on' and be composed of only English letters. 5697 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 5698 5699 /** 5700 * @ngdoc method 5701 * @name $compileProvider#directive 5702 * @kind function 5703 * 5704 * @description 5705 * Register a new directive with the compiler. 5706 * 5707 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 5708 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 5709 * names and the values are the factories. 5710 * @param {Function|Array} directiveFactory An injectable directive factory function. See 5711 * {@link guide/directive} for more info. 5712 * @returns {ng.$compileProvider} Self for chaining. 5713 */ 5714 this.directive = function registerDirective(name, directiveFactory) { 5715 assertNotHasOwnProperty(name, 'directive'); 5716 if (isString(name)) { 5717 assertArg(directiveFactory, 'directiveFactory'); 5718 if (!hasDirectives.hasOwnProperty(name)) { 5719 hasDirectives[name] = []; 5720 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 5721 function($injector, $exceptionHandler) { 5722 var directives = []; 5723 forEach(hasDirectives[name], function(directiveFactory, index) { 5724 try { 5725 var directive = $injector.invoke(directiveFactory); 5726 if (isFunction(directive)) { 5727 directive = { compile: valueFn(directive) }; 5728 } else if (!directive.compile && directive.link) { 5729 directive.compile = valueFn(directive.link); 5730 } 5731 directive.priority = directive.priority || 0; 5732 directive.index = index; 5733 directive.name = directive.name || name; 5734 directive.require = directive.require || (directive.controller && directive.name); 5735 directive.restrict = directive.restrict || 'A'; 5736 directives.push(directive); 5737 } catch (e) { 5738 $exceptionHandler(e); 5739 } 5740 }); 5741 return directives; 5742 }]); 5743 } 5744 hasDirectives[name].push(directiveFactory); 5745 } else { 5746 forEach(name, reverseParams(registerDirective)); 5747 } 5748 return this; 5749 }; 5750 5751 5752 /** 5753 * @ngdoc method 5754 * @name $compileProvider#aHrefSanitizationWhitelist 5755 * @kind function 5756 * 5757 * @description 5758 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5759 * urls during a[href] sanitization. 5760 * 5761 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5762 * 5763 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 5764 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 5765 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 5766 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5767 * 5768 * @param {RegExp=} regexp New regexp to whitelist urls with. 5769 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5770 * chaining otherwise. 5771 */ 5772 this.aHrefSanitizationWhitelist = function(regexp) { 5773 if (isDefined(regexp)) { 5774 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 5775 return this; 5776 } else { 5777 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 5778 } 5779 }; 5780 5781 5782 /** 5783 * @ngdoc method 5784 * @name $compileProvider#imgSrcSanitizationWhitelist 5785 * @kind function 5786 * 5787 * @description 5788 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5789 * urls during img[src] sanitization. 5790 * 5791 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5792 * 5793 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 5794 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 5795 * regular expression. If a match is found, the original url is
5795written into the dom. Otherwise, 5796 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5797 * 5798 * @param {RegExp=} regexp New regexp to whitelist urls with. 5799 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5800 * chaining otherwise. 5801 */ 5802 this.imgSrcSanitizationWhitelist = function(regexp) { 5803 if (isDefined(regexp)) { 5804 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 5805 return this; 5806 } else { 5807 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 5808 } 5809 }; 5810 5811 this.$get = [ 5812 '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', 5813 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 5814 function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, 5815 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 5816 5817 var Attributes = function(element, attr) { 5818 this.$$element = element; 5819 this.$attr = attr || {}; 5820 }; 5821 5822 Attributes.prototype = { 5823 /** 5824 * @ngdoc method 5825 * @name $compile.directive.Attributes#$normalize 5826 * @kind function 5827 * 5828 * @description 5829 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 5830 * `data-`) to its normalized, camelCase form. 5831 * 5832 * Also there is special case for Moz prefix starting with upper case letter. 5833 * 5834 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 5835 * 5836 * @param {string} name Name to normalize 5837 */ 5838 $normalize: directiveNormalize, 5839 5840 5841 /** 5842 * @ngdoc method 5843 * @name $compile.directive.Attributes#$addClass 5844 * @kind function 5845 * 5846 * @description 5847 * Adds the CSS class value specified by the classVal parameter to the element. If animations 5848 * are enabled then an animation will be triggered for the class addition. 5849 * 5850 * @param {string} classVal The className value that will be added to the element 5851 */ 5852 $addClass : function(classVal) { 5853 if(classVal && classVal.length > 0) { 5854 $animate.addClass(this.$$element, classVal); 5855 } 5856 }, 5857 5858 /** 5859 * @ngdoc method 5860 * @name $compile.directive.Attributes#$removeClass 5861 * @kind function 5862 * 5863 * @description 5864 * Removes the CSS class value specified by the classVal parameter from the element. If 5865 * animations are enabled then an animation will be triggered for the class removal. 5866 * 5867 * @param {string} classVal The className value that will be removed from the element 5868 */ 5869 $removeClass : function(classVal) { 5870 if(classVal && classVal.length > 0) { 5871 $animate.removeClass(this.$$element, classVal); 5872 } 5873 }, 5874 5875 /** 5876 * @ngdoc method 5877 * @name $compile.directive.Attributes#$updateClass 5878 * @kind function 5879 * 5880 * @description 5881 * Adds and removes the appropriate CSS class values to the element based on the difference 5882 * between the new and old CSS class values (specified as newClasses and oldClasses). 5883 * 5884 * @param {string} newClasses The current CSS className value 5885 * @param {string} oldClasses The former CSS className value 5886 */ 5887 $updateClass : function(newClasses, oldClasses) { 5888 var toAdd = tokenDifference(newClasses, oldClasses); 5889 var toRemove = tokenDifference(oldClasses, newClasses); 5890 5891 if(toAdd.length === 0) { 5892 $animate.removeClass(this.$$element, toRemove); 5893 } else if(toRemove.length === 0) { 5894 $animate.addClass(this.$$element, toAdd); 5895 } else { 5896 $animate.setClass(this.$$element, toAdd, toRemove); 5897 } 5898 }, 5899 5900 /** 5901 * Set a normalized attribute on the element in a way such that all directives 5902 * can share the attribute. This function properly handles boolean attributes. 5903 * @param {string} key Normalized key. (ie ngAttribute) 5904 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 5905 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 5906 * Defaults to true. 5907 * @param {string=} attrName Optional none normalized name. Defaults to key. 5908 */ 5909 $set: function(key, value, writeAttr, attrName) { 5910 // TODO: decide whether or not to throw an error if "class" 5911 //is set through this function since it may cause $updateClass to 5912 //become unstable. 5913 5914 var booleanKey = getBooleanAttrName(this.$$element[0], key), 5915 normalizedVal, 5916 nodeName; 5917 5918 if (booleanKey) { 5919 this.$$element.prop(key, value); 5920 attrName = booleanKey; 5921 } 5922 5923 this[key] = value; 5924 5925 // translate normalized key to actual key 5926 if (attrName) { 5927 this.$attr[key] = attrName; 5928 } else { 5929 attrName = this.$attr[key]; 5930 if (!attrName) { 5931 this.$attr[key] = attrName = snake_case(key, '-'); 5932 } 5933 } 5934 5935 // SVG elements' `nodeName` can be lowercase 5936 nodeName = nodeName_(this.$$element).toUpperCase(); 5937 5938 // sanitize a[href] and img[src] values 5939 if ((nodeName === 'A' && (key === 'href' || key === 'xlinkHref')) || 5940 (nodeName === 'IMG' && key === 'src')) { 5941 this[key] = value = $$sanitizeUri(value, key === 'src'); 5942 } 5943 5944 if (writeAttr !== false) { 5945 if (value === null || value === undefined) { 5946 this.$$element.removeAttr(attrName); 5947 } else { 5948 this.$$element.attr(attrName, value); 5949 } 5950 } 5951 5952 // fire observers 5953 var $$observers = this.$$observers;
5954 $$observers && forEach($$observers[key], function(fn) { 5955 try { 5956 fn(value); 5957 } catch (e) { 5958 $exceptionHandler(e); 5959 } 5960 }); 5961 }, 5962 5963 5964 /** 5965 * @ngdoc method 5966 * @name $compile.directive.Attributes#$observe 5967 * @kind function 5968 * 5969 * @description 5970 * Observes an interpolated attribute. 5971 * 5972 * The observer function will be invoked once during the next `$digest` following 5973 * compilation. The observer is then invoked whenever the interpolated value 5974 * changes. 5975 * 5976 * @param {string} key Normalized key. (ie ngAttribute) . 5977 * @param {function(interpolatedValue)} fn Function that will be called whenever 5978 the interpolated value of the attribute changes. 5979 * See the {@link guide/directive#Attributes Directives} guide for more info. 5980 * @returns {function()} the `fn` parameter. 5981 */ 5982 $observe: function(key, fn) { 5983 var attrs = this, 5984 $$observers = (attrs.$$observers || (attrs.$$observers = {})), 5985 listeners = ($$observers[key] || ($$observers[key] = [])); 5986 5987 listeners.push(fn); 5988 $rootScope.$evalAsync(function() { 5989 if (!listeners.$$inter) { 5990 // no one registered attribute interpolation function, so lets call it manually 5991 fn(attrs[key]); 5992 } 5993 }); 5994 return fn; 5995 } 5996 }; 5997 5998 var startSymbol = $interpolate.startSymbol(), 5999 endSymbol = $interpolate.endSymbol(), 6000 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 6001 ? identity 6002 : function denormalizeTemplate(template) { 6003 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 6004 }, 6005 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 6006 6007 6008 return compile; 6009 6010 //================================ 6011 6012 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 6013 previousCompileContext) { 6014 if (!($compileNodes instanceof jqLite)) { 6015 // jquery always rewraps, whereas we need to preserve the original selector so that we can 6016 // modify it. 6017 $compileNodes = jqLite($compileNodes); 6018 } 6019 // We can not compile top level text elements since text nodes can be merged and we will 6020 // not be able to attach scope data to them, so we will wrap them in <span> 6021 forEach($compileNodes, function(node, index){ 6022 if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { 6023 $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0]; 6024 } 6025 }); 6026 var compositeLinkFn = 6027 compileNodes($compileNodes, transcludeFn, $compileNodes, 6028 maxPriority, ignoreDirective, previousCompileContext); 6029 safeAddClass($compileNodes, 'ng-scope'); 6030 return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){ 6031 assertArg(scope, 'scope'); 6032 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 6033 // and sometimes changes the structure of the DOM. 6034 var $linkNode = cloneConnectFn 6035 ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! 6036 : $compileNodes; 6037 6038 forEach(transcludeControllers, function(instance, name) { 6039 $linkNode.data('$' + name + 'Controller', instance); 6040 }); 6041 6042 // Attach scope only to non-text nodes. 6043 for(var i = 0, ii = $linkNode.length; i<ii; i++) { 6044 var node = $linkNode[i], 6045 nodeType = node.nodeType; 6046 if (nodeType === 1 /* element */ || nodeType === 9 /* document */) { 6047 $linkNode.eq(i).data('$scope', scope); 6048 } 6049 } 6050 6051 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 6052 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 6053 return $linkNode; 6054 }; 6055 } 6056 6057 function safeAddClass($element, className) {
vendor: 4,887 bytes, lines 6058-6168
6058 try { 6059 $element.addClass(className); 6060 } catch(e) { 6061 // ignore, since it means that we are trying to set class on 6062 // SVG element, where class name is read-only. 6063 } 6064 } 6065 6066 /** 6067 * Compile function matches each node in nodeList against the directives. Once all directives 6068 * for a particular node are collected their compile functions are executed. The compile 6069 * functions return values - the linking functions - are combined into a composite linking 6070 * function, which is the a linking function for the node. 6071 * 6072 * @param {NodeList} nodeList an array of nodes or NodeList to compile 6073 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 6074 * scope argument is auto-generated to the new child of the transcluded parent scope. 6075 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 6076 * the rootElement must be set the jqLite collection of the compile root. This is 6077 * needed so that the jqLite collection items can be replaced with widgets. 6078 * @param {number=} maxPriority Max directive priority. 6079 * @returns {Function} A composite linking function of all of the matched directives or null. 6080 */ 6081 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 6082 previousCompileContext) { 6083 var linkFns = [], 6084 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound; 6085 6086 for (var i = 0; i < nodeList.length; i++) { 6087 attrs = new Attributes(); 6088 6089 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 6090 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 6091 ignoreDirective); 6092 6093 nodeLinkFn = (directives.length) 6094 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 6095 null, [], [], previousCompileContext) 6096 : null; 6097 6098 if (nodeLinkFn && nodeLinkFn.scope) { 6099 safeAddClass(attrs.$$element, 'ng-scope'); 6100 } 6101 6102 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 6103 !(childNodes = nodeList[i].childNodes) || 6104 !childNodes.length) 6105 ? null 6106 : compileNodes(childNodes, 6107 nodeLinkFn ? ( 6108 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 6109 && nodeLinkFn.transclude) : transcludeFn); 6110 6111 linkFns.push(nodeLinkFn, childLinkFn); 6112 linkFnFound = linkFnFound || nodeLinkFn || childLinkFn; 6113 //use the previous context only for the first element in the virtual group 6114 previousCompileContext = null; 6115 } 6116 6117 // return a linking function if we have found anything, null otherwise 6118 return linkFnFound ? compositeLinkFn : null; 6119 6120 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 6121 var nodeLinkFn, childLinkFn, node, childScope, i, ii, n, childBoundTranscludeFn; 6122 6123 // copy nodeList so that linking doesn't break due to live list updates. 6124 var nodeListLength = nodeList.length, 6125 stableNodeList = new Array(nodeListLength); 6126 for (i = 0; i < nodeListLength; i++) { 6127 stableNodeList[i] = nodeList[i]; 6128 } 6129 6130 for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) { 6131 node = stableNodeList[n]; 6132 nodeLinkFn = linkFns[i++]; 6133 childLinkFn = linkFns[i++]; 6134 6135 if (nodeLinkFn) { 6136 if (nodeLinkFn.scope) { 6137 childScope = scope.$new(); 6138 jqLite.data(node, '$scope', childScope); 6139 } else { 6140 childScope = scope; 6141 } 6142 6143 if ( nodeLinkFn.transcludeOnThisElement ) { 6144 childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude, parentBoundTranscludeFn); 6145 6146 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 6147 childBoundTranscludeFn = parentBoundTranscludeFn; 6148 6149 } else if (!parentBoundTranscludeFn && transcludeFn) { 6150 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 6151 6152 } else { 6153 childBoundTranscludeFn = null; 6154 } 6155 6156 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); 6157 6158 } else if (childLinkFn) { 6159 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 6160 } 6161 } 6162 } 6163 } 6164 6165 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 6166 6167 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) { 6168 var scopeCreated = false;
6169 6170 if (!transcludedScope) { 6171 transcludedScope = scope.$new(); 6172 transcludedScope.$$transcluded = true; 6173 scopeCreated = true; 6174 } 6175 6176 var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn); 6177 if (scopeCreated) { 6178 clone.on('$destroy', function() { transcludedScope.$destroy(); }); 6179 } 6180 return clone; 6181 }; 6182 6183 return boundTranscludeFn; 6184 } 6185 6186 /** 6187 * Looks for directives on the given node and adds them to the directive collection which is 6188 * sorted. 6189 * 6190 * @param node Node to search. 6191 * @param directives An array to which the directives are added to. This array is sorted before 6192 * the function returns. 6193 * @param attrs The shared attrs object which is used to populate the normalized attributes. 6194 * @param {number=} maxPriority Max directive priority. 6195 */ 6196 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 6197 var nodeType = node.nodeType, 6198 attrsMap = attrs.$attr, 6199 match, 6200 nodeName, 6201 className; 6202 6203 switch(nodeType) { 6204 case 1: /* Element */ 6205 6206 nodeName = nodeName_(node).toLowerCase(); 6207 6208 // use the node name: <directive> 6209 addDirective(directives, 6210 directiveNormalize(nodeName), 'E', maxPriority, ignoreDirective); 6211 6212 // iterate over the attributes 6213 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 6214 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 6215 var attrStartName = false; 6216 var attrEndName = false; 6217 6218 attr = nAttrs[j]; 6219 if (!msie || msie >= 8 || attr.specified) { 6220 name = attr.name; 6221 value = trim(attr.value); 6222 6223 // support ngAttr attribute binding 6224 ngAttrName = directiveNormalize(name); 6225 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 6226 name = snake_case(ngAttrName.substr(6), '-'); 6227 } 6228 6229 var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); 6230 if (ngAttrName === directiveNName + 'Start') { 6231 attrStartName = name; 6232 attrEndName = name.substr(0, name.length - 5) + 'end'; 6233 name = name.substr(0, name.length - 6); 6234 } 6235 6236 nName = directiveNormalize(name.toLowerCase()); 6237 attrsMap[nName] = name; 6238 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 6239 attrs[nName] = value; 6240 if (getBooleanAttrName(node, nName)) { 6241 attrs[nName] = true; // presence means true 6242 } 6243 } 6244 addAttrInterpolateDirective(node, directives, value, nName); 6245 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 6246 attrEndName); 6247 } 6248 } 6249 6250 if (nodeName === 'input' && node.getAttribute('type') === 'hidden') { 6251 // Hidden input elements can have strange behaviour when navigating back to the page 6252 // This tells the browser not to try to cache and reinstate previous values 6253 node.setAttribute('autocomplete', 'off'); 6254 } 6255 6256 // use class as directive 6257 className = node.className; 6258 if (isString(className) && className !== '') { 6259 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 6260 nName = directiveNormalize(match[2]); 6261 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 6262 attrs[nName] = trim(match[3]); 6263 } 6264 className = className.substr(match.index + match[0].length); 6265 } 6266 } 6267 break; 6268 case 3: /* Text Node */ 6269 if (msie === 11) { 6270 // Workaround for #11781 6271 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === 3 /* Text Node */) { 6272 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; 6273 node.parentNode.removeChild(node.nextSibling); 6274 } 6275 } 6276 addTextInterpolateDirective(directives, node.nodeValue); 6277 break; 6278 case 8: /* Comment */ 6279 try { 6280 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 6281 if (match) { 6282 nName = directiveNormalize(match[1]); 6283 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 6284 attrs[nName] = trim(match[2]); 6285 } 6286 } 6287 } catch (e) { 6288 // turns out that under some circumstances IE9 throws errors when one attempts to read 6289 // comment's node value. 6290 // Just ignore it and continue. (Can't seem to reproduce in test case.) 6291 } 6292 break; 6293 } 6294 6295 directives.sort(byPriority); 6296 return directives; 6297 } 6298 6299 /**
6300 * Given a node with an directive-start it collects all of the siblings until it finds 6301 * directive-end. 6302 * @param node 6303 * @param attrStart 6304 * @param attrEnd 6305 * @returns {*} 6306 */ 6307 function groupScan(node, attrStart, attrEnd) { 6308 var nodes = []; 6309 var depth = 0; 6310 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 6311 var startNode = node; 6312 do { 6313 if (!node) { 6314 throw $compileMinErr('uterdir', 6315 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 6316 attrStart, attrEnd); 6317 } 6318 if (node.nodeType == 1 /** Element **/) { 6319 if (node.hasAttribute(attrStart)) depth++; 6320 if (node.hasAttribute(attrEnd)) depth--; 6321 } 6322 nodes.push(node); 6323 node = node.nextSibling; 6324 } while (depth > 0); 6325 } else { 6326 nodes.push(node); 6327 } 6328 6329 return jqLite(nodes); 6330 } 6331 6332 /** 6333 * Wrapper for linking function which converts normal linking function into a grouped 6334 * linking function. 6335 * @param linkFn 6336 * @param attrStart 6337 * @param attrEnd 6338 * @returns {Function} 6339 */ 6340 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 6341 return function(scope, element, attrs, controllers, transcludeFn) { 6342 element = groupScan(element[0], attrStart, attrEnd); 6343 return linkFn(scope, element, attrs, controllers, transcludeFn); 6344 }; 6345 } 6346 6347 /** 6348 * Once the directives have been collected, their compile functions are executed. This method 6349 * is responsible for inlining directive templates as well as terminating the application 6350 * of the directives if the terminal directive has been reached. 6351 * 6352 * @param {Array} directives Array of collected directives to execute their compile function. 6353 * this needs to be pre-sorted by priority order. 6354 * @param {Node} compileNode The raw DOM node to apply the compile functions to 6355 * @param {Object} templateAttrs The shared attribute function 6356 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 6357 * scope argument is auto-generated to the new 6358 * child of the transcluded parent scope. 6359 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 6360 * argument has the root jqLite array so that we can replace nodes 6361 * on it. 6362 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 6363 * compiling the transclusion. 6364 * @param {Array.<Function>} preLinkFns 6365 * @param {Array.<Function>} postLinkFns 6366 * @param {Object} previousCompileContext Context used for previous compilation of the current 6367 * node 6368 * @returns {Function} linkFn 6369 */ 6370 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 6371 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 6372 previousCompileContext) { 6373 previousCompileContext = previousCompileContext || {}; 6374 6375 var terminalPriority = -Number.MAX_VALUE, 6376 newScopeDirective, 6377 controllerDirectives = previousCompileContext.controllerDirectives, 6378 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 6379 templateDirective = previousCompileContext.templateDirective, 6380 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 6381 hasTranscludeDirective = false, 6382 hasTemplate = false, 6383 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 6384 $compileNode = templateAttrs.$$element = jqLite(compileNode), 6385 directive, 6386 directiveName, 6387 $template, 6388 replaceDirective = originalReplaceDirective, 6389 childTranscludeFn = transcludeFn, 6390 linkFn, 6391 directiveValue; 6392 6393 // executes all directives on the current element 6394 for(var i = 0, ii = directives.length; i < ii; i++) { 6395 directive = directives[i]; 6396 var attrStart = directive.$$start; 6397 var attrEnd = directive.$$end; 6398 6399 // collect multiblock sections 6400 if (attrStart) { 6401 $compileNode = groupScan(compileNode, attrStart, attrEnd); 6402 } 6403 $template = undefined; 6404 6405 if (terminalPriority > directive.priority) { 6406 break; // prevent further processing of directives 6407 } 6408 6409 if (directiveValue = directive.scope) { 6410 newScopeDirective = newScopeDirective || directive; 6411 6412 // skip the check for directives with async templates, we'll check the derived sync 6413 // directive when the template arrives 6414 if (!directive.templateUrl) { 6415 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 6416 $compileNode); 6417 if (isObject(directiveValue)) { 6418 newIsolateScopeDirective = directive; 6419 } 6420 } 6421 } 6422 6423 directiveName = directive.name; 6424 6425 if (!directive.templateUrl && directive.controller) { 6426 directiveValue = directive.controller; 6427 controllerDirectives = controllerDirectives || {}; 6428 assertNoDuplicate("'" + directiveName + "' controller", 6429 controllerDirectives[directiveName], directive, $compileNode); 6430 controllerDirectives[directiveName] = directive; 6431 } 6432 6433 if (directiveValue = directive.transclude) { 6434 hasTranscludeDirective = true; 6435 6436 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 6437 // This option should only be used by directives that know how to safely handle element transclusion, 6438 // where the transcluded nodes are added or replaced after linking. 6439 if (!directive.$$tlb) { 6440 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 6441 nonTlbTranscludeDirective = directive; 6442 } 6443 6444 if (directiveValue == 'element') { 6445 hasElementTranscludeDirective = true; 6446 terminalPriority = directive.priority; 6447 $template = $compileNode; 6448 $compileNode = templateAttrs.$$element = 6449 jqLite(document.createComment(' ' + directiveName + ': ' + 6450 templateAttrs[directiveName] + ' ')); 6451 compileNode = $compileNode[0]; 6452 replaceWith(jqCollection, sliceArgs($template), compileNode); 6453 6454 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 6455 replaceDirective && replaceDirective.name, { 6456 // Don't pass in: 6457 // - controllerDirectives - otherwise we'll create duplicates controllers 6458 // - newIsolateScopeDirective or templateDirective - comb
vendor: 4,603 bytes, lines 6458-6565
6458ining templates with 6459 // element transclusion doesn't make sense. 6460 // 6461 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 6462 // on the same element more than once. 6463 nonTlbTranscludeDirective: nonTlbTranscludeDirective 6464 }); 6465 } else { 6466 $template = jqLite(jqLiteClone(compileNode)).contents(); 6467 $compileNode.empty(); // clear contents 6468 childTranscludeFn = compile($template, transcludeFn); 6469 } 6470 } 6471 6472 if (directive.template) { 6473 hasTemplate = true; 6474 assertNoDuplicate('template', templateDirective, directive, $compileNode); 6475 templateDirective = directive; 6476 6477 directiveValue = (isFunction(directive.template)) 6478 ? directive.template($compileNode, templateAttrs) 6479 : directive.template; 6480 6481 directiveValue = denormalizeTemplate(directiveValue); 6482 6483 if (directive.replace) { 6484 replaceDirective = directive; 6485 if (jqLiteIsTextNode(directiveValue)) { 6486 $template = []; 6487 } else { 6488 $template = jqLite(trim(directiveValue)); 6489 } 6490 compileNode = $template[0]; 6491 6492 if ($template.length != 1 || compileNode.nodeType !== 1) { 6493 throw $compileMinErr('tplrt', 6494 "Template for directive '{0}' must have exactly one root element. {1}", 6495 directiveName, ''); 6496 } 6497 6498 replaceWith(jqCollection, $compileNode, compileNode); 6499 6500 var newTemplateAttrs = {$attr: {}}; 6501 6502 // combine directives from the original node and from the template: 6503 // - take the array of directives for this element 6504 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 6505 // - collect directives from the template and sort them by priority 6506 // - combine directives as: processed + template + unprocessed 6507 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 6508 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 6509 6510 if (newIsolateScopeDirective) { 6511 markDirectivesAsIsolate(templateDirectives); 6512 } 6513 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 6514 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 6515 6516 ii = directives.length; 6517 } else { 6518 $compileNode.html(directiveValue); 6519 } 6520 } 6521 6522 if (directive.templateUrl) { 6523 hasTemplate = true; 6524 assertNoDuplicate('template', templateDirective, directive, $compileNode); 6525 templateDirective = directive; 6526 6527 if (directive.replace) { 6528 replaceDirective = directive; 6529 } 6530 6531 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 6532 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 6533 controllerDirectives: controllerDirectives, 6534 newIsolateScopeDirective: newIsolateScopeDirective, 6535 templateDirective: templateDirective, 6536 nonTlbTranscludeDirective: nonTlbTranscludeDirective 6537 }); 6538 ii = directives.length; 6539 } else if (directive.compile) { 6540 try { 6541 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 6542 if (isFunction(linkFn)) { 6543 addLinkFns(null, linkFn, attrStart, attrEnd); 6544 } else if (linkFn) { 6545 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 6546 } 6547 } catch (e) { 6548 $exceptionHandler(e, startingTag($compileNode)); 6549 } 6550 } 6551 6552 if (directive.terminal) { 6553 nodeLinkFn.terminal = true; 6554 terminalPriority = Math.max(terminalPriority, directive.priority); 6555 } 6556 6557 } 6558 6559 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 6560 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 6561 nodeLinkFn.templateOnThisElement = hasTemplate; 6562 nodeLinkFn.transclude = childTranscludeFn; 6563 6564 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 6565
6566 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 6567 return nodeLinkFn; 6568 6569 //////////////////// 6570 6571 function addLinkFns(pre, post, attrStart, attrEnd) { 6572 if (pre) { 6573 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 6574 pre.require = directive.require; 6575 pre.directiveName = directiveName; 6576 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6577 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 6578 } 6579 preLinkFns.push(pre); 6580 } 6581 if (post) { 6582 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 6583 post.require = directive.require; 6584 post.directiveName = directiveName; 6585 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6586 post = cloneAndAnnotateFn(post, {isolateScope: true}); 6587 } 6588 postLinkFns.push(post); 6589 } 6590 } 6591 6592 6593 function getControllers(directiveName, require, $element, elementControllers) { 6594 var value, retrievalMethod = 'data', optional = false;
6595 if (isString(require)) { 6596 while((value = require.charAt(0)) == '^' || value == '?') { 6597 require = require.substr(1); 6598 if (value == '^') { 6599 retrievalMethod = 'inheritedData'; 6600 } 6601 optional = optional || value == '?'; 6602 } 6603 value = null; 6604 6605 if (elementControllers && retrievalMethod === 'data') { 6606 value = elementControllers[require]; 6607 } 6608 value = value || $element[retrievalMethod]('$' + require + 'Controller'); 6609 6610 if (!value && !optional) { 6611 throw $compileMinErr('ctreq', 6612 "Controller '{0}', required by directive '{1}', can't be found!", 6613 require, directiveName); 6614 } 6615 return value; 6616 } else if (isArray(require)) { 6617 value = []; 6618 forEach(require, function(require) { 6619 value.push(getControllers(directiveName, require, $element, elementControllers)); 6620 }); 6621 } 6622 return value; 6623 } 6624 6625 6626 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 6627 var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; 6628 6629 attrs = (compileNode === linkNode) 6630 ? templateAttrs 6631 : shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); 6632 $element = attrs.$$element; 6633 6634 if (newIsolateScopeDirective) { 6635 var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; 6636 6637 isolateScope = scope.$new(true); 6638 6639 if (templateDirective && (templateDirective === newIsolateScopeDirective || 6640 templateDirective === newIsolateScopeDirective.$$originalDirective)) { 6641 $element.data('$isolateScope', isolateScope); 6642 } else { 6643 $element.data('$isolateScopeNoTemplate', isolateScope); 6644 } 6645 6646 6647 6648 safeAddClass($element, 'ng-isolate-scope'); 6649 6650 forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { 6651 var match = definition.match(LOCAL_REGEXP) || [], 6652 attrName = match[3] || scopeName, 6653 optional = (match[2] == '?'), 6654 mode = match[1], // @, =, or & 6655 lastValue, 6656 parentGet, parentSet, compare; 6657 6658 isolateScope.$$isolateBindings[scopeName] = mode + attrName; 6659 6660 switch (mode) { 6661 6662 case '@': 6663 attrs.$observe(attrName, function(value) { 6664 isolateScope[scopeName] = value; 6665 }); 6666 attrs.$$observers[attrName].$$scope = scope; 6667 if( attrs[attrName] ) { 6668 // If the attribute has been provided then we trigger an interpolation to ensure 6669 // the value is there for use in the link fn 6670 isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); 6671 } 6672 break; 6673 6674 case '=': 6675 if (optional && !attrs[attrName]) { 6676 return; 6677 } 6678 parentGet = $parse(attrs[attrName]); 6679 if (parentGet.literal) { 6680 compare = equals; 6681 } else { 6682 compare = function(a,b) { return a === b || (a !== a && b !== b); }; 6683 } 6684 parentSet = parentGet.assign || function() { 6685 // reset the change, or we will throw this exception on every $digest 6686 lastValue = isolateScope[scopeName] = parentGet(scope); 6687 throw $compileMinErr('nonassign', 6688 "Expression '{0}' used with directive '{1}' is non-assignable!", 6689 attrs[attrName], newIsolateScopeDirective.name); 6690 }; 6691 lastValue = isolateScope[scopeName] = parentGet(scope); 6692 isolateScope.$watch(function parentValueWatch() { 6693 var parentValue = parentGet(scope); 6694 if (!compare(parentValue, isolateScope[scopeName])) { 6695 // we are out of sync and need to copy 6696 if (!compare(parentValue, lastValue)) { 6697 // parent changed and it has precedence 6698 isolateScope[scopeName] = parentValue; 6699 } else { 6700 // if the parent can be assigned then do so 6701 parentSet(scope, parentValue = isolateScope[scopeName]); 6702 } 6703 } 6704 return lastValue = parentValue; 6705 }, null, parentGet.literal); 6706 break; 6707 6708 case '&': 6709 parentGet = $parse(attrs[attrName]); 6710 isolateScope[scopeName] = function(locals) { 6711 return parentGet(scope, locals); 6712 }; 6713 break; 6714 6715 default: 6716 throw $compileMinErr('iscp', 6717 "Invalid isolate scope definition for directive '{0}'." + 6718 " Definition: {... {1}: '{2}' ...}", 6719 newIsolateScopeDirective.name, scopeName, definition); 6720 } 6721 }); 6722 } 6723 transcludeFn = boundTranscludeFn && controllersBoundTransclude; 6724 if (controllerDirectives) {
6725 forEach(controllerDirectives, function(directive) { 6726 var locals = { 6727 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 6728 $element: $element, 6729 $attrs: attrs, 6730 $transclude: transcludeFn 6731 }, controllerInstance; 6732 6733 controller = directive.controller; 6734 if (controller == '@') { 6735 controller = attrs[directive.name]; 6736 } 6737 6738 controllerInstance = $controller(controller, locals); 6739 // For directives with element transclusion the element is a comment, 6740 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 6741 // clean up (http://bugs.jquery.com/ticket/8335). 6742 // Instead, we save the controllers for the element in a local hash and attach to .data 6743 // later, once we have the actual element. 6744 elementControllers[directive.name] = controllerInstance; 6745 if (!hasElementTranscludeDirective) { 6746 $element.data('$' + directive.name + 'Controller', controllerInstance); 6747 } 6748 6749 if (directive.controllerAs) { 6750 locals.$scope[directive.controllerAs] = controllerInstance; 6751 } 6752 }); 6753 } 6754 6755 // PRELINKING 6756 for(i = 0, ii = preLinkFns.length; i < ii; i++) { 6757 try { 6758 linkFn = preLinkFns[i]; 6759 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6760 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); 6761 } catch (e) { 6762 $exceptionHandler(e, startingTag($element)); 6763 } 6764 } 6765 6766 // RECURSION 6767 // We only pass the isolate scope, if the isolate directive has a template, 6768 // otherwise the child elements do not belong to the isolate directive. 6769 var scopeToChild = scope; 6770 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 6771 scopeToChild = isolateScope; 6772 } 6773 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 6774 6775 // POSTLINKING 6776 for(i = postLinkFns.length - 1; i >= 0; i--) { 6777 try { 6778 linkFn = postLinkFns[i]; 6779 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6780 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); 6781 } catch (e) { 6782 $exceptionHandler(e, startingTag($element)); 6783 } 6784 } 6785 6786 // This is the function that is injected as `$transclude`. 6787 function controllersBoundTransclude(scope, cloneAttachFn) { 6788 var transcludeControllers; 6789 6790 // no scope passed 6791 if (arguments.length < 2) { 6792 cloneAttachFn = scope; 6793 scope = undefined; 6794 } 6795 6796 if (hasElementTranscludeDirective) { 6797 transcludeControllers = elementControllers; 6798 } 6799 6800 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); 6801 } 6802 } 6803 } 6804 6805 function markDirectivesAsIsolate(directives) { 6806 // mark all directives as needing isolate scope. 6807 for (var j = 0, jj = directives.length; j < jj; j++) { 6808 directives[j] = inherit(directives[j], {$$isolateScope: true}); 6809 } 6810 } 6811 6812 /** 6813 * looks up the directive and decorates it with exception handling and proper parameters. We 6814 * call this the boundDirective. 6815 * 6816 * @param {string} name name of the directive to look up. 6817 * @param {string} location The directive must be found in specific format. 6818 * String containing any of theses characters: 6819 * 6820 * * `E`: element name 6821 * * `A': attribute 6822 * * `C`: class 6823 * * `M`: comment 6824 * @returns {boolean} true if directive was added. 6825 */ 6826 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 6827 endAttrName) { 6828 if (name === ignoreDirective) return null; 6829 var match = null; 6830 if (hasDirectives.hasOwnProperty(name)) { 6831 for(var directive, directives = $injector.get(name + Suffix), 6832 i = 0, ii = directives.length; i<ii; i++) { 6833 try { 6834 directive = directives[i]; 6835 if ( (maxPriority === undefined || maxPriority > directive.priority) && 6836 directive.restrict.indexOf(location) != -1) { 6837 if (startAttrName) { 6838 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 6839 } 6840 tDirectives.push(directive); 6841 match = directive; 6842 } 6843 } catch(e) { $exceptionHandler(e); } 6844 } 6845 } 6846 return match; 6847 } 6848 6849 6850 /** 6851 * When the element is replaced with HTML template then the new attributes 6852 * on the template need to be merged with the existing attributes in the DOM. 6853 * The desired effect is to have both of the attributes present. 6854 * 6855 * @param {object} dst destination attributes (original DOM) 6856 * @param {object} src source attributes (from the directive template) 6857 */ 6858 function mergeTemplateAttributes(dst, src) { 6859 var srcAttr = src.$attr, 6860 dstAttr = dst.$attr, 6861 $element = dst.$$element; 6862 6863 // reapply the old attributes to the new element
6864 forEach(dst, function(value, key) { 6865 if (key.charAt(0) != '$') { 6866 if (src[key] && src[key] !== value) { 6867 value += (key === 'style' ? ';' : ' ') + src[key]; 6868 } 6869 dst.$set(key, value, true, srcAttr[key]); 6870 } 6871 }); 6872 6873 // copy the new attributes on the old attrs object 6874 forEach(src, function(value, key) { 6875 if (key == 'class') { 6876 safeAddClass($element, value); 6877 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 6878 } else if (key == 'style') { 6879 $element.attr('style', $element.attr('style') + ';' + value); 6880 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 6881 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 6882 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 6883 // have an attribute like "has-own-property" or "data-has-own-property", etc. 6884 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 6885 dst[key] = value; 6886 dstAttr[key] = srcAttr[key]; 6887 } 6888 }); 6889 } 6890 6891 6892 function compileTemplateUrl(directives, $compileNode, tAttrs, 6893 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 6894 var linkQueue = [], 6895 afterTemplateNodeLinkFn, 6896 afterTemplateChildLinkFn, 6897 beforeTemplateCompileNode = $compileNode[0], 6898 origAsyncDirective = directives.shift(), 6899 // The fact that we have to copy and patch the directive seems wrong! 6900 derivedSyncDirective = extend({}, origAsyncDirective, { 6901 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 6902 }), 6903 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 6904 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 6905 : origAsyncDirective.templateUrl; 6906 6907 $compileNode.empty(); 6908 6909 $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). 6910 success(function(content) { 6911 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 6912 6913 content = denormalizeTemplate(content); 6914 6915 if (origAsyncDirective.replace) { 6916 if (jqLiteIsTextNode(content)) { 6917 $template = []; 6918 } else { 6919 $template = jqLite(trim(content)); 6920 } 6921 compileNode = $template[0]; 6922 6923 if ($template.length != 1 || compileNode.nodeType !== 1) { 6924 throw $compileMinErr('tplrt', 6925 "Template for directive '{0}' must have exactly one root element. {1}", 6926 origAsyncDirective.name, templateUrl); 6927 } 6928 6929 tempTemplateAttrs = {$attr: {}}; 6930 replaceWith($rootElement, $compileNode, compileNode); 6931 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 6932 6933 if (isObject(origAsyncDirective.scope)) { 6934 markDirectivesAsIsolate(templateDirectives); 6935 } 6936 directives = templateDirectives.concat(directives); 6937 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 6938 } else { 6939 compileNode = beforeTemplateCompileNode; 6940 $compileNode.html(content); 6941 } 6942 6943 directives.unshift(derivedSyncDirective); 6944 6945 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 6946 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 6947 previousCompileContext); 6948 forEach($rootElement, function(node, i) { 6949 if (node == compileNode) { 6950 $rootElement[i] = $compileNode[0]; 6951 } 6952 }); 6953 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 6954 6955 while(linkQueue.length) { 6956 var scope = linkQueue.shift(), 6957 beforeTemplateLinkNode = linkQueue.shift(), 6958 linkRootElement = linkQueue.shift(), 6959 boundTranscludeFn = linkQueue.shift(), 6960 linkNode = $compileNode[0]; 6961 6962 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) {
6963 var oldClasses = beforeTemplateLinkNode.className; 6964 6965 if (!(previousCompileContext.hasElementTranscludeDirective && 6966 origAsyncDirective.replace)) { 6967 // it was cloned therefore we have to clone as well. 6968 linkNode = jqLiteClone(compileNode); 6969 } 6970 6971 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 6972 6973 // Copy in CSS classes from original node 6974 safeAddClass(jqLite(linkNode), oldClasses); 6975 } 6976 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 6977 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 6978 } else { 6979 childBoundTranscludeFn = boundTranscludeFn; 6980 } 6981 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 6982 childBoundTranscludeFn); 6983 } 6984 linkQueue = null; 6985 }). 6986 error(function(response, code, headers, config) { 6987 throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); 6988 }); 6989 6990 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 6991 var childBoundTranscludeFn = boundTranscludeFn; 6992 if (linkQueue) { 6993 linkQueue.push(scope); 6994 linkQueue.push(node); 6995 linkQueue.push(rootElement); 6996 linkQueue.push(childBoundTranscludeFn); 6997 } else { 6998 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 6999 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 7000 } 7001 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); 7002 } 7003 }; 7004 } 7005 7006 7007 /** 7008 * Sorting function for bound directives. 7009 */ 7010 function byPriority(a, b) { 7011 var diff = b.priority - a.priority; 7012 if (diff !== 0) return diff; 7013 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 7014 return a.index - b.index; 7015 } 7016 7017 7018 function assertNoDuplicate(what, previousDirective, directive, element) { 7019 if (previousDirective) { 7020 throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', 7021 previousDirective.name, directive.name, what, startingTag(element)); 7022 } 7023 } 7024 7025 7026 function addTextInterpolateDirective(directives, text) { 7027 var interpolateFn = $interpolate(text, true); 7028 if (interpolateFn) { 7029 directives.push({ 7030 priority: 0, 7031 compile: function textInterpolateCompileFn(templateNode) { 7032 // when transcluding a template that has bindings in the root 7033 // then we don't have a parent and should do this in the linkFn 7034 var parent = templateNode.parent(), hasCompileParent = parent.length; 7035 if (hasCompileParent) safeAddClass(templateNode.parent(), 'ng-binding'); 7036 7037 return function textInterpolateLinkFn(scope, node) { 7038 var parent = node.parent(), 7039 bindings = parent.data('$binding') || []; 7040 bindings.push(interpolateFn); 7041 parent.data('$binding', bindings); 7042 if (!hasCompileParent) safeAddClass(parent, 'ng-binding'); 7043 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 7044 node[0].nodeValue = value; 7045 }); 7046 }; 7047 } 7048 }); 7049 } 7050 } 7051 7052 7053 function getTrustedContext(node, attrNormalizedName) { 7054 if (attrNormalizedName == "srcdoc") { 7055 return $sce.HTML; 7056 } 7057 var tag = nodeName_(node); 7058 // maction[xlink:href] can source SVG. It's not limited to <maction>. 7059 if (attrNormalizedName == "xlinkHref" || 7060 (tag == "FORM" && attrNormalizedName == "action") || 7061 // links can be stylesheets or imports, which can run script in the current origin 7062 (tag == "LINK" && attrNormalizedName == "href") || 7063 (tag != "IMG" && (attrNormalizedName == "src" || 7064 attrNormalizedName == "ngSrc"))) { 7065 return $sce.RESOURCE_URL; 7066 } 7067 } 7068 7069 7070 function addAttrInterpolateDirective(node, directives, value, name) { 7071 var interpolateFn = $interpolate(value, true); 7072 7073 // no interpolation found -> ignore 7074 if (!interpolateFn) return; 7075 7076 7077 if (name === "multiple" && nodeName_(node) === "SELECT") { 7078 throw $compileMinErr("selmulti", 7079 "Binding to the 'multiple' attribute is not supported. Element: {0}", 7080 startingTag(node)); 7081 } 7082 7083 directives.push({ 7084 priority: 100, 7085 compile: function() { 7086 return { 7087 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 7088 var $$observers = (attr.$$observers || (attr.$$observers = {})); 7089 7090 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 7091 throw $compileMinErr('nodomevents', 7092 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 7093 "ng- versions (such as ng-click instead of onclick) instead."); 7094 } 7095 7096 // we need to interpolate again, in case the attribute value has been updated 7097 // (e.g. by another directive's compile function) 7098 interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); 7099 7100 // if attribute was updated so that there is no interpolation going on we don't want to 7101 // register any observers 7102 if (!interpolateFn) return; 7103 7104 // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the 7105 // actual attr value 7106 attr[name] = interpolateFn(scope); 7107 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 7108 (attr.$$observers && attr.$$observers[name].$$scope || scope). 7109 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 7110 //special case for class attribute addition + removal 7111 //so that class changes can tap into the animation 7112 //hooks provided by the $animate service. Be sure to 7113 //skip animations when the first digest occurs (when 7114 //both the new and the old values are the same) since 7115 //the CSS classes are the non-interpolated values 7116 if(name === 'class' && newValue != oldValue) { 7117 attr.$updateClass(newValue, oldValue); 7118 } else { 7119 attr.$set(name, newValue); 7120 } 7121 }); 7122 } 7123 }; 7124 } 7125 }); 7126 } 7127 7128 7129 /** 7130 * This is a special jqLite.replaceWith, which can replace items which 7131 * have no parents, provided that the containing jqLite collection is provided. 7132 * 7133 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 7134 * in the root of the tree. 7135 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 7136 * the shell, but replace its DOM node reference. 7137 * @param {Node} newNode The new DOM node. 7138 */ 7139 function replaceWith($rootElement, elementsToRemove, newNode) { 7140 var firstElementToRemove = elementsToRemove[0], 7141 removeCount = elementsToRemove.length, 7142 parent = firstElementToRemove.parentNode, 7143 i, ii; 7144 7145 if ($rootElement) { 7146 for(i = 0, ii = $rootElement.length; i < ii; i++) { 7147 if ($rootElement[i] == firstElementToRemove) { 7148 $rootElement[i++] = newNode; 7149 for (var j = i, j2 = j + removeCount - 1, 7150 jj = $rootElement.length; 7151 j < jj; j++, j2++) { 7152 if (j2 < jj) { 7153 $rootElement[j] = $rootElement[j2]; 7154 } else { 7155 delete $rootElement[j]; 7156 } 7157 } 7158 $rootElement.length -= removeCount - 1; 7159 break; 7160 } 7161 } 7162 } 7163 7164 if (parent) { 7165 parent.replaceChild(newNode, firstElementToRemove); 7166 } 7167 var fragment = document.createDocumentFragment(); 7168 fragment.appendChild(firstElementToRemove); 7169 newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; 7170 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 7171 var element = elementsToRemove[k]; 7172 jqLite(element).remove(); // must do this way to clean up expando 7173 fragment.appendChild(element); 7174 delete elementsToRemove[k]; 7175 } 7176 7177 elementsToRemove[0] = newNode; 7178 elementsToRemove.length = 1; 7179 } 7180 7181 7182 function cloneAndAnnotateFn(fn, annotation) { 7183 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 7184 } 7185 }]; 7186} 7187 7188var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; 7189/** 7190 * Converts all accepted directives format into proper directive name. 7191 * @param name Name to normalize 7192 */ 7193function directiveNormalize(name) { 7194 return camelCase(name.replace(PREFIX_REGEXP, '')); 7195} 7196 7197/** 7198 * @ngdoc type 7199 * @name $compile.directive.Attributes 7200 * 7201 * @description 7202 * A shared object between directive compile / linking functions which contains
7202normalized DOM 7203 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 7204 * needed since all of these are treated as equivalent in Angular: 7205 * 7206 * ``` 7207 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 7208 * ``` 7209 */ 7210 7211/** 7212 * @ngdoc property 7213 * @name $compile.directive.Attributes#$attr 7214 * 7215 * @description 7216 * A map of DOM element attribute names to the normalized name. This is 7217 * needed to do reverse lookup from normalized name back to actual name. 7218 */ 7219 7220 7221/** 7222 * @ngdoc method 7223 * @name $compile.directive.Attributes#$set 7224 * @kind function 7225 * 7226 * @description 7227 * Set DOM element attribute value. 7228 * 7229 * 7230 * @param {string} name Normalized element attribute name of the property to modify. The name is 7231 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 7232 * property to the original name. 7233 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 7234 */ 7235 7236 7237 7238/** 7239 * Closure compiler type information 7240 */ 7241 7242function nodesetLinkingFn( 7243 /* angular.Scope */ scope, 7244 /* NodeList */ nodeList, 7245 /* Element */ rootElement, 7246 /* function(Function) */ boundTranscludeFn 7247){} 7248 7249function directiveLinkingFn( 7250 /* nodesetLinkingFn */ nodesetLinkingFn, 7251 /* angular.Scope */ scope, 7252 /* Node */ node, 7253 /* Element */ rootElement, 7254 /* function(Function) */ boundTranscludeFn 7255){} 7256 7257function tokenDifference(str1, str2) { 7258 var values = '', 7259 tokens1 = str1.split(/\s+/), 7260 tokens2 = str2.split(/\s+/); 7261 7262 outer: 7263 for(var i = 0; i < tokens1.length; i++) { 7264 var token = tokens1[i]; 7265 for(var j = 0; j < tokens2.length; j++) { 7266 if(token == tokens2[j]) continue outer; 7267 } 7268 values += (values.length > 0 ? ' ' : '') + token; 7269 } 7270 return values; 7271} 7272 7273/** 7274 * @ngdoc provider 7275 * @name $controllerProvider 7276 * @description 7277 * The {@link ng.$controller $controller service} is used by Angular to create new 7278 * controllers. 7279 * 7280 * This provider allows controller registration via the 7281 * {@link ng.$controllerProvider#register register} method. 7282 */ 7283function $ControllerProvider() { 7284 var controllers = {}, 7285 CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 7286 7287 7288 /** 7289 * @ngdoc method 7290 * @name $controllerProvider#register 7291 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 7292 * the names and the values are the constructors. 7293 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 7294 * annotations in the array notation). 7295 */ 7296 this.register = function(name, constructor) { 7297 assertNotHasOwnProperty(name, 'controller'); 7298 if (isObject(name)) { 7299 extend(controllers, name); 7300 } else { 7301 controllers[name] = constructor; 7302 } 7303 }; 7304 7305 7306 this.$get = ['$injector', '$window', function($injector, $window) { 7307 7308 /** 7309 * @ngdoc service 7310 * @name $controller 7311 * @requires $injector 7312 * 7313 * @param {Function|string} constructor If called with a function then it's considered to be the 7314 * controller constructor function. Otherwise it's considered to be a string which is used 7315 * to retrieve the controller constructor using the following steps: 7316 * 7317 * * check if a controller with given name is registered via `$controllerProvider` 7318 * * check if evaluating the string on the current scope returns a constructor 7319 * * check `window[constructor]` on the global `window` object 7320 * 7321 * @param {Object} locals Injection locals for Controller. 7322 * @return {Object} Instance of given controller. 7323 * 7324 * @description 7325 * `$controller` service is responsible for instantiating controllers. 7326 * 7327 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 7328 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 7329 */ 7330 return function(expression, locals) { 7331 var instance, match, constructor, identifier; 7332 7333 if(isString(expression)) { 7334 match = expression.match(CNTRL_REG), 7335 constructor = match[1], 7336 identifier = match[3]; 7337 expression = controllers.hasOwnProperty(constructor) 7338 ? controllers[constructor] 7339 : getter(locals.$scope, constructor, true) || getter($window, constructor, true); 7340 7341 assertArgFn(expression, constructor, true); 7342 } 7343 7344 instance = $injector.instantiate(expression, locals); 7345 7346 if (identifier) { 7347 if (!(locals && typeof locals.$scope === 'object')) { 7348 throw minErr('$controller')('noscp', 7349 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 7350 constructor || expression.name, identifier); 7351 } 7352 7353 locals.$scope[identifier] = instance; 7354 } 7355 7356 return instance; 7357 }; 7358 }]; 7359} 7360 7361/** 7362 * @ngdoc service 7363 * @name $document 7364 * @requires $window 7365 * 7366 * @description 7367 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 7368 * 7369 * @example 7370 <example module="documentExample"> 7371 <file name="index.html">
7372 <div ng-controller="ExampleController"> 7373 <p>$document title: <b ng-bind="title"></b></p> 7374 <p>window.document title: <b ng-bind="windowTitle"></b></p> 7375 </div> 7376 </file> 7377 <file name="script.js"> 7378 angular.module('documentExample', []) 7379 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 7380 $scope.title = $document[0].title; 7381 $scope.windowTitle = angular.element(window.document)[0].title; 7382 }]); 7383 </file> 7384 </example> 7385 */ 7386function $DocumentProvider(){ 7387 this.$get = ['$window', function(window){ 7388 return jqLite(window.document); 7389 }]; 7390} 7391 7392/** 7393 * @ngdoc service 7394 * @name $exceptionHandler 7395 * @requires ng.$log 7396 * 7397 * @description 7398 * Any uncaught exception in angular expressions is delegated to this service. 7399 * The default implementation simply delegates to `$log.error` which logs it into 7400 * the browser console. 7401 * 7402 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 7403 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 7404 * 7405 * ## Example: 7406 * 7407 * ```js 7408 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { 7409 * return function (exception, cause) { 7410 * exception.message += ' (caused by "' + cause + '")'; 7411 * throw exception; 7412 * }; 7413 * }); 7414 * ``` 7415 * 7416 * This example will override the normal action of `$exceptionHandler`, to make angular 7417 * exceptions fail hard when they happen, instead of just logging to the console. 7418 * 7419 * @param {Error} exception Exception associated with the error. 7420 * @param {string=} cause optional information about the context in which 7421 * the error was thrown. 7422 * 7423 */ 7424function $ExceptionHandlerProvider() { 7425 this.$get = ['$log', function($log) { 7426 return function(exception, cause) { 7427 $log.error.apply($log, arguments); 7428 }; 7429 }]; 7430} 7431 7432/** 7433 * Parse headers into key value object 7434 * 7435 * @param {string} headers Raw headers as a string 7436 * @returns {Object} Parsed headers as key value object 7437 */ 7438function parseHeaders(headers) { 7439 var parsed = {}, key, val, i; 7440 7441 if (!headers) return parsed; 7442 7443 forEach(headers.split('\n'), function(line) { 7444 i = line.indexOf(':');
7445 key = lowercase(trim(line.substr(0, i))); 7446 val = trim(line.substr(i + 1)); 7447 7448 if (key) { 7449 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 7450 } 7451 }); 7452 7453 return parsed; 7454} 7455 7456 7457/** 7458 * Returns a function that provides access to parsed headers. 7459 * 7460 * Headers are lazy parsed when first requested. 7461 * @see parseHeaders 7462 * 7463 * @param {(string|Object)} headers Headers to provide access to. 7464 * @returns {function(string=)} Returns a getter function which if called with: 7465 * 7466 * - if called with single an argument returns a single header value or null 7467 * - if called with no arguments returns an object containing all headers. 7468 */ 7469function headersGetter(headers) { 7470 var headersObj = isObject(headers) ? headers : undefined; 7471 7472 return function(name) { 7473 if (!headersObj) headersObj = parseHeaders(headers); 7474 7475 if (name) { 7476 return headersObj[lowercase(name)] || null; 7477 } 7478 7479 return headersObj; 7480 }; 7481} 7482 7483 7484/** 7485 * Chain all given functions 7486 * 7487 * This function is used for both request and response transforming 7488 * 7489 * @param {*} data Data to transform. 7490 * @param {function(string=)} headers Http headers getter fn. 7491 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 7492 * @returns {*} Transformed data. 7493 */ 7494function transformData(data, headers, fns) { 7495 if (isFunction(fns)) 7496 return fns(data, headers); 7497 7498 forEach(fns, function(fn) { 7499 data = fn(data, headers); 7500 }); 7501 7502 return data; 7503} 7504 7505 7506function isSuccess(status) { 7507 return 200 <= status && status < 300; 7508} 7509 7510 7511/** 7512 * @ngdoc provider 7513 * @name $httpProvider 7514 * @description 7515 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 7516 * */ 7517function $HttpProvider() { 7518 var JSON_START = /^\s*(\[|\{[^\{])/, 7519 JSON_END = /[\}\]]\s*$/, 7520 PROTECTION_PREFIX = /^\)\]\}',?\n/, 7521 CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; 7522 7523 /** 7524 * @ngdoc property 7525 * @name $httpProvider#defaults 7526 * @description 7527 * 7528 * Object containing default values for all {@link ng.$http $http} requests. 7529 * 7530 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 7531 * Defaults value is `'XSRF-TOKEN'`. 7532 * 7533 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 7534 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 7535 * 7536 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 7537 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 7538 * setting default headers. 7539 * - **`defaults.headers.common`** 7540 * - **`defaults.headers.post`** 7541 * - **`defaults.headers.put`** 7542 * - **`defaults.headers.patch`** 7543 **/ 7544 var defaults = this.defaults = { 7545 // transform incoming response data 7546 transformResponse: [function(data) { 7547 if (isString(data)) { 7548 // strip json vulnerability protection prefix 7549 data = data.replace(PROTECTION_PREFIX, ''); 7550 if (JSON_START.test(data) && JSON_END.test(data)) 7551 data = fromJson(data); 7552 } 7553 return data; 7554 }], 7555 7556 // transform outgoing request data 7557 transformRequest: [function(d) { 7558 return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; 7559 }], 7560 7561 // default headers 7562 headers: { 7563 common: { 7564 'Accept': 'application/json, text/plain, */*' 7565 }, 7566 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 7567 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 7568 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 7569 }, 7570 7571 xsrfCookieName: 'XSRF-TOKEN', 7572 xsrfHeaderName: 'X-XSRF-TOKEN' 7573 }; 7574 7575 /** 7576 * @ngdoc property 7577 * @name $httpProvider#interceptors 7578 * @description 7579 * 7580 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 7581 * pre-processing of request or postprocessing of responses. 7582 * 7583 * These service factories are ordered by request, i.e. they are applied in the same order as the 7584 * array, on request, but reverse order, on response. 7585 * 7586 * {@link ng.$http#interceptors Interceptors detailed info} 7587 **/ 7588 var interceptorFactories = this.interceptors = []; 7589 7590 /** 7591 * For historical reasons, response interceptors are ordered by the order in which 7592 * they are applied to the response. (This is the opposite of interceptorFactories) 7593 */ 7594 var responseInterceptorFactories = this.responseInterceptors = []; 7595 7596 this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', 7597 function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { 7598 7599 var defaultCache = $cacheFactory('$http'); 7600 7601 /** 7602 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 7603 * The reversal is needed so that we can build up the interception chain around the 7604 * server request. 7605 */ 7606 var reversedInterceptors = []; 7607
7608 forEach(interceptorFactories, function(interceptorFactory) { 7609 reversedInterceptors.unshift(isString(interceptorFactory) 7610 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 7611 }); 7612 7613 forEach(responseInterceptorFactories, function(interceptorFactory, index) { 7614 var responseFn = isString(interceptorFactory) 7615 ? $injector.get(interceptorFactory) 7616 : $injector.invoke(interceptorFactory); 7617 7618 /** 7619 * Response interceptors go before "around" interceptors (no real reason, just 7620 * had to pick one.) But they are already reversed, so we can't use unshift, hence 7621 * the splice. 7622 */ 7623 reversedInterceptors.splice(index, 0, { 7624 response: function(response) { 7625 return responseFn($q.when(response)); 7626 }, 7627 responseError: function(response) { 7628 return responseFn($q.reject(response)); 7629 } 7630 }); 7631 }); 7632 7633 7634 /** 7635 * @ngdoc service 7636 * @kind function 7637 * @name $http 7638 * @requires ng.$httpBackend 7639 * @requires $cacheFactory 7640 * @requires $rootScope 7641 * @requires $q 7642 * @requires $injector 7643 * 7644 * @description 7645 * The `$http` service is a core Angular service that facilitates communication with the remote 7646 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 7647 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 7648 * 7649 * For unit testing applications that use `$http` service, see 7650 * {@link ngMock.$httpBackend $httpBackend mock}. 7651 * 7652 * For a higher level of abstraction, please check out the {@link ngResource.$resource 7653 * $resource} service. 7654 * 7655 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 7656 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 7657 * it is important to familiarize yourself with these APIs and the guarantees they provide. 7658 * 7659 * 7660 * # General usage 7661 * The `$http` service is a function which takes a single argument â a configuration object â 7662 * that is used to generate an HTTP request and returns a {@link ng.$q promise} 7663 * with two $http specific methods: `success` and `error`. 7664 * 7665 * ```js 7666 * $http({method: 'GET', url: '/someUrl'}). 7667 * success(function(data, status, headers, config) { 7668 * // this callback will be called asynchronously 7669 * // when the response is available 7670 * }). 7671 * error(function(data, status, headers, config) { 7672 * // called asynchronously if an error occurs 7673 * // or server returns response with an error status. 7674 * }); 7675 * ``` 7676 * 7677 * Since the returned value of calling the $http function is a `promise`, you can also use 7678 * the `then` method to register callbacks, and these callbacks will receive a single argument â 7679 * an object representing the response. See the API signature and type info below for more 7680 * details. 7681 * 7682 * A response status code between 200 and 299 is considered a success status and 7683 * will result in the success callback being called. Note that if the response is a redirect, 7684 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 7685 * called for such responses. 7686 * 7687 * # Writing Unit Tests that use $http 7688 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 7689 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 7690 * request using trained responses. 7691 * 7692 * ``` 7693 * $httpBackend.expectGET(...); 7694 * $http.get(...); 7695 * $httpBackend.flush(); 7696 * ``` 7697 * 7698 * # Shortcut methods 7699 * 7700 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 7701 * request data must be passed in for POST/PUT requests. 7702 * 7703 * ```js 7704 * $http.get('/someUrl').success(successCallback); 7705 * $http.post('/someUrl', data).success(successCallback); 7706 * ``` 7707 * 7708 * Complete list of shortcut methods: 7709 * 7710 * - {@link ng.$http#get $http.get} 7711 * - {@link ng.$http#head $http.head} 7712 * - {@link ng.$http#post $http.post} 7713 * - {@link ng.$http#put $http.put} 7714 * - {@link ng.$http#delete $http.delete} 7715 * - {@link ng.$http#jsonp $http.jsonp} 7716 * - {@link ng.$http#patch $http.patch} 7717 * 7718 * 7719 * # Setting HTTP Headers 7720 * 7721 * The $http service will automatically add certain HTTP headers to all requests. These defaults 7722 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 7723 * object, which currently contains this default configuration: 7724 * 7725 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 7726 * - `Accept: application/json, text/plain, * / *` 7727 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 7728 * - `Content-Type: application/json` 7729 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 7730 * - `Content-Type: application/json` 7731 *
7732 * To add or overwrite these defaults, simply add or remove a property from these configuration 7733 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 7734 * with the lowercased HTTP method name as the key, e.g. 7735 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. 7736 * 7737 * The defaults can also be set at runtime via the `$http.defaults` object in the same 7738 * fashion. For example: 7739 * 7740 * ``` 7741 * module.run(function($http) { 7742 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' 7743 * }); 7744 * ``` 7745 * 7746 * In addition, you can supply a `headers` property in the config object passed when 7747 * calling `$http(config)`, which overrides the defaults without changing them globally. 7748 * 7749 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 7750 * Use the `headers` property, setting the desired header to `undefined`. For example: 7751 * 7752 * ```js 7753 * var req = { 7754 * method: 'POST', 7755 * url: 'http://example.com', 7756 * headers: { 7757 * 'Content-Type': undefined 7758 * }, 7759 * data: { test: 'test' }, 7760 * } 7761 * 7762 * $http(req).success(function(){...}).error(function(){...}); 7763 * ``` 7764 * 7765 * # Transforming Requests and Responses 7766 * 7767 * Both requests and responses can be transformed using transform functions. By default, Angular 7768 * applies these transformations: 7769 * 7770 * Request transformations: 7771 * 7772 * - If the `data` property of the request configuration object contains an object, serialize it 7773 * into JSON format. 7774 * 7775 * Response transformations: 7776 * 7777 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 7778 * - If JSON response is detected, deserialize it using a JSON parser. 7779 * 7780 * To globally augment or override the default transforms, modify the 7781 * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` 7782 * properties. These properties are by default an array of transform functions, which allows you 7783 * to `push` or `unshift` a new transformation function into the transformation chain. You can 7784 * also decide to completely override any default transformations by assigning your 7785 * transformation functions to these properties directly without the array wrapper. These defaults 7786 * are again available on the $http factory at run-time, which may be useful if you have run-time 7787 * services you wish to be involved in your transformations. 7788 * 7789 * Similarly, to locally override the request/response transforms, augment the 7790 * `transformRequest` and/or `transformResponse` properties of the configuration object passed 7791 * into `$http`. 7792 * 7793 * 7794 * # Caching 7795 * 7796 * To enable caching, set the request configuration `cache` property to `true` (to use default 7797 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 7798 * When the cache is enabled, `$http` stores the response from the server in the specified 7799 * cache. The next time the same request is made, the response is served from the cache without 7800 * sending a request to the server. 7801 * 7802 * Note that even if the response is served from cache, delivery of the data is asynchronous in 7803 * the same way that real requests are. 7804 * 7805 * If there are multiple GET requests for the same URL that should be cached using the same 7806 * cache, but the cache is not populated yet, only one request to the server will be made and 7807 * the remaining requests will be fulfilled using the response from the first request. 7808 * 7809 * You can change the default cache to a new object (built with 7810 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 7811 * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set 7812 * their `cache` property to `true` will now use this cache object. 7813 * 7814 * If you set the default cache to `false` then only requests that specify their own custom 7815 * cache object will be cached. 7816 * 7817 * # Interceptors 7818 * 7819 * Before you start creating interceptors, be sure to understand the 7820 * {@link ng.$q $q and deferred/promise APIs}. 7821 * 7822 * For purposes of global error handling, authentication, or any kind of synchronous or 7823 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 7824 * able to intercept requests before they are handed to the server and 7825 * responses before they are handed over to the application code that 7826 * initiated these requests. The interceptors leverage the {@link ng.$q 7827 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 7828 * 7829 * The interceptors are service factories that are registered with the `$httpProvider` by 7830 * adding them to the `$httpProvider.interceptors` array. The factory is called and 7831 * injected with dependencies (if specified) and returns the interceptor. 7832 * 7833 * There are two kinds of interceptors (and two kinds of rejection interceptors): 7834 * 7835 * * `request`: interceptors get called with a http `config` object. The function is free to 7836 * modify the `config` object or create a new one. The function needs to return the `config` 7837 * object directly, or a promise containing the `config` or a new `config` object. 7838 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 7839 * resolved with a rejection. 7840 * * `response`: interceptors get called with http `response` object. The function is free to 7841 * modify the `response` object or create a new one. The function needs to return the `response` 7842 * object directly, or as a promise containing the `response` or a new `response` object. 7843 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 7844 * resolved with a rejection. 7845 * 7846 * 7847 * ```js 7848 * // register the interceptor as a service 7849 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7850 * return { 7851 * // optional method 7852 * 'request': function(config) {
7853 * // do something on success 7854 * return config; 7855 * }, 7856 * 7857 * // optional method 7858 * 'requestError': function(rejection) { 7859 * // do something on error 7860 * if (canRecover(rejection)) { 7861 * return responseOrNewPromise 7862 * } 7863 * return $q.reject(rejection); 7864 * }, 7865 * 7866 * 7867 * 7868 * // optional method 7869 * 'response': function(response) { 7870 * // do something on success 7871 * return response; 7872 * }, 7873 * 7874 * // optional method 7875 * 'responseError': function(rejection) { 7876 * // do something on error 7877 * if (canRecover(rejection)) { 7878 * return responseOrNewPromise 7879 * } 7880 * return $q.reject(rejection); 7881 * } 7882 * }; 7883 * }); 7884 * 7885 * $httpProvider.interceptors.push('myHttpInterceptor'); 7886 * 7887 * 7888 * // alternatively, register the interceptor via an anonymous factory 7889 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 7890 * return { 7891 * 'request': function(config) { 7892 * // same as above 7893 * }, 7894 * 7895 * 'response': function(response) { 7896 * // same as above 7897 * } 7898 * }; 7899 * }); 7900 * ``` 7901 * 7902 * # Response interceptors (DEPRECATED) 7903 * 7904 * Before you start creating interceptors, be sure to understand the 7905 * {@link ng.$q $q and deferred/promise APIs}. 7906 * 7907 * For purposes of global error handling, authentication or any kind of synchronous or 7908 * asynchronous preprocessing of received responses, it is desirable to be able to intercept 7909 * responses for http requests before they are handed over to the application code that 7910 * initiated these requests. The response interceptors leverage the {@link ng.$q 7911 * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. 7912 * 7913 * The interceptors are service factories that are registered with the $httpProvider by 7914 * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and 7915 * injected with dependencies (if specified) and returns the interceptor â a function that 7916 * takes a {@link ng.$q promise} and returns the original or a new promise. 7917 * 7918 * ```js 7919 * // register the interceptor as a service 7920 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7921 * return function(promise) { 7922 * return promise.then(function(response) { 7923 * // do something on success 7924 * return response; 7925 * }, function(response) { 7926 * // do something on error 7927 * if (canRecover(response)) { 7928 * return responseOrNewPromise 7929 * } 7930 * return $q.reject(response); 7931 * }); 7932 * } 7933 * }); 7934 * 7935 * $httpProvider.responseInterceptors.push('myHttpInterceptor'); 7936 * 7937 * 7938 * // register the interceptor via an anonymous factory 7939 * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) { 7940 * return function(promise) { 7941 * // same as above 7942 * } 7943 * }); 7944 * ``` 7945 * 7946 * 7947 * # Security Considerations 7948 * 7949 * When designing web applications, consider security threats from: 7950 * 7951 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 7952 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 7953 * 7954 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 7955 * pre-configured with strategies that address these issues, but for this to work backend server 7956 * cooperation is required. 7957 * 7958 * ## JSON Vulnerability Protection 7959 * 7960 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 7961 * allows third party website to turn your JSON resource URL into 7962 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 7963 * counter this your server can prefix all JSON requests with following str
7963ing `")]}',\n"`. 7964 * Angular will automatically strip the prefix before processing it as JSON. 7965 * 7966 * For example if your server needs to return: 7967 * ```js 7968 * ['one','two'] 7969 * ``` 7970 * 7971 * which is vulnerable to attack, your server can return: 7972 * ```js 7973 * )]}', 7974 * ['one','two'] 7975 * ``` 7976 * 7977 * Angular will strip the prefix, before processing the JSON. 7978 * 7979 * 7980 * ## Cross Site Request Forgery (XSRF) Protection 7981 * 7982 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which 7983 * an unauthorized site can gain your user's private data. Angular provides a mechanism 7984 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 7985 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 7986 * JavaScript that runs on your domain could read the cookie, your server can be assured that 7987 * the XHR came from JavaScript running on your domain. The header will not be set for 7988 * cross-domain requests. 7989 * 7990 * To take advantage of this, your server needs to set a token in a JavaScript readable session 7991 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 7992 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 7993 * that only JavaScript running on your domain could have sent the request. The token must be 7994 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 7995 * making up its own tokens). We recommend that the token is a digest of your site's 7996 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 7997 * for added security. 7998 * 7999 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 8000 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 8001 * or the per-request config object. 8002 * 8003 * 8004 * @param {object} config Object describing the request to be made and how it should be 8005 * processed. The object has following properties: 8006 * 8007 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 8008 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 8009 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be turned 8010 * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be 8011 * JSONified. 8012 * - **data** â `{string|Object}` â Data to be sent as the request message data. 8013 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 8014 * HTTP headers to send to the server. If the return value of a function is null, the 8015 * header will not be sent. 8016 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 8017 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 8018 * - **transformRequest** â 8019 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 8020 * transform function or an array of such functions. The transform function takes the http 8021 * request body and headers and returns its transformed (typically serialized) version. 8022 * - **transformResponse** â 8023 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 8024 * transform function or an array of such functions. The transform function takes the http 8025 * response body and headers and returns its transformed (typically deserialized) version. 8026 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 8027 * GET request, otherwise if a cache instance built with 8028 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 8029 * caching. 8030 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 8031 * that should abort the request when resolved. 8032 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 8033 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 8034 * for more information. 8035 * - **responseType** - `{string}` - see 8036 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). 8037 * 8038 * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the 8039 * standard `then` method and two http specific methods: `success` and `error`. The `then` 8040 * method takes two arguments a success and an error callback which will be called with a 8041 * response object. The `success` and `error` methods take a single argument - a function that 8042 * will be called when the request succeeds or fails respectively. The arguments passed into 8043 * these functions are destructured representation of the response object passed into the 8044 * `then` method. The response object has these properties: 8045 * 8046 * - **data** â `{string|Object}` â The response body transformed with the transform 8047 * functions. 8048 * - **status** â `{number}` â HTTP status code of the response. 8049 * - **headers** â `{function([headerName])}` â Header getter function. 8050 * - **config** â `{Object}` â The configuration object that was used to generate the request. 8051 * - **statusText** â `{string}` â HTTP status text of the response. 8052 * 8053 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 8054 * requests. This is primarily meant to be used for debugging purposes. 8055 * 8056 * 8057 * @example 8058<example module="httpExample"> 8059<file name="index.html"> 8060 <div ng-controller="FetchController"> 8061 <select ng-model="method"> 8062 <option>GET</option> 8063 <option>JSONP</option> 8064 </select> 8065 <input type="text" ng-model="url" size="80"/> 8066 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 8067 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 8068 <button id="samplejsonpbtn" 8069 ng-click="updateModel('JSONP', 8070 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 8071 Sample JSONP 8072 </button> 8073 <button id="invalidjsonpbtn" 8074 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 8075 Invalid JSONP 8076 </button> 8077 <pre>http status code: {{status}}</pre> 8078 <pre>http response data: {{data}}</pre> 8079 </div> 8080</file> 8081<file name="script.js"> 8082 angular.module('httpExample', []) 8083 .controller('FetchController', ['$scope', '$http', '$templateCache', 8084 function($scope, $http, $templateCache) { 8085 $scope.method = 'GET'; 8086 $scope.url = 'http-hello.html'; 8087 8088 $scope.fetch = function() { 8089 $scope.code = null; 8090 $scope.response = null; 8091 8092 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 8093 success(function(data, status) { 8094 $scope.status = status; 8095 $scope.data = data; 8096 }). 8097 error(function(data, status) { 8098 $scope.data = data || "Request failed"; 8099 $scope.status = status; 8100 }); 8101 }; 8102 8103 $scope.updateModel = function(method, url) { 8104 $scope.method = method; 8105 $scope.url = url; 8106 }; 8107 }]); 8108</file> 8109<file name="http-hello.html"> 8110 Hello, $http! 8111</file> 8112<file name="protractor.js" type="protractor"> 8113 var status = element(by.binding('status')); 8114 var data = element(by.binding('data')); 8115 var fetchBtn = element(by.id('fetchbtn')); 8116 var sampleGetBtn = element(by.id('samplegetbtn')); 8117 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 8118 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 8119 8120 it('should make an xhr GET request', function() { 8121 sampleGetBtn.click(); 8122 fetchBtn.click(); 8123 expect(status.getText()).toMatch('200'); 8124 expect(data.getText()).toMatch(/Hello, \$http!/); 8125 }); 8126 8127// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 8128// it('should make a JSONP request to angularjs.org', function() { 8129// sampleJsonpBtn.click(); 8130// fetchBtn.click(); 8131// expect(status.getText()).toMatch('200'); 8132// expect(data.getText()).toMatch(/Super Hero!/); 8133// }); 8134
8135 it('should make JSONP request to invalid URL and invoke the error handler', 8136 function() { 8137 invalidJsonpBtn.click(); 8138 fetchBtn.click(); 8139 expect(status.getText()).toMatch('0'); 8140 expect(data.getText()).toMatch('Request failed'); 8141 }); 8142</file> 8143</example> 8144 */ 8145 function $http(requestConfig) { 8146 var config = { 8147 method: 'get', 8148 transformRequest: defaults.transformRequest, 8149 transformResponse: defaults.transformResponse 8150 }; 8151 var headers = mergeHeaders(requestConfig); 8152 8153 extend(config, requestConfig); 8154 config.headers = headers; 8155 config.method = uppercase(config.method); 8156 8157 var serverRequest = function(config) { 8158 headers = config.headers; 8159 var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); 8160 8161 // strip content-type if data is undefined 8162 if (isUndefined(reqData)) { 8163 forEach(headers, function(value, header) { 8164 if (lowercase(header) === 'content-type') { 8165 delete headers[header]; 8166 } 8167 }); 8168 } 8169 8170 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 8171 config.withCredentials = defaults.withCredentials; 8172 } 8173 8174 // send request 8175 return sendReq(config, reqData, headers).then(transformResponse, transformResponse); 8176 }; 8177 8178 var chain = [serverRequest, undefined]; 8179 var promise = $q.when(config); 8180 8181 // apply interceptors 8182 forEach(reversedInterceptors, function(interceptor) { 8183 if (interceptor.request || interceptor.requestError) { 8184 chain.unshift(interceptor.request, interceptor.requestError); 8185 } 8186 if (interceptor.response || interceptor.responseError) { 8187 chain.push(interceptor.response, interceptor.responseError); 8188 } 8189 }); 8190 8191 while(chain.length) { 8192 var thenFn = chain.shift(); 8193 var rejectFn = chain.shift(); 8194 8195 promise = promise.then(thenFn, rejectFn); 8196 } 8197 8198 promise.success = function(fn) { 8199 promise.then(function(response) { 8200 fn(response.data, response.status, response.headers, config); 8201 }); 8202 return promise; 8203 }; 8204 8205 promise.error = function(fn) { 8206 promise.then(null, function(response) { 8207 fn(response.data, response.status, response.headers, config); 8208 }); 8209 return promise; 8210 }; 8211 8212 return promise; 8213 8214 function transformResponse(response) { 8215 // make a copy since the response must be cacheable 8216 var resp = extend({}, response, { 8217 data: transformData(response.data, response.headers, config.transformResponse) 8218 }); 8219 return (isSuccess(response.status)) 8220 ? resp 8221 : $q.reject(resp); 8222 } 8223 8224 function mergeHeaders(config) { 8225 var defHeaders = defaults.headers, 8226 reqHeaders = extend({}, config.headers), 8227 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 8228 8229 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 8230 8231 // using for-in instead of forEach to avoid unecessary iteration after header has been found 8232 defaultHeadersIteration: 8233 for (defHeaderName in defHeaders) { 8234 lowercaseDefHeaderName = lowercase(defHeaderName); 8235 8236 for (reqHeaderName in reqHeaders) { 8237 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 8238 continue defaultHeadersIteration; 8239 } 8240 } 8241 8242 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 8243 } 8244 8245 // execute if header value is a function for merged headers 8246 execHeaders(reqHeaders); 8247 return reqHeaders; 8248 8249 function execHeaders(headers) { 8250 var headerContent; 8251 8252 forEach(headers, function(headerFn, header) { 8253 if (isFunction(headerFn)) { 8254 headerContent = headerFn(); 8255 if (headerContent != null) { 8256 headers[header] = headerContent; 8257 } else { 8258 delete headers[header]; 8259 } 8260 } 8261 }); 8262 } 8263 } 8264 } 8265 8266 $http.pendingRequests = []; 8267 8268 /** 8269 * @ngdoc method 8270 * @name $http#get 8271 *
8272 * @description 8273 * Shortcut method to perform `GET` request. 8274 * 8275 * @param {string} url Relative or absolute URL specifying the destination of the request 8276 * @param {Object=} config Optional configuration object 8277 * @returns {HttpPromise} Future object 8278 */ 8279 8280 /** 8281 * @ngdoc method 8282 * @name $http#delete 8283 * 8284 * @description 8285 * Shortcut method to perform `DELETE` request. 8286 * 8287 * @param {string} url Relative or absolute URL specifying the destination of the request 8288 * @param {Object=} config Optional configuration object 8289 * @returns {HttpPromise} Future object 8290 */ 8291 8292 /** 8293 * @ngdoc method 8294 * @name $http#head 8295 * 8296 * @description 8297 * Shortcut method to perform `HEAD` request. 8298 * 8299 * @param {string} url Relative or absolute URL specifying the destination of the request 8300 * @param {Object=} config Optional configuration object 8301 * @returns {HttpPromise} Future object 8302 */ 8303 8304 /** 8305 * @ngdoc method 8306 * @name $http#jsonp 8307 * 8308 * @description 8309 * Shortcut method to perform `JSONP` request. 8310 * 8311 * @param {string} url Relative or absolute URL specifying the destination of the request. 8312 * The name of the callback should be the string `JSON_CALLBACK`. 8313 * @param {Object=} config Optional configuration object 8314 * @returns {HttpPromise} Future object 8315 */ 8316 createShortMethods('get', 'delete', 'head', 'jsonp'); 8317 8318 /** 8319 * @ngdoc method 8320 * @name $http#post 8321 * 8322 * @description 8323 * Shortcut method to perform `POST` request. 8324 * 8325 * @param {string} url Relative or absolute URL specifying the destination of the request 8326 * @param {*} data Request content 8327 * @param {Object=} config Optional configuration object 8328 * @returns {HttpPromise} Future object 8329 */ 8330 8331 /** 8332 * @ngdoc method 8333 * @name $http#put 8334 * 8335 * @description 8336 * Shortcut method to perform `PUT` request. 8337 * 8338 * @param {string} url Relative or absolute URL specifying the destination of the request 8339 * @param {*} data Request content 8340 * @param {Object=} config Optional configuration object 8341 * @returns {HttpPromise} Future object 8342 */ 8343 8344 /** 8345 * @ngdoc method 8346 * @name $http#patch 8347 * 8348 * @description 8349 * Shortcut method to perform `PATCH` request. 8350 * 8351 * @param {string} url Relative or absolute URL specifying the destination of the request 8352 * @param {*} data Request content 8353 * @param {Object=} config Optional configuration object 8354 * @returns {HttpPromise} Future object 8355 */ 8356 createShortMethodsWithData('post', 'put', 'patch'); 8357 8358 /** 8359 * @ngdoc property 8360 * @name $http#defaults 8361 * 8362 * @description 8363 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 8364 * default headers, withCredentials as well as request and response transformations. 8365 * 8366 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 8367 */ 8368 $http.defaults = defaults; 8369 8370 8371 return $http; 8372 8373 8374 function createShortMethods(names) { 8375 forEach(arguments, function(name) { 8376 $http[name] = function(url, config) { 8377 return $http(extend(config || {}, { 8378 method: name, 8379 url: url 8380 })); 8381 }; 8382 }); 8383 } 8384 8385 8386 function createShortMethodsWithData(name) { 8387 forEach(arguments, function(name) { 8388 $http[name] = function(url, data, config) { 8389 return $http(extend(config || {}, { 8390 method: name, 8391 url: url, 8392 data: data 8393 })); 8394 }; 8395 }); 8396 } 8397 8398 8399 /** 8400 * Makes the request. 8401 * 8402 * !!! ACCESSES CLOSURE VARS: 8403 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 8404 */ 8405 function sendReq(config, reqData, reqHeaders) { 8406 var deferred = $q.defer(), 8407 promise = deferred.promise, 8408 cache, 8409 cachedResp, 8410 url = buildUrl(config.url, config.params); 8411 8412 $http.pendingRequests.push(config); 8413 promise.then(removePendingReq, removePendingReq); 8414 8415 8416 if ((config.cache || defaults.cache) && config.cache !== false && 8417 (config.method === 'GET' || config.method === 'JSONP')) { 8418 cache = isObject(config.cache) ? config.cache 8419 : isObject(defaults.cache) ? defaults.cache 8420 : defaultCache;
8421 } 8422 8423 if (cache) { 8424 cachedResp = cache.get(url); 8425 if (isDefined(cachedResp)) { 8426 if (isPromiseLike(cachedResp)) { 8427 // cached request has already been sent, but there is no response yet 8428 cachedResp.then(removePendingReq, removePendingReq); 8429 return cachedResp; 8430 } else { 8431 // serving from cache 8432 if (isArray(cachedResp)) { 8433 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 8434 } else { 8435 resolvePromise(cachedResp, 200, {}, 'OK'); 8436 } 8437 } 8438 } else { 8439 // put the promise for the non-transformed response into cache as a placeholder 8440 cache.put(url, promise); 8441 } 8442 } 8443 8444 8445 // if we won't have the response in cache, set the xsrf headers and 8446 // send the request to the backend 8447 if (isUndefined(cachedResp)) { 8448 var xsrfValue = urlIsSameOrigin(config.url) 8449 ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] 8450 : undefined; 8451 if (xsrfValue) { 8452 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 8453 } 8454 8455 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 8456 config.withCredentials, config.responseType); 8457 } 8458 8459 return promise; 8460 8461 8462 /** 8463 * Callback registered to $httpBackend(): 8464 * - caches the response if desired 8465 * - resolves the raw $http promise 8466 * - calls $apply 8467 */ 8468 function done(status, response, headersString, statusText) { 8469 if (cache) { 8470 if (isSuccess(status)) { 8471 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 8472 } else { 8473 // remove promise from the cache 8474 cache.remove(url); 8475 } 8476 } 8477 8478 resolvePromise(response, status, headersString, statusText); 8479 if (!$rootScope.$$phase) $rootScope.$apply(); 8480 } 8481 8482 8483 /** 8484 * Resolves the raw $http promise. 8485 */ 8486 function resolvePromise(response, status, headers, statusText) { 8487 // normalize internal statuses to 0 8488 status = Math.max(status, 0); 8489 8490 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 8491 data: response, 8492 status: status, 8493 headers: headersGetter(headers), 8494 config: config, 8495 statusText : statusText 8496 }); 8497 } 8498 8499 8500 function removePendingReq() { 8501 var idx = indexOf($http.pendingRequests, config); 8502 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 8503 } 8504 } 8505 8506 8507 function buildUrl(url, params) { 8508 if (!params) return url; 8509 var parts = []; 8510 forEachSorted(params, function(value, key) { 8511 if (value === null || isUndefined(value)) return; 8512 if (!isArray(value)) value = [value]; 8513 8514 forEach(value, function(v) { 8515 if (isObject(v)) { 8516 if (isDate(v)){ 8517 v = v.toISOString(); 8518 } else { 8519 v = toJson(v); 8520 } 8521 } 8522 parts.push(encodeUriQuery(key) + '=' + 8523 encodeUriQuery(v)); 8524 }); 8525 }); 8526 if(parts.length > 0) { 8527 url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); 8528 } 8529 return url; 8530 } 8531 }]; 8532} 8533 8534function createXhr(method) { 8535 //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest 8536 //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest 8537 //if it is available 8538 if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) || 8539 !window.XMLHttpRequest)) { 8540 return new window.ActiveXObject("Microsoft.XMLHTTP"); 8541 } else if (window.XMLHttpRequest) { 8542 return new window.XMLHttpRequest(); 8543 } 8544 8545 throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); 8546} 8547 8548/** 8549 * @ngdoc service 8550 * @name $httpBackend 8551 * @requires $window 8552 * @requires $document 8553 * 8554 * @description 8555 * HTTP backend used by the {@link ng.$http service} that delegates to 8556 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 8557 * 8558 * You should never need to use this service directly, instead use the higher-level abstractions: 8559 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 8560 * 8561 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 8562 * $httpBackend} which can be trained with responses. 8563 */ 8564function $HttpBackendProvider() { 8565 this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { 8566 return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); 8567 }]; 8568} 8569 8570function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 8571 var ABORTED = -1; 8572 8573 // TODO(vojta): fix the signature 8574 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 8575 var status; 8576 $browser.$$incOutstandingRequestCount(); 8577 url = url || $browser.url(); 8578 8579 if (lowercase(method) == 'jsonp') { 8580 var callbackId = '_' + (callbacks.counter++).toString(36); 8581 callbacks[callbackId] = function(data) { 8582 callbacks[callbackId].data = data; 8583 callbacks[callbackId].called = true; 8584 }; 8585 8586 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 8587 callbackId, function(status, text) {
8588 completeRequest(callback, status, callbacks[callbackId].data, "", text); 8589 callbacks[callbackId] = noop; 8590 }); 8591 } else { 8592 8593 var xhr = createXhr(method); 8594 8595 xhr.open(method, url, true); 8596 forEach(headers, function(value, key) { 8597 if (isDefined(value)) { 8598 xhr.setRequestHeader(key, value); 8599 } 8600 }); 8601 8602 // In IE6 and 7, this might be called synchronously when xhr.send below is called and the 8603 // response is in the cache. the promise api will ensure that to the app code the api is 8604 // always async 8605 xhr.onreadystatechange = function() { 8606 // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by 8607 // xhrs that are resolved while the app is in the background (see #5426). 8608 // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before 8609 // continuing 8610 // 8611 // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and 8612 // Safari respectively. 8613 if (xhr && xhr.readyState == 4) { 8614 var responseHeaders = null, 8615 response = null, 8616 statusText = ''; 8617 8618 if(status !== ABORTED) { 8619 responseHeaders = xhr.getAllResponseHeaders(); 8620 8621 // responseText is the old-school way of retrieving response (supported by IE8 & 9) 8622 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 8623 response = ('response' in xhr) ? xhr.response : xhr.responseText; 8624 } 8625 8626 // Accessing statusText on an aborted xhr object will 8627 // throw an 'c00c023f error' in IE9 and lower, don't touch it. 8628 if (!(status === ABORTED && msie < 10)) { 8629 statusText = xhr.statusText; 8630 } 8631 8632 completeRequest(callback, 8633 status || xhr.status, 8634 response, 8635 responseHeaders, 8636 statusText); 8637 } 8638 }; 8639 8640 if (withCredentials) { 8641 xhr.withCredentials = true; 8642 } 8643 8644 if (responseType) { 8645 try { 8646 xhr.responseType = responseType; 8647 } catch (e) { 8648 // WebKit added support for the json responseType value on 09/03/2013 8649 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 8650 // known to throw when setting the value "json" as the response type. Other older 8651 // browsers implementing the responseType 8652 // 8653 // The json response type can be ignored if not supported, because JSON payloads are 8654 // parsed on the client-side regardless. 8655 if (responseType !== 'json') { 8656 throw e; 8657 } 8658 } 8659 } 8660 8661 xhr.send(post || null); 8662 } 8663 8664 if (timeout > 0) { 8665 var timeoutId = $browserDefer(timeoutRequest, timeout); 8666 } else if (isPromiseLike(timeout)) { 8667 timeout.then(timeoutRequest); 8668 } 8669 8670 8671 function timeoutRequest() { 8672 status = ABORTED; 8673 jsonpDone && jsonpDone(); 8674 xhr && xhr.abort(); 8675 } 8676 8677 function completeRequest(callback, status, response, headersString, statusText) { 8678 // cancel timeout and subsequent timeout promise resolution 8679 timeoutId && $browserDefer.cancel(timeoutId); 8680 jsonpDone = xhr = null; 8681 8682 // fix status code when it is 0 (0 status is undocumented). 8683 // Occurs when accessing file resources or on Android 4.1 stock browser 8684 // while retrieving files from application cache. 8685 if (status === 0) { 8686 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 8687 } 8688 8689 // normalize IE bug (http://bugs.jquery.com/ticket/1450) 8690 status = status === 1223 ? 204 : status; 8691 statusText = statusText || ''; 8692 8693 callback(status, response, headersString, statusText); 8694 $browser.$$completeOutstandingRequest(noop); 8695 } 8696 }; 8697 8698 function jsonpReq(url, callbackId, done) { 8699 // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: 8700 // - fetches local scripts via XHR and evals them 8701 // - adds and immediately removes script elements from the document 8702 var script = rawDocument.createElement('script'), callback = null; 8703 script.type = "text/javascript"; 8704 script.src = url; 8705 script.async = true; 8706 8707 callback = function(event) { 8708 removeEventListenerFn(script, "load", callback); 8709 removeEventListenerFn(script, "error", callback); 8710 rawDocument.body.removeChild(script); 8711 script = null; 8712 var status = -1; 8713 var text = "unknown"; 8714 8715 if (event) { 8716 if (event.type === "load" && !callbacks[callbackId].called) { 8717 event = { type: "error" }; 8718 } 8719 text = event.type; 8720 status = event.type === "error" ? 404 : 200; 8721 } 8722 8723 if (done) { 8724 done(status, text); 8725 } 8726 }; 8727 8728 addEventListenerFn(script, "load", callback); 8729 addEventListenerFn(script, "error", callback); 8730 8731 if (msie <= 8) { 8732 script.onreadystatechange = function() { 8733 if (isString(script.readyState) && /loaded|complete/.test(script.readyState)) { 8734 script.onreadystatechange = null; 8735 callback({ 8736 type: 'load' 8737 }); 8738 } 8739 }; 8740 } 8741 8742 rawDocument.body.appendChild(script); 8743 return callback; 8744 } 8745} 8746 8747var $interpolateMinErr = minErr('$interpolate'); 8748 8749/** 8750 * @ngdoc provider 8751 * @name $interpolateProvider 8752 * @kind function 8753 * 8754 * @description 8755 *
8756 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 8757 * 8758 * @example 8759<example module="customInterpolationApp"> 8760<file name="index.html"> 8761<script> 8762 var customInterpolationApp = angular.module('customInterpolationApp', []); 8763 8764 customInterpolationApp.config(function($interpolateProvider) { 8765 $interpolateProvider.startSymbol('//'); 8766 $interpolateProvider.endSymbol('//'); 8767 }); 8768 8769 8770 customInterpolationApp.controller('DemoController', function() { 8771 this.label = "This binding is brought you by // interpolation symbols."; 8772 }); 8773</script> 8774<div ng-app="App" ng-controller="DemoController as demo"> 8775 //demo.label// 8776</div> 8777</file> 8778<file name="protractor.js" type="protractor"> 8779 it('should interpolate binding with custom symbols', function() { 8780 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 8781 }); 8782</file> 8783</example> 8784 */ 8785function $InterpolateProvider() { 8786 var startSymbol = '{{'; 8787 var endSymbol = '}}'; 8788 8789 /** 8790 * @ngdoc method 8791 * @name $interpolateProvider#startSymbol 8792 * @description 8793 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 8794 * 8795 * @param {string=} value new value to set the starting symbol to. 8796 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8797 */ 8798 this.startSymbol = function(value){ 8799 if (value) { 8800 startSymbol = value; 8801 return this; 8802 } else { 8803 return startSymbol; 8804 } 8805 }; 8806 8807 /** 8808 * @ngdoc method 8809 * @name $interpolateProvider#endSymbol 8810 * @description 8811 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8812 * 8813 * @param {string=} value new value to set the ending symbol to. 8814 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8815 */ 8816 this.endSymbol = function(value){ 8817 if (value) { 8818 endSymbol = value; 8819 return this; 8820 } else { 8821 return endSymbol; 8822 } 8823 }; 8824 8825 8826 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 8827 var startSymbolLength = startSymbol.length, 8828 endSymbolLength = endSymbol.length; 8829 8830 /** 8831 * @ngdoc service 8832 * @name $interpolate 8833 * @kind function 8834 * 8835 * @requires $parse 8836 * @requires $sce 8837 * 8838 * @description 8839 * 8840 * Compiles a string with markup into an interpolation function. This service is used by the 8841 * HTML {@link ng.$compile $compile} service for data binding. See 8842 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 8843 * interpolation markup. 8844 * 8845 * 8846 * ```js 8847 * var $interpolate = ...; // injected 8848 * var exp = $interpolate('Hello {{name | uppercase}}!'); 8849 * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); 8850 * ``` 8851 * 8852 * 8853 * @param {string} text The text with markup to interpolate. 8854 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 8855 * embedded expression in order to return an interpolation function. Strings with no 8856 * embedded expression will return null for the interpolation function. 8857 * @param {string=} trustedContext when provided, the returned function passes the interpolated 8858 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 8859 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 8860 * provides Strict Contextual Escaping for details. 8861 * @returns {function(context)} an interpolation function which is used to compute the 8862 * interpolated string. The function has these parameters: 8863 * 8864 * * `context`: an object against which any expressions embedded in the strings are evaluated 8865 * against. 8866 * 8867 */ 8868 function $interpolate(text, mustHaveExpression, trustedContext) { 8869 var startIndex, 8870 endIndex, 8871 index = 0, 8872 parts = [], 8873 length = text.length, 8874 hasInterpolation = false, 8875 fn, 8876 exp, 8877 concat = []; 8878 8879 while(index < length) { 8880 if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && 8881 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { 8882 (index != startIndex) && parts.push(text.substring(index, startIndex)); 8883 parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); 8884 fn.exp = exp;
8885 index = endIndex + endSymbolLength; 8886 hasInterpolation = true; 8887 } else { 8888 // we did not find anything, so we have to add the remainder to the parts array 8889 (index != length) && parts.push(text.substring(index)); 8890 index = length; 8891 } 8892 } 8893 8894 if (!(length = parts.length)) { 8895 // we added, nothing, must have been an empty string. 8896 parts.push(''); 8897 length = 1; 8898 } 8899 8900 // Concatenating expressions makes it hard to reason about whether some combination of 8901 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 8902 // single expression be used for iframe[src], object[src], etc., we ensure that the value 8903 // that's used is assigned or constructed by some JS code somewhere that is more testable or 8904 // make it obvious that you bound the value to some user controlled value. This helps reduce 8905 // the load when auditing for XSS issues. 8906 if (trustedContext && parts.length > 1) { 8907 throw $interpolateMinErr('noconcat', 8908 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 8909 "interpolations that concatenate multiple expressions when a trusted value is " + 8910 "required. See http://docs.angularjs.org/api/ng.$sce", text); 8911 } 8912 8913 if (!mustHaveExpression || hasInterpolation) { 8914 concat.length = length; 8915 fn = function(context) { 8916 try { 8917 for(var i = 0, ii = length, part; i<ii; i++) { 8918 if (typeof (part = parts[i]) == 'function') { 8919 part = part(context); 8920 if (trustedContext) { 8921 part = $sce.getTrusted(trustedContext, part); 8922 } else { 8923 part = $sce.valueOf(part); 8924 } 8925 if (part == null) { // null || undefined 8926 part = ''; 8927 } else { 8928 switch (typeof part) { 8929 case 'string': 8930 { 8931 break; 8932 } 8933 case 'number': 8934 { 8935 part = '' + part; 8936 break; 8937 } 8938 default: 8939 { 8940 part = toJson(part); 8941 } 8942 } 8943 } 8944 } 8945 concat[i] = part; 8946 } 8947 return concat.join(''); 8948 } 8949 catch(err) { 8950 var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, 8951 err.toString()); 8952 $exceptionHandler(newErr); 8953 } 8954 }; 8955 fn.exp = text; 8956 fn.parts = parts; 8957 return fn; 8958 } 8959 } 8960 8961 8962 /** 8963 * @ngdoc method 8964 * @name $interpolate#startSymbol 8965 * @description 8966 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 8967 * 8968 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 8969 * the symbol. 8970 * 8971 * @returns {string} start symbol. 8972 */ 8973 $interpolate.startSymbol = function() { 8974 return startSymbol; 8975 }; 8976 8977 8978 /** 8979 * @ngdoc method 8980 * @name $interpolate#endSymbol 8981 * @description 8982 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8983 * 8984 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 8985 * the symbol. 8986 * 8987 * @returns {string} end symbol. 8988 */ 8989 $interpolate.endSymbol = function() { 8990 return endSymbol; 8991 }; 8992 8993 return $interpolate; 8994 }]; 8995} 8996 8997function $IntervalProvider() { 8998 this.$get = ['$rootScope', '$window', '$q', 8999 function($rootScope, $window, $q) { 9000 var intervals = {}; 9001 9002 9003 /** 9004 * @ngdoc service 9005 * @name $interval 9006 * 9007 * @description 9008 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 9009 * milliseconds. 9010 * 9011 * The return value of registering an interval function is a promise. This promise will be 9012 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 9013 * run indefinitely if `count` is not defined. The value of the notification will be the 9014 * number of iterations that have run. 9015 * To cancel an interval, call `$interval.cancel(promise)`. 9016 * 9017 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 9018 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 9019 * time. 9020 * 9021 * <div class="alert alert-warning">
9022 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 9023 * with them. In particular they are not automatically destroyed when a controller's scope or a 9024 * directive's element are destroyed. 9025 * You should take this into consideration and make sure to always cancel the interval at the 9026 * appropriate moment. See the example below for more details on how and when to do this. 9027 * </div> 9028 * 9029 * @param {function()} fn A function that should be called repeatedly. 9030 * @param {number} delay Number of milliseconds between each function call. 9031 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 9032 * indefinitely. 9033 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 9034 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 9035 * @returns {promise} A promise which will be notified on each iteration. 9036 * 9037 * @example 9038 * <example module="intervalExample"> 9039 * <file name="index.html"> 9040 * <script> 9041 * angular.module('intervalExample', []) 9042 * .controller('ExampleController', ['$scope', '$interval', 9043 * function($scope, $interval) { 9044 * $scope.format = 'M/d/yy h:mm:ss a'; 9045 * $scope.blood_1 = 100; 9046 * $scope.blood_2 = 120; 9047 * 9048 * var stop; 9049 * $scope.fight = function() { 9050 * // Don't start a new fight if we are already fighting 9051 * if ( angular.isDefined(stop) ) return; 9052 * 9053 * stop = $interval(function() { 9054 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 9055 * $scope.blood_1 = $scope.blood_1 - 3; 9056 * $scope.blood_2 = $scope.blood_2 - 4; 9057 * } else { 9058 * $scope.stopFight(); 9059 * } 9060 * }, 100); 9061 * }; 9062 * 9063 * $scope.stopFight = function() { 9064 * if (angular.isDefined(stop)) { 9065 * $interval.cancel(stop); 9066 * stop = undefined; 9067 * } 9068 * }; 9069 * 9070 * $scope.resetFight = function() { 9071 * $scope.blood_1 = 100; 9072 * $scope.blood_2 = 120; 9073 * }; 9074 * 9075 * $scope.$on('$destroy', function() { 9076 * // Make sure that the interval is destroyed too 9077 * $scope.stopFight(); 9078 * }); 9079 * }]) 9080 * // Register the 'myCurrentTime' directive factory method. 9081 * // We inject $interval and dateFilter service since the factory method is DI. 9082 * .directive('myCurrentTime', ['$interval', 'dateFilter', 9083 * function($interval, dateFilter) { 9084 * // return the directive link function. (compile function not needed) 9085 * return function(scope, element, attrs) { 9086 * var format, // date format 9087 * stopTime; // so that we can cancel the time updates 9088 * 9089 * // used to update the UI 9090 * function updateTime() { 9091 * element.text(dateFilter(new Date(), format)); 9092 * } 9093 * 9094 * // watch the expression, and update the UI on change. 9095 * scope.$watch(attrs.myCurrentTime, function(value) { 9096 * format = value; 9097 * updateTime(); 9098 * }); 9099 * 9100 * stopTime = $interval(updateTime, 1000); 9101 * 9102 * // listen on DOM destroy (removal) event, and cancel the next UI update 9103 * // to prevent updating time after the DOM element was removed. 9104 * element.bind('$destroy', function() { 9105 * $interval.cancel(stopTime); 9106 * }); 9107 * } 9108 * }]); 9109 * </script> 9110 * 9111 * <div>
9112 * <div ng-controller="ExampleController"> 9113 * Date format: <input ng-model="format"> <hr/> 9114 * Current time is: <span my-current-time="format"></span> 9115 * <hr/> 9116 * Blood 1 : <font color='red'>{{blood_1}}</font> 9117 * Blood 2 : <font color='red'>{{blood_2}}</font> 9118 * <button type="button" data-ng-click="fight()">Fight</button> 9119 * <button type="button" data-ng-click="stopFight()">StopFight</button> 9120 * <button type="button" data-ng-click="resetFight()">resetFight</button> 9121 * </div> 9122 * </div> 9123 * 9124 * </file> 9125 * </example> 9126 */ 9127 function interval(fn, delay, count, invokeApply) { 9128 var setInterval = $window.setInterval, 9129 clearInterval = $window.clearInterval, 9130 deferred = $q.defer(), 9131 promise = deferred.promise, 9132 iteration = 0, 9133 skipApply = (isDefined(invokeApply) && !invokeApply); 9134 9135 count = isDefined(count) ? count : 0; 9136 9137 promise.then(null, null, fn); 9138 9139 promise.$$intervalId = setInterval(function tick() { 9140 deferred.notify(iteration++); 9141 9142 if (count > 0 && iteration >= count) { 9143 deferred.resolve(iteration); 9144 clearInterval(promise.$$intervalId); 9145 delete intervals[promise.$$intervalId]; 9146 } 9147 9148 if (!skipApply) $rootScope.$apply(); 9149 9150 }, delay); 9151 9152 intervals[promise.$$intervalId] = deferred; 9153 9154 return promise; 9155 } 9156 9157 9158 /** 9159 * @ngdoc method 9160 * @name $interval#cancel 9161 * 9162 * @description 9163 * Cancels a task associated with the `promise`. 9164 * 9165 * @param {promise} promise returned by the `$interval` function. 9166 * @returns {boolean} Returns `true` if the task was successfully canceled. 9167 */ 9168 interval.cancel = function(promise) { 9169 if (promise && promise.$$intervalId in intervals) { 9170 intervals[promise.$$intervalId].reject('canceled'); 9171 $window.clearInterval(promise.$$intervalId); 9172 delete intervals[promise.$$intervalId]; 9173 return true; 9174 } 9175 return false; 9176 }; 9177 9178 return interval; 9179 }]; 9180} 9181 9182/** 9183 * @ngdoc service 9184 * @name $locale 9185 * 9186 * @description 9187 * $locale service provides localization rules for various Angular components. As of right now the 9188 * only public api is: 9189 * 9190 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 9191 */ 9192function $LocaleProvider(){ 9193 this.$get = function() { 9194 return { 9195 id: 'en-us', 9196 9197 NUMBER_FORMATS: { 9198 DECIMAL_SEP: '.', 9199 GROUP_SEP: ',', 9200 PATTERNS: [ 9201 { // Decimal Pattern 9202 minInt: 1, 9203 minFrac: 0, 9204 maxFrac: 3, 9205 posPre: '', 9206 posSuf: '', 9207 negPre: '-', 9208 negSuf: '', 9209 gSize: 3, 9210 lgSize: 3 9211 },{ //Currency Pattern 9212 minInt: 1, 9213 minFrac: 2, 9214 maxFrac: 2, 9215 posPre: '\u00A4', 9216 posSuf: '', 9217 negPre: '(\u00A4', 9218 negSuf: ')', 9219 gSize: 3, 9220 lgSize: 3 9221 } 9222 ], 9223 CURRENCY_SYM: '$' 9224 }, 9225 9226 DATETIME_FORMATS: { 9227 MONTH: 9228 'January,February,March,April,May,June,July,August,September,October,November,December' 9229 .split(','), 9230 SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), 9231 DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), 9232 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), 9233 AMPMS: ['AM','PM'], 9234 medium: 'MMM d, y h:mm:ss a', 9235 short: 'M/d/yy h:mm a', 9236 fullDate: 'EEEE, MMMM d, y', 9237 longDate: 'MMMM d, y', 9238 mediumDate: 'MMM d, y', 9239 shortDate: 'M/d/yy', 9240 mediumTime: 'h:mm:ss a', 9241 shortTime: 'h:mm a' 9242 }, 9243 9244 pluralCat: function(num) { 9245 if (num === 1) { 9246 return 'one'; 9247 } 9248 return 'other'; 9249 } 9250 }; 9251 }; 9252} 9253 9254var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 9255 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 9256var $locationMinErr = minErr('$location'); 9257 9258 9259/** 9260 * Encode path using encodeUriSegment, ignoring forward slashes 9261 * 9262 * @param {string} path Path to encode 9263 * @returns {string} 9264 */ 9265function encodePath(path) { 9266 var segments = path.split('/'), 9267 i = segments.length; 9268 9269 while (i--) { 9270 segments[i] = encodeUriSegment(segments[i]); 9271 } 9272 9273 return segments.join('/'); 9274} 9275 9276function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
9277 var parsedUrl = urlResolve(absoluteUrl, appBase); 9278 9279 locationObj.$$protocol = parsedUrl.protocol; 9280 locationObj.$$host = parsedUrl.hostname; 9281 locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 9282} 9283 9284 9285function parseAppUrl(relativeUrl, locationObj, appBase) { 9286 var prefixed = (relativeUrl.charAt(0) !== '/'); 9287 if (prefixed) { 9288 relativeUrl = '/' + relativeUrl; 9289 } 9290 var match = urlResolve(relativeUrl, appBase); 9291 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 9292 match.pathname.substring(1) : match.pathname); 9293 locationObj.$$search = parseKeyValue(match.search); 9294 locationObj.$$hash = decodeURIComponent(match.hash); 9295 9296 // make sure path starts with '/'; 9297 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 9298 locationObj.$$path = '/' + locationObj.$$path; 9299 } 9300} 9301 9302 9303/** 9304 * 9305 * @param {string} begin 9306 * @param {string} whole 9307 * @returns {string} returns text from whole after begin or undefined if it does not begin with 9308 * expected string. 9309 */ 9310function beginsWith(begin, whole) { 9311 if (whole.indexOf(begin) === 0) { 9312 return whole.substr(begin.length); 9313 } 9314} 9315 9316 9317function stripHash(url) { 9318 var index = url.indexOf('#'); 9319 return index == -1 ? url : url.substr(0, index); 9320} 9321 9322function trimEmptyHash(url) { 9323 return url.replace(/(#.+)|#$/, '$1'); 9324} 9325 9326 9327function stripFile(url) { 9328 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 9329} 9330 9331/* return the server only (scheme://host:port) */ 9332function serverBase(url) { 9333 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 9334} 9335 9336 9337/** 9338 * LocationHtml5Url represents an url 9339 * This object is exposed as $location service when HTML5 mode is enabled and supported 9340 * 9341 * @constructor 9342 * @param {string} appBase application base URL 9343 * @param {string} basePrefix url path prefix 9344 */ 9345function LocationHtml5Url(appBase, basePrefix) { 9346 this.$$html5 = true; 9347 basePrefix = basePrefix || ''; 9348 var appBaseNoFile = stripFile(appBase); 9349 parseAbsoluteUrl(appBase, this, appBase); 9350 9351 9352 /** 9353 * Parse given html5 (regular) url string into properties 9354 * @param {string} newAbsoluteUrl HTML5 url 9355 * @private 9356 */ 9357 this.$$parse = function(url) { 9358 var pathUrl = beginsWith(appBaseNoFile, url); 9359 if (!isString(pathUrl)) { 9360 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 9361 appBaseNoFile); 9362 } 9363 9364 parseAppUrl(pathUrl, this, appBase); 9365 9366 if (!this.$$path) { 9367 this.$$path = '/'; 9368 } 9369 9370 this.$$compose(); 9371 }; 9372 9373 /** 9374 * Compose url and update `absUrl` property 9375 * @private 9376 */ 9377 this.$$compose = function() { 9378 var search = toKeyValue(this.$$search), 9379 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9380 9381 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9382 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 9383 }; 9384 9385 this.$$parseLinkUrl = function(url, relHref) { 9386 var appUrl, prevAppUrl; 9387 var rewrittenUrl; 9388 9389 if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { 9390 prevAppUrl = appUrl; 9391 if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { 9392 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 9393 } else { 9394 rewrittenUrl = appBase + prevAppUrl; 9395 } 9396 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { 9397 rewrittenUrl = appBaseNoFile + appUrl; 9398 } else if (appBaseNoFile == url + '/') { 9399 rewrittenUrl = appBaseNoFile; 9400 } 9401 if (rewrittenUrl) { 9402 this.$$parse(rewrittenUrl); 9403 } 9404 return !!rewrittenUrl; 9405 }; 9406} 9407 9408 9409/** 9410 * LocationHashbangUrl represents url 9411 * This object is exposed as $location service when developer doesn't opt into html5 mode. 9412 * It also serves as the base class for html5 mode fallback on legacy browsers. 9413 * 9414 * @constructor 9415 * @param {string} appBase application base URL 9416 * @param {string} hashPrefix hashbang prefix 9417 */ 9418function LocationHashbangUrl(appBase, hashPrefix) { 9419 var appBaseNoFile = stripFile(appBase); 9420 9421 parseAbsoluteUrl(appBase, this, appBase); 9422 9423 9424 /** 9425 * Parse given hashbang url into properties 9426 * @param {string} url Hashbang url 9427 * @private 9428 */ 9429 this.$$parse = function(url) { 9430 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 9431 var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' 9432 ? beginsWith(hashPrefix, withoutBaseUrl) 9433 : (this.$$html5) 9434 ? withoutBaseUrl 9435 : ''; 9436 9437 if (!isString(withoutHashUrl)) { 9438 throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, 9439 hashPrefix); 9440 } 9441 parseAppUrl(withoutHashUrl, this, appBase); 9442 9443 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 9444 9445 this.$$compose(); 9446 9447 /* 9448 * In Windows, on an anchor node on documents loaded from 9449 * the filesystem, the browser will return a pathname
9450 * prefixed with the drive name ('/C:/path') when a 9451 * pathname without a drive is set: 9452 * * a.setAttribute('href', '/foo') 9453 * * a.pathname === '/C:/foo' //true 9454 * 9455 * Inside of Angular, we're always using pathnames that 9456 * do not include drive names for routing. 9457 */ 9458 function removeWindowsDriveName (path, url, base) { 9459 /* 9460 Matches paths for file protocol on windows, 9461 such as /C:/foo/bar, and captures only /foo/bar. 9462 */ 9463 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 9464 9465 var firstPathSegmentMatch; 9466 9467 //Get the relative path from the input URL. 9468 if (url.indexOf(base) === 0) { 9469 url = url.replace(base, ''); 9470 } 9471 9472 // The input URL intentionally contains a first path segment that ends with a colon. 9473 if (windowsFilePathExp.exec(url)) { 9474 return path; 9475 } 9476 9477 firstPathSegmentMatch = windowsFilePathExp.exec(path); 9478 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 9479 } 9480 }; 9481 9482 /** 9483 * Compose hashbang url and update `absUrl` property 9484 * @private 9485 */ 9486 this.$$compose = function() { 9487 var search = toKeyValue(this.$$search), 9488 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9489 9490 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9491 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 9492 }; 9493 9494 this.$$parseLinkUrl = function(url, relHref) { 9495 if(stripHash(appBase) == stripHash(url)) { 9496 this.$$parse(url); 9497 return true; 9498 } 9499 return false; 9500 }; 9501} 9502 9503 9504/** 9505 * LocationHashbangUrl represents url 9506 * This object is exposed as $location service when html5 history api is enabled but the browser 9507 * does not support it. 9508 * 9509 * @constructor 9510 * @param {string} appBase application base URL 9511 * @param {string} hashPrefix hashbang prefix 9512 */ 9513function LocationHashbangInHtml5Url(appBase, hashPrefix) { 9514 this.$$html5 = true; 9515 LocationHashbangUrl.apply(this, arguments); 9516 9517 var appBaseNoFile = stripFile(appBase); 9518 9519 this.$$parseLinkUrl = function(url, relHref) { 9520 var rewrittenUrl; 9521 var appUrl; 9522 9523 if ( appBase == stripHash(url) ) { 9524 rewrittenUrl = url; 9525 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { 9526 rewrittenUrl = appBase + hashPrefix + appUrl; 9527 } else if ( appBaseNoFile === url + '/') { 9528 rewrittenUrl = appBaseNoFile; 9529 } 9530 if (rewrittenUrl) { 9531 this.$$parse(rewrittenUrl); 9532 } 9533 return !!rewrittenUrl; 9534 }; 9535 9536 this.$$compose = function() { 9537 var search = toKeyValue(this.$$search), 9538 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9539 9540 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9541 // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#' 9542 this.$$absUrl = appBase + hashPrefix + this.$$url; 9543 }; 9544 9545} 9546 9547 9548LocationHashbangInHtml5Url.prototype = 9549 LocationHashbangUrl.prototype = 9550 LocationHtml5Url.prototype = { 9551 9552 /** 9553 * Are we in html5 mode? 9554 * @private 9555 */ 9556 $$html5: false, 9557 9558 /** 9559 * Has any change been replacing ? 9560 * @private 9561 */ 9562 $$replace: false, 9563 9564 /** 9565 * @ngdoc method 9566 * @name $location#absUrl 9567 * 9568 * @description 9569 * This method is getter only. 9570 * 9571 * Return full url representation with all segments encoded according to rules specified in 9572 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 9573 * 9574 * @return {string} full url 9575 */ 9576 absUrl: locationGetter('$$absUrl'), 9577 9578 /** 9579 * @ngdoc method 9580 * @name $location#url 9581 * 9582 * @description 9583 * This method is getter / setter. 9584 * 9585 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 9586 * 9587 * Change path, search and hash, when called with parameter and return `$location`. 9588 * 9589 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 9590 * @return {string} url 9591 */ 9592 url: function(url) { 9593 if (isUndefined(url)) 9594 return this.$$url; 9595 9596 var match = PATH_MATCH.exec(url); 9597 if (match[1]) this.path(decodeURIComponent(match[1])); 9598 if (match[2] || match[1]) this.search(match[3] || ''); 9599 this.hash(match[5] || ''); 9600 9601 return this; 9602 }, 9603 9604 /** 9605 * @ngdoc method 9606 * @name $location#protocol 9607 * 9608 * @description 9609 * This method is getter only. 9610 * 9611 * Return protocol of current url. 9612 * 9613 * @return {string} protocol of current url 9614 */ 9615 protocol: locationGetter('$$protocol'), 9616 9617 /** 9618 * @ngdoc method 9619 * @name $location#host 9620 * 9621 * @description 9622 * This method is getter only. 9623 * 9624 * Return host of current url. 9625 * 9626 * @return {string} host of current url. 9627 */ 9628 host: locationGetter('$$host'), 9629 9630 /** 9631 * @ngdoc method 9632 * @name $location#port 9633 * 9634 * @description 9635 * This method is getter only. 9636 * 9637 * Return port of current url. 9638 * 9639 * @return {Number} port 9640 */ 9641 port: locationGetter('$$port'), 9642 9643 /** 9644 * @ngdoc method 9645 * @name $location#path 9646 * 9647 * @description 9648 * This method is getter / setter. 9649 * 9650 * Return path of current url when called without any parameter. 9651 * 9652 * Change path when called with parameter and return `$location`. 9653 * 9654 * Note: Path should always begin with forward slash (/), this method will add the forward slash 9655 * if it is missing. 9656 * 9657 * @param {(string|number)=} path New path 9658 * @return {string} path 9659 */ 9660 path: locationGetterSetter('$$path', function(path) { 9661 path = path !== null ? path.toString() : ''; 9662 return path.charAt(0) == '/' ? path : '/' + path; 9663 }), 9664 9665 /** 9666 * @ngdoc method 9667 * @name $location#search 9668 * 9669 * @description 9670 * This method is getter / setter. 9671 * 9672 * Return search part (as object) of current url when called without any parameter. 9673 * 9674 * Change search part when called with parameter and return `$location`. 9675 * 9676 * 9677 * ```js
9678 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 9679 * var searchObject = $location.search(); 9680 * // => {foo: 'bar', baz: 'xoxo'} 9681 * 9682 * 9683 * // set foo to 'yipee' 9684 * $location.search('foo', 'yipee'); 9685 * // => $location 9686 * ``` 9687 * 9688 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 9689 * hash object. 9690 * 9691 * When called with a single argument the method acts as a setter, setting the `search` component 9692 * of `$location` to the specified value. 9693 * 9694 * If the argument is a hash object containing an array of values, these values will be encoded 9695 * as duplicate search parameters in the url. 9696 * 9697 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 9698 * will override only a single search property. 9699 * 9700 * If `paramValue` is an array, it will override the property of the `search` component of 9701 * `$location` specified via the first argument. 9702 * 9703 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 9704 * 9705 * If `paramValue` is `true`, the property specified via the first argument will be added with no 9706 * value nor trailing equal sign. 9707 * 9708 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 9709 * one or more arguments returns `$location` object itself. 9710 */ 9711 search: function(search, paramValue) { 9712 switch (arguments.length) { 9713 case 0: 9714 return this.$$search; 9715 case 1: 9716 if (isString(search) || isNumber(search)) { 9717 search = search.toString(); 9718 this.$$search = parseKeyValue(search); 9719 } else if (isObject(search)) { 9720 // remove object undefined or null properties 9721 forEach(search, function(value, key) { 9722 if (value == null) delete search[key]; 9723 }); 9724 9725 this.$$search = search; 9726 } else { 9727 throw $locationMinErr('isrcharg', 9728 'The first argument of the `$location#search()` call must be a string or an object.'); 9729 } 9730 break; 9731 default: 9732 if (isUndefined(paramValue) || paramValue === null) { 9733 delete this.$$search[search]; 9734 } else { 9735 this.$$search[search] = paramValue; 9736 } 9737 } 9738 9739 this.$$compose(); 9740 return this; 9741 }, 9742 9743 /** 9744 * @ngdoc method 9745 * @name $location#hash 9746 * 9747 * @description 9748 * This method is getter / setter. 9749 * 9750 * Return hash fragment when called without any parameter. 9751 * 9752 * Change hash fragment when called with parameter and return `$location`. 9753 * 9754 * @param {(string|number)=} hash New hash fragment 9755 * @return {string} hash 9756 */ 9757 hash: locationGetterSetter('$$hash', function(hash) { 9758 return hash !== null ? hash.toString() : ''; 9759 }), 9760 9761 /** 9762 * @ngdoc method 9763 * @name $location#replace 9764 * 9765 * @description 9766 * If called, all changes to $location during current `$digest` will be replacing current history 9767 * record, instead of adding new one. 9768 */ 9769 replace: function() { 9770 this.$$replace = true; 9771 return this; 9772 } 9773}; 9774 9775function locationGetter(property) { 9776 return function() { 9777 return this[property]; 9778 }; 9779} 9780 9781 9782function locationGetterSetter(property, preprocess) { 9783 return function(value) { 9784 if (isUndefined(value)) 9785 return this[property]; 9786 9787 this[property] = preprocess(value); 9788 this.$$compose(); 9789 9790 return this; 9791 }; 9792} 9793 9794 9795/** 9796 * @ngdoc service 9797 * @name $location 9798 * 9799 * @requires $rootElement 9800 * 9801 * @description 9802 * The $location service parses the URL in the browser address bar (based on the 9803 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 9804 * available to your application. Changes to the URL in the address bar are reflected into 9805 * $location service and changes to $location are reflected into the browser address bar. 9806 * 9807 * **The $location service:** 9808 * 9809 * - Exposes the current URL in the browser address bar, so you can 9810 * - Watch and observe the URL. 9811 * - Change the URL. 9812 * - Synchronizes the URL with the browser when the user 9813 * - Changes the address bar. 9814 * - Clicks the back or forward button (or clicks a History link). 9815 * - Clicks on a link. 9816 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 9817 * 9818 * For more information see {@link guide/$location Developer Guide: Using $location} 9819 */ 9820
9821/** 9822 * @ngdoc provider 9823 * @name $locationProvider 9824 * @description 9825 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 9826 */ 9827function $LocationProvider(){ 9828 var hashPrefix = '', 9829 html5Mode = false; 9830 9831 /** 9832 * @ngdoc method 9833 * @name $locationProvider#hashPrefix 9834 * @description 9835 * @param {string=} prefix Prefix for hash part (containing path and search) 9836 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9837 */ 9838 this.hashPrefix = function(prefix) { 9839 if (isDefined(prefix)) { 9840 hashPrefix = prefix; 9841 return this; 9842 } else { 9843 return hashPrefix; 9844 } 9845 }; 9846 9847 /** 9848 * @ngdoc method 9849 * @name $locationProvider#html5Mode 9850 * @description 9851 * @param {boolean=} mode Use HTML5 strategy if available. 9852 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9853 */ 9854 this.html5Mode = function(mode) { 9855 if (isDefined(mode)) { 9856 html5Mode = mode; 9857 return this; 9858 } else { 9859 return html5Mode; 9860 } 9861 }; 9862 9863 /** 9864 * @ngdoc event 9865 * @name $location#$locationChangeStart 9866 * @eventType broadcast on root scope 9867 * @description 9868 * Broadcasted before a URL will change. This change can be prevented by calling 9869 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 9870 * details about event object. Upon successful change 9871 * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. 9872 * 9873 * @param {Object} angularEvent Synthetic event object. 9874 * @param {string} newUrl New URL 9875 * @param {string=} oldUrl URL that was before it was changed. 9876 */ 9877 9878 /** 9879 * @ngdoc event 9880 * @name $location#$locationChangeSuccess 9881 * @eventType broadcast on root scope 9882 * @description 9883 * Broadcasted after a URL was changed. 9884 * 9885 * @param {Object} angularEvent Synthetic event object. 9886 * @param {string} newUrl New URL 9887 * @param {string=} oldUrl URL that was before it was changed. 9888 */ 9889 9890 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', 9891 function( $rootScope, $browser, $sniffer, $rootElement) { 9892 var $location, 9893 LocationMode, 9894 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 9895 initialUrl = $browser.url(), 9896 appBase; 9897 9898 if (html5Mode) { 9899 appBase = serverBase(initialUrl) + (baseHref || '/'); 9900 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 9901 } else { 9902 appBase = stripHash(initialUrl); 9903 LocationMode = LocationHashbangUrl; 9904 } 9905 $location = new LocationMode(appBase, '#' + hashPrefix); 9906 $location.$$parseLinkUrl(initialUrl, initialUrl); 9907 9908 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 9909 9910 $rootElement.on('click', function(event) { 9911 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 9912 // currently we open nice url link and redirect then 9913 9914 if (event.ctrlKey || event.metaKey || event.which == 2) return; 9915 9916 var elm = jqLite(event.target); 9917 9918 // traverse the DOM up to find first A tag 9919 while (lowercase(elm[0].nodeName) !== 'a') { 9920 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 9921 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 9922 } 9923 9924 var absHref = elm.prop('href'); 9925 // get the actual href attribute - see 9926 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 9927 var relHref = elm.attr('href') || elm.attr('xlink:href'); 9928 9929 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 9930 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 9931 // an animation. 9932 absHref = urlResolve(absHref.animVal).href; 9933 } 9934 9935 // Ignore when url is started with javascript: or mailto: 9936 if (IGNORE_URI_REGEXP.test(absHref)) return; 9937 9938 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 9939 if ($location.$$parseLinkUrl(absHref, relHref)) { 9940 // We do a preventDefault for all urls that are part of the angular application, 9941 // in html5mode and also without, so that we are able to abort navigation without 9942 // getting double entries in the location history. 9943 event.preventDefault(); 9944 // update location manually 9945 if ($location.absUrl() != $browser.url()) { 9946 $rootScope.$apply(); 9947 // hack to work around FF6 bug 684208 when scenario runner clicks on links 9948 window.angular['ff-684208-preventDefault'] = true; 9949 } 9950 } 9951 } 9952 }); 9953 9954 9955 // rewrite hashbang url <> html5 url 9956 if ($location.absUrl() != initialUrl) { 9957 $browser.url($location.absUrl(), true); 9958 } 9959 9960 // update $location when $browser url changes 9961 $browser.onUrlChange(function(newUrl) { 9962 if ($location.absUrl() != newUrl) { 9963 $rootScope.$evalAsync(function() { 9964 var oldUrl = $location.absUrl(); 9965 9966 $location.$$parse(newUrl); 9967 if ($rootScope.$broadcast('$locationChangeStart', newUrl, 9968 oldUrl).defaultPrevented) { 9969 $location.$$parse(oldUrl); 9970 $browser.url(oldUrl); 9971 } else { 9972 afterLocationChange(oldUrl); 9973 } 9974 }); 9975 if (!$rootScope.$$phase) $rootScope.$digest(); 9976 } 9977 }); 9978 9979 // update browser 9980 var changeCounter = 0; 9981 $rootScope.$watch(function $locationWatch() { 9982 var oldUrl = trimEmptyHash($browser.url()); 9983 var newUrl = trimEmptyHash($location.absUrl()); 9984 var currentReplace = $location.$$replace; 9985 9986 if (!changeCounter || oldUrl != newUrl) { 9987 changeCounter++; 9988 $rootScope.$evalAsync(function() { 9989 if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). 9990 defaultPrevented) { 9991 $location.$$parse(oldUrl); 9992 } else { 9993 $browser.url($location.absUrl(), currentReplace); 9994 afterLocationChange(oldUrl); 9995 } 9996 }); 9997 } 9998 $location.$$replace = false;
9999 10000 return changeCounter; 10001 }); 10002 10003 return $location; 10004 10005 function afterLocationChange(oldUrl) { 10006 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); 10007 } 10008}]; 10009} 10010 10011/** 10012 * @ngdoc service 10013 * @name $log 10014 * @requires $window 10015 * 10016 * @description 10017 * Simple service for logging. Default implementation safely writes the message 10018 * into the browser's console (if present). 10019 * 10020 * The main purpose of this service is to simplify debugging and troubleshooting. 10021 * 10022 * The default is to log `debug` messages. You can use 10023 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 10024 * 10025 * @example 10026 <example module="logExample"> 10027 <file name="script.js"> 10028 angular.module('logExample', []) 10029 .controller('LogController', ['$scope', '$log', function($scope, $log) { 10030 $scope.$log = $log; 10031 $scope.message = 'Hello World!'; 10032 }]); 10033 </file> 10034 <file name="index.html"> 10035 <div ng-controller="LogController"> 10036 <p>Reload this page with open console, enter text and hit the log button...</p> 10037 Message: 10038 <input type="text" ng-model="message"/> 10039 <button ng-click="$log.log(message)">log</button> 10040 <button ng-click="$log.warn(message)">warn</button> 10041 <button ng-click="$log.info(message)">info</button> 10042 <button ng-click="$log.error(message)">error</button> 10043 </div> 10044 </file> 10045 </example> 10046 */ 10047 10048/** 10049 * @ngdoc provider 10050 * @name $logProvider 10051 * @description 10052 * Use the `$logProvider` to configure how the application logs messages 10053 */ 10054function $LogProvider(){ 10055 var debug = true, 10056 self = this; 10057 10058 /** 10059 * @ngdoc method 10060 * @name $logProvider#debugEnabled 10061 * @description 10062 * @param {boolean=} flag enable or disable debug level messages 10063 * @returns {*} current value if used as getter or itself (chaining) if used as setter 10064 */ 10065 this.debugEnabled = function(flag) { 10066 if (isDefined(flag)) { 10067 debug = flag; 10068 return this; 10069 } else { 10070 return debug; 10071 } 10072 }; 10073 10074 this.$get = ['$window', function($window){ 10075 return { 10076 /** 10077 * @ngdoc method 10078 * @name $log#log 10079 * 10080 * @description 10081 * Write a log message 10082 */ 10083 log: consoleLog('log'), 10084 10085 /** 10086 * @ngdoc method 10087 * @name $log#info 10088 * 10089 * @description 10090 * Write an information message 10091 */ 10092 info: consoleLog('info'), 10093 10094 /** 10095 * @ngdoc method 10096 * @name $log#warn 10097 * 10098 * @description 10099 * Write a warning message 10100 */ 10101 warn: consoleLog('warn'), 10102 10103 /** 10104 * @ngdoc method 10105 * @name $log#error 10106 * 10107 * @description 10108 * Write an error message 10109 */ 10110 error: consoleLog('error'), 10111 10112 /** 10113 * @ngdoc method 10114 * @name $log#debug 10115 * 10116 * @description 10117 * Write a debug message 10118 */ 10119 debug: (function () { 10120 var fn = consoleLog('debug'); 10121 10122 return function() { 10123 if (debug) { 10124 fn.apply(self, arguments); 10125 } 10126 }; 10127 }()) 10128 }; 10129 10130 function formatError(arg) { 10131 if (arg instanceof Error) { 10132 if (arg.stack) { 10133 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 10134 ? 'Error: ' + arg.message + '\n' + arg.stack 10135 : arg.stack; 10136 } else if (arg.sourceURL) { 10137 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 10138 } 10139 } 10140 return arg; 10141 } 10142 10143 function consoleLog(type) { 10144 var console = $window.console || {}, 10145 logFn = console[type] || console.log || noop, 10146 hasApply = false; 10147 10148 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 10149 // The reason behind this is that console.log has type "object" in IE8... 10150 try { 10151 hasApply = !!logFn.apply; 10152 } catch (e) {} 10153 10154 if (hasApply) { 10155 return function() { 10156 var args = []; 10157 forEach(arguments, function(arg) { 10158 args.push(formatError(arg)); 10159 }); 10160 return logFn.apply(console, args); 10161 }; 10162 } 10163 10164 // we are IE which either doesn't have window.console => this is noop and we do nothing, 10165 // or we are IE where console.log doesn't have apply so we log at least first 2 args 10166 return function(arg1, arg2) { 10167 logFn(arg1, arg2 == null ? '' : arg2); 10168 }; 10169 } 10170 }]; 10171} 10172 10173var $parseMinErr = minErr('$parse'); 10174var promiseWarningCache = {}; 10175var promiseWarning; 10176 10177// Sandboxing Angular Expressions 10178// ------------------------------ 10179// Angular expressions are generally considered safe because these expressions only have direct 10180// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by 10181// obtaining a reference to native JS functions such as the Function constructor. 10182// 10183// As an example, consider the following Angular expression: 10184// 10185// {}.toString.constructor('alert("evil JS code")') 10186// 10187// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 10188// against the expression language, but not to prevent exploits that were enabled by exposing 10189// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good 10190// practice and therefore we are not even trying to protect against interaction with an object 10191// explicitly exposed in this way. 10192// 10193// In general, it is not possible to access a Window object from an angular expression unless a 10194// window or some DOM object that has a reference to window is published onto a Scope. 10195// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
10196// native objects. 10197// 10198// See https://docs.angularjs.org/guide/security 10199 10200 10201function ensureSafeMemberName(name, fullExpression) { 10202 if (name === "__defineGetter__" || name === "__defineSetter__" 10203 || name === "__lookupGetter__" || name === "__lookupSetter__" 10204 || name === "__proto__") { 10205 throw $parseMinErr('isecfld', 10206 'Attempting to access a disallowed field in Angular expressions! ' 10207 + 'Expression: {0}', fullExpression); 10208 } 10209 return name; 10210} 10211 10212function getStringValue(name, fullExpression) { 10213 // From the JavaScript docs: 10214 // Property names must be strings. This means that non-string objects cannot be used 10215 // as keys in an object. Any non-string object, including a number, is typecasted 10216 // into a string via the toString method. 10217 // 10218 // So, to ensure that we are checking the same `name` that JavaScript would use, 10219 // we cast it to a string, if possible. 10220 // Doing `name + ''` can cause a repl error if the result to `toString` is not a string, 10221 // this is, this will handle objects that misbehave. 10222 name = name + ''; 10223 if (!isString(name)) { 10224 throw $parseMinErr('iseccst', 10225 'Cannot convert object to primitive value! ' 10226 + 'Expression: {0}', fullExpression); 10227 } 10228 return name; 10229} 10230 10231function ensureSafeObject(obj, fullExpression) { 10232 // nifty check if obj is Function that is fast and works across iframes and other contexts 10233 if (obj) { 10234 if (obj.constructor === obj) { 10235 throw $parseMinErr('isecfn', 10236 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 10237 fullExpression); 10238 } else if (// isWindow(obj) 10239 obj.document && obj.location && obj.alert && obj.setInterval) { 10240 throw $parseMinErr('isecwindow', 10241 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 10242 fullExpression); 10243 } else if (// isElement(obj) 10244 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 10245 throw $parseMinErr('isecdom', 10246 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 10247 fullExpression); 10248 } else if (// block Object so that we can't get hold of dangerous Object.* methods 10249 obj === Object) { 10250 throw $parseMinErr('isecobj', 10251 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 10252 fullExpression); 10253 } 10254 } 10255 return obj; 10256} 10257 10258var CALL = Function.prototype.call; 10259var APPLY = Function.prototype.apply; 10260var BIND = Function.prototype.bind; 10261 10262function ensureSafeFunction(obj, fullExpression) { 10263 if (obj) { 10264 if (obj.constructor === obj) { 10265 throw $parseMinErr('isecfn', 10266 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 10267 fullExpression); 10268 } else if (obj === CALL || obj === APPLY || (BIND && obj === BIND)) { 10269 throw $parseMinErr('isecff', 10270 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 10271 fullExpression); 10272 } 10273 } 10274} 10275 10276var OPERATORS = { 10277 /* jshint bitwise : false */ 10278 'null':function(){return null;}, 10279 'true':function(){return true;}, 10280 'false':function(){return false;}, 10281 undefined:noop, 10282 '+':function(self, locals, a,b){ 10283 a=a(self, locals); b=b(self, locals); 10284 if (isDefined(a)) { 10285 if (isDefined(b)) { 10286 return a + b; 10287 } 10288 return a; 10289 } 10290 return isDefined(b)?b:undefined;}, 10291 '-':function(self, locals, a,b){ 10292 a=a(self, locals); b=b(self, locals); 10293 return (isDefined(a)?a:0)-(isDefined(b)?b:0); 10294 }, 10295 '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, 10296 '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, 10297 '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, 10298 '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, 10299 '=':noop, 10300 '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, 10301 '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, 10302 '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, 10303 '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, 10304 '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);}, 10305 '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, 10306 '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, 10307 '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, 10308 '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, 10309 '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, 10310 '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, 10311// '|':function(self, locals, a,b){return a|b;}, 10312 '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, 10313 '!':function(self, locals, a){return !a(self, locals);} 10314}; 10315/* jshint bitwise: true */ 10316var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 10317 10318
10319///////////////////////////////////////// 10320 10321 10322/** 10323 * @constructor 10324 */ 10325var Lexer = function (options) { 10326 this.options = options; 10327}; 10328 10329Lexer.prototype = { 10330 constructor: Lexer, 10331 10332 lex: function (text) { 10333 this.text = text; 10334 10335 this.index = 0; 10336 this.ch = undefined; 10337 this.lastCh = ':'; // can start regexp 10338 10339 this.tokens = []; 10340 10341 while (this.index < this.text.length) { 10342 this.ch = this.text.charAt(this.index); 10343 if (this.is('"\'')) { 10344 this.readString(this.ch); 10345 } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { 10346 this.readNumber(); 10347 } else if (this.isIdent(this.ch)) { 10348 this.readIdent(); 10349 } else if (this.is('(){}[].,;:?')) { 10350 this.tokens.push({ 10351 index: this.index, 10352 text: this.ch 10353 }); 10354 this.index++; 10355 } else if (this.isWhitespace(this.ch)) { 10356 this.index++; 10357 continue; 10358 } else { 10359 var ch2 = this.ch + this.peek(); 10360 var ch3 = ch2 + this.peek(2); 10361 var fn = OPERATORS[this.ch]; 10362 var fn2 = OPERATORS[ch2]; 10363 var fn3 = OPERATORS[ch3]; 10364 if (fn3) { 10365 this.tokens.push({index: this.index, text: ch3, fn: fn3}); 10366 this.index += 3; 10367 } else if (fn2) { 10368 this.tokens.push({index: this.index, text: ch2, fn: fn2}); 10369 this.index += 2; 10370 } else if (fn) { 10371 this.tokens.push({ 10372 index: this.index, 10373 text: this.ch, 10374 fn: fn 10375 }); 10376 this.index += 1; 10377 } else { 10378 this.throwError('Unexpected next character ', this.index, this.index + 1); 10379 } 10380 } 10381 this.lastCh = this.ch; 10382 } 10383 return this.tokens; 10384 }, 10385 10386 is: function(chars) { 10387 return chars.indexOf(this.ch) !== -1; 10388 }, 10389 10390 was: function(chars) { 10391 return chars.indexOf(this.lastCh) !== -1; 10392 }, 10393 10394 peek: function(i) { 10395 var num = i || 1; 10396 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 10397 }, 10398 10399 isNumber: function(ch) { 10400 return ('0' <= ch && ch <= '9'); 10401 }, 10402 10403 isWhitespace: function(ch) { 10404 // IE treats non-breaking space as \u00A0 10405 return (ch === ' ' || ch === '\r' || ch === '\t' || 10406 ch === '\n' || ch === '\v' || ch === '\u00A0'); 10407 }, 10408 10409 isIdent: function(ch) { 10410 return ('a' <= ch && ch <= 'z' || 10411 'A' <= ch && ch <= 'Z' || 10412 '_' === ch || ch === '$'); 10413 }, 10414 10415 isExpOperator: function(ch) { 10416 return (ch === '-' || ch === '+' || this.isNumber(ch)); 10417 }, 10418 10419 throwError: function(error, start, end) { 10420 end = end || this.index; 10421 var colStr = (isDefined(start) 10422 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 10423 : ' ' + end); 10424 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 10425 error, colStr, this.text); 10426 }, 10427 10428 readNumber: function() { 10429 var number = ''; 10430 var start = this.index; 10431 while (this.index < this.text.length) { 10432 var ch = lowercase(this.text.charAt(this.index)); 10433 if (ch == '.' || this.isNumber(ch)) { 10434 number += ch; 10435 } else { 10436 var peekCh = this.peek(); 10437 if (ch == 'e' && this.isExpOperator(peekCh)) { 10438 number += ch; 10439 } else if (this.isExpOperator(ch) && 10440 peekCh && this.isNumber(peekCh) && 10441 number.charAt(number.length - 1) == 'e') { 10442 number += ch; 10443 } else if (this.isExpOperator(ch) && 10444 (!peekCh || !this.isNumber(peekCh)) && 10445 number.charAt(number.length - 1) == 'e') { 10446 this.throwError('Invalid exponent'); 10447 } else { 10448 break; 10449 } 10450 } 10451 this.index++; 10452 } 10453 number = 1 * number; 10454 this.tokens.push({ 10455 index: start, 10456 text: number, 10457 literal: true, 10458 constant: true, 10459 fn: function() { return number; } 10460 }); 10461 }, 10462 10463 readIdent: function() { 10464 var parser = this; 10465 10466 var ident = ''; 10467 var start = this.index; 10468 10469 var lastDot, peekIndex, methodName, ch; 10470 10471 while (this.index < this.text.length) { 10472 ch = this.text.charAt(this.index); 10473 if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { 10474 if (ch === '.') lastDot = this.index; 10475 ident += ch; 10476 } else { 10477 break; 10478 } 10479 this.index++; 10480 } 10481 10482 //check if this is not a method invocation and if it is back out to last dot 10483 if (lastDot) { 10484 peekIndex = this.index; 10485 while (peekIndex < this.text.length) { 10486 ch = this.text.charAt(peekIndex); 10487 if (ch === '(') { 10488 methodName = ident.substr(lastDot - start + 1); 10489 ident = ident.substr(0, lastDot - start); 10490 this.index = peekIndex; 10491 break; 10492 } 10493 if (this.isWhitespace(ch)) { 10494 peekIndex++; 10495 } else { 10496 break; 10497 } 10498 } 10499 } 10500 10501 10502 var token = { 10503 index: start, 10504 text: ident 10505 }; 10506 10507 // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn 10508 if (OPERATORS.hasOwnProperty(ident)) { 10509 token.fn = OPERATORS[ident]; 10510 token.literal = true; 10511 token.constant = true; 10512 } else { 10513 var getter = getterFn(ident, this.options, this.text); 10514 token.fn = extend(function(self, locals) { 10515 return (getter(self, locals)); 10516 }, { 10517 assign: function(self, value) { 10518 return setter(self, ident, value, parser.text, parser.options); 10519 } 10520 }); 10521 } 10522 10523 this.tokens.push(token); 10524 10525 if (methodName) { 10526 this.tokens.push({ 10527 index:lastDot, 10528 text: '.' 10529 }); 10530 this.tokens.push({ 10531 index: lastDot + 1, 10532 text: methodName 10533 }); 10534 } 10535 }, 10536 10537 readString: function(quote) { 10538 var start = this.index; 10539 this.index++; 10540 var string = ''; 10541 var rawString = quote; 10542 var escape = false;
10543 while (this.index < this.text.length) { 10544 var ch = this.text.charAt(this.index); 10545 rawString += ch; 10546 if (escape) { 10547 if (ch === 'u') { 10548 var hex = this.text.substring(this.index + 1, this.index + 5); 10549 if (!hex.match(/[\da-f]{4}/i)) 10550 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 10551 this.index += 4; 10552 string += String.fromCharCode(parseInt(hex, 16)); 10553 } else { 10554 var rep = ESCAPE[ch]; 10555 string = string + (rep || ch); 10556 } 10557 escape = false; 10558 } else if (ch === '\\') { 10559 escape = true; 10560 } else if (ch === quote) { 10561 this.index++; 10562 this.tokens.push({ 10563 index: start, 10564 text: rawString, 10565 string: string, 10566 literal: true, 10567 constant: true, 10568 fn: function() { return string; } 10569 }); 10570 return; 10571 } else { 10572 string += ch; 10573 } 10574 this.index++; 10575 } 10576 this.throwError('Unterminated quote', start); 10577 } 10578}; 10579 10580 10581/** 10582 * @constructor 10583 */ 10584var Parser = function (lexer, $filter, options) { 10585 this.lexer = lexer; 10586 this.$filter = $filter; 10587 this.options = options; 10588}; 10589 10590Parser.ZERO = extend(function () { 10591 return 0; 10592}, { 10593 constant: true 10594}); 10595 10596Parser.prototype = { 10597 constructor: Parser, 10598 10599 parse: function (text) { 10600 this.text = text; 10601 10602 this.tokens = this.lexer.lex(text); 10603 10604 var value = this.statements(); 10605 10606 if (this.tokens.length !== 0) { 10607 this.throwError('is an unexpected token', this.tokens[0]); 10608 } 10609 10610 value.literal = !!value.literal; 10611 value.constant = !!value.constant; 10612 10613 return value; 10614 }, 10615 10616 primary: function () { 10617 var primary; 10618 if (this.expect('(')) { 10619 primary = this.filterChain(); 10620 this.consume(')'); 10621 } else if (this.expect('[')) { 10622 primary = this.arrayDeclaration(); 10623 } else if (this.expect('{')) { 10624 primary = this.object(); 10625 } else { 10626 var token = this.expect(); 10627 primary = token.fn; 10628 if (!primary) { 10629 this.throwError('not a primary expression', token); 10630 } 10631 primary.literal = !!token.literal; 10632 primary.constant = !!token.constant; 10633 } 10634 10635 var next, context; 10636 while ((next = this.expect('(', '[', '.'))) { 10637 if (next.text === '(') { 10638 primary = this.functionCall(primary, context); 10639 context = null; 10640 } else if (next.text === '[') { 10641 context = primary; 10642 primary = this.objectIndex(primary); 10643 } else if (next.text === '.') { 10644 context = primary; 10645 primary = this.fieldAccess(primary); 10646 } else { 10647 this.throwError('IMPOSSIBLE'); 10648 } 10649 } 10650 return primary; 10651 }, 10652 10653 throwError: function(msg, token) { 10654 throw $parseMinErr('syntax', 10655 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 10656 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 10657 }, 10658 10659 peekToken: function() { 10660 if (this.tokens.length === 0) 10661 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 10662 return this.tokens[0]; 10663 }, 10664 10665 peek: function(e1, e2, e3, e4) { 10666 if (this.tokens.length > 0) { 10667 var token = this.tokens[0]; 10668 var t = token.text; 10669 if (t === e1 || t === e2 || t === e3 || t === e4 || 10670 (!e1 && !e2 && !e3 && !e4)) { 10671 return token; 10672 } 10673 } 10674 return false; 10675 }, 10676 10677 expect: function(e1, e2, e3, e4){ 10678 var token = this.peek(e1, e2, e3, e4); 10679 if (token) { 10680 this.tokens.shift(); 10681 return token; 10682 } 10683 return false; 10684 }, 10685 10686 consume: function(e1){ 10687 if (!this.expect(e1)) { 10688 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 10689 } 10690 }, 10691 10692 unaryFn: function(fn, right) { 10693 return extend(function(self, locals) { 10694 return fn(self, locals, right); 10695 }, { 10696 constant:right.constant 10697 }); 10698 }, 10699 10700 ternaryFn: function(left, middle, right){ 10701 return extend(function(self, locals){ 10702 return left(self, locals) ? middle(self, locals) : right(self, locals); 10703 }, {
10704 constant: left.constant && middle.constant && right.constant 10705 }); 10706 }, 10707 10708 binaryFn: function(left, fn, right) { 10709 return extend(function(self, locals) { 10710 return fn(self, locals, left, right); 10711 }, { 10712 constant:left.constant && right.constant 10713 }); 10714 }, 10715 10716 statements: function() { 10717 var statements = []; 10718 while (true) { 10719 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 10720 statements.push(this.filterChain()); 10721 if (!this.expect(';')) { 10722 // optimize for the common case where there is only one statement. 10723 // TODO(size): maybe we should not support multiple statements? 10724 return (statements.length === 1) 10725 ? statements[0] 10726 : function(self, locals) { 10727 var value; 10728 for (var i = 0; i < statements.length; i++) { 10729 var statement = statements[i]; 10730 if (statement) { 10731 value = statement(self, locals); 10732 } 10733 } 10734 return value; 10735 }; 10736 } 10737 } 10738 }, 10739 10740 filterChain: function() { 10741 var left = this.expression(); 10742 var token; 10743 while (true) { 10744 if ((token = this.expect('|'))) { 10745 left = this.binaryFn(left, token.fn, this.filter()); 10746 } else { 10747 return left; 10748 } 10749 } 10750 }, 10751 10752 filter: function() { 10753 var token = this.expect(); 10754 var fn = this.$filter(token.text); 10755 var argsFn = []; 10756 while (true) { 10757 if ((token = this.expect(':'))) { 10758 argsFn.push(this.expression()); 10759 } else { 10760 var fnInvoke = function(self, locals, input) { 10761 var args = [input]; 10762 for (var i = 0; i < argsFn.length; i++) { 10763 args.push(argsFn[i](self, locals)); 10764 } 10765 return fn.apply(self, args); 10766 }; 10767 return function() { 10768 return fnInvoke; 10769 }; 10770 } 10771 } 10772 }, 10773 10774 expression: function() { 10775 return this.assignment(); 10776 }, 10777 10778 assignment: function() { 10779 var left = this.ternary(); 10780 var right; 10781 var token; 10782 if ((token = this.expect('='))) { 10783 if (!left.assign) { 10784 this.throwError('implies assignment but [' + 10785 this.text.substring(0, token.index) + '] can not be assigned to', token); 10786 } 10787 right = this.ternary(); 10788 return function(scope, locals) { 10789 return left.assign(scope, right(scope, locals), locals); 10790 }; 10791 } 10792 return left; 10793 }, 10794 10795 ternary: function() { 10796 var left = this.logicalOR(); 10797 var middle; 10798 var token; 10799 if ((token = this.expect('?'))) { 10800 middle = this.assignment(); 10801 if ((token = this.expect(':'))) { 10802 return this.ternaryFn(left, middle, this.assignment()); 10803 } else { 10804 this.throwError('expected :', token); 10805 } 10806 } else { 10807 return left; 10808 } 10809 }, 10810 10811 logicalOR: function() { 10812 var left = this.logicalAND(); 10813 var token; 10814 while (true) { 10815 if ((token = this.expect('||'))) { 10816 left = this.binaryFn(left, token.fn, this.logicalAND()); 10817 } else { 10818 return left; 10819 } 10820 } 10821 }, 10822 10823 logicalAND: function() { 10824 var left = this.equality(); 10825 var token; 10826 if ((token = this.expect('&&'))) { 10827 left = this.binaryFn(left, token.fn, this.logicalAND()); 10828 } 10829 return left; 10830 }, 10831 10832 equality: function() { 10833 var left = this.relational(); 10834 var token; 10835 if ((token = this.expect('==','!=','===','!=='))) { 10836 left = this.binaryFn(left, token.fn, this.equality()); 10837 } 10838 return left; 10839 }, 10840 10841 relational: function() { 10842 var left = this.additive(); 10843 var token; 10844 if ((token = this.expect('<', '>', '<=', '>='))) { 10845 left = this.binaryFn(left, token.fn, this.relational()); 10846 } 10847 return left; 10848 }, 10849 10850 additive: function() { 10851 var left = this.multiplicative(); 10852 var token; 10853 while ((token = this.expect('+','-'))) { 10854 left = this.binaryFn(left, token.fn, this.multiplicative()); 10855 } 10856 return left; 10857 }, 10858 10859 multiplicative: function() { 10860 var left = this.unary(); 10861 var token; 10862 while ((token = this.expect('*','/','%'))) { 10863 left = this.binaryFn(left, token.fn, this.unary()); 10864 } 10865 return left; 10866 }, 10867 10868 unary: function() { 10869 var token; 10870 if (this.expect('+')) { 10871 return this.primary(); 10872 } else if ((token = this.expect('-'))) { 10873 return this.binaryFn(Parser.ZERO, token.fn, this.unary()); 10874 } else if ((token = this.expect('!'))) { 10875 return this.unaryFn(token.fn, this.unary()); 10876 } else { 10877 return this.primary(); 10878 } 10879 }, 10880 10881 fieldAccess: function(object) { 10882 var parser = this; 10883 var field = this.expect().text; 10884 var getter = getterFn(field, this.options, this.text); 10885 10886 return extend(function(scope, locals, self) { 10887 return getter(self || object(scope, locals)); 10888 }, {
10889 assign: function(scope, value, locals) { 10890 var o = object(scope, locals); 10891 if (!o) object.assign(scope, o = {}); 10892 return setter(o, field, value, parser.text, parser.options); 10893 } 10894 }); 10895 }, 10896 10897 objectIndex: function(obj) { 10898 var parser = this; 10899 10900 var indexFn = this.expression(); 10901 this.consume(']'); 10902 10903 return extend(function(self, locals) { 10904 var o = obj(self, locals), 10905 i = getStringValue(indexFn(self, locals), parser.text), 10906 v, p; 10907 10908 ensureSafeMemberName(i, parser.text); 10909 if (!o) return undefined; 10910 v = ensureSafeObject(o[i], parser.text); 10911 if (v && v.then && parser.options.unwrapPromises) { 10912 p = v; 10913 if (!('$$v' in v)) { 10914 p.$$v = undefined; 10915 p.then(function(val) { p.$$v = val; }); 10916 } 10917 v = v.$$v; 10918 } 10919 return v; 10920 }, { 10921 assign: function(self, value, locals) { 10922 var key = ensureSafeMemberName(getStringValue(indexFn(self, locals), parser.text), parser.text); 10923 // prevent overwriting of Function.constructor which would break ensureSafeObject check 10924 var o = ensureSafeObject(obj(self, locals), parser.text); 10925 if (!o) obj.assign(self, o = {}); 10926 return o[key] = value; 10927 } 10928 }); 10929 }, 10930 10931 functionCall: function(fn, contextGetter) { 10932 var argsFn = []; 10933 if (this.peekToken().text !== ')') { 10934 do { 10935 argsFn.push(this.expression()); 10936 } while (this.expect(',')); 10937 } 10938 this.consume(')'); 10939 10940 var parser = this; 10941 10942 return function(scope, locals) { 10943 var args = []; 10944 var context = contextGetter ? contextGetter(scope, locals) : scope; 10945 10946 for (var i = 0; i < argsFn.length; i++) { 10947 args.push(ensureSafeObject(argsFn[i](scope, locals), parser.text)); 10948 } 10949 var fnPtr = fn(scope, locals, context) || noop; 10950 10951 ensureSafeObject(context, parser.text); 10952 ensureSafeFunction(fnPtr, parser.text); 10953 10954 // IE doesn't have apply for some native functions 10955 var v = fnPtr.apply 10956 ? fnPtr.apply(context, args) 10957 : fnPtr(args[0], args[1], args[2], args[3], args[4]); 10958 10959 return ensureSafeObject(v, parser.text); 10960 }; 10961 }, 10962 10963 // This is used with json array declaration 10964 arrayDeclaration: function () { 10965 var elementFns = []; 10966 var allConstant = true; 10967 if (this.peekToken().text !== ']') { 10968 do { 10969 if (this.peek(']')) { 10970 // Support trailing commas per ES5.1. 10971 break; 10972 } 10973 var elementFn = this.expression(); 10974 elementFns.push(elementFn); 10975 if (!elementFn.constant) { 10976 allConstant = false; 10977 } 10978 } while (this.expect(',')); 10979 } 10980 this.consume(']'); 10981 10982 return extend(function(self, locals) { 10983 var array = []; 10984 for (var i = 0; i < elementFns.length; i++) { 10985 array.push(elementFns[i](self, locals)); 10986 } 10987 return array; 10988 }, { 10989 literal: true, 10990 constant: allConstant 10991 }); 10992 }, 10993 10994 object: function () { 10995 var keyValues = []; 10996 var allConstant = true; 10997 if (this.peekToken().text !== '}') { 10998 do { 10999 if (this.peek('}')) { 11000 // Support trailing commas per ES5.1. 11001 break; 11002 } 11003 var token = this.expect(), 11004 key = token.string || token.text; 11005 this.consume(':'); 11006 var value = this.expression(); 11007 keyValues.push({key: key, value: value}); 11008 if (!value.constant) { 11009 allConstant = false; 11010 } 11011 } while (this.expect(',')); 11012 } 11013 this.consume('}'); 11014 11015 return extend(function(self, locals) { 11016 var object = {}; 11017 for (var i = 0; i < keyValues.length; i++) { 11018 var keyValue = keyValues[i]; 11019 object[keyValue.key] = keyValue.value(self, locals); 11020 } 11021 return object; 11022 }, { 11023 literal: true, 11024 constant: allConstant 11025 }); 11026 } 11027}; 11028 11029 11030////////////////////////////////////////////////// 11031// Parser helper functions 11032////////////////////////////////////////////////// 11033 11034function setter(obj, path, setValue, fullExp, options) { 11035 ensureSafeObject(obj, fullExp); 11036 11037 //needed? 11038 options = options || {}; 11039 11040 var element = path.split('.'), key; 11041 for (var i = 0; element.length > 1; i++) { 11042 key = ensureSafeMemberName(element.shift(), fullExp); 11043 var propertyObj = ensureSafeObject(obj[key], fullExp); 11044 if (!propertyObj) { 11045 propertyObj = {}; 11046 obj[key] = propertyObj; 11047 } 11048 obj = propertyObj; 11049 if (obj.then && options.unwrapPromises) { 11050 promiseWarning(fullExp); 11051 if (!("$$v" in obj)) { 11052 (function(promise) { 11053 promise.then(function(val) { promise.$$v = val; }); } 11054 )(obj); 11055 } 11056 if (obj.$$v === undefined) { 11057 obj.$$v = {}; 11058 } 11059 obj = obj.$$v; 11060 } 11061 } 11062 key = ensureSafeMemberName(element.shift(), fullExp); 11063 ensureSafeObject(obj[key], fullExp); 11064 obj[key] = setValue; 11065 return setValue; 11066} 11067 11068var getterFnCacheDefault = {}; 11069var getterFnCacheExpensive = {}; 11070 11071function isPossiblyDangerousMemberName(name) { 11072 return name == 'constructor'; 11073} 11074 11075/** 11076 * Implementation of the "Black Hole" variant from: 11077 * - http://jsperf.com/angularjs-parse-getter/4 11078 * - http://jsperf.com/path-evaluation-simplified/7 11079 */ 11080function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { 11081 ensureSafeMemberName(key0, fullExp); 11082 ensureSafeMemberName(key1, fullExp); 11083 ensureSafeMemberName(key2, fullExp); 11084 ensureSafeMemberName(key3, fullExp); 11085 ensureSafeMemberName(key4, fullExp); 11086 var eso = function(o) { 11087 return ensureSafeObject(o, fullExp); 11088 }; 11089 var expensiveChecks = options.expensiveChecks; 11090 var eso0 = (expensiveChecks || isPossiblyDangerousMemberName(key0)) ? eso : identity; 11091 var eso1 = (expensiveChecks || isPossiblyDangerousMemberName(key1)) ? eso : identity; 11092 var eso2 = (expensiveChecks || isPossiblyDangerousMemberName(key2)) ? eso : identity; 11093 var eso3 = (expensiveChecks || isPossiblyDangerousMemberName(key3)) ? eso : identity; 11094 var eso4 = (expensiveChecks || isPossiblyDangerousMemberName(key4)) ? eso : identity; 11095 11096 return !options.unwrapPromises 11097 ? function cspSafeGetter(scope, locals) { 11098 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; 11099 11100 if (pathVal == null) return pathVal; 11101 pathVal = eso0(pathVal[key0]); 11102 11103 if (!key1) return pathVal; 11104 if (pathVal == null) return undefined; 11105 pathVal = eso1(pathVal[key1]); 11106 11107 if (!key2) return pathVal; 11108 if (pathVal == null) return undefined; 11109 pathVal = eso2(pathVal[key2]); 11110 11111 if (!key3) return pathVal; 11112 if (pathVal == null) return undefined; 11113 pathVal = eso3(pathVal[key3]); 11114 11115 if (!key4) return pathVal; 11116 if (pathVal == null) return undefined; 11117 pathVal = eso4(pathVal[key4]); 11118 11119 return pathVal; 11120 } 11121 : function cspSafePromiseEnabledGetter(scope, locals) { 11122 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, 11123 promise; 11124 11125 if (pathVal == null) return pathVal; 11126 11127 pathVal = eso0(pathVal[key0]); 11128 if (pathVal && pathVal.then) { 11129 promiseWarning(fullExp); 11130 if (!("$$v" in pathVal)) { 11131 promise = pathVal; 11132 promise.$$v = undefined; 11133 promise.then(function(val) { promise.$$v = eso0(val); }); 11134 } 11135 pathVal = eso0(pathVal.$$v); 11136 } 11137 11138 if (!key1) return pathVal; 11139 if (pathVal == null) return undefined; 11140 pathVal = eso1(pathVal[key1]); 11141 if (pathVal && pathVal.then) { 11142 promiseWarning(fullExp); 11143 if (!("$$v" in pathVal)) { 11144 promise = pathVal; 11145 promise.$$v = undefined; 11146 promise.then(function(val) { promise.$$v = eso1(val); }); 11147 } 11148 pathVal = eso1(pathVal.$$v); 11149 } 11150 11151 if (!key2) return pathVal; 11152 if (pathVal == null) return undefined; 11153 pathVal = eso2(pathVal[key2]); 11154 if (pathVal && pathVal.then) { 11155 promiseWarning(fullExp); 11156 if (!("$$v" in pathVal)) { 11157 promise = pathVal; 11158 promise.$$v = undefined; 11159 promise.then(function(val) { promise.$$v = eso2(val); }); 11160 } 11161 pathVal = eso2(pathVal.$$v); 11162 } 11163 11164 if (!key3) return pathVal; 11165 if (pathVal == null) return undefined; 11166 pathVal = eso3(pathVal[key3]); 11167 if (pathVal && pathVal.then) { 11168 promiseWarning(fullExp); 11169 if (!("$$v" in pathVal)) { 11170 promise = pathVal; 11171 promise.$$v = undefined; 11172 promise.then(function(val) { promise.$$v = eso3(val); }); 11173 } 11174 pathVal = eso3(pathVal.$$v); 11175 } 11176 11177 if (!key4) return pathVal; 11178 if (pathVal == null) return undefined; 11179 pathVal = eso4(pathVal[key4]); 11180 if (pathVal && pathVal.then) { 11181 promiseWarning(fullExp); 11182 if (!("$$v" in pathVal)) { 11183 promise = pathVal; 11184 promise.$$v = undefined; 11185 promise.then(function(val) { promise.$$v = eso4(val); }); 11186 } 11187 pathVal = eso4(pathVal.$$v); 11188 } 11189 return pathVal; 11190 }; 11191} 11192 11193function getterFnWithExtraArgs(fn, fullExpression) { 11194 return function(s, l) { 11195 return fn(s, l, promiseWarning, ensureSafeObject, fullExpression); 11196 }; 11197} 11198 11199function getterFn(path, options, fullExp) { 11200 var expensiveChecks = options.expensiveChecks; 11201 var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault); 11202 // Check whether the cache has this getter already. 11203 // We can use hasOwnProperty directly on the cache because we ensure, 11204 // see below, that the cache never stores a path called 'hasOwnProperty' 11205 if (getterFnCache.hasOwnProperty(path)) { 11206 return getterFnCache[path]; 11207 } 11208 11209 var pathKeys = path.split('.'), 11210 pathKeysLength = pathKeys.length, 11211 fn; 11212 11213 // http://jsperf.com/angularjs-parse-getter/6 11214 if (options.csp) { 11215 if (pathKeysLength < 6) { 11216 fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, 11217 options); 11218 } else { 11219 fn = function(scope, locals) { 11220 var i = 0, val; 11221 do { 11222 val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], 11223 pathKeys[i++], fullExp, options)(scope, locals); 11224 11225 locals = undefined; // clear after first iteration 11226 scope = val; 11227 } while (i < pathKeysLength); 11228 return val; 11229 }; 11230 } 11231 } else { 11232 var code = 'var p;\n'; 11233 if (expensiveChecks) { 11234 code += 's = eso(s, fe);\nl = eso(l, fe);\n'; 11235 } 11236 var needsEnsureSafeObject = expensiveChecks;
11237 forEach(pathKeys, function(key, index) { 11238 ensureSafeMemberName(key, fullExp); 11239 var lookupJs = (index 11240 // we simply dereference 's' on any .dot notation 11241 ? 's' 11242 // but if we are first then we check locals first, and if so read it first 11243 : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '["' + key + '"]'; 11244 var wrapWithEso = expensiveChecks || isPossiblyDangerousMemberName(key); 11245 if (wrapWithEso) { 11246 lookupJs = 'eso(' + lookupJs + ', fe)'; 11247 needsEnsureSafeObject = true; 11248 } 11249 code += 'if(s == null) return undefined;\n' + 11250 's=' + lookupJs + ';\n'; 11251 if (options.unwrapPromises) { 11252 code += 'if (s && s.then) {\n' + 11253 ' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' + 11254 ' if (!("$$v" in s)) {\n' + 11255 ' p=s;\n' + 11256 ' p.$$v = undefined;\n' + 11257 ' p.then(function(v) {p.$$v=' + (wrapWithEso ? 'eso(v)' : 'v') + ';});\n' + 11258 '}\n' + 11259 ' s=' + (wrapWithEso ? 'eso(s.$$v)' : 's.$$v') + '\n' + 11260 '}\n'; 11261 11262 } 11263 }); 11264 code += 'return s;'; 11265 11266 /* jshint -W054 */ 11267 // s=scope, l=locals, pw=promiseWarning, eso=ensureSafeObject, fe=fullExpression 11268 var evaledFnGetter = new Function('s', 'l', 'pw', 'eso', 'fe', code); 11269 /* jshint +W054 */ 11270 evaledFnGetter.toString = valueFn(code); 11271 if (needsEnsureSafeObject || options.unwrapPromises) { 11272 evaledFnGetter = getterFnWithExtraArgs(evaledFnGetter, fullExp); 11273 } 11274 fn = evaledFnGetter; 11275 } 11276 11277 // Only cache the value if it's not going to mess up the cache object 11278 // This is more performant that using Object.prototype.hasOwnProperty.call 11279 if (path !== 'hasOwnProperty') { 11280 getterFnCache[path] = fn; 11281 } 11282 return fn; 11283} 11284 11285/////////////////////////////////// 11286 11287/** 11288 * @ngdoc service 11289 * @name $parse 11290 * @kind function 11291 * 11292 * @description 11293 * 11294 * Converts Angular {@link guide/expression expression} into a function. 11295 * 11296 * ```js 11297 * var getter = $parse('user.name'); 11298 * var setter = getter.assign; 11299 * var context = {user:{name:'angular'}}; 11300 * var locals = {user:{name:'local'}}; 11301 * 11302 * expect(getter(context)).toEqual('angular'); 11303 * setter(context, 'newValue'); 11304 * expect(context.user.name).toEqual('newValue'); 11305 * expect(getter(context, locals)).toEqual('local'); 11306 * ``` 11307 * 11308 * 11309 * @param {string} expression String expression to compile. 11310 * @returns {function(context, locals)} a function which represents the compiled expression: 11311 * 11312 * * `context` â `{object}` â an object against which any expressions embedded in the strings 11313 * are evaluated against (typically a scope object). 11314 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 11315 * `context`. 11316 * 11317 * The returned function also has the following properties: 11318 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 11319 * literal. 11320 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 11321 * constant literals. 11322 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 11323 * set to a function to change its value on the given context. 11324 * 11325 */ 11326 11327 11328/** 11329 * @ngdoc provider 11330 * @name $parseProvider 11331 * @kind function 11332 * 11333 * @description 11334 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 11335 * service. 11336 */ 11337function $ParseProvider() { 11338 var cacheDefault = {}; 11339 var cacheExpensive = {}; 11340 11341 var $parseOptions = { 11342 csp: false, 11343 unwrapPromises: false, 11344 logPromiseWarnings: true, 11345 expensiveChecks: false 11346 }; 11347 11348 11349 /** 11350 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 11351 * 11352 * @ngdoc method 11353 * @name $parseProvider#unwrapPromises 11354 * @description 11355 * 11356 * **This feature is deprecated, see deprecation notes below for more info** 11357 * 11358 * If set to true (default is false), $parse will unwrap promises automatically when a promise is 11359 * found at any part of the expression. In other words, if set to true, the expression will always
11360 * result in a non-promise value. 11361 * 11362 * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, 11363 * the fulfillment value is used in place of the promise while evaluating the expression. 11364 * 11365 * **Deprecation notice** 11366 * 11367 * This is a feature that didn't prove to be wildly useful or popular, primarily because of the 11368 * dichotomy between data access in templates (accessed as raw values) and controller code 11369 * (accessed as promises). 11370 * 11371 * In most code we ended up resolving promises manually in controllers anyway and thus unifying 11372 * the model access there. 11373 * 11374 * Other downsides of automatic promise unwrapping: 11375 * 11376 * - when building components it's often desirable to receive the raw promises 11377 * - adds complexity and slows down expression evaluation 11378 * - makes expression code pre-generation unattractive due to the amount of code that needs to be 11379 * generated 11380 * - makes IDE auto-completion and tool support hard 11381 * 11382 * **Warning Logs** 11383 * 11384 * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a 11385 * promise (to reduce the noise, each expression is logged only once). To disable this logging use 11386 * `$parseProvider.logPromiseWarnings(false)` api. 11387 * 11388 * 11389 * @param {boolean=} value New value. 11390 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 11391 * setter. 11392 */ 11393 this.unwrapPromises = function(value) { 11394 if (isDefined(value)) { 11395 $parseOptions.unwrapPromises = !!value; 11396 return this; 11397 } else { 11398 return $parseOptions.unwrapPromises; 11399 } 11400 }; 11401 11402 11403 /** 11404 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 11405 * 11406 * @ngdoc method 11407 * @name $parseProvider#logPromiseWarnings 11408 * @description 11409 * 11410 * Controls whether Angular should log a warning on any encounter of a promise in an expression. 11411 * 11412 * The default is set to `true`. 11413 * 11414 * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. 11415 * 11416 * @param {boolean=} value New value. 11417 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 11418 * setter. 11419 */ 11420 this.logPromiseWarnings = function(value) { 11421 if (isDefined(value)) { 11422 $parseOptions.logPromiseWarnings = value; 11423 return this; 11424 } else { 11425 return $parseOptions.logPromiseWarnings; 11426 } 11427 }; 11428 11429 11430 this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { 11431 $parseOptions.csp = $sniffer.csp; 11432 var $parseOptionsExpensive = { 11433 csp: $parseOptions.csp, 11434 unwrapPromises: $parseOptions.unwrapPromises, 11435 logPromiseWarnings: $parseOptions.logPromiseWarnings, 11436 expensiveChecks: true 11437 }; 11438 11439 promiseWarning = function promiseWarningFn(fullExp) { 11440 if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; 11441 promiseWarningCache[fullExp] = true; 11442 $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + 11443 'Automatic unwrapping of promises in Angular expressions is deprecated.'); 11444 }; 11445 11446 return function(exp, expensiveChecks) { 11447 var parsedExpression; 11448 11449 switch (typeof exp) { 11450 case 'string': 11451 11452 var cache = (expensiveChecks ? cacheExpensive : cacheDefault); 11453 if (cache.hasOwnProperty(exp)) { 11454 return cache[exp]; 11455 } 11456 11457 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; 11458 var lexer = new Lexer(parseOptions); 11459 var parser = new Parser(lexer, $filter, parseOptions); 11460 parsedExpression = parser.parse(exp); 11461 11462 if (exp !== 'hasOwnProperty') { 11463 // Only cache the value if it's not going to mess up the cache object 11464 // This is more performant that using Object.prototype.hasOwnProperty.call 11465 cache[exp] = parsedExpression; 11466 } 11467 11468 return parsedExpression; 11469 11470 case 'function': 11471 return exp; 11472 11473 default: 11474 return noop; 11475 } 11476 }; 11477 }]; 11478} 11479 11480/** 11481 * @ngdoc service 11482 * @name $q 11483 * @requires $rootScope 11484 * 11485 * @description 11486 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 11487 * when they are done processing. 11488 * 11489 * This is an implementation of promises/deferred objects inspired by 11490 * [Kris Kowal's Q](https://github.com/kriskowal/q). 11491 * 11492 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 11493 * interface for interacting with an object that represents the result of an action that is 11494 * performed asynchronously, and may or may not be finished at any given point in t
11494ime. 11495 * 11496 * From the perspective of dealing with error handling, deferred and promise APIs are to 11497 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 11498 * 11499 * ```js 11500 * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` 11501 * // are available in the current lexical scope (they could have been injected or passed in). 11502 * 11503 * function asyncGreet(name) { 11504 * var deferred = $q.defer(); 11505 * 11506 * setTimeout(function() { 11507 * deferred.notify('About to greet ' + name + '.'); 11508 * 11509 * if (okToGreet(name)) { 11510 * deferred.resolve('Hello, ' + name + '!'); 11511 * } else { 11512 * deferred.reject('Greeting ' + name + ' is not allowed.'); 11513 * } 11514 * }, 1000); 11515 * 11516 * return deferred.promise; 11517 * } 11518 * 11519 * var promise = asyncGreet('Robin Hood'); 11520 * promise.then(function(greeting) { 11521 * alert('Success: ' + greeting); 11522 * }, function(reason) { 11523 * alert('Failed: ' + reason); 11524 * }, function(update) { 11525 * alert('Got notification: ' + update); 11526 * }); 11527 * ``` 11528 * 11529 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 11530 * comes in the way of guarantees that promise and deferred APIs make, see 11531 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 11532 * 11533 * Additionally the promise api allows for composition that is very hard to do with the 11534 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 11535 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 11536 * section on serial or parallel joining of promises. 11537 * 11538 * 11539 * # The Deferred API 11540 * 11541 * A new instance of deferred is constructed by calling `$q.defer()`. 11542 * 11543 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 11544 * that can be used for signaling the successful or unsuccessful completion, as well as the status 11545 * of the task. 11546 * 11547 * **Methods** 11548 * 11549 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 11550 * constructed via `$q.reject`, the promise will be rejected instead. 11551 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 11552 * resolving it with a rejection constructed via `$q.reject`. 11553 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 11554 * multiple times before the promise is either resolved or rejected. 11555 * 11556 * **Properties** 11557 * 11558 * - promise â `{Promise}` â promise object associated with this deferred. 11559 * 11560 * 11561 * # The Promise API 11562 * 11563 * A new promise instance is created when a deferred instance is created and can be retrieved by 11564 * calling `deferred.promise`. 11565 * 11566 * The purpose of the promise object is to allow for interested parties to get access to the result 11567 * of the deferred task when it completes. 11568 * 11569 * **Methods** 11570 * 11571 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 11572 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 11573 * as soon as the result is available. The callbacks are called with a single argument: the result 11574 * or rejection reason. Additionally, the notify callback may be called zero or more times to 11575 * provide a progress indication, before the promise is resolved or rejected. 11576 * 11577 * This method *returns a new promise* which is resolved or rejected via the return value of the 11578 * `successCallback`, `errorCallback`. It also notifies via the return value of the 11579 * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback 11580 * method. 11581 * 11582 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 11583 * 11584 * Because `catch` is a reserved word in JavaScript and reserved keywords are not supported as 11585 * property names by ES3, you'll need to invoke the method like `promise['catch'](callback)` or 11586 * `promise.then(null, errorCallback)` to make your code IE8 and Android 2.x compatible. 11587 * 11588 * - `finally(callback)` â allows you to observe either the fulfillment or rejection of a promise, 11589 * but to do so without modifying the final value. This is useful to release resources or do some 11590 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 11591 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 11592 * more information. 11593 * 11594 * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as 11595 * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to
11596 * make your code IE8 and Android 2.x compatible. 11597 * 11598 * # Chaining promises 11599 * 11600 * Because calling the `then` method of a promise returns a new derived promise, it is easily 11601 * possible to create a chain of promises: 11602 * 11603 * ```js 11604 * promiseB = promiseA.then(function(result) { 11605 * return result + 1; 11606 * }); 11607 * 11608 * // promiseB will be resolved immediately after promiseA is resolved and its value 11609 * // will be the result of promiseA incremented by 1 11610 * ``` 11611 * 11612 * It is possible to create chains of any length and since a promise can be resolved with another 11613 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 11614 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 11615 * $http's response interceptors. 11616 * 11617 * 11618 * # Differences between Kris Kowal's Q and $q 11619 * 11620 * There are two main differences: 11621 * 11622 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 11623 * mechanism in angular, which means faster propagation of resolution or rejection into your 11624 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 11625 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
11626 * all the important functionality needed for common async tasks. 11627 * 11628 * # Testing 11629 * 11630 * ```js 11631 * it('should simulate promise', inject(function($q, $rootScope) { 11632 * var deferred = $q.defer(); 11633 * var promise = deferred.promise; 11634 * var resolvedValue; 11635 * 11636 * promise.then(function(value) { resolvedValue = value; }); 11637 * expect(resolvedValue).toBeUndefined(); 11638 * 11639 * // Simulate resolving of promise 11640 * deferred.resolve(123); 11641 * // Note that the 'then' function does not get called synchronously. 11642 * // This is because we want the promise API to always be async, whether or not 11643 * // it got called synchronously or asynchronously. 11644 * expect(resolvedValue).toBeUndefined(); 11645 * 11646 * // Propagate promise resolution to 'then' functions using $apply(). 11647 * $rootScope.$apply(); 11648 * expect(resolvedValue).toEqual(123); 11649 * })); 11650 * ``` 11651 */ 11652function $QProvider() { 11653 11654 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 11655 return qFactory(function(callback) { 11656 $rootScope.$evalAsync(callback); 11657 }, $exceptionHandler); 11658 }]; 11659} 11660 11661 11662/** 11663 * Constructs a promise manager. 11664 * 11665 * @param {function(Function)} nextTick Function for executing functions in the next turn. 11666 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 11667 * debugging purposes. 11668 * @returns {object} Promise manager. 11669 */ 11670function qFactory(nextTick, exceptionHandler) { 11671 11672 /** 11673 * @ngdoc method 11674 * @name $q#defer 11675 * @kind function 11676 * 11677 * @description 11678 * Creates a `Deferred` object which represents a task which will finish in the future. 11679 * 11680 * @returns {Deferred} Returns a new instance of deferred. 11681 */ 11682 var defer = function() { 11683 var pending = [], 11684 value, deferred; 11685 11686 deferred = { 11687 11688 resolve: function(val) { 11689 if (pending) { 11690 var callbacks = pending; 11691 pending = undefined; 11692 value = ref(val); 11693 11694 if (callbacks.length) { 11695 nextTick(function() { 11696 var callback; 11697 for (var i = 0, ii = callbacks.length; i < ii; i++) { 11698 callback = callbacks[i]; 11699 value.then(callback[0], callback[1], callback[2]); 11700 } 11701 }); 11702 } 11703 } 11704 }, 11705 11706 11707 reject: function(reason) { 11708 deferred.resolve(createInternalRejectedPromise(reason)); 11709 }, 11710 11711 11712 notify: function(progress) { 11713 if (pending) { 11714 var callbacks = pending; 11715 11716 if (pending.length) { 11717 nextTick(function() { 11718 var callback; 11719 for (var i = 0, ii = callbacks.length; i < ii; i++) { 11720 callback = callbacks[i]; 11721 callback[2](progress); 11722 } 11723 }); 11724 } 11725 } 11726 }, 11727 11728 11729 promise: { 11730 then: function(callback, errback, progressback) { 11731 var result = defer(); 11732 11733 var wrappedCallback = function(value) { 11734 try { 11735 result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); 11736 } catch(e) { 11737 result.reject(e); 11738 exceptionHandler(e); 11739 } 11740 }; 11741 11742 var wrappedErrback = function(reason) { 11743 try { 11744 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11745 } catch(e) { 11746 result.reject(e); 11747 exceptionHandler(e); 11748 } 11749 }; 11750 11751 var wrappedProgressback = function(progress) { 11752 try { 11753 result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); 11754 } catch(e) { 11755 exceptionHandler(e); 11756 } 11757 }; 11758 11759 if (pending) { 11760 pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); 11761 } else { 11762 value.then(wrappedCallback, wrappedErrback, wrappedProgressback); 11763 } 11764 11765 return result.promise; 11766 }, 11767 11768 "catch": function(callback) { 11769 return this.then(null, callback); 11770 }, 11771 11772 "finally": function(callback) { 11773 11774 function makePromise(value, resolved) { 11775 var result = defer(); 11776 if (resolved) { 11777 result.resolve(value); 11778 } else { 11779 result.reject(value); 11780 } 11781 return result.promise; 11782 } 11783 11784 function handleCallback(value, isResolved) { 11785 var callbackOutput = null; 11786 try { 11787 callbackOutput = (callback ||defaultCallback)(); 11788 } catch(e) { 11789 return makePromise(e, false); 11790 }
11791 if (isPromiseLike(callbackOutput)) { 11792 return callbackOutput.then(function() { 11793 return makePromise(value, isResolved); 11794 }, function(error) { 11795 return makePromise(error, false); 11796 }); 11797 } else { 11798 return makePromise(value, isResolved); 11799 } 11800 } 11801 11802 return this.then(function(value) { 11803 return handleCallback(value, true); 11804 }, function(error) { 11805 return handleCallback(error, false); 11806 }); 11807 } 11808 } 11809 }; 11810 11811 return deferred; 11812 }; 11813 11814 11815 var ref = function(value) { 11816 if (isPromiseLike(value)) return value; 11817 return { 11818 then: function(callback) { 11819 var result = defer(); 11820 nextTick(function() { 11821 result.resolve(callback(value)); 11822 }); 11823 return result.promise; 11824 } 11825 }; 11826 }; 11827 11828 11829 /** 11830 * @ngdoc method 11831 * @name $q#reject 11832 * @kind function 11833 * 11834 * @description 11835 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 11836 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 11837 * a promise chain, you don't need to worry about it. 11838 * 11839 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 11840 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 11841 * a promise error callback and you want to forward the error to the promise derived from the 11842 * current promise, you have to "rethrow" the error by returning a rejection constructed via 11843 * `reject`. 11844 * 11845 * ```js 11846 * promiseB = promiseA.then(function(result) { 11847 * // success: do something and resolve promiseB 11848 * // with the old or a new result 11849 * return result; 11850 * }, function(reason) { 11851 * // error: handle the error if possible and 11852 * // resolve promiseB with newPromiseOrValue, 11853 * // otherwise forward the rejection to promiseB 11854 * if (canHandle(reason)) { 11855 * // handle the error and recover 11856 * return newPromiseOrValue; 11857 * } 11858 * return $q.reject(reason); 11859 * }); 11860 * ``` 11861 * 11862 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 11863 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 11864 */ 11865 var reject = function(reason) { 11866 var result = defer(); 11867 result.reject(reason); 11868 return result.promise; 11869 }; 11870 11871 var createInternalRejectedPromise = function(reason) { 11872 return { 11873 then: function(callback, errback) { 11874 var result = defer(); 11875 nextTick(function() { 11876 try { 11877 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11878 } catch(e) { 11879 result.reject(e); 11880 exceptionHandler(e); 11881 } 11882 }); 11883 return result.promise; 11884 } 11885 }; 11886 }; 11887 11888 11889 /** 11890 * @ngdoc method 11891 * @name $q#when 11892 * @kind function 11893 * 11894 * @description 11895 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 11896 * This is useful when you are dealing with an object that might or might not be a promise, or if 11897 * the promise comes from a source that can't be trusted. 11898 * 11899 * @param {*} value Value or a promise 11900 * @returns {Promise} Returns a promise of the passed value or promise 11901 */ 11902 var when = function(value, callback, errback, progressback) { 11903 var result = defer(), 11904 done; 11905 11906 var wrappedCallback = function(value) { 11907 try { 11908 return (isFunction(callback) ? callback : defaultCallback)(value); 11909 } catch (e) { 11910 exceptionHandler(e); 11911 return reject(e); 11912 } 11913 }; 11914 11915 var wrappedErrback = function(reason) { 11916 try { 11917 return (isFunction(errback) ? errback : defaultErrback)(reason); 11918 } catch (e) { 11919 exceptionHandler(e); 11920 return reject(e); 11921 } 11922 }; 11923 11924 var wrappedProgressback = function(progress) { 11925 try { 11926 return (isFunction(progressback) ? progressback : defaultCallback)(progress); 11927 } catch (e) { 11928 exceptionHandler(e); 11929 } 11930 }; 11931 11932 nextTick(function() { 11933 ref(value).then(function(value) { 11934 if (done) return; 11935 done = true; 11936 result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); 11937 }, function(reason) { 11938 if (done) return; 11939 done = true; 11940 result.resolve(wrappedErrback(reason)); 11941 }, function(progress) { 11942 if (done) return; 11943 result.notify(wrappedProgressback(progress)); 11944 }); 11945 }); 11946 11947 return result.promise; 11948 }; 11949 11950 11951 function defaultCallback(value) { 11952 return value; 11953 } 11954 11955 11956 function defaultErrback(reason) { 11957 return reject(reason); 11958 } 11959 11960 11961 /** 11962 * @ngdoc method 11963 * @name $q#all 11964 * @kind function 11965 * 11966 * @description 11967 * Combines multiple promises into a single promise that is resolved when all of the input 11968 * promises are resolved. 11969 * 11970 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 11971 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 11972 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 11973 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 11974 * with the same rejection value. 11975 */ 11976 function all(promises) { 11977 var deferred = defer(), 11978 counter = 0, 11979 results = isArray(promises) ? [] : {}; 11980
11981 forEach(promises, function(promise, key) { 11982 counter++; 11983 ref(promise).then(function(value) { 11984 if (results.hasOwnProperty(key)) return; 11985 results[key] = value; 11986 if (!(--counter)) deferred.resolve(results); 11987 }, function(reason) { 11988 if (results.hasOwnProperty(key)) return; 11989 deferred.reject(reason); 11990 }); 11991 }); 11992 11993 if (counter === 0) { 11994 deferred.resolve(results); 11995 } 11996 11997 return deferred.promise; 11998 } 11999 12000 return { 12001 defer: defer, 12002 reject: reject, 12003 when: when, 12004 all: all 12005 }; 12006} 12007 12008function $$RAFProvider(){ //rAF 12009 this.$get = ['$window', '$timeout', function($window, $timeout) { 12010 var requestAnimationFrame = $window.requestAnimationFrame || 12011 $window.webkitRequestAnimationFrame || 12012 $window.mozRequestAnimationFrame; 12013 12014 var cancelAnimationFrame = $window.cancelAnimationFrame || 12015 $window.webkitCancelAnimationFrame || 12016 $window.mozCancelAnimationFrame || 12017 $window.webkitCancelRequestAnimationFrame; 12018 12019 var rafSupported = !!requestAnimationFrame; 12020 var raf = rafSupported 12021 ? function(fn) { 12022 var id = requestAnimationFrame(fn); 12023 return function() { 12024 cancelAnimationFrame(id); 12025 }; 12026 } 12027 : function(fn) { 12028 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 12029 return function() { 12030 $timeout.cancel(timer); 12031 }; 12032 }; 12033 12034 raf.supported = rafSupported; 12035 12036 return raf; 12037 }]; 12038} 12039 12040/** 12041 * DESIGN NOTES 12042 * 12043 * The design decisions behind the scope are heavily favored for speed and memory consumption. 12044 * 12045 * The typical use of scope is to watch the expressions, which most of the time return the same 12046 * value as last time so we optimize the operation. 12047 * 12048 * Closures construction is expensive in terms of speed as well as memory: 12049 * - No closures, instead use prototypical inheritance for API 12050 * - Internal state needs to be stored on scope directly, which means that private state is 12051 * exposed as $$____ properties 12052 * 12053 * Loop operations are optimized by using while(count--) { ... } 12054 * - this means that in order to keep the same order of execution as addition we have to add 12055 * items to the array at the beginning (unshift) instead of at the end (push) 12056 * 12057 * Child scopes are created and removed often 12058 * - Using an array would be slow since inserts in middle are expensive so we use linked list 12059 * 12060 * There are few watches then a lot of observers. This is why you don't want the observer to be 12061 * implemented in the same way as watch. Watch requires return of initialization function which 12062 * are expensive to construct. 12063 */ 12064 12065 12066/** 12067 * @ngdoc provider 12068 * @name $rootScopeProvider 12069 * @description 12070 * 12071 * Provider for the $rootScope service. 12072 */ 12073 12074/** 12075 * @ngdoc method 12076 * @name $rootScopeProvider#digestTtl 12077 * @description 12078 * 12079 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 12080 * assuming that the model is unstable. 12081 * 12082 * The current default is 10 iterations. 12083 * 12084 * In complex applications it's possible that the dependencies between `$watch`s will result in 12085 * several digest iterations. However if an application needs more than the default 10 digest 12086 * iterations for its model to stabilize then you should investigate what is causing the model to 12087 * continuously change during the digest. 12088 * 12089 * Increasing the TTL could have performance implications, so you should not change it without 12090 * proper justification. 12091 * 12092 * @param {number} limit The number of digest iterations. 12093 */ 12094 12095 12096/** 12097 * @ngdoc service 12098 * @name $rootScope 12099 * @description 12100 * 12101 * Every application has a single root {@link ng.$rootScope.Scope scope}. 12102 * All other scopes are descendant scopes of the root scope. Scopes provide separation 12103 * between the model and the view, via a mechanism for watching the model for changes. 12104 * They also provide an event emission/broadcast and subscription facility. See the 12105 * {@link guide/scope developer guide on scopes}. 12106 */ 12107function $RootScopeProvider(){ 12108 var TTL = 10; 12109 var $rootScopeMinErr = minErr('$rootScope'); 12110 var lastDirtyWatch = null; 12111 12112 this.digestTtl = function(value) { 12113 if (arguments.length) { 12114 TTL = value; 12115 } 12116 return TTL; 12117 }; 12118 12119 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 12120 function( $injector, $exceptionHandler, $parse, $browser) { 12121 12122 /** 12123 * @ngdoc type 12124 * @name $rootScope.Scope 12125 *
12126 * @description 12127 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 12128 * {@link auto.$injector $injector}. Child scopes are created using the 12129 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 12130 * compiled HTML template is executed.) 12131 * 12132 * Here is a simple scope snippet to show how you can interact with the scope. 12133 * ```html 12134 * <file src="./test/ng/rootScopeSpec.js" tag="docs1" /> 12135 * ``` 12136 * 12137 * # Inheritance 12138 * A scope can inherit from a parent scope, as in this example: 12139 * ```js 12140 var parent = $rootScope; 12141 var child = parent.$new(); 12142 12143 parent.salutation = "Hello"; 12144 child.name = "World"; 12145 expect(child.salutation).toEqual('Hello'); 12146 12147 child.salutation = "Welcome"; 12148 expect(child.salutation).toEqual('Welcome'); 12149 expect(parent.salutation).toEqual('Hello'); 12150 * ``` 12151 * 12152 * 12153 * @param {Object.<string, function()>=} providers Map of service factory which need to be 12154 * provided for the current scope. Defaults to {@link ng}. 12155 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 12156 * append/override services provided by `providers`. This is handy 12157 * when unit-testing and having the need to override a default 12158 * service. 12159 * @returns {Object} Newly created scope. 12160 * 12161 */ 12162 function Scope() { 12163 this.$id = nextUid(); 12164 this.$$phase = this.$parent = this.$$watchers = 12165 this.$$nextSibling = this.$$prevSibling = 12166 this.$$childHead = this.$$childTail = null; 12167 this['this'] = this.$root = this; 12168 this.$$destroyed = false; 12169 this.$$asyncQueue = []; 12170 this.$$postDigestQueue = []; 12171 this.$$listeners = {}; 12172 this.$$listenerCount = {}; 12173 this.$$isolateBindings = {}; 12174 } 12175 12176 /** 12177 * @ngdoc property 12178 * @name $rootScope.Scope#$id 12179 * 12180 * @description 12181 * Unique scope ID (monotonically increasing) useful for debugging. 12182 */ 12183 12184 /** 12185 * @ngdoc property 12186 * @name $rootScope.Scope#$parent 12187 * 12188 * @description 12189 * Reference to the parent scope. 12190 */ 12191 12192 /** 12193 * @ngdoc property 12194 * @name $rootScope.Scope#$root 12195 * 12196 * @description 12197 * Reference to the root scope. 12198 */ 12199 12200 Scope.prototype = { 12201 constructor: Scope, 12202 /** 12203 * @ngdoc method 12204 * @name $rootScope.Scope#$new 12205 * @kind function 12206 * 12207 * @description 12208 * Creates a new child {@link ng.$rootScope.Scope scope}. 12209 * 12210 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 12211 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 12212 * 12213 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 12214 * desired for the scope and its child scopes to be permanently detached from the parent and 12215 * thus stop participating in model change detection and listener notification by invoking. 12216 * 12217 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 12218 * parent scope. The scope is isolated, as it can not see parent scope properties. 12219 * When creating widgets, it is useful for the widget to not accidentally read parent 12220 * state. 12221 * 12222 * @returns {Object} The newly created child scope. 12223 * 12224 */ 12225 $new: function(isolate) { 12226 var ChildScope, 12227 child; 12228 12229 if (isolate) { 12230 child = new Scope(); 12231 child.$root = this.$root; 12232 // ensure that there is just one async queue per $rootScope and its children 12233 child.$$asyncQueue = this.$$asyncQueue; 12234 child.$$postDigestQueue = this.$$postDigestQueue; 12235 } else { 12236 // Only create a child scope class if somebody asks for one, 12237 // but cache it to allow the VM to optimize lookups. 12238 if (!this.$$childScopeClass) { 12239 this.$$childScopeClass = function() {
12240 this.$$watchers = this.$$nextSibling = 12241 this.$$childHead = this.$$childTail = null; 12242 this.$$listeners = {}; 12243 this.$$listenerCount = {}; 12244 this.$id = nextUid(); 12245 this.$$childScopeClass = null; 12246 }; 12247 this.$$childScopeClass.prototype = this; 12248 } 12249 child = new this.$$childScopeClass(); 12250 } 12251 child['this'] = child; 12252 child.$parent = this; 12253 child.$$prevSibling = this.$$childTail; 12254 if (this.$$childHead) { 12255 this.$$childTail.$$nextSibling = child; 12256 this.$$childTail = child; 12257 } else { 12258 this.$$childHead = this.$$childTail = child; 12259 } 12260 return child; 12261 }, 12262 12263 /** 12264 * @ngdoc method 12265 * @name $rootScope.Scope#$watch 12266 * @kind function 12267 * 12268 * @description 12269 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 12270 * 12271 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 12272 * $digest()} and should return the value that will be watched. (Since 12273 * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the 12274 * `watchExpression` can execute multiple times per 12275 * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) 12276 * - The `listener` is called only when the value from the current `watchExpression` and the 12277 * previous call to `watchExpression` are not equal (with the exception of the initial run, 12278 * see below). Inequality is determined according to reference inequality, 12279 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 12280 * via the `!==` Javascript operator, unless `objectEquality == true` 12281 * (see next point) 12282 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 12283 * according to the {@link angular.equals} function. To save the value of the object for 12284 * later comparison, the {@link angular.copy} function is used. This therefore means that 12285 * watching complex objects will have adverse memory and performance implications. 12286 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 12287 * This is achieved by rerunning the watchers until no changes are detected. The rerun 12288 * iteration limit is 10 to prevent an infinite loop deadlock. 12289 * 12290 * 12291 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 12292 * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` 12293 * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a 12294 * change is detected, be prepared for multiple calls to your listener.) 12295 * 12296 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 12297 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 12298 * watcher. In rare cases, this is undesirable because the listener is called when the result 12299 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 12300 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 12301 * listener was called due to initialization. 12302 * 12303 * The example below contains an illustration of using a function as your $watch listener 12304 * 12305 * 12306 * # Example 12307 * ```js 12308 // let's assume that scope was dependency injected as the $rootScope 12309 var scope = $rootScope; 12310 scope.name = 'misko'; 12311 scope.counter = 0; 12312 12313 expect(scope.counter).toEqual(0); 12314 scope.$watch('name', function(newValue, oldValue) { 12315 scope.counter = scope.counter + 1; 12316 }); 12317 expect(scope.counter).toEqual(0); 12318 12319 scope.$digest(); 12320 // the listener is always called during the first $digest loop after it was registered 12321 expect(scope.counter).toEqual(1); 12322 12323 scope.$digest(); 12324 // but now it will not be called unless the value changes 12325 expect(scope.counter).toEqual(1); 12326 12327 scope.name = 'adam'; 12328 scope.$digest(); 12329 expect(scope.counter).toEqual(2); 12330 12331 12332 12333 // Using a listener function 12334 var food; 12335 scope.foodCounter = 0; 12336 expect(scope.foodCounter).toEqual(0); 12337 scope.$watch( 12338 // This is the listener function 12339 function() { return food; }, 12340 // This is the change handler 12341 function(newValue, oldValue) { 12342 if ( newValue !== oldValue ) { 12343 // Only increment the counter if the value changed 12344 scope.foodCounter = scope.foodCounter + 1; 12345 } 12346 } 12347 ); 12348 // No digest has been run so the counter will be zero 12349 expect(scope.foodCounter).toEqual(0); 12350 12351 // Run the digest but since food has not changed count will still be zero 12352 scope.$digest(); 12353 expect(scope.foodCounter).toEqual(0); 12354 12355 // Update food and run digest. Now the counter will increment 12356 food = 'cheeseburger'; 12357 scope.$digest(); 12358 expect(scope.foodCounter).toEqual(1); 12359 12360 * ``` 12361 * 12362 * 12363 * 12364 * @param {(function()|string)} watchExpression Expression that is evaluated on each 12365 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 12366 * a call to the `listener`. 12367 * 12368 * - `string`: Evaluated as {@link guide/expression expression} 12369 * - `function(scope)`: called with current `scope` as a parameter. 12370 * @param {(function()|string)=} listener Callback called whenever the return value of 12371 * the `watchExpression` changes. 12372 * 12373 * - `string`: Evaluated as {@link guide/expression expression} 12374 * - `function(newValue, oldValue, scope)`: called with current and previous values as 12375 * parameters. 12376 * 12377 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of 12378 * comparing for reference equality. 12379 * @returns {function()} Returns a deregistration function for this listener. 12380 */ 12381 $watch: function(watchExp, listener, objectEquality) { 12382 var scope = this, 12383 get = compileToFn(watchExp, 'watch'), 12384 array = scope.$$watchers, 12385 watcher = { 12386 fn: listener, 12387 last: initWatchVal, 12388 get: get, 12389 exp: watchExp, 12390 eq: !!objectEquality 12391 }; 12392 12393 lastDirtyWatch = null; 12394 12395 // in the case user pass string, we need to compile it, do we really need this ? 12396 if (!isFunction(listener)) { 12397 var listenFn = compileToFn(listener || noop, 'listener'); 12398 watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; 12399 } 12400 12401 if (typeof watchExp == 'string' && get.constant) { 12402 var originalFn = watcher.fn; 12403 watcher.fn = function(newVal, oldVal, scope) { 12404 originalFn.call(this, newVal, oldVal, scope); 12405 arrayRemove(array, watcher); 12406 }; 12407 } 12408 12409 if (!array) { 12410 array = scope.$$watchers = []; 12411 } 12412 // we use unshift since we use a while loop in $digest for speed. 12413 // the while loop reads in reverse order. 12414 array.unshift(watcher); 12415 12416 return function deregisterWatch() { 12417 arrayRemove(array, watcher); 12418 lastDirtyWatch = null; 12419 }; 12420 }, 12421 12422 12423 /** 12424 * @ngdoc method 12425 * @name $rootScope.Scope#$watchCollection 12426 * @kind function 12427 *
12428 * @description 12429 * Shallow watches the properties of an object and fires whenever any of the properties change 12430 * (for arrays, this implies watching the array items; for object maps, this implies watching 12431 * the properties). If a change is detected, the `listener` callback is fired. 12432 * 12433 * - The `obj` collection is observed via standard $watch operation and is examined on every 12434 * call to $digest() to see if any items have been added, removed, or moved. 12435 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 12436 * adding, removing, and moving items belonging to an object or array. 12437 * 12438 * 12439 * # Example 12440 * ```js 12441 $scope.names = ['igor', 'matias', 'misko', 'james']; 12442 $scope.dataCount = 4; 12443 12444 $scope.$watchCollection('names', function(newNames, oldNames) { 12445 $scope.dataCount = newNames.length; 12446 }); 12447 12448 expect($scope.dataCount).toEqual(4); 12449 $scope.$digest(); 12450 12451 //still at 4 ... no changes 12452 expect($scope.dataCount).toEqual(4); 12453 12454 $scope.names.pop(); 12455 $scope.$digest(); 12456 12457 //now there's been a change 12458 expect($scope.dataCount).toEqual(3); 12459 * ``` 12460 * 12461 * 12462 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 12463 * expression value should evaluate to an object or an array which is observed on each 12464 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 12465 * collection will trigger a call to the `listener`. 12466 * 12467 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 12468 * when a change is detected. 12469 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 12470 * - The `oldCollection` object is a copy of the former collection data. 12471 * Due to performance considerations, the`oldCollection` value is computed only if the 12472 * `listener` function declares two or more arguments. 12473 * - The `scope` argument refers to the current scope. 12474 * 12475 * @returns {function()} Returns a de-registration function for this listener. When the 12476 * de-registration function is executed, the internal watch operation is terminated. 12477 */ 12478 $watchCollection: function(obj, listener) { 12479 var self = this; 12480 // the current value, updated on each dirty-check run 12481 var newValue; 12482 // a shallow copy of the newValue from the last dirty-check run, 12483 // updated to match newValue during dirty-check run 12484 var oldValue; 12485 // a shallow copy of the newValue from when the last change happened 12486 var veryOldValue; 12487 // only track veryOldValue if the listener is asking for it 12488 var trackVeryOldValue = (listener.length > 1); 12489 var changeDetected = 0; 12490 var objGetter = $parse(obj); 12491 var internalArray = []; 12492 var internalObject = {}; 12493 var initRun = true; 12494 var oldLength = 0; 12495 12496 function $watchCollectionWatch() { 12497 newValue = objGetter(self); 12498 var newLength, key, bothNaN; 12499 12500 if (!isObject(newValue)) { // if primitive 12501 if (oldValue !== newValue) { 12502 oldValue = newValue; 12503 changeDetected++; 12504 } 12505 } else if (isArrayLike(newValue)) { 12506 if (oldValue !== internalArray) { 12507 // we are transitioning from something which was not an array into array. 12508 oldValue = internalArray; 12509 oldLength = oldValue.length = 0; 12510 changeDetected++; 12511 } 12512 12513 newLength = newValue.length; 12514 12515 if (oldLength !== newLength) { 12516 // if lengths do not match we need to trigger change notification 12517 changeDetected++; 12518 oldValue.length = oldLength = newLength; 12519 } 12520 // copy the items to oldValue and look for changes. 12521 for (var i = 0; i < newLength; i++) { 12522 bothNaN = (oldValue[i] !== oldValue[i]) && 12523 (newValue[i] !== newValue[i]);
12524 if (!bothNaN && (oldValue[i] !== newValue[i])) { 12525 changeDetected++; 12526 oldValue[i] = newValue[i]; 12527 } 12528 } 12529 } else { 12530 if (oldValue !== internalObject) { 12531 // we are transitioning from something which was not an object into object. 12532 oldValue = internalObject = {}; 12533 oldLength = 0; 12534 changeDetected++; 12535 } 12536 // copy the items to oldValue and look for changes. 12537 newLength = 0; 12538 for (key in newValue) { 12539 if (newValue.hasOwnProperty(key)) { 12540 newLength++; 12541 if (oldValue.hasOwnProperty(key)) { 12542 bothNaN = (oldValue[key] !== oldValue[key]) && 12543 (newValue[key] !== newValue[key]); 12544 if (!bothNaN && (oldValue[key] !== newValue[key])) { 12545 changeDetected++; 12546 oldValue[key] = newValue[key]; 12547 } 12548 } else { 12549 oldLength++; 12550 oldValue[key] = newValue[key]; 12551 changeDetected++; 12552 } 12553 } 12554 } 12555 if (oldLength > newLength) { 12556 // we used to have more keys, need to find them and destroy them. 12557 changeDetected++; 12558 for(key in oldValue) { 12559 if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { 12560 oldLength--; 12561 delete oldValue[key]; 12562 } 12563 } 12564 } 12565 } 12566 return changeDetected; 12567 } 12568 12569 function $watchCollectionAction() { 12570 if (initRun) { 12571 initRun = false; 12572 listener(newValue, newValue, self); 12573 } else { 12574 listener(newValue, veryOldValue, self); 12575 } 12576 12577 // make a copy for the next time a collection is changed 12578 if (trackVeryOldValue) { 12579 if (!isObject(newValue)) { 12580 //primitive 12581 veryOldValue = newValue; 12582 } else if (isArrayLike(newValue)) { 12583 veryOldValue = new Array(newValue.length); 12584 for (var i = 0; i < newValue.length; i++) { 12585 veryOldValue[i] = newValue[i]; 12586 } 12587 } else { // if object 12588 veryOldValue = {}; 12589 for (var key in newValue) { 12590 if (hasOwnProperty.call(newValue, key)) { 12591 veryOldValue[key] = newValue[key]; 12592 } 12593 } 12594 } 12595 } 12596 } 12597 12598 return this.$watch($watchCollectionWatch, $watchCollectionAction); 12599 }, 12600 12601 /** 12602 * @ngdoc method 12603 * @name $rootScope.Scope#$digest 12604 * @kind function 12605 * 12606 * @description 12607 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 12608 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 12609 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 12610 * until no more listeners are firing. This means that it is possible to get into an infinite 12611 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 12612 * iterations exceeds 10. 12613 * 12614 * Usually, you don't call `$digest()` directly in 12615 * {@link ng.directive:ngController controllers} or in 12616 * {@link ng.$compileProvider#directive directives}. 12617 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 12618 * a {@link ng.$compileProvider#directive directives}), which will force a `$digest()`. 12619 * 12620 * If you want to be notified whenever `$digest()` is called, 12621 * you can register a `watchExpression` function with 12622 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 12623 * 12624 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 12625 * 12626 * # Example 12627 * ```js 12628 var scope = ...; 12629 scope.name = 'misko'; 12630 scope.counter = 0; 12631 12632 expect(scope.counter).toEqual(0); 12633 scope.$watch('name', function(newValue, oldValue) { 12634 scope.counter = scope.counter + 1; 12635 }); 12636 expect(scope.counter).toEqual(0); 12637 12638 scope.$digest(); 12639 // the listener is always called during the first $digest loop after it was registered 12640 expect(scope.counter).toEqual(1); 12641 12642 scope.$digest(); 12643 // but now it will not be called unless the value changes 12644 expect(scope.counter).toEqual(1); 12645 12646 scope.name = 'adam'; 12647 scope.$digest(); 12648 expect(scope.counter).toEqual(2); 12649 * ``` 12650 * 12651 */ 12652 $digest: function() { 12653 var watch, value, last, 12654 watchers, 12655 asyncQueue = this.$$asyncQueue, 12656 postDigestQueue = this.$$postDigestQueue, 12657 length, 12658 dirty, ttl = TTL, 12659 next, current, target = this, 12660 watchLog = [], 12661 logIdx, logMsg, asyncTask; 12662 12663 beginPhase('$digest'); 12664 // Check for changes to browser url that happened in sync before the call to $digest 12665 $browser.$$checkUrlChange(); 12666 12667 lastDirtyWatch = null; 12668
12669 do { // "while dirty" loop 12670 dirty = false; 12671 current = target; 12672 12673 while(asyncQueue.length) { 12674 try { 12675 asyncTask = asyncQueue.shift(); 12676 asyncTask.scope.$eval(asyncTask.expression); 12677 } catch (e) { 12678 clearPhase(); 12679 $exceptionHandler(e); 12680 } 12681 lastDirtyWatch = null; 12682 } 12683 12684 traverseScopesLoop: 12685 do { // "traverse the scopes" loop 12686 if ((watchers = current.$$watchers)) { 12687 // process our watches 12688 length = watchers.length; 12689 while (length--) { 12690 try { 12691 watch = watchers[length]; 12692 // Most common watches are on primitives, in which case we can short 12693 // circuit it with === operator, only when === fails do we use .equals 12694 if (watch) { 12695 if ((value = watch.get(current)) !== (last = watch.last) && 12696 !(watch.eq 12697 ? equals(value, last) 12698 : (typeof value === 'number' && typeof last === 'number' 12699 && isNaN(value) && isNaN(last)))) { 12700 dirty = true; 12701 lastDirtyWatch = watch; 12702 watch.last = watch.eq ? copy(value, null) : value; 12703 watch.fn(value, ((last === initWatchVal) ? value : last), current); 12704 if (ttl < 5) { 12705 logIdx = 4 - ttl; 12706 if (!watchLog[logIdx]) watchLog[logIdx] = []; 12707 logMsg = (isFunction(watch.exp)) 12708 ? 'fn: ' + (watch.exp.name || watch.exp.toString()) 12709 : watch.exp; 12710 logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); 12711 watchLog[logIdx].push(logMsg); 12712 } 12713 } else if (watch === lastDirtyWatch) { 12714 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 12715 // have already been tested. 12716 dirty = false; 12717 break traverseScopesLoop; 12718 } 12719 } 12720 } catch (e) { 12721 clearPhase(); 12722 $exceptionHandler(e); 12723 } 12724 } 12725 } 12726 12727 // Insanity Warning: scope depth-first traversal 12728 // yes, this code is a bit crazy, but it works and we have tests to prove it! 12729 // this piece should be kept in sync with the traversal in $broadcast 12730 if (!(next = (current.$$childHead || 12731 (current !== target && current.$$nextSibling)))) { 12732 while(current !== target && !(next = current.$$nextSibling)) { 12733 current = current.$parent; 12734 } 12735 } 12736 } while ((current = next)); 12737 12738 // `break traverseScopesLoop;` takes us to here 12739 12740 if((dirty || asyncQueue.length) && !(ttl--)) { 12741 clearPhase(); 12742 throw $rootScopeMinErr('infdig', 12743 '{0} $digest() iterations reached. Aborting!\n' + 12744 'Watchers fired in the last 5 iterations: {1}', 12745 TTL, toJson(watchLog)); 12746 } 12747 12748 } while (dirty || asyncQueue.length); 12749 12750 clearPhase(); 12751 12752 while(postDigestQueue.length) { 12753 try { 12754 postDigestQueue.shift()(); 12755 } catch (e) { 12756 $exceptionHandler(e); 12757 } 12758 } 12759 }, 12760 12761 12762 /** 12763 * @ngdoc event 12764 * @name $rootScope.Scope#$destroy 12765 * @eventType broadcast on scope being destroyed 12766 * 12767 * @description 12768 * Broadcasted when a scope and its children are being destroyed. 12769 * 12770 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 12771 * clean up DOM bindings before an element is removed from the DOM. 12772 */ 12773 12774 /** 12775 * @ngdoc method 12776 * @name $rootScope.Scope#$destroy 12777 * @kind function 12778 *
12779 * @description 12780 * Removes the current scope (and all of its children) from the parent scope. Removal implies 12781 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 12782 * propagate to the current scope and its children. Removal also implies that the current 12783 * scope is eligible for garbage collection. 12784 * 12785 * The `$destroy()` is usually used by directives such as 12786 * {@link ng.directive:ngRepeat ngRepeat} for managing the 12787 * unrolling of the loop. 12788 * 12789 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 12790 * Application code can register a `$destroy` event handler that will give it a chance to 12791 * perform any necessary cleanup. 12792 * 12793 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 12794 * clean up DOM bindings before an element is removed from the DOM. 12795 */ 12796 $destroy: function() { 12797 // we can't destroy the root scope or a scope that has been already destroyed 12798 if (this.$$destroyed) return; 12799 var parent = this.$parent; 12800 12801 this.$broadcast('$destroy'); 12802 this.$$destroyed = true; 12803 if (this === $rootScope) return; 12804 12805 forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); 12806 12807 // sever all the references to parent scopes (after this cleanup, the current scope should 12808 // not be retained by any of our references and should be eligible for garbage collection) 12809 if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 12810 if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 12811 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 12812 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 12813 12814 12815 // All of the code below is bogus code that works around V8's memory leak via optimized code 12816 // and inline caches. 12817 // 12818 // see: 12819 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 12820 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 12821 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 12822 12823 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 12824 this.$$childTail = this.$root = null; 12825 12826 // don't reset these to null in case some async task tries to register a listener/watch/task 12827 this.$$listeners = {}; 12828 this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = []; 12829 12830 // prevent NPEs since these methods have references to properties we nulled out 12831 this.$destroy = this.$digest = this.$apply = noop; 12832 this.$on = this.$watch = function() { return noop; }; 12833 }, 12834 12835 /** 12836 * @ngdoc method 12837 * @name $rootScope.Scope#$eval 12838 * @kind function 12839 * 12840 * @description 12841 * Executes the `expression` on the current scope and returns the result. Any exceptions in 12842 * the expression are propagated (uncaught). This is useful when evaluating Angular 12843 * expressions. 12844 * 12845 * # Example 12846 * ```js 12847 var scope = ng.$rootScope.Scope(); 12848 scope.a = 1; 12849 scope.b = 2; 12850 12851 expect(scope.$eval('a+b')).toEqual(3); 12852 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 12853 * ``` 12854 * 12855 * @param {(string|function())=} expression An angular expression to be executed. 12856 * 12857 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12858 * - `function(scope)`: execute the function with the current `scope` parameter. 12859 * 12860 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 12861 * @returns {*} The result of evaluating the expression. 12862 */ 12863 $eval: function(expr, locals) { 12864 return $parse(expr)(this, locals); 12865 }, 12866 12867 /** 12868 * @ngdoc method 12869 * @name $rootScope.Scope#$evalAsync 12870 * @kind function 12871 *
12872 * @description 12873 * Executes the expression on the current scope at a later point in time. 12874 * 12875 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 12876 * that: 12877 * 12878 * - it will execute after the function that scheduled the evaluation (preferably before DOM 12879 * rendering). 12880 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 12881 * `expression` execution. 12882 * 12883 * Any exceptions from the execution of the expression are forwarded to the 12884 * {@link ng.$exceptionHandler $exceptionHandler} service. 12885 * 12886 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 12887 * will be scheduled. However, it is encouraged to always call code that changes the model 12888 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 12889 * 12890 * @param {(string|function())=} expression An angular expression to be executed. 12891 * 12892 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12893 * - `function(scope)`: execute the function with the current `scope` parameter. 12894 * 12895 */ 12896 $evalAsync: function(expr) { 12897 // if we are outside of an $digest loop and this is the first time we are scheduling async 12898 // task also schedule async auto-flush 12899 if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { 12900 $browser.defer(function() { 12901 if ($rootScope.$$asyncQueue.length) { 12902 $rootScope.$digest(); 12903 } 12904 }); 12905 } 12906 12907 this.$$asyncQueue.push({scope: this, expression: expr}); 12908 }, 12909 12910 $$postDigest : function(fn) { 12911 this.$$postDigestQueue.push(fn); 12912 }, 12913 12914 /** 12915 * @ngdoc method 12916 * @name $rootScope.Scope#$apply 12917 * @kind function 12918 * 12919 * @description 12920 * `$apply()` is used to execute an expression in angular from outside of the angular 12921 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 12922 * Because we are calling into the angular framework we need to perform proper scope life 12923 * cycle of {@link ng.$exceptionHandler exception handling}, 12924 * {@link ng.$rootScope.Scope#$digest executing watches}. 12925 * 12926 * ## Life cycle 12927 * 12928 * # Pseudo-Code of `$apply()` 12929 * ```js 12930 function $apply(expr) { 12931 try { 12932 return $eval(expr); 12933 } catch (e) { 12934 $exceptionHandler(e); 12935 } finally { 12936 $root.$digest(); 12937 } 12938 } 12939 * ``` 12940 * 12941 * 12942 * Scope's `$apply()` method transitions through the following stages: 12943 * 12944 * 1. The {@link guide/expression expression} is executed using the 12945 * {@link ng.$rootScope.Scope#$eval $eval()} method. 12946 * 2. Any exceptions from the execution of the expression are forwarded to the 12947 * {@link ng.$exceptionHandler $exceptionHandler} service. 12948 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 12949 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 12950 * 12951 * 12952 * @param {(string|function())=} exp An angular expression to be executed. 12953 * 12954 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12955 * - `function(scope)`: execute the function with current `scope` parameter. 12956 * 12957 * @returns {*} The result of evaluating the expression. 12958 */ 12959 $apply: function(expr) { 12960 try { 12961 beginPhase('$apply'); 12962 return this.$eval(expr); 12963 } catch (e) { 12964 $exceptionHandler(e); 12965 } finally { 12966 clearPhase(); 12967 try { 12968 $rootScope.$digest(); 12969 } catch (e) { 12970 $exceptionHandler(e); 12971 throw e; 12972 } 12973 } 12974 }, 12975 12976 /** 12977 * @ngdoc method 12978 * @name $rootScope.Scope#$on 12979 * @kind function 12980 *
12981 * @description 12982 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 12983 * discussion of event life cycle. 12984 * 12985 * The event listener function format is: `function(event, args...)`. The `event` object 12986 * passed into the listener has the following attributes: 12987 * 12988 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 12989 * `$broadcast`-ed. 12990 * - `currentScope` - `{Scope}`: the current scope which is handling the event. 12991 * - `name` - `{string}`: name of the event. 12992 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 12993 * further event propagation (available only for events that were `$emit`-ed). 12994 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 12995 * to true. 12996 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 12997 * 12998 * @param {string} name Event name to listen on. 12999 * @param {function(event, ...args)} listener Function to call when the event is emitted. 13000 * @returns {function()} Returns a deregistration function for this listener. 13001 */ 13002 $on: function(name, listener) { 13003 var namedListeners = this.$$listeners[name]; 13004 if (!namedListeners) { 13005 this.$$listeners[name] = namedListeners = []; 13006 } 13007 namedListeners.push(listener); 13008 13009 var current = this; 13010 do { 13011 if (!current.$$listenerCount[name]) { 13012 current.$$listenerCount[name] = 0; 13013 } 13014 current.$$listenerCount[name]++; 13015 } while ((current = current.$parent)); 13016 13017 var self = this; 13018 return function() { 13019 var indexOfListener = indexOf(namedListeners, listener); 13020 if (indexOfListener !== -1) { 13021 namedListeners[indexOfListener] = null; 13022 decrementListenerCount(self, 1, name); 13023 } 13024 }; 13025 }, 13026 13027 13028 /** 13029 * @ngdoc method 13030 * @name $rootScope.Scope#$emit 13031 * @kind function 13032 * 13033 * @description 13034 * Dispatches an event `name` upwards through the scope hierarchy notifying the 13035 * registered {@link ng.$rootScope.Scope#$on} listeners. 13036 * 13037 * The event life cycle starts at the scope on which `$emit` was called. All 13038 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 13039 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 13040 * registered listeners along the way. The event will stop propagating if one of the listeners 13041 * cancels it. 13042 * 13043 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 13044 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 13045 * 13046 * @param {string} name Event name to emit. 13047 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 13048 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 13049 */ 13050 $emit: function(name, args) { 13051 var empty = [], 13052 namedListeners, 13053 scope = this, 13054 stopPropagation = false, 13055 event = { 13056 name: name, 13057 targetScope: scope, 13058 stopPropagation: function() {stopPropagation = true;}, 13059 preventDefault: function() { 13060 event.defaultPrevented = true; 13061 }, 13062 defaultPrevented: false 13063 }, 13064 listenerArgs = concat([event], arguments, 1), 13065 i, length; 13066 13067 do { 13068 namedListeners = scope.$$listeners[name] || empty; 13069 event.currentScope = scope; 13070 for (i=0, length=namedListeners.length; i<length; i++) { 13071 13072 // if listeners were deregistered, defragment the array 13073 if (!namedListeners[i]) { 13074 namedListeners.splice(i, 1); 13075 i--; 13076 length--; 13077 continue; 13078 } 13079 try { 13080 //allow all listeners attached to the current scope to run 13081 namedListeners[i].apply(null, listenerArgs); 13082 } catch (e) { 13083 $exceptionHandler(e); 13084 } 13085 } 13086 //if any listener on the current scope stops propagation, prevent bubbling 13087 if (stopPropagation) return event; 13088 //traverse upwards 13089 scope = scope.$parent; 13090 } while (scope); 13091 13092 return event; 13093 }, 13094 13095 13096 /** 13097 * @ngdoc method 13098 * @name $rootScope.Scope#$broadcast 13099 * @kind function 13100 *
13101 * @description 13102 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 13103 * registered {@link ng.$rootScope.Scope#$on} listeners. 13104 * 13105 * The event life cycle starts at the scope on which `$broadcast` was called. All 13106 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 13107 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 13108 * scope and calls all registered listeners along the way. The event cannot be canceled. 13109 * 13110 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 13111 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 13112 * 13113 * @param {string} name Event name to broadcast. 13114 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 13115 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 13116 */ 13117 $broadcast: function(name, args) { 13118 var target = this, 13119 current = target, 13120 next = target, 13121 event = { 13122 name: name, 13123 targetScope: target, 13124 preventDefault: function() { 13125 event.defaultPrevented = true; 13126 }, 13127 defaultPrevented: false 13128 }, 13129 listenerArgs = concat([event], arguments, 1), 13130 listeners, i, length; 13131 13132 //down while you can, then up and next sibling or up and next sibling until back at root 13133 while ((current = next)) { 13134 event.currentScope = current; 13135 listeners = current.$$listeners[name] || []; 13136 for (i=0, length = listeners.length; i<length; i++) { 13137 // if listeners were deregistered, defragment the array 13138 if (!listeners[i]) { 13139 listeners.splice(i, 1); 13140 i--; 13141 length--; 13142 continue; 13143 } 13144 13145 try { 13146 listeners[i].apply(null, listenerArgs); 13147 } catch(e) { 13148 $exceptionHandler(e); 13149 } 13150 } 13151 13152 // Insanity Warning: scope depth-first traversal 13153 // yes, this code is a bit crazy, but it works and we have tests to prove it! 13154 // this piece should be kept in sync with the traversal in $digest 13155 // (though it differs due to having the extra check for $$listenerCount) 13156 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 13157 (current !== target && current.$$nextSibling)))) { 13158 while(current !== target && !(next = current.$$nextSibling)) { 13159 current = current.$parent; 13160 } 13161 } 13162 } 13163 13164 return event; 13165 } 13166 }; 13167 13168 var $rootScope = new Scope(); 13169 13170 return $rootScope; 13171 13172 13173 function beginPhase(phase) { 13174 if ($rootScope.$$phase) { 13175 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 13176 } 13177 13178 $rootScope.$$phase = phase; 13179 } 13180 13181 function clearPhase() { 13182 $rootScope.$$phase = null; 13183 } 13184 13185 function compileToFn(exp, name) { 13186 var fn = $parse(exp); 13187 assertArgFn(fn, name); 13188 return fn; 13189 } 13190 13191 function decrementListenerCount(current, count, name) { 13192 do { 13193 current.$$listenerCount[name] -= count; 13194 13195 if (current.$$listenerCount[name] === 0) { 13196 delete current.$$listenerCount[name]; 13197 } 13198 } while ((current = current.$parent)); 13199 } 13200 13201 /** 13202 * function used as an initial value for watchers. 13203 * because it's unique we can easily tell it apart from other values 13204 */ 13205 function initWatchVal() {} 13206 }]; 13207} 13208 13209/** 13210 * @ngdoc service 13211 * @name $rootElement 13212 * 13213 * @description 13214 * The root element of Angular application. This is either the element where {@link 13215 * ng.directive:ngApp ngApp} was declared or the element passed into 13216 * {@link angular.bootstrap}. The element represent the root element of application. It is also the 13217 * location where the applications {@link auto.$injector $injector} service gets 13218 * published, it can be retrieved using `$rootElement.injector()`. 13219 */ 13220 13221 13222// the implementation is in angular.bootstrap 13223 13224/** 13225 * @description 13226 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 13227 */
13228function $$SanitizeUriProvider() { 13229 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 13230 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file):|data:image\/)/; 13231 13232 /** 13233 * @description 13234 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 13235 * urls during a[href] sanitization. 13236 * 13237 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 13238 * 13239 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 13240 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 13241 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 13242 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 13243 * 13244 * @param {RegExp=} regexp New regexp to whitelist urls with. 13245 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 13246 * chaining otherwise. 13247 */ 13248 this.aHrefSanitizationWhitelist = function(regexp) { 13249 if (isDefined(regexp)) { 13250 aHrefSanitizationWhitelist = regexp; 13251 return this; 13252 } 13253 return aHrefSanitizationWhitelist; 13254 }; 13255 13256 13257 /** 13258 * @description 13259 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 13260 * urls during img[src] sanitization. 13261 * 13262 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 13263 * 13264 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 13265 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 13266 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 13267 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 13268 * 13269 * @param {RegExp=} regexp New regexp to whitelist urls with. 13270 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 13271 * chaining otherwise. 13272 */ 13273 this.imgSrcSanitizationWhitelist = function(regexp) { 13274 if (isDefined(regexp)) { 13275 imgSrcSanitizationWhitelist = regexp; 13276 return this; 13277 } 13278 return imgSrcSanitizationWhitelist; 13279 }; 13280 13281 this.$get = function() { 13282 return function sanitizeUri(uri, isImage) { 13283 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 13284 var normalizedVal; 13285 // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case. 13286 if (!msie || msie >= 8 ) { 13287 normalizedVal = urlResolve(uri).href; 13288 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 13289 return 'unsafe:'+normalizedVal; 13290 } 13291 } 13292 return uri; 13293 }; 13294 }; 13295} 13296 13297var $sceMinErr = minErr('$sce'); 13298 13299var SCE_CONTEXTS = { 13300 HTML: 'html', 13301 CSS: 'css', 13302 URL: 'url', 13303 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 13304 // url. (e.g. ng-include, script src, templateUrl) 13305 RESOURCE_URL: 'resourceUrl', 13306 JS: 'js' 13307}; 13308 13309// Helper functions follow. 13310 13311// Copied from: 13312// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 13313// Prereq: s is a string. 13314function escapeForRegexp(s) { 13315 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 13316 replace(/\x08/g, '\\x08'); 13317} 13318 13319 13320function adjustMatcher(matcher) { 13321 if (matcher === 'self') { 13322 return matcher; 13323 } else if (isString(matcher)) { 13324 // Strings match exactly except for 2 wildcards - '*' and '**'. 13325 // '*' matches any character except those from the set ':/.?&'. 13326 // '**' matches any character (like .* in a RegExp). 13327 // More than 2 *'s raises an error as it's ill defined. 13328 if (matcher.indexOf('***') > -1) { 13329 throw $sceMinErr('iwcard', 13330 'Illegal sequence *** in string matcher. String: {0}', matcher); 13331 } 13332 matcher = escapeForRegexp(matcher).
13333 replace('\\*\\*', '.*'). 13334 replace('\\*', '[^:/.?&;]*'); 13335 return new RegExp('^' + matcher + '$'); 13336 } else if (isRegExp(matcher)) { 13337 // The only other type of matcher allowed is a Regexp. 13338 // Match entire URL / disallow partial matches. 13339 // Flags are reset (i.e. no global, ignoreCase or multiline) 13340 return new RegExp('^' + matcher.source + '$'); 13341 } else { 13342 throw $sceMinErr('imatcher', 13343 'Matchers may only be "self", string patterns or RegExp objects'); 13344 } 13345} 13346 13347 13348function adjustMatchers(matchers) { 13349 var adjustedMatchers = []; 13350 if (isDefined(matchers)) { 13351 forEach(matchers, function(matcher) { 13352 adjustedMatchers.push(adjustMatcher(matcher)); 13353 }); 13354 } 13355 return adjustedMatchers; 13356} 13357 13358 13359/** 13360 * @ngdoc service 13361 * @name $sceDelegate 13362 * @kind function 13363 * 13364 * @description 13365 * 13366 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 13367 * Contextual Escaping (SCE)} services to AngularJS. 13368 * 13369 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 13370 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 13371 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 13372 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 13373 * work because `$sce` delegates to `$sceDelegate` for these operations. 13374 * 13375 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 13376 * 13377 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 13378 * can override it completely to change the behavior of `$sce`, the common case would 13379 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 13380 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 13381 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 13382 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 13383 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 13384 */ 13385 13386/** 13387 * @ngdoc provider 13388 * @name $sceDelegateProvider 13389 * @description 13390 * 13391 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 13392 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 13393 * that the URLs used for sourcing Angular templates are safe. Refer {@link 13394 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 13395 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 13396 * 13397 * For the general details about this service in Angular, read the main page for {@link ng.$sce 13398 * Strict Contextual Escaping (SCE)}. 13399 * 13400 * **Example**: Consider the following case. <a name="example"></a> 13401 * 13402 * - your app is hosted at url `http://myapp.example.com/` 13403 * - but some of your templates are hosted on other domains you control such as 13404 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 13405 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 13406 * 13407 * Here is what a secure configuration for this scenario might look like: 13408 * 13409 * ``` 13410 * angular.module('myApp', []).config(function($sceDelegateProvider) { 13411 * $sceDelegateProvider.resourceUrlWhitelist([ 13412 * // Allow same origin resource loads. 13413 * 'self', 13414 * // Allow loading from our assets domain. Notice the difference between * and **. 13415 * 'http://srv*.assets.example.com/**' 13416 * ]); 13417 * 13418 * // The blacklist overrides the whitelist so the open redirect here is blocked. 13419 * $sceDelegateProvider.resourceUrlBlacklist([ 13420 * 'http://myapp.example.com/clickThru**' 13421 * ]); 13422 * }); 13423 * ``` 13424 */ 13425 13426function $SceDelegateProvider() { 13427 this.SCE_CONTEXTS = SCE_CONTEXTS; 13428 13429 // Resource URLs can also be trusted by policy. 13430 var resourceUrlWhitelist = ['self'], 13431 resourceUrlBlacklist = []; 13432 13433 /** 13434 * @ngdoc method 13435 * @name $sceDelegateProvider#resourceUrlWhitelist 13436 * @kind function 13437 * 13438 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 13439 * provided. This must be an array or null. A snapshot of this array is used so further 13440 * changes to the array are ignored. 13441 * 13442 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 13443 * allowed in this array.
vendor: 4,150 bytes, lines 13444-13568
13444 * 13445 * Note: **an empty whitelist array will block all URLs**! 13446 * 13447 * @return {Array} the currently set whitelist array. 13448 * 13449 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 13450 * same origin resource requests. 13451 * 13452 * @description 13453 * Sets/Gets the whitelist of trusted resource URLs. 13454 */ 13455 this.resourceUrlWhitelist = function (value) { 13456 if (arguments.length) { 13457 resourceUrlWhitelist = adjustMatchers(value); 13458 } 13459 return resourceUrlWhitelist; 13460 }; 13461 13462 /** 13463 * @ngdoc method 13464 * @name $sceDelegateProvider#resourceUrlBlacklist 13465 * @kind function 13466 * 13467 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 13468 * provided. This must be an array or null. A snapshot of this array is used so further 13469 * changes to the array are ignored. 13470 * 13471 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 13472 * allowed in this array. 13473 * 13474 * The typical usage for the blacklist is to **block 13475 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 13476 * these would otherwise be trusted but actually return content from the redirected domain. 13477 * 13478 * Finally, **the blacklist overrides the whitelist** and has the final say. 13479 * 13480 * @return {Array} the currently set blacklist array. 13481 * 13482 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 13483 * is no blacklist.) 13484 * 13485 * @description 13486 * Sets/Gets the blacklist of trusted resource URLs. 13487 */ 13488 13489 this.resourceUrlBlacklist = function (value) { 13490 if (arguments.length) { 13491 resourceUrlBlacklist = adjustMatchers(value); 13492 } 13493 return resourceUrlBlacklist; 13494 }; 13495 13496 this.$get = ['$injector', function($injector) { 13497 13498 var htmlSanitizer = function htmlSanitizer(html) { 13499 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 13500 }; 13501 13502 if ($injector.has('$sanitize')) { 13503 htmlSanitizer = $injector.get('$sanitize'); 13504 } 13505 13506 13507 function matchUrl(matcher, parsedUrl) { 13508 if (matcher === 'self') { 13509 return urlIsSameOrigin(parsedUrl); 13510 } else { 13511 // definitely a regex. See adjustMatchers() 13512 return !!matcher.exec(parsedUrl.href); 13513 } 13514 } 13515 13516 function isResourceUrlAllowedByPolicy(url) { 13517 var parsedUrl = urlResolve(url.toString()); 13518 var i, n, allowed = false; 13519 // Ensure that at least one item from the whitelist allows this url. 13520 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 13521 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 13522 allowed = true; 13523 break; 13524 } 13525 } 13526 if (allowed) { 13527 // Ensure that no item from the blacklist blocked this url. 13528 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 13529 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 13530 allowed = false; 13531 break; 13532 } 13533 } 13534 } 13535 return allowed; 13536 } 13537 13538 function generateHolderType(Base) { 13539 var holderType = function TrustedValueHolderType(trustedValue) { 13540 this.$$unwrapTrustedValue = function() { 13541 return trustedValue; 13542 }; 13543 }; 13544 if (Base) { 13545 holderType.prototype = new Base(); 13546 } 13547 holderType.prototype.valueOf = function sceValueOf() { 13548 return this.$$unwrapTrustedValue(); 13549 }; 13550 holderType.prototype.toString = function sceToString() { 13551 return this.$$unwrapTrustedValue().toString(); 13552 }; 13553 return holderType; 13554 } 13555 13556 var trustedValueHolderBase = generateHolderType(), 13557 byType = {}; 13558 13559 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 13560 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 13561 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 13562 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 13563 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 13564 13565 /** 13566 * @ngdoc method 13567 * @name $sceDelegate#trustAs 13568 *
13569 * @description 13570 * Returns an object that is trusted by angular for use in specified strict 13571 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 13572 * attribute interpolation, any dom event binding attribute interpolation 13573 * such as for onclick, etc.) that uses the provided value. 13574 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 13575 * 13576 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 13577 * resourceUrl, html, js and css. 13578 * @param {*} value The value that that should be considered trusted/safe. 13579 * @returns {*} A value that can be used to stand in for the provided `value` in places 13580 * where Angular expects a $sce.trustAs() return value. 13581 */ 13582 function trustAs(type, trustedValue) { 13583 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 13584 if (!Constructor) { 13585 throw $sceMinErr('icontext', 13586 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 13587 type, trustedValue); 13588 } 13589 if (trustedValue === null || trustedValue === undefined || trustedValue === '') { 13590 return trustedValue; 13591 } 13592 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 13593 // mutable objects, we ensure here that the value passed in is actually a string. 13594 if (typeof trustedValue !== 'string') { 13595 throw $sceMinErr('itype', 13596 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 13597 type); 13598 } 13599 return new Constructor(trustedValue); 13600 } 13601 13602 /** 13603 * @ngdoc method 13604 * @name $sceDelegate#valueOf 13605 * 13606 * @description 13607 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 13608 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 13609 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 13610 * 13611 * If the passed parameter is not a value that had been returned by {@link 13612 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 13613 * 13614 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 13615 * call or anything else. 13616 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 13617 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 13618 * `value` unchanged. 13619 */ 13620 function valueOf(maybeTrusted) { 13621 if (maybeTrusted instanceof trustedValueHolderBase) { 13622 return maybeTrusted.$$unwrapTrustedValue(); 13623 } else { 13624 return maybeTrusted; 13625 } 13626 } 13627 13628 /** 13629 * @ngdoc method 13630 * @name $sceDelegate#getTrusted 13631 * 13632 * @description 13633 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 13634 * returns the originally supplied value if the queried context type is a supertype of the 13635 * created type. If this condition isn't satisfied, throws an exception. 13636 * 13637 * @param {string} type The kind of context in which this value is to be used. 13638 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 13639 * `$sceDelegate.trustAs`} call. 13640 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 13641 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 13642 */ 13643 function getTrusted(type, maybeTrusted) { 13644 if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { 13645 return maybeTrusted; 13646 } 13647 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 13648 if (constructor && maybeTrusted instanceof constructor) { 13649 return maybeTrusted.$$unwrapTrustedValue(); 13650 } 13651 // If we get here, then we may only take one of two actions. 13652 // 1. sanitize the value for the requested type, or 13653 // 2. throw an exception. 13654 if (type === SCE_CONTEXTS.RESOURCE_URL) { 13655 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 13656 return maybeTrusted; 13657 } else { 13658 throw $sceMinErr('insecurl', 13659 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 13660 maybeTrusted.toString()); 13661 } 13662 } else if (type === SCE_CONTEXTS.HTML) { 13663 return htmlSanitizer(maybeTrusted); 13664 } 13665 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 13666 } 13667 13668 return { trustAs: trustAs, 13669 getTrusted: getTrusted,
13670 valueOf: valueOf }; 13671 }]; 13672} 13673 13674 13675/** 13676 * @ngdoc provider 13677 * @name $sceProvider 13678 * @description 13679 * 13680 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 13681 * - enable/disable Strict Contextual Escaping (SCE) in a module 13682 * - override the default implementation with a custom delegate 13683 * 13684 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 13685 */ 13686 13687/* jshint maxlen: false*/ 13688 13689/** 13690 * @ngdoc service 13691 * @name $sce 13692 * @kind function 13693 * 13694 * @description 13695 * 13696 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 13697 * 13698 * # Strict Contextual Escaping 13699 * 13700 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 13701 * contexts to result in a value that is marked as safe to use for that context. One example of 13702 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 13703 * to these contexts as privileged or SCE contexts. 13704 * 13705 * As of version 1.2, Angular ships with SCE enabled by default. 13706 * 13707 * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows 13708 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 13709 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
13710 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 13711 * to the top of your HTML document. 13712 * 13713 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 13714 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 13715 * 13716 * Here's an example of a binding in a privileged context: 13717 * 13718 * ``` 13719 * <input ng-model="userHtml"> 13720 * <div ng-bind-html="userHtml"></div> 13721 * ``` 13722 * 13723 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 13724 * disabled, this application allows the user to render arbitrary HTML into the DIV. 13725 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 13726 * bindings. (HTML is just one example of a context where rendering user controlled input creates 13727 * security vulnerabilities.) 13728 * 13729 * For the case of HTML, you might use a library, either on the client side, or on the server side, 13730 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 13731 * 13732 * How would you ensure that every place that used these types of bindings was bound to a value that 13733 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 13734 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 13735 * properties/fields and forgot to update the binding to the sanitized value? 13736 * 13737 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 13738 * determine that something explicitly says it's safe to use a value for binding in that
13739 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 13740 * for those values that you can easily tell are safe - because they were received from your server, 13741 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 13742 * allowing only the files in a specific directory to do this. Ensuring that the internal API 13743 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 13744 * 13745 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 13746 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 13747 * obtain values that will be accepted by SCE / privileged contexts. 13748 * 13749 * 13750 * ## How does it work? 13751 * 13752 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 13753 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 13754 * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 13755 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 13756 * 13757 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 13758 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 13759 * simplified): 13760 * 13761 * ``` 13762 * var ngBindHtmlDirective = ['$sce', function($sce) { 13763 * return function(scope, element, attr) { 13764 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 13765 * element.html(value || ''); 13766 * }); 13767 * }; 13768 * }]; 13769 * ``` 13770 * 13771 * ## Impact on loading templates 13772 * 13773 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 13774 * `templateUrl`'s specified by {@link guide/directive directives}. 13775 * 13776 * By default, Angular only loads templates from the same domain and protocol as the application 13777 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 13778 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 13779 * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 13780 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 13781 * 13782 * *Please note*: 13783 * The browser's 13784 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 13785 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 13786 * policy apply in addition to this and may further restrict whether the template is successfully 13787 * loaded. This means that without the right CORS policy, loading templates from a different domain 13788 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 13789 * browsers. 13790 * 13791 * ## This feels like too much overhead for the developer? 13792 * 13793 * It's important to remember that SCE only applies to interpolation expressions. 13794 * 13795 * If your expressions are constant literals, they're automatically trusted and you don't need to 13796 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 13797 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 13798 * 13799 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 13800 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 13801 * 13802 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 13803 * templates in `ng-include` from your application's domain without having to even know about SCE. 13804 * It blocks loading templates from other domains or loading templates over http from an https 13805 * served document. You can change these by setting your own custom {@link 13806 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 13807 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 13808 * 13809 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 13810 * application that's secure and can be audited to verify that with much more ease than bolting 13811 * security onto an application later. 13812 * 13813 * <a name="contexts"></a> 13814 * ## What trusted context types are supported? 13815 * 13816 * | Context | Notes | 13817 * |---------------------|----------------| 13818 * | `$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. | 13819 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 13820 * | `$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. | 13821 * | `$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. | 13822 * | `$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. | 13823 * 13824 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 13825 *
13826 * Each element in these arrays must be one of the following: 13827 * 13828 * - **'self'** 13829 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 13830 * domain** as the application document using the **same protocol**. 13831 * - **String** (except the special value `'self'`) 13832 * - The string is matched against the full *normalized / absolute URL* of the resource 13833 * being tested (substring matches are not good enough.) 13834 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 13835 * match themselves. 13836 * - `*`: matches zero or more occurrences of any character other than one of the following 6 13837 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use 13838 * in a whitelist. 13839 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 13840 * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. 13841 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 13842 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 13843 * http://foo.example.com/templates/**). 13844 * - **RegExp** (*see caveat below*) 13845 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 13846 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 13847 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 13848 * have good test coverage.). For instance, the use of `.` in the regex is correct only in a 13849 * small number of cases. A `.` character in the regex used when matching the scheme or a 13850 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 13851 * is highly recommended to use the string patterns and only fall back to regular expressions 13852 * if they as a last resort. 13853 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 13854 * matched against the **entire** *normalized / absolute URL* of the resource being tested 13855 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 13856 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 13857 * - If you are generating your JavaScript from some other templating engine (not 13858 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 13859 * remember to escape your regular expression (and be aware that you might need more than 13860 * one level of escaping depending on your templating engine and the way you interpolated 13861 * the value.) Do make use of your platform's escaping mechanism as it might be good 13862 * enough before coding your own. e.g. Ruby has 13863 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 13864 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 13865 * Javascript lacks a similar built in function for escaping. Take a look at Google 13866 * Closure library's [goog.string.regExpEscape(s)]( 13867 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 13868 * 13869 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 13870 * 13871 * ## Show me an example using SCE. 13872 * 13873 * <example module="mySceApp" deps="angular-sanitize.js"> 13874 * <file name="index.html"> 13875 * <div ng-controller="myAppController as myCtrl"> 13876 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 13877 * <b>User comments</b><br> 13878 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 13879 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 13880 * exploit. 13881 * <div class="well"> 13882 * <div ng-repeat="userComment in myCtrl.userComments"> 13883 * <b>{{userComment.name}}</b>: 13884 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 13885 * <br> 13886 * </div>
13887 * </div> 13888 * </div> 13889 * </file> 13890 * 13891 * <file name="script.js"> 13892 * var mySceApp = angular.module('mySceApp', ['ngSanitize']); 13893 * 13894 * mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { 13895 * var self = this; 13896 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 13897 * self.userComments = userComments; 13898 * }); 13899 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 13900 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 13901 * 'sanitization."">Hover over this text.</span>'); 13902 * }); 13903 * </file> 13904 * 13905 * <file name="test_data.json"> 13906 * [ 13907 * { "name": "Alice", 13908 * "htmlComment": 13909 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 13910 * }, 13911 * { "name": "Bob", 13912 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 13913 * } 13914 * ] 13915 * </file> 13916 * 13917 * <file name="protractor.js" type="protractor"> 13918 * describe('SCE doc demo', function() { 13919 * it('should sanitize untrusted values', function() { 13920 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) 13921 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 13922 * }); 13923 * 13924 * it('should NOT sanitize explicitly trusted values', function() { 13925 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 13926 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 13927 * 'sanitization."">Hover over this text.</span>'); 13928 * }); 13929 * }); 13930 * </file> 13931 * </example> 13932 * 13933 * 13934 * 13935 * ## Can I disable SCE completely? 13936 * 13937 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 13938 * for little coding overhead. It will be much harder to take an SCE disabled application and 13939 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 13940 * for cases where you have a lot of existing code that was written before SCE was introduced and 13941 * you're migrating them a module at a time. 13942 * 13943 * That said, here's how you can completely disable SCE: 13944 * 13945 * ``` 13946 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 13947 * // Completely disable SCE. For demonstration purposes only! 13948 * // Do not use in new projects. 13949 * $sceProvider.enabled(false); 13950 * }); 13951 * ``` 13952 * 13953 */ 13954/* jshint maxlen: 100 */ 13955 13956function $SceProvider() { 13957 var enabled = true; 13958 13959 /** 13960 * @ngdoc method 13961 * @name $sceProvider#enabled 13962 * @kind function 13963 * 13964 * @param {boolean=} value If provided, then enables/disables SCE. 13965 * @return {boolean} true if SCE is enabled, false otherwise. 13966 * 13967 * @description 13968 * Enables/disables SCE and returns the current value. 13969 */ 13970 this.enabled = function (value) { 13971 if (arguments.length) { 13972 enabled = !!value; 13973 } 13974 return enabled; 13975 }; 13976 13977 13978 /* Design notes on the default implementation for SCE. 13979 * 13980 * The API contract for the SCE delegate 13981 * ------------------------------------- 13982 * The SCE delegate object must provide the following 3 methods: 13983 * 13984 * - trustAs(contextEnum, value) 13985 * This method is used to tell the SCE service that the provided value is OK to use in the 13986 * contexts specified by contextEnum. It must return an object that will be accepted by 13987 * getTrusted() for a compatible contextEnum and return this value. 13988 * 13989 * - valueOf(value) 13990 * For values that were not produced by trustAs(), return them as is. For values that were 13991 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 13992 * trustAs is wrapping the given values into some type, this operation unwraps it when given 13993 * such a value. 13994 * 13995 * - getTrusted(contextEnum, value) 13996 * This function should return the a value that is safe to use in the context specified by 13997 * contextEnum or throw and exception otherwise. 13998 * 13999 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 14000 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 14001 * instance, an implementation could maintain a registry of all trusted objects by context. In 14002 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 14003 * return the same object passed in if it was found in the registry under a compatible context or 14004 * throw an exception otherwise. An implementation might only wrap values some of the time based 14005 * on some criteria. getTrusted() might return a value and not throw an exception for special 14006 * constants or objects even if not wrapped. All such implementations fulfill this contract. 14007 * 14008 * 14009 * A note on the inheritance model for SCE contexts 14010 * ------------------------------------------------ 14011 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 14012 * is purely an implementation details. 14013 * 14014 * The contract is simply this: 14015 * 14016 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 14017 * will also succeed. 14018 * 14019 * Inheritance happens to capture this in a natural way. In some future, we
14020 * may not use inheritance anymore. That is OK because no code outside of 14021 * sce.js and sceSpecs.js would need to be aware of this detail. 14022 */ 14023 14024 this.$get = ['$parse', '$sniffer', '$sceDelegate', function( 14025 $parse, $sniffer, $sceDelegate) { 14026 // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows 14027 // the "expression(javascript expression)" syntax which is insecure. 14028 if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { 14029 throw $sceMinErr('iequirks', 14030 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + 14031 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 14032 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 14033 } 14034 14035 var sce = shallowCopy(SCE_CONTEXTS); 14036 14037 /** 14038 * @ngdoc method 14039 * @name $sce#isEnabled 14040 * @kind function 14041 * 14042 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 14043 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 14044 * 14045 * @description 14046 * Returns a boolean indicating if SCE is enabled. 14047 */ 14048 sce.isEnabled = function () { 14049 return enabled; 14050 }; 14051 sce.trustAs = $sceDelegate.trustAs; 14052 sce.getTrusted = $sceDelegate.getTrusted; 14053 sce.valueOf = $sceDelegate.valueOf; 14054 14055 if (!enabled) { 14056 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 14057 sce.valueOf = identity; 14058 } 14059 14060 /** 14061 * @ngdoc method 14062 * @name $sce#parseAs 14063 * 14064 * @description 14065 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 14066 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 14067 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 14068 * *result*)} 14069 * 14070 * @param {string} type The kind of SCE context in which this result will be used. 14071 * @param {string} expression String expression to compile. 14072 * @returns {function(context, locals)} a function which represents the compiled expression: 14073 * 14074 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14075 * are evaluated against (typically a scope object). 14076 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14077 * `context`. 14078 */ 14079 sce.parseAs = function sceParseAs(type, expr) { 14080 var parsed = $parse(expr); 14081 if (parsed.literal && parsed.constant) { 14082 return parsed; 14083 } else { 14084 return function sceParseAsTrusted(self, locals) { 14085 return sce.getTrusted(type, parsed(self, locals)); 14086 }; 14087 } 14088 }; 14089 14090 /** 14091 * @ngdoc method 14092 * @name $sce#trustAs 14093 * 14094 * @description 14095 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 14096 * returns an object that is trusted by angular for use in specified strict contextual 14097 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 14098 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 14099 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 14100 * escaping. 14101 * 14102 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 14103 * resource_url, html, js and css. 14104 * @param {*} value The value that that should be considered trusted/safe. 14105 * @returns {*} A value that can be used to stand in for the provided `value` in places 14106 * where Angular expects a $sce.trustAs() return value. 14107 */ 14108 14109 /** 14110 * @ngdoc method 14111 * @name $sce#trustAsHtml 14112 * 14113 * @description 14114 * Shorthand method. `$sce.trustAsHtml(value)` â 14115 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 14116 * 14117 * @param {*} value The value to trustAs. 14118 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 14119 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 14120 * only accept expressions that are either literal constants or are the 14121 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 14122 */ 14123 14124 /** 14125 * @ngdoc method 14126 * @name $sce#trustAsUrl 14127 *
14128 * @description 14129 * Shorthand method. `$sce.trustAsUrl(value)` â 14130 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 14131 * 14132 * @param {*} value The value to trustAs. 14133 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 14134 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 14135 * only accept expressions that are either literal constants or are the 14136 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 14137 */ 14138 14139 /** 14140 * @ngdoc method 14141 * @name $sce#trustAsResourceUrl 14142 * 14143 * @description 14144 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 14145 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 14146 * 14147 * @param {*} value The value to trustAs. 14148 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 14149 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 14150 * only accept expressions that are either literal constants or are the return 14151 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 14152 */ 14153 14154 /** 14155 * @ngdoc method 14156 * @name $sce#trustAsJs 14157 * 14158 * @description 14159 * Shorthand method. `$sce.trustAsJs(value)` â 14160 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 14161 * 14162 * @param {*} value The value to trustAs. 14163 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 14164 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 14165 * only accept expressions that are either literal constants or are the 14166 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 14167 */ 14168 14169 /** 14170 * @ngdoc method 14171 * @name $sce#getTrusted 14172 * 14173 * @description 14174 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 14175 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 14176 * originally supplied value if the queried context type is a supertype of the created type. 14177 * If this condition isn't satisfied, throws an exception. 14178 * 14179 * @param {string} type The kind of context in which this value is to be used. 14180 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 14181 * call. 14182 * @returns {*} The value the was originally provided to 14183 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 14184 * Otherwise, throws an exception. 14185 */ 14186 14187 /** 14188 * @ngdoc method 14189 * @name $sce#getTrustedHtml 14190 * 14191 * @description 14192 * Shorthand method. `$sce.getTrustedHtml(value)` â 14193 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 14194 * 14195 * @param {*} value The value to pass to `$sce.getTrusted`. 14196 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 14197 */ 14198 14199 /** 14200 * @ngdoc method 14201 * @name $sce#getTrustedCss 14202 * 14203 * @description 14204 * Shorthand method. `$sce.getTrustedCss(value)` â 14205 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 14206 * 14207 * @param {*} value The value to pass to `$sce.getTrusted`. 14208 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 14209 */ 14210 14211 /** 14212 * @ngdoc method 14213 * @name $sce#getTrustedUrl 14214 * 14215 * @description 14216 * Shorthand method. `$sce.getTrustedUrl(value)` â 14217 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 14218 * 14219 * @param {*} value The value to pass to `$sce.getTrusted`. 14220 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 14221 */ 14222 14223 /** 14224 * @ngdoc method 14225 * @name $sce#getTrustedResourceUrl 14226 * 14227 * @description 14228 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 14229 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 14230 * 14231 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 14232 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 14233 */ 14234 14235 /** 14236 * @ngdoc method 14237 * @name $sce#getTrustedJs 14238 *
14239 * @description 14240 * Shorthand method. `$sce.getTrustedJs(value)` â 14241 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 14242 * 14243 * @param {*} value The value to pass to `$sce.getTrusted`. 14244 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 14245 */ 14246 14247 /** 14248 * @ngdoc method 14249 * @name $sce#parseAsHtml 14250 * 14251 * @description 14252 * Shorthand method. `$sce.parseAsHtml(expression string)` â 14253 * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} 14254 * 14255 * @param {string} expression String expression to compile. 14256 * @returns {function(context, locals)} a function which represents the compiled expression: 14257 * 14258 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14259 * are evaluated against (typically a scope object). 14260 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14261 * `context`. 14262 */ 14263 14264 /** 14265 * @ngdoc method 14266 * @name $sce#parseAsCss 14267 * 14268 * @description 14269 * Shorthand method. `$sce.parseAsCss(value)` â 14270 * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} 14271 * 14272 * @param {string} expression String expression to compile. 14273 * @returns {function(context, locals)} a function which represents the compiled expression: 14274 * 14275 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14276 * are evaluated against (typically a scope object). 14277 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14278 * `context`. 14279 */ 14280 14281 /** 14282 * @ngdoc method 14283 * @name $sce#parseAsUrl 14284 * 14285 * @description 14286 * Shorthand method. `$sce.parseAsUrl(value)` â 14287 * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} 14288 * 14289 * @param {string} expression String expression to compile. 14290 * @returns {function(context, locals)} a function which represents the compiled expression: 14291 * 14292 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14293 * are evaluated against (typically a scope object). 14294 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14295 * `context`. 14296 */ 14297 14298 /** 14299 * @ngdoc method 14300 * @name $sce#parseAsResourceUrl 14301 * 14302 * @description 14303 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 14304 * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} 14305 * 14306 * @param {string} expression String expression to compile. 14307 * @returns {function(context, locals)} a function which represents the compiled expression: 14308 * 14309 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14310 * are evaluated against (typically a scope object). 14311 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14312 * `context`. 14313 */ 14314 14315 /** 14316 * @ngdoc method 14317 * @name $sce#parseAsJs 14318 * 14319 * @description 14320 * Shorthand method. `$sce.parseAsJs(value)` â 14321 * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} 14322 * 14323 * @param {string} expression String expression to compile. 14324 * @returns {function(context, locals)} a function which represents the compiled expression: 14325 * 14326 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14327 * are evaluated against (typically a scope object). 14328 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14329 * `context`. 14330 */ 14331 14332 // Shorthand delegations. 14333 var parse = sce.parseAs, 14334 getTrusted = sce.getTrusted, 14335 trustAs = sce.trustAs; 14336 14337 forEach(SCE_CONTEXTS, function (enumValue, name) { 14338 var lName = lowercase(name); 14339 sce[camelCase("parse_as_" + lName)] = function (expr) { 14340 return parse(enumValue, expr); 14341 }; 14342 sce[camelCase("get_trusted_" + lName)] = function (value) { 14343 return getTrusted(enumValue, value); 14344 }; 14345 sce[camelCase("trust_as_" + lName)] = function (value) { 14346 return trustAs(enumValue, value); 14347 }; 14348 }); 14349 14350 return sce; 14351 }]; 14352} 14353 14354/** 14355 * !!! This is an undocumented "private" service !!! 14356 * 14357 * @name $sniffer 14358 * @requires $window 14359 * @requires $document 14360 * 14361 * @property {boolean} history Does the browser support html5 history api ? 14362 * @property {boolean} hashchange Does the browser support hashchange event ? 14363 * @property {boolean} transitions Does the browser support CSS transition events ? 14364 * @property {boolean} animations Does the browser support CSS animation events ? 14365 * 14366 * @description 14367 * This is very simple implementation of testing browser's features. 14368 */ 14369function $SnifferProvider() { 14370 this.$get = ['$window', '$document', function($window, $document) { 14371 var eventSupport = {}, 14372 android = 14373 int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 14374 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 14375 document = $document[0] || {}, 14376 documentMode = document.documentMode, 14377 vendorPrefix, 14378 vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, 14379 bodyStyle = document.body && document.body.style, 14380 transitions = false, 14381 animations = false, 14382 match; 14383 14384 if (bodyStyle) { 14385 for(var prop in bodyStyle) {
14386 if(match = vendorRegex.exec(prop)) { 14387 vendorPrefix = match[0]; 14388 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 14389 break; 14390 } 14391 } 14392 14393 if(!vendorPrefix) { 14394 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 14395 } 14396 14397 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 14398 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 14399 14400 if (android && (!transitions||!animations)) { 14401 transitions = isString(document.body.style.webkitTransition); 14402 animations = isString(document.body.style.webkitAnimation); 14403 } 14404 } 14405 14406 14407 return { 14408 // Android has history.pushState, but it does not update location correctly 14409 // so let's not use the history API at all. 14410 // http://code.google.com/p/android/issues/detail?id=17471 14411 // https://github.com/angular/angular.js/issues/904 14412 14413 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 14414 // so let's not use the history API also 14415 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 14416 // jshint -W018 14417 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 14418 // jshint +W018 14419 hashchange: 'onhashchange' in $window && 14420 // IE8 compatible mode lies 14421 (!documentMode || documentMode > 7), 14422 hasEvent: function(event) { 14423 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 14424 // it. In particular the event is not fired when backspace or delete key are pressed or 14425 // when cut operation is performed. 14426 if (event == 'input' && msie == 9) return false; 14427 14428 if (isUndefined(eventSupport[event])) { 14429 var divElm = document.createElement('div'); 14430 eventSupport[event] = 'on' + event in divElm; 14431 } 14432 14433 return eventSupport[event]; 14434 }, 14435 csp: csp(), 14436 vendorPrefix: vendorPrefix, 14437 transitions : transitions, 14438 animations : animations, 14439 android: android, 14440 msie : msie, 14441 msieDocumentMode: documentMode 14442 }; 14443 }]; 14444} 14445 14446function $TimeoutProvider() { 14447 this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', 14448 function($rootScope, $browser, $q, $exceptionHandler) { 14449 var deferreds = {}; 14450 14451 14452 /** 14453 * @ngdoc service 14454 * @name $timeout 14455 * 14456 * @description 14457 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 14458 * block and delegates any exceptions to 14459 * {@link ng.$exceptionHandler $exceptionHandler} service. 14460 * 14461 * The return value of registering a timeout function is a promise, which will be resolved when 14462 * the timeout is reached and the timeout function is executed. 14463 * 14464 * To cancel a timeout request, call `$timeout.cancel(promise)`. 14465 * 14466 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 14467 * synchronously flush the queue of deferred functions. 14468 * 14469 * @param {function()} fn A function, whose execution should be delayed. 14470 * @param {number=} [delay=0] Delay in milliseconds. 14471 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 14472 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 14473 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 14474 * promise will be resolved with is the return value of the `fn` function. 14475 * 14476 */ 14477 function timeout(fn, delay, invokeApply) { 14478 var deferred = $q.defer(), 14479 promise = deferred.promise, 14480 skipApply = (isDefined(invokeApply) && !invokeApply), 14481 timeoutId; 14482 14483 timeoutId = $browser.defer(function() { 14484 try { 14485 deferred.resolve(fn()); 14486 } catch(e) { 14487 deferred.reject(e); 14488 $exceptionHandler(e); 14489 } 14490 finally { 14491 delete deferreds[promise.$$timeoutId]; 14492 } 14493 14494 if (!skipApply) $rootScope.$apply(); 14495 }, delay); 14496 14497 promise.$$timeoutId = timeoutId; 14498 deferreds[timeoutId] = deferred; 14499 14500 return promise; 14501 } 14502 14503 14504 /** 14505 * @ngdoc method 14506 * @name $timeout#cancel 14507 *
14508 * @description 14509 * Cancels a task associated with the `promise`. As a result of this, the promise will be 14510 * resolved with a rejection. 14511 * 14512 * @param {Promise=} promise Promise returned by the `$timeout` function. 14513 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 14514 * canceled. 14515 */ 14516 timeout.cancel = function(promise) { 14517 if (promise && promise.$$timeoutId in deferreds) { 14518 deferreds[promise.$$timeoutId].reject('canceled'); 14519 delete deferreds[promise.$$timeoutId]; 14520 return $browser.defer.cancel(promise.$$timeoutId); 14521 } 14522 return false; 14523 }; 14524 14525 return timeout; 14526 }]; 14527} 14528 14529// NOTE: The usage of window and document instead of $window and $document here is 14530// deliberate. This service depends on the specific behavior of anchor nodes created by the 14531// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 14532// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 14533// doesn't know about mocked locations and resolves URLs to the real document - which is 14534// exactly the behavior needed here. There is little value is mocking these out for this 14535// service. 14536var urlParsingNode = document.createElement("a"); 14537var originUrl = urlResolve(window.location.href, true); 14538 14539 14540/** 14541 * 14542 * Implementation Notes for non-IE browsers 14543 * ---------------------------------------- 14544 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 14545 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 14546 * URL will be resolved into an absolute URL in the context of the application document. 14547 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 14548 * properties are all populated to reflect the normalized URL. This approach has wide 14549 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 14550 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 14551 * 14552 * Implementation Notes for IE 14553 * --------------------------- 14554 * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other 14555 * browsers. However, the parsed components will not be set if the URL assigned did not specify 14556 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 14557 * work around that by performing the parsing in a 2nd step by taking a previously normalized 14558 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 14559 * properties such as protocol, hostname, port, etc. 14560 * 14561 * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one 14562 * uses the inner HTML approach to assign the URL as part of an HTML snippet - 14563 * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. 14564 * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. 14565 * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that 14566 * method and IE < 8 is unsupported. 14567 * 14568 * References: 14569 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 14570 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 14571 * http://url.spec.whatwg.org/#urlutils 14572 * https://github.com/angular/angular.js/pull/2902 14573 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 14574 * 14575 * @kind function 14576 * @param {string} url The URL to be parsed. 14577 * @description Normalizes and parses a URL. 14578 * @returns {object} Returns the normalized URL as a dictionary. 14579 * 14580 * | member name | Description | 14581 * |---------------|----------------| 14582 * | href | A normalized version of the provided URL if it was not an absolute URL | 14583 * | protocol | The protocol including the trailing colon | 14584 * | host | The host and port (if the port is non-default) of the normalizedUrl | 14585 * | search | The search params, minus the question mark | 14586 * | hash | The hash string, minus the hash symbol 14587 * | hostname | The hostname 14588 * | port | The port, without ":" 14589 * | pathname | The pathname, beginning with "/" 14590 * 14591 */ 14592function urlResolve(url, base) { 14593 var href = url; 14594 14595 if (msie) { 14596 // Normalize before parse. Refer Implementation Notes on why this is 14597 // done in two steps on IE. 14598 urlParsingNode.setAttribute("href", href); 14599 href = urlParsingNode.href; 14600 } 14601 14602 urlParsingNode.setAttribute('href', href); 14603 14604 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 14605 return { 14606 href: urlParsingNode.href, 14607 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 14608 host: urlParsingNode.host, 14609 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 14610 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 14611 hostname: urlParsingNode.hostname, 14612 port: urlParsingNode.port, 14613 pathname: (urlParsingNode.pathname.charAt(0) === '/') 14614 ? urlParsingNode.pathname 14615 : '/' + urlParsingNode.pathname 14616 }; 14617} 14618 14619/** 14620 * Parse a request URL and determine whether this is a same-origin request as the application document. 14621 * 14622 * @param {string|object} requestUrl The url of the request as a string that will be resolved 14623 * or a parsed URL object. 14624 * @returns {boolean} Whether the request is for the same origin as the application document. 14625 */ 14626function urlIsSameOrigin(requestUrl) { 14627 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 14628 return (parsed.protocol === originUrl.protocol && 14629 parsed.host === originUrl.host); 14630} 14631 14632/** 14633 * @ngdoc service 14634 * @name $window 14635 * 14636 * @description 14637 * A reference to the browser's `window` object. While `window` 14638 * is globally available in JavaScript, it causes testability problems, because 14639 * it is a global variable. In angular we always refer to it through the 14640 * `$window` service, so it may be overridden, removed or mocked for testing. 14641 * 14642 * Expressions, like the one defined for the `ngClick` directive in the example 14643 * below, are evaluated with respect to the current scope. Therefore, there is 14644 * no risk of inadvertently coding in a dependency on a global value in such an 14645 * expression. 14646 * 14647 * @example 14648 <example module="windowExample"> 14649 <file name="index.html"> 14650 <script> 14651 angular.module('windowExample', [])
14652 .controller('ExampleController', ['$scope', '$window', function ($scope, $window) { 14653 $scope.greeting = 'Hello, World!'; 14654 $scope.doGreeting = function(greeting) { 14655 $window.alert(greeting); 14656 }; 14657 }]); 14658 </script> 14659 <div ng-controller="ExampleController"> 14660 <input type="text" ng-model="greeting" /> 14661 <button ng-click="doGreeting(greeting)">ALERT</button> 14662 </div> 14663 </file> 14664 <file name="protractor.js" type="protractor"> 14665 it('should display the greeting in the input box', function() { 14666 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 14667 // If we click the button it will block the test runner 14668 // element(':button').click(); 14669 }); 14670 </file> 14671 </example> 14672 */ 14673function $WindowProvider(){ 14674 this.$get = valueFn(window); 14675} 14676 14677/* global currencyFilter: true, 14678 dateFilter: true, 14679 filterFilter: true, 14680 jsonFilter: true, 14681 limitToFilter: true, 14682 lowercaseFilter: true, 14683 numberFilter: true, 14684 orderByFilter: true, 14685 uppercaseFilter: true, 14686 */ 14687 14688/** 14689 * @ngdoc provider 14690 * @name $filterProvider 14691 * @description 14692 * 14693 * Filters are just functions which transform input to an output. However filters need to be 14694 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 14695 * annotated with dependencies and is responsible for creating a filter function. 14696 * 14697 * ```js 14698 * // Filter registration 14699 * function MyModule($provide, $filterProvider) { 14700 * // create a service to demonstrate injection (not always needed) 14701 * $provide.value('greet', function(name){ 14702 * return 'Hello ' + name + '!'; 14703 * }); 14704 * 14705 * // register a filter factory which uses the 14706 * // greet service to demonstrate DI. 14707 * $filterProvider.register('greet', function(greet){ 14708 * // return the filter function which uses the greet service 14709 * // to generate salutation 14710 * return function(text) { 14711 * // filters need to be forgiving so check input validity 14712 * return text && greet(text) || text; 14713 * }; 14714 * }); 14715 * } 14716 * ``` 14717 * 14718 * The filter function is registered with the `$injector` under the filter name suffix with 14719 * `Filter`. 14720 * 14721 * ```js 14722 * it('should be the same instance', inject( 14723 * function($filterProvider) { 14724 * $filterProvider.register('reverse', function(){ 14725 * return ...; 14726 * }); 14727 * }, 14728 * function($filter, reverseFilter) { 14729 * expect($filter('reverse')).toBe(reverseFilter); 14730 * }); 14731 * ``` 14732 * 14733 * 14734 * For more information about how angular filters work, and how to create your own filters, see 14735 * {@link guide/filter Filters} in the Angular Developer Guide. 14736 */ 14737 14738/** 14739 * @ngdoc service 14740 * @name $filter 14741 * @kind function 14742 * @description 14743 * Filters are used for formatting data displayed to the user. 14744 * 14745 * The general syntax in templates is as follows: 14746 * 14747 * {{ expression [| filter_name[:parameter_value] ... ] }} 14748 * 14749 * @param {String} name Name of the filter function to retrieve 14750 * @return {Function} the filter function 14751 * @example 14752 <example name="$filter" module="filterExample"> 14753 <file name="index.html"> 14754 <div ng-controller="MainCtrl"> 14755 <h3>{{ originalText }}</h3> 14756 <h3>{{ filteredText }}</h3> 14757 </div> 14758 </file> 14759 14760 <file name="script.js"> 14761 angular.module('filterExample', []) 14762 .controller('MainCtrl', function($scope, $filter) { 14763 $scope.originalText = 'hello'; 14764 $scope.filteredText = $filter('uppercase')($scope.originalText); 14765 }); 14766 </file> 14767 </example> 14768 */ 14769$FilterProvider.$inject = ['$provide']; 14770function $FilterProvider($provide) { 14771 var suffix = 'Filter'; 14772 14773 /** 14774 * @ngdoc method 14775 * @name $filterProvider#register 14776 * @param {string|Object} name Name of the filter function, or an object map of filters where 14777 * the keys are the filter names and the values are the filter factories. 14778 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 14779 * of the registered filter instances. 14780 */ 14781 function register(name, factory) { 14782 if(isObject(name)) { 14783 var filters = {};
14784 forEach(name, function(filter, key) { 14785 filters[key] = register(key, filter); 14786 }); 14787 return filters; 14788 } else { 14789 return $provide.factory(name + suffix, factory); 14790 } 14791 } 14792 this.register = register; 14793 14794 this.$get = ['$injector', function($injector) { 14795 return function(name) { 14796 return $injector.get(name + suffix); 14797 }; 14798 }]; 14799 14800 //////////////////////////////////////// 14801 14802 /* global 14803 currencyFilter: false, 14804 dateFilter: false, 14805 filterFilter: false, 14806 jsonFilter: false, 14807 limitToFilter: false, 14808 lowercaseFilter: false, 14809 numberFilter: false, 14810 orderByFilter: false, 14811 uppercaseFilter: false, 14812 */ 14813 14814 register('currency', currencyFilter); 14815 register('date', dateFilter); 14816 register('filter', filterFilter); 14817 register('json', jsonFilter); 14818 register('limitTo', limitToFilter); 14819 register('lowercase', lowercaseFilter); 14820 register('number', numberFilter); 14821 register('orderBy', orderByFilter); 14822 register('uppercase', uppercaseFilter); 14823} 14824 14825/** 14826 * @ngdoc filter 14827 * @name filter 14828 * @kind function 14829 * 14830 * @description 14831 * Selects a subset of items from `array` and returns it as a new array. 14832 * 14833 * @param {Array} array The source array. 14834 * @param {string|Object|function()} expression The predicate to be used for selecting items from 14835 * `array`. 14836 * 14837 * Can be one of: 14838 * 14839 * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against 14840 * the contents of the `array`. All strings or objects with string properties in `array` that contain this string 14841 * will be returned. The predicate can be negated by prefixing the string with `!`. 14842 * 14843 * - `Object`: A pattern object can be used to filter specific properties on objects contained 14844 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 14845 * which have property `name` containing "M" and property `phone` containing "1". A special 14846 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 14847 * property of the object. That's equivalent to the simple substring match with a `string` 14848 * as described above. The predicate can be negated by prefixing the string with `!`. 14849 * For Example `{name: "!M"}` predicate will return an array of items which have property `name` 14850 * not containing "M". 14851 * 14852 * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is 14853 * called for each element of `array`. The final result is an array of those elements that 14854 * the predicate returned true for. 14855 * 14856 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 14857 * determining if the expected value (from the filter expression) and actual value (from 14858 * the object in the array) should be considered a match. 14859 * 14860 * Can be one of: 14861 * 14862 * - `function(actual, expected)`: 14863 * The function will be given the object value and the predicate value to compare and 14864 * should return true if the item should be included in filtered result. 14865 * 14866 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. 14867 * this is essentially strict comparison of expected and actual. 14868 * 14869 * - `false|undefined`: A short hand for a function which will look for a substring match in case 14870 * insensitive way. 14871 * 14872 * @example 14873 <example> 14874 <file name="index.html"> 14875 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 14876 {name:'Mary', phone:'800-BIG-MARY'}, 14877 {name:'Mike', phone:'555-4321'}, 14878 {name:'Adam', phone:'555-5678'}, 14879 {name:'Julie', phone:'555-8765'}, 14880 {name:'Juliette', phone:'555-5678'}]"></div> 14881 14882 Search: <input ng-model="searchText"> 14883 <table id="searchTextResults"> 14884 <tr><th>Name</th><th>Phone</th></tr> 14885 <tr ng-repeat="friend in friends | filter:searchText"> 14886 <td>{{friend.name}}</td> 14887 <td>{{friend.phone}}</td> 14888 </tr> 14889 </table> 14890 <hr> 14891 Any: <input ng-model="search.$"> <br> 14892 Name only <input ng-model="search.name"><br> 14893 Phone only <input ng-model="search.phone"><br> 14894 Equality <input type="checkbox" ng-model="strict"><br> 14895 <table id="searchObjResults"> 14896 <tr><th>
14896Name</th><th>Phone</th></tr> 14897 <tr ng-repeat="friendObj in friends | filter:search:strict"> 14898 <td>{{friendObj.name}}</td> 14899 <td>{{friendObj.phone}}</td> 14900 </tr> 14901 </table> 14902 </file> 14903 <file name="protractor.js" type="protractor"> 14904 var expectFriendNames = function(expectedNames, key) { 14905 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 14906 arr.forEach(function(wd, i) { 14907 expect(wd.getText()).toMatch(expectedNames[i]); 14908 }); 14909 }); 14910 }; 14911 14912 it('should search across all fields when filtering with a string', function() { 14913 var searchText = element(by.model('searchText')); 14914 searchText.clear(); 14915 searchText.sendKeys('m'); 14916 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 14917 14918 searchText.clear(); 14919 searchText.sendKeys('76'); 14920 expectFriendNames(['John', 'Julie'], 'friend'); 14921 }); 14922 14923 it('should search in specific fields when filtering with a predicate object', function() { 14924 var searchAny = element(by.model('search.$')); 14925 searchAny.clear(); 14926 searchAny.sendKeys('i'); 14927 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 14928 }); 14929 it('should use a equal comparison when comparator is true', function() { 14930 var searchName = element(by.model('search.name')); 14931 var strict = element(by.model('strict')); 14932 searchName.clear(); 14933 searchName.sendKeys('Julie'); 14934 strict.click(); 14935 expectFriendNames(['Julie'], 'friendObj'); 14936 }); 14937 </file> 14938 </example> 14939 */ 14940function filterFilter() { 14941 return function(array, expression, comparator) { 14942 if (!isArray(array)) return array; 14943 14944 var comparatorType = typeof(comparator), 14945 predicates = []; 14946 14947 predicates.check = function(value) { 14948 for (var j = 0; j < predicates.length; j++) { 14949 if(!predicates[j](value)) { 14950 return false; 14951 } 14952 } 14953 return true; 14954 }; 14955 14956 if (comparatorType !== 'function') { 14957 if (comparatorType === 'boolean' && comparator) { 14958 comparator = function(obj, text) { 14959 return angular.equals(obj, text); 14960 }; 14961 } else { 14962 comparator = function(obj, text) { 14963 if (obj && text && typeof obj === 'object' && typeof text === 'object') { 14964 for (var objKey in obj) { 14965 if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) && 14966 comparator(obj[objKey], text[objKey])) { 14967 return true; 14968 } 14969 } 14970 return false; 14971 } 14972 text = (''+text).toLowerCase(); 14973 return (''+obj).toLowerCase().indexOf(text) > -1; 14974 }; 14975 } 14976 } 14977 14978 var search = function(obj, text){ 14979 if (typeof text === 'string' && text.charAt(0) === '!') { 14980 return !search(obj, text.substr(1)); 14981 } 14982 switch (typeof obj) { 14983 case 'boolean': 14984 case 'number': 14985 case 'string': 14986 return comparator(obj, text); 14987 case 'object': 14988 switch (typeof text) { 14989 case 'object': 14990 return comparator(obj, text); 14991 default: 14992 for ( var objKey in obj) { 14993 if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { 14994 return true; 14995 } 14996 } 14997 break; 14998 } 14999 return false; 15000 case 'array': 15001 for ( var i = 0; i < obj.length; i++) { 15002 if (search(obj[i], text)) { 15003 return true; 15004 } 15005 } 15006 return false; 15007 default: 15008 return false; 15009 } 15010 }; 15011 switch (typeof expression) { 15012 case 'boolean': 15013 case 'number': 15014 case 'string': 15015 // Set up expression object and fall through 15016 expression = {$:expression}; 15017 // jshint -W086 15018 case 'object': 15019 // jshint +W086 15020 for (var key in expression) { 15021 (function(path) { 15022 if (typeof expression[path] === 'undefined') return; 15023 predicates.push(function(value) { 15024 return search(path == '$' ? value : (value && value[path]), expression[path]); 15025 }); 15026 })(key); 15027 } 15028 break; 15029 case 'function': 15030 predicates.push(expression); 15031 break; 15032 default: 15033 return array; 15034 } 15035 var filtered = []; 15036 for ( var j = 0; j < array.length; j++) { 15037 var value = array[j]; 15038 if (predicates.check(value)) { 15039 filtered.push(value); 15040 } 15041 } 15042 return filtered; 15043 }; 15044} 15045 15046/** 15047 * @ngdoc filter 15048 * @name currency 15049 * @kind function 15050 * 15051 * @description 15052 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 15053 * symbol for current locale is used. 15054 * 15055 * @param {number} amount Input to filter. 15056 * @param {string=} symbol Currency symbol or identifier to be displayed. 15057 * @returns {string} Formatted number. 15058 * 15059 * 15060 * @example 15061 <example module="currencyExample"> 15062 <file name="index.html"> 15063 <script>
15064 angular.module('currencyExample', []) 15065 .controller('ExampleController', ['$scope', function($scope) { 15066 $scope.amount = 1234.56; 15067 }]); 15068 </script> 15069 <div ng-controller="ExampleController"> 15070 <input type="number" ng-model="amount"> <br> 15071 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 15072 custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span> 15073 </div> 15074 </file> 15075 <file name="protractor.js" type="protractor"> 15076 it('should init with 1234.56', function() { 15077 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 15078 expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56'); 15079 }); 15080 it('should update', function() { 15081 if (browser.params.browser == 'safari') { 15082 // Safari does not understand the minus key. See 15083 // https://github.com/angular/protractor/issues/481 15084 return; 15085 } 15086 element(by.model('amount')).clear(); 15087 element(by.model('amount')).sendKeys('-1234'); 15088 expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); 15089 expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)'); 15090 }); 15091 </file> 15092 </example> 15093 */ 15094currencyFilter.$inject = ['$locale']; 15095function currencyFilter($locale) { 15096 var formats = $locale.NUMBER_FORMATS; 15097 return function(amount, currencySymbol){ 15098 if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; 15099 return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). 15100 replace(/\u00A4/g, currencySymbol); 15101 }; 15102} 15103 15104/** 15105 * @ngdoc filter 15106 * @name number 15107 * @kind function 15108 * 15109 * @description 15110 * Formats a number as text. 15111 * 15112 * If the input is not a number an empty string is returned. 15113 * 15114 * @param {number|string} number Number to format. 15115 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 15116 * If this is not provided then the fraction size is computed from the current locale's number 15117 * formatting pattern. In the case of the default locale, it will be 3. 15118 * @returns {string} Number rounded to decimalPlaces and places a â,â after each third digit. 15119 * 15120 * @example 15121 <example module="numberFilterExample"> 15122 <file name="index.html"> 15123 <script> 15124 angular.module('numberFilterExample', []) 15125 .controller('ExampleController', ['$scope', function($scope) { 15126 $scope.val = 1234.56789; 15127 }]); 15128 </script>
15129 <div ng-controller="ExampleController"> 15130 Enter number: <input ng-model='val'><br> 15131 Default formatting: <span id='number-default'>{{val | number}}</span><br> 15132 No fractions: <span>{{val | number:0}}</span><br> 15133 Negative number: <span>{{-val | number:4}}</span> 15134 </div> 15135 </file> 15136 <file name="protractor.js" type="protractor"> 15137 it('should format numbers', function() { 15138 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 15139 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 15140 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 15141 }); 15142 15143 it('should update', function() { 15144 element(by.model('val')).clear(); 15145 element(by.model('val')).sendKeys('3374.333'); 15146 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 15147 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 15148 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 15149 }); 15150 </file> 15151 </example> 15152 */ 15153 15154 15155numberFilter.$inject = ['$locale']; 15156function numberFilter($locale) { 15157 var formats = $locale.NUMBER_FORMATS; 15158 return function(number, fractionSize) { 15159 return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 15160 fractionSize); 15161 }; 15162} 15163 15164var DECIMAL_SEP = '.'; 15165function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 15166 if (number == null || !isFinite(number) || isObject(number)) return ''; 15167 15168 var isNegative = number < 0; 15169 number = Math.abs(number); 15170 var numStr = number + '', 15171 formatedText = '', 15172 parts = []; 15173 15174 var hasExponent = false; 15175 if (numStr.indexOf('e') !== -1) { 15176 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 15177 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 15178 numStr = '0'; 15179 number = 0; 15180 } else { 15181 formatedText = numStr; 15182 hasExponent = true; 15183 } 15184 } 15185 15186 if (!hasExponent) { 15187 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 15188 15189 // determine fractionSize if it is not specified 15190 if (isUndefined(fractionSize)) { 15191 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 15192 } 15193 15194 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics 15195 // inspired by: 15196 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round 15197 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); 15198 15199 if (number === 0) { 15200 isNegative = false; 15201 } 15202 15203 var fraction = ('' + number).split(DECIMAL_SEP); 15204 var whole = fraction[0]; 15205 fraction = fraction[1] || ''; 15206 15207 var i, pos = 0, 15208 lgroup = pattern.lgSize, 15209 group = pattern.gSize; 15210 15211 if (whole.length >= (lgroup + group)) { 15212 pos = whole.length - lgroup; 15213 for (i = 0; i < pos; i++) { 15214 if ((pos - i)%group === 0 && i !== 0) { 15215 formatedText += groupSep; 15216 } 15217 formatedText += whole.charAt(i); 15218 } 15219 } 15220 15221 for (i = pos; i < whole.length; i++) { 15222 if ((whole.length - i)%lgroup === 0 && i !== 0) { 15223 formatedText += groupSep; 15224 } 15225 formatedText += whole.charAt(i); 15226 } 15227 15228 // format fraction part. 15229 while(fraction.length < fractionSize) { 15230 fraction += '0'; 15231 } 15232 15233 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 15234 } else { 15235 15236 if (fractionSize > 0 && number > -1 && number < 1) { 15237 formatedText = number.toFixed(fractionSize); 15238 } 15239 } 15240 15241 parts.push(isNegative ? pattern.negPre : pattern.posPre); 15242 parts.push(formatedText); 15243 parts.push(isNegative ? pattern.negSuf : pattern.posSuf); 15244 return parts.join(''); 15245} 15246 15247function padNumber(num, digits, trim) { 15248 var neg = ''; 15249 if (num < 0) { 15250 neg = '-'; 15251 num = -num; 15252 } 15253 num = '' + num; 15254 while(num.length < digits) num = '0' + num; 15255 if (trim) 15256 num = num.substr(num.length - digits); 15257 return neg + num; 15258} 15259 15260 15261function dateGetter(name, size, offset, trim) { 15262 offset = offset || 0; 15263 return function(date) { 15264 var value = date['get' + name](); 15265 if (offset > 0 || value > -offset) 15266 value += offset; 15267 if (value === 0 && offset == -12 ) value = 12; 15268 return padNumber(value, size, trim); 15269 }; 15270} 15271 15272function dateStrGetter(name, shortForm) { 15273 return function(date, formats) { 15274 var value = date['get' + name](); 15275 var get = uppercase(shortForm ? ('SHORT' + name) : name); 15276 15277 return formats[get][value]; 15278 }; 15279} 15280 15281function timeZoneGetter(date) { 15282 var zone = -1 * date.getTimezoneOffset(); 15283 var paddedZone = (zone >= 0) ? "+" : ""; 15284 15285 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 15286 padNumber(Math.abs(zone % 60), 2); 15287 15288 return paddedZone; 15289} 15290 15291function ampmGetter(date, formats) { 15292 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 15293} 15294 15295var DATE_FORMATS = { 15296 yyyy: dateGetter('FullYear', 4), 15297 yy: dateGetter('FullYear', 2, 0, true), 15298 y: dateGetter('FullYear', 1), 15299 MMMM: dateStrGetter('Month'),
15300 MMM: dateStrGetter('Month', true), 15301 MM: dateGetter('Month', 2, 1), 15302 M: dateGetter('Month', 1, 1), 15303 dd: dateGetter('Date', 2), 15304 d: dateGetter('Date', 1), 15305 HH: dateGetter('Hours', 2), 15306 H: dateGetter('Hours', 1), 15307 hh: dateGetter('Hours', 2, -12), 15308 h: dateGetter('Hours', 1, -12), 15309 mm: dateGetter('Minutes', 2), 15310 m: dateGetter('Minutes', 1), 15311 ss: dateGetter('Seconds', 2), 15312 s: dateGetter('Seconds', 1), 15313 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 15314 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 15315 sss: dateGetter('Milliseconds', 3), 15316 EEEE: dateStrGetter('Day'), 15317 EEE: dateStrGetter('Day', true), 15318 a: ampmGetter, 15319 Z: timeZoneGetter 15320}; 15321 15322var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, 15323 NUMBER_STRING = /^\-?\d+$/; 15324 15325/** 15326 * @ngdoc filter 15327 * @name date 15328 * @kind function 15329 * 15330 * @description 15331 * Formats `date` to a string based on the requested `format`. 15332 * 15333 * `format` string can be composed of the following elements: 15334 * 15335 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 15336 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 15337 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 15338 * * `'MMMM'`: Month in year (January-December) 15339 * * `'MMM'`: Month in year (Jan-Dec) 15340 * * `'MM'`: Month in year, padded (01-12) 15341 * * `'M'`: Month in year (1-12) 15342 * * `'dd'`: Day in month, padded (01-31) 15343 * * `'d'`: Day in month (1-31) 15344 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 15345 * * `'EEE'`: Day in Week, (Sun-Sat) 15346 * * `'HH'`: Hour in day, padded (00-23) 15347 * * `'H'`: Hour in day (0-23) 15348 * * `'hh'`: Hour in am/pm, padded (01-12) 15349 * * `'h'`: Hour in am/pm, (1-12) 15350 * * `'mm'`: Minute in hour, padded (00-59) 15351 * * `'m'`: Minute in hour (0-59) 15352 * * `'ss'`: Second in minute, padded (00-59) 15353 * * `'s'`: Second in minute (0-59) 15354 * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) 15355 * * `'a'`: am/pm marker 15356 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 15357 * 15358 * `format` string can also be one of the following predefined 15359 * {@link guide/i18n localizable formats}: 15360 * 15361 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 15362 * (e.g. Sep 3, 2010 12:05:08 pm) 15363 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) 15364 * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale 15365 * (e.g. Friday, September 3, 2010) 15366 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 15367 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 15368 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 15369 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) 15370 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) 15371 * 15372 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 15373 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 15374 * (e.g. `"h 'o''clock'"`). 15375 * 15376 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 15377 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 15378 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 15379 * specified in the string input, the time is considered to be in the local timezone. 15380 * @param {string=} format Formatting rules (see Description). If not specified, 15381 * `mediumDate` is used. 15382 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 15383 * 15384 * @example 15385 <example> 15386 <file name="index.html"> 15387 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 15388 <span>{{1288323623006 | date:'medium'}}</span><br>
15389 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 15390 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 15391 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 15392 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 15393 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 15394 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 15395 </file> 15396 <file name="protractor.js" type="protractor"> 15397 it('should format date', function() { 15398 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 15399 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 15400 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 15401 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 15402 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 15403 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 15404 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 15405 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 15406 }); 15407 </file> 15408 </example> 15409 */ 15410dateFilter.$inject = ['$locale']; 15411function dateFilter($locale) { 15412 15413 15414 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 15415 // 1 2 3 4 5 6 7 8 9 10 11 15416 function jsonStringToDate(string) { 15417 var match; 15418 if (match = string.match(R_ISO8601_STR)) { 15419 var date = new Date(0), 15420 tzHour = 0, 15421 tzMin = 0, 15422 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 15423 timeSetter = match[8] ? date.setUTCHours : date.setHours; 15424 15425 if (match[9]) { 15426 tzHour = int(match[9] + match[10]); 15427 tzMin = int(match[9] + match[11]); 15428 } 15429 dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); 15430 var h = int(match[4]||0) - tzHour; 15431 var m = int(match[5]||0) - tzMin; 15432 var s = int(match[6]||0); 15433 var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); 15434 timeSetter.call(date, h, m, s, ms); 15435 return date; 15436 } 15437 return string; 15438 } 15439 15440 15441 return function(date, format) { 15442 var text = '', 15443 parts = [], 15444 fn, match; 15445 15446 format = format || 'mediumDate'; 15447 format = $locale.DATETIME_FORMATS[format] || format; 15448 if (isString(date)) { 15449 date = NUMBER_STRING.test(date) ? int(date) : jsonStringToDate(date); 15450 } 15451 15452 if (isNumber(date)) { 15453 date = new Date(date); 15454 } 15455 15456 if (!isDate(date)) { 15457 return date; 15458 } 15459 15460 while(format) { 15461 match = DATE_FORMATS_SPLIT.exec(format); 15462 if (match) { 15463 parts = concat(parts, match, 1); 15464 format = parts.pop(); 15465 } else { 15466 parts.push(format); 15467 format = null; 15468 } 15469 } 15470 15471 forEach(parts, function(value){ 15472 fn = DATE_FORMATS[value]; 15473 text += fn ? fn(date, $locale.DATETIME_FORMATS) 15474 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 15475 }); 15476 15477 return text; 15478 }; 15479} 15480 15481 15482/** 15483 * @ngdoc filter 15484 * @name json 15485 * @kind function 15486 * 15487 * @description 15488 * Allows you to convert a JavaScript object into JSON string. 15489 * 15490 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 15491 * the binding is automatically converted to JSON. 15492 * 15493 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 15494 * @returns {string} JSON string. 15495 * 15496 * 15497 * @example 15498 <example> 15499 <file name="index.html"> 15500 <pre>{{ {'name':'value'} | json }}</pre> 15501 </file> 15502 <file name="protractor.js" type="protractor"> 15503 it('should jsonify filtered objects', function() { 15504 expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/); 15505 }); 15506 </file> 15507 </example> 15508 * 15509 */ 15510function jsonFilter() { 15511 return function(object) { 15512 return toJson(object, true); 15513 }; 15514} 15515 15516 15517/** 15518 * @ngdoc filter 15519 * @name lowercase 15520 * @kind function 15521 * @description 15522 * Converts string to lowercase. 15523 * @see angular.lowercase 15524 */ 15525var lowercaseFilter = valueFn(lowercase); 15526 15527
15528/** 15529 * @ngdoc filter 15530 * @name uppercase 15531 * @kind function 15532 * @description 15533 * Converts string to uppercase. 15534 * @see angular.uppercase 15535 */ 15536var uppercaseFilter = valueFn(uppercase); 15537 15538/** 15539 * @ngdoc filter 15540 * @name limitTo 15541 * @kind function 15542 * 15543 * @description 15544 * Creates a new array or string containing only a specified number of elements. The elements 15545 * are taken from either the beginning or the end of the source array or string, as specified by 15546 * the value and sign (positive or negative) of `limit`. 15547 * 15548 * @param {Array|string} input Source array or string to be limited. 15549 * @param {string|number} limit The length of the returned array or string. If the `limit` number 15550 * is positive, `limit` number of items from the beginning of the source array/string are copied. 15551 * If the number is negative, `limit` number of items from the end of the source array/string 15552 * are copied. The `limit` will be trimmed if it exceeds `array.length` 15553 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 15554 * had less than `limit` elements. 15555 * 15556 * @example 15557 <example module="limitToExample"> 15558 <file name="index.html"> 15559 <script> 15560 angular.module('limitToExample', []) 15561 .controller('ExampleController', ['$scope', function($scope) { 15562 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 15563 $scope.letters = "abcdefghi"; 15564 $scope.numLimit = 3; 15565 $scope.letterLimit = 3; 15566 }]); 15567 </script>
15568 <div ng-controller="ExampleController"> 15569 Limit {{numbers}} to: <input type="number" step="1" ng-model="numLimit"> 15570 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 15571 Limit {{letters}} to: <input type="number" step="1" ng-model="letterLimit"> 15572 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 15573 </div> 15574 </file> 15575 <file name="protractor.js" type="protractor"> 15576 var numLimitInput = element(by.model('numLimit')); 15577 var letterLimitInput = element(by.model('letterLimit')); 15578 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 15579 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 15580 15581 it('should limit the number array to first three items', function() { 15582 expect(numLimitInput.getAttribute('value')).toBe('3'); 15583 expect(letterLimitInput.getAttribute('value')).toBe('3'); 15584 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 15585 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 15586 }); 15587 15588 // There is a bug in safari and protractor that doesn't like the minus key 15589 // it('should update the output when -3 is entered', function() { 15590 // numLimitInput.clear(); 15591 // numLimitInput.sendKeys('-3'); 15592 // letterLimitInput.clear(); 15593 // letterLimitInput.sendKeys('-3'); 15594 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 15595 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 15596 // }); 15597 15598 it('should not exceed the maximum size of input array', function() { 15599 numLimitInput.clear(); 15600 numLimitInput.sendKeys('100'); 15601 letterLimitInput.clear(); 15602 letterLimitInput.sendKeys('100'); 15603 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 15604 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 15605 }); 15606 </file> 15607 </example> 15608 */ 15609function limitToFilter(){ 15610 return function(input, limit) { 15611 if (!isArray(input) && !isString(input)) return input; 15612 15613 if (Math.abs(Number(limit)) === Infinity) { 15614 limit = Number(limit); 15615 } else { 15616 limit = int(limit); 15617 } 15618 15619 //NaN check on limit 15620 if (limit) { 15621 return limit > 0 ? input.slice(0, limit) : input.slice(limit); 15622 } else { 15623 return isString(input) ? "" : []; 15624 } 15625 }; 15626} 15627 15628/** 15629 * @ngdoc filter 15630 * @name orderBy 15631 * @kind function 15632 * 15633 * @description 15634 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 15635 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 15636 * correctly, make sure they are actually being saved as numbers and not strings. 15637 * 15638 * @param {Array} array The array to sort. 15639 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be 15640 * used by the comparator to determine the order of elements. 15641 * 15642 * Can be one of: 15643 * 15644 * - `function`: Getter function. The result of this function will be sorted using the 15645 * `<`, `=`, `>` operator. 15646 * - `string`: An Angular expression. The result of this expression is used to compare elements 15647 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by 15648 * 3 first characters of a property called `name`). The result of a constant expression 15649 * is interpreted as a property name to be used in comparisons (for example `"special name"` 15650 * to sort object by the value of their `special name` property). An expression can be 15651 * optionally prefixed with `+` or `-` to control ascending or descending sort order 15652 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array 15653 * element itself is used to compare where sorting. 15654 * - `Array`: An array of function or string predicates. The first predicate in the array 15655 * is used for sorting, but when two items are equivalent, the next predicate is used. 15656 * 15657 * If the predicate is missing or empty then it defaults to `'+'`. 15658 * 15659 * @param {boolean=} reverse Reverse the order of the array. 15660 * @returns {Array} Sorted copy of the source array. 15661 * 15662 * @example 15663 <example module="orderByExample"> 15664 <file name="index.html"> 15665 <script> 15666 angular.module('orderByExample', [])
15667 .controller('ExampleController', ['$scope', function($scope) { 15668 $scope.friends = 15669 [{name:'John', phone:'555-1212', age:10}, 15670 {name:'Mary', phone:'555-9876', age:19}, 15671 {name:'Mike', phone:'555-4321', age:21}, 15672 {name:'Adam', phone:'555-5678', age:35}, 15673 {name:'Julie', phone:'555-8765', age:29}]; 15674 $scope.predicate = '-age'; 15675 }]); 15676 </script> 15677 <div ng-controller="ExampleController"> 15678 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 15679 <hr/> 15680 [ <a href="" ng-click="predicate=''">unsorted</a> ] 15681 <table class="friend"> 15682 <tr> 15683 <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a> 15684 (<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th> 15685 <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> 15686 <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th> 15687 </tr> 15688 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 15689 <td>{{friend.name}}</td> 15690 <td>{{friend.phone}}</td> 15691 <td>{{friend.age}}</td> 15692 </tr> 15693 </table> 15694 </div> 15695 </file> 15696 </example> 15697 * 15698 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the 15699 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 15700 * desired parameters. 15701 * 15702 * Example: 15703 * 15704 * @example 15705 <example module="orderByExample"> 15706 <file name="index.html">
15707 <div ng-controller="ExampleController"> 15708 <table class="friend"> 15709 <tr> 15710 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a> 15711 (<a href="" ng-click="order('-name',false)">^</a>)</th> 15712 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th> 15713 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th> 15714 </tr> 15715 <tr ng-repeat="friend in friends"> 15716 <td>{{friend.name}}</td> 15717 <td>{{friend.phone}}</td> 15718 <td>{{friend.age}}</td> 15719 </tr> 15720 </table> 15721 </div> 15722 </file> 15723 15724 <file name="script.js"> 15725 angular.module('orderByExample', []) 15726 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { 15727 var orderBy = $filter('orderBy'); 15728 $scope.friends = [ 15729 { name: 'John', phone: '555-1212', age: 10 }, 15730 { name: 'Mary', phone: '555-9876', age: 19 }, 15731 { name: 'Mike', phone: '555-4321', age: 21 }, 15732 { name: 'Adam', phone: '555-5678', age: 35 }, 15733 { name: 'Julie', phone: '555-8765', age: 29 } 15734 ]; 15735 $scope.order = function(predicate, reverse) { 15736 $scope.friends = orderBy($scope.friends, predicate, reverse); 15737 }; 15738 $scope.order('-age',false); 15739 }]); 15740 </file> 15741</example> 15742 */ 15743orderByFilter.$inject = ['$parse']; 15744function orderByFilter($parse){ 15745 return function(array, sortPredicate, reverseOrder) { 15746 if (!(isArrayLike(array))) return array; 15747 sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; 15748 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 15749 sortPredicate = map(sortPredicate, function(predicate){ 15750 var descending = false, get = predicate || identity; 15751 if (isString(predicate)) { 15752 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 15753 descending = predicate.charAt(0) == '-'; 15754 predicate = predicate.substring(1); 15755 } 15756 if ( predicate === '' ) { 15757 // Effectively no predicate was passed so we compare identity 15758 return reverseComparator(function(a,b) { 15759 return compare(a, b); 15760 }, descending); 15761 } 15762 get = $parse(predicate); 15763 if (get.constant) { 15764 var key = get(); 15765 return reverseComparator(function(a,b) { 15766 return compare(a[key], b[key]); 15767 }, descending); 15768 } 15769 } 15770 return reverseComparator(function(a,b){ 15771 return compare(get(a),get(b)); 15772 }, descending); 15773 }); 15774 return slice.call(array).sort(reverseComparator(comparator, reverseOrder)); 15775 15776 function comparator(o1, o2){ 15777 for ( var i = 0; i < sortPredicate.length; i++) { 15778 var comp = sortPredicate[i](o1, o2); 15779 if (comp !== 0) return comp; 15780 } 15781 return 0; 15782 } 15783 function reverseComparator(comp, descending) { 15784 return toBoolean(descending) 15785 ? function(a,b){return comp(b,a);} 15786 : comp; 15787 } 15788 function compare(v1, v2){ 15789 var t1 = typeof v1; 15790 var t2 = typeof v2; 15791 if (t1 == t2) { 15792 if (isDate(v1) && isDate(v2)) { 15793 v1 = v1.valueOf(); 15794 v2 = v2.valueOf(); 15795 } 15796 if (t1 == "string") { 15797 v1 = v1.toLowerCase(); 15798 v2 = v2.toLowerCase(); 15799 } 15800 if (v1 === v2) return 0; 15801 return v1 < v2 ? -1 : 1; 15802 } else { 15803 return t1 < t2 ? -1 : 1; 15804 } 15805 } 15806 }; 15807} 15808 15809function ngDirective(directive) { 15810 if (isFunction(directive)) { 15811 directive = { 15812 link: directive 15813 }; 15814 } 15815 directive.restrict = directive.restrict || 'AC'; 15816 return valueFn(directive); 15817} 15818 15819/** 15820 * @ngdoc directive 15821 * @name a 15822 * @restrict E 15823 * 15824 * @description 15825 * Modifies the default behavior of the html A tag so that the default action is prevented when 15826 * the href attribute is empty. 15827 * 15828 * This change permits the easy creation of action links with the `ngClick` directive 15829 * without changing the location or causing page reloads, e.g.: 15830 * `<a href="" ng-click="list.addItem()">Add Item</a>` 15831 */ 15832var htmlAnchorDirective = valueFn({ 15833 restrict: 'E', 15834 compile: function(element, attr) { 15835 15836 if (msie <= 8) { 15837 15838 // turn <a href ng-click="..">link</a> into a stylable link in IE 15839 // but only if it doesn't have name attribute, in which case it's an anchor 15840 if (!attr.href && !attr.name) { 15841 attr.$set('href', ''); 15842 } 15843 15844 // add a comment node to anchors to workaround IE bug that causes element content to be reset 15845 // to new attribute content if attribute is updated with value containing @ and element also 15846 // contains value with @ 15847 // see issue #1949 15848 element.append(document.createComment('IE fix')); 15849 } 15850 15851 if (!attr.href && !attr.xlinkHref && !attr.name) { 15852 return function(scope, element) { 15853 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 15854 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 15855 'xlink:href' : 'href'; 15856 element.on('click', function(event){ 15857 // if we have no href url, then don't navigate anywhere. 15858 if (!element.attr(href)) { 15859 event.preventDefault(); 15860 } 15861 }); 15862 }; 15863 } 15864 } 15865}); 15866 15867/** 15868 * @ngdoc directive 15869 * @name ngHref 15870 * @restrict A 15871 * @priority 99 15872 * 15873 * @description 15874 * Using Angular markup like `{{hash}}` in an href attribute will 15875 * make the link go to the wrong URL if the user clicks it before 15876 * Angular has a chance to replace the `{{hash}}
15876` markup with its 15877 * value. Until Angular replaces the markup the link will be broken 15878 * and will most likely return a 404 error. The `ngHref` directive 15879 * solves this problem. 15880 * 15881 * The wrong way to write it: 15882 * ```html 15883 * <a href="http://www.gravatar.com/avatar/{{hash}}"/> 15884 * ``` 15885 * 15886 * The correct way to write it: 15887 * ```html 15888 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/> 15889 * ``` 15890 * 15891 * @element A 15892 * @param {template} ngHref any string which can contain `{{}}` markup. 15893 * 15894 * @example 15895 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 15896 * in links and their different behaviors: 15897 <example> 15898 <file name="index.html"> 15899 <input ng-model="value" /><br /> 15900 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 15901 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 15902 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 15903 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 15904 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 15905 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 15906 </file> 15907 <file name="protractor.js" type="protractor"> 15908 it('should execute ng-click but not reload when href without value', function() { 15909 element(by.id('link-1')).click(); 15910 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 15911 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 15912 }); 15913 15914 it('should execute ng-click but not reload when href empty string', function() { 15915 element(by.id('link-2')).click(); 15916 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 15917 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 15918 }); 15919 15920 it('should execute ng-click and change url when ng-href specified', function() { 15921 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 15922 15923 element(by.id('link-3')).click(); 15924 15925 // At this point, we navigate away from an Angular page, so we need 15926 // to use browser.driver to get the base webdriver. 15927 15928 browser.wait(function() { 15929 return browser.driver.getCurrentUrl().then(function(url) { 15930 return url.match(/\/123$/); 15931 }); 15932 }, 5000, 'page should navigate to /123'); 15933 }); 15934 15935 xit('should execute ng-click but not reload when href empty string and name specified', function() { 15936 element(by.id('link-4')).click(); 15937 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 15938 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 15939 }); 15940 15941 it('should execute ng-click but not reload when no href but name specified', function() { 15942 element(by.id('link-5')).click(); 15943 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 15944 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 15945 }); 15946 15947 it('should only change url when only ng-href', function() { 15948 element(by.model('value')).clear(); 15949 element(by.model('value')).sendKeys('6'); 15950 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 15951 15952 element(by.id('link-6')).click(); 15953 15954 // At this point, we navigate away from an Angular page, so we need 15955 // to use browser.driver to get the base webdriver. 15956 browser.wait(function() { 15957 return browser.driver.getCurrentUrl().then(function(url) { 15958 return url.match(/\/6$/); 15959 }); 15960 }, 5000, 'page should navigate to /6'); 15961 }); 15962 </file> 15963 </example> 15964 */ 15965 15966/** 15967 * @ngdoc directive 15968 * @name ngSrc 15969 * @restrict A 15970 * @priority 99 15971 * 15972 * @description 15973 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 15974 * work right: The browser will fetch from the URL with the literal 15975 * text `{{hash}}` until Angular replaces the expression inside 15976 * `{{hash}}`. The `ngSrc` directive solves this problem. 15977 * 15978 * The buggy way to write it: 15979 * ```html 15980 * <img src="http://www.gravatar.com/avatar/{{hash}}"/> 15981 * ``` 15982 * 15983 * The correct way to write it: 15984 * ```html 15985 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/> 15986 * ``` 15987 * 15988 * @element IMG 15989 * @param {template} ngSrc any string which can contain `{{}}` markup. 15990 */ 15991 15992/** 15993 * @ngdoc directive 15994 * @name ngSrcset 15995 * @restrict A 15996 * @priority 99 15997 * 15998 * @description 15999 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 16000 * work right: The browser will fetch from the URL with the literal 16001 * text `{{hash}}` until Angular replaces the expression inside 16002 * `{{hash}}`. The `ngSrcset` directive solves this problem. 16003 * 16004 * The buggy way to write it: 16005 * ```html 16006 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 16007 * ``` 16008 * 16009 * The correct way to write it: 16010 * ```html 16011 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 16012 * ``` 16013 * 16014 * @element IMG 16015 * @param {template} ngSrcset any string which can contain `{{}}` markup. 16016 */ 16017 16018/** 16019 * @ngdoc directive 16020 * @name ngDisabled 16021 * @restrict A 16022 * @priority 100 16023 * 16024 * @description 16025 * 16026 * We shouldn't do this, because it will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: 16027 * ```html 16028 * <div ng-init="scope = { isDisabled: false }"> 16029 * <button disabled="{{scope.isDisabled}}">Disabled</button> 16030 * </div> 16031 * ``` 16032 * 16033 * The HTML specification does not require browsers to preserve the values of boolean attributes 16034 * such as disabled. (Their presence means true and their absence means false.) 16035 * If we put an Angular interpolation expression into such an attribute then the 16036 * binding information would be lost when the browser removes the attribute. 16037 * The `ngDisabled` directive solves this problem for the `disabled` attribute. 16038 * This complementary directive is not removed by the browser and so provides 16039 * a permanent reliable place to store the binding information. 16040 * 16041 * @example 16042 <example> 16043 <file name="index.html"> 16044 Click me to toggle: <input type="checkbox" ng-model="checked"><br/> 16045 <button ng-model="button" ng-disabled="checked">Button</button> 16046 </file> 16047 <file name="protractor.js" type="protractor"> 16048 it('should toggle button', function() { 16049 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
16050 element(by.model('checked')).click(); 16051 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 16052 }); 16053 </file> 16054 </example> 16055 * 16056 * @element INPUT 16057 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 16058 * then special attribute "disabled" will be set on the element 16059 */ 16060 16061 16062/** 16063 * @ngdoc directive 16064 * @name ngChecked 16065 * @restrict A 16066 * @priority 100 16067 * 16068 * @description 16069 * The HTML specification does not require browsers to preserve the values of boolean attributes 16070 * such as checked. (Their presence means true and their absence means false.) 16071 * If we put an Angular interpolation expression into such an attribute then the 16072 * binding information would be lost when the browser removes the attribute. 16073 * The `ngChecked` directive solves this problem for the `checked` attribute. 16074 * This complementary directive is not removed by the browser and so provides 16075 * a permanent reliable place to store the binding information. 16076 * @example 16077 <example> 16078 <file name="index.html"> 16079 Check me to check both: <input type="checkbox" ng-model="master"><br/> 16080 <input id="checkSlave" type="checkbox" ng-checked="master"> 16081 </file> 16082 <file name="protractor.js" type="protractor"> 16083 it('should check both checkBoxes', function() { 16084 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 16085 element(by.model('master')).click(); 16086 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 16087 }); 16088 </file> 16089 </example> 16090 * 16091 * @element INPUT 16092 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 16093 * then special attribute "checked" will be set on the element 16094 */ 16095 16096 16097/** 16098 * @ngdoc directive 16099 * @name ngReadonly 16100 * @restrict A 16101 * @priority 100 16102 * 16103 * @description 16104 * The HTML specification does not require browsers to preserve the values of boolean attributes 16105 * such as readonly. (Their presence means true and their absence means false.) 16106 * If we put an Angular interpolation expression into such an attribute then the 16107 * binding information would be lost when the browser removes the attribute. 16108 * The `ngReadonly` directive solves this problem for the `readonly` attribute. 16109 * This complementary directive is not removed by the browser and so provides 16110 * a permanent reliable place to store the binding information. 16111 * @example 16112 <example> 16113 <file name="index.html"> 16114 Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/> 16115 <input type="text" ng-readonly="checked" value="I'm Angular"/> 16116 </file> 16117 <file name="protractor.js" type="protractor"> 16118 it('should toggle readonly attr', function() { 16119 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 16120 element(by.model('checked')).click(); 16121 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 16122 }); 16123 </file> 16124 </example> 16125 * 16126 * @element INPUT 16127 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 16128 * then special attribute "readonly" will be set on the element 16129 */ 16130 16131 16132/** 16133 * @ngdoc directive 16134 * @name ngSelected 16135 * @restrict A 16136 * @priority 100 16137 * 16138 * @description 16139 * The HTML specification does not require browsers to preserve the values of boolean attributes 16140 * such as selected. (Their presence means true and their absence means false.) 16141 * If we put an Angular interpolation expression into such an attribute then the 16142 * binding information would be lost when the browser removes the attribute. 16143 * The `ngSelected` directive solves this problem for the `selected` attribute. 16144 * This complementary directive is not removed by the browser and so provides 16145 * a permanent reliable place to store the binding information. 16146 * 16147 * @example 16148 <example> 16149 <file name="index.html"> 16150 Check me to select: <input type="checkbox" ng-model="selected"><br/> 16151 <select> 16152 <option>Hello!</option> 16153 <option id="greet" ng-selected="selected">Greetings!</option> 16154 </select> 16155 </file> 16156 <file name="protractor.js" type="protractor"> 16157 it('should select Greetings!', function() { 16158 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
16159 element(by.model('selected')).click(); 16160 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 16161 }); 16162 </file> 16163 </example> 16164 * 16165 * @element OPTION 16166 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 16167 * then special attribute "selected" will be set on the element 16168 */ 16169 16170/** 16171 * @ngdoc directive 16172 * @name ngOpen 16173 * @restrict A 16174 * @priority 100 16175 * 16176 * @description 16177 * The HTML specification does not require browsers to preserve the values of boolean attributes 16178 * such as open. (Their presence means true and their absence means false.) 16179 * If we put an Angular interpolation expression into such an attribute then the 16180 * binding information would be lost when the browser removes the attribute. 16181 * The `ngOpen` directive solves this problem for the `open` attribute. 16182 * This complementary directive is not removed by the browser and so provides 16183 * a permanent reliable place to store the binding information. 16184 * @example 16185 <example> 16186 <file name="index.html"> 16187 Check me check multiple: <input type="checkbox" ng-model="open"><br/> 16188 <details id="details" ng-open="open"> 16189 <summary>Show/Hide me</summary> 16190 </details> 16191 </file> 16192 <file name="protractor.js" type="protractor"> 16193 it('should toggle open', function() { 16194 expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); 16195 element(by.model('open')).click(); 16196 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 16197 }); 16198 </file> 16199 </example> 16200 * 16201 * @element DETAILS 16202 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 16203 * then special attribute "open" will be set on the element 16204 */ 16205 16206var ngAttributeAliasDirectives = {}; 16207 16208 16209// boolean attrs are evaluated 16210forEach(BOOLEAN_ATTR, function(propName, attrName) { 16211 // binding to multiple is not supported 16212 if (propName == "multiple") return; 16213 16214 var normalized = directiveNormalize('ng-' + attrName); 16215 ngAttributeAliasDirectives[normalized] = function() { 16216 return { 16217 priority: 100, 16218 link: function(scope, element, attr) { 16219 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 16220 attr.$set(attrName, !!value); 16221 }); 16222 } 16223 }; 16224 }; 16225}); 16226 16227 16228// ng-src, ng-srcset, ng-href are interpolated 16229forEach(['src', 'srcset', 'href'], function(attrName) { 16230 var normalized = directiveNormalize('ng-' + attrName); 16231 ngAttributeAliasDirectives[normalized] = function() { 16232 return { 16233 priority: 99, // it needs to run after the attributes are interpolated 16234 link: function(scope, element, attr) { 16235 var propName = attrName, 16236 name = attrName; 16237 16238 if (attrName === 'href' && 16239 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 16240 name = 'xlinkHref'; 16241 attr.$attr[name] = 'xlink:href'; 16242 propName = null; 16243 } 16244 16245 attr.$observe(normalized, function(value) { 16246 if (!value) { 16247 if (attrName === 'href') { 16248 attr.$set(name, null); 16249 } 16250 return; 16251 } 16252 16253 attr.$set(name, value); 16254 16255 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 16256 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 16257 // to set the property as well to achieve the desired effect. 16258 // we use attr[attrName] value since $set can sanitize the url. 16259 if (msie && propName) element.prop(propName, attr[name]); 16260 }); 16261 } 16262 }; 16263 }; 16264}); 16265 16266/* global -nullFormCtrl */ 16267var nullFormCtrl = { 16268 $addControl: noop, 16269 $removeControl: noop, 16270 $setValidity: noop, 16271 $setDirty: noop, 16272 $setPristine: noop 16273}; 16274 16275/** 16276 * @ngdoc type 16277 * @name form.FormController 16278 * 16279 * @property {boolean} $pristine True if user has not interacted with the form yet. 16280 * @property {boolean} $dirty True if user has already interacted with the form. 16281 * @property {boolean} $valid True if all of the containing forms and controls are valid. 16282 * @property {boolean}
16282 $invalid True if at least one containing control or form is invalid. 16283 * 16284 * @property {Object} $error Is an object hash, containing references to all invalid controls or 16285 * forms, where: 16286 * 16287 * - keys are validation tokens (error names), 16288 * - values are arrays of controls or forms that are invalid for given error name. 16289 * 16290 * 16291 * Built-in validation tokens: 16292 * 16293 * - `email` 16294 * - `max` 16295 * - `maxlength` 16296 * - `min` 16297 * - `minlength` 16298 * - `number` 16299 * - `pattern` 16300 * - `required` 16301 * - `url` 16302 * 16303 * @description 16304 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 16305 * such as being valid/invalid or dirty/pristine. 16306 * 16307 * Each {@link ng.directive:form form} directive creates an instance 16308 * of `FormController`. 16309 * 16310 */ 16311//asks for $scope to fool the BC controller module 16312FormController.$inject = ['$element', '$attrs', '$scope', '$animate']; 16313function FormController(element, attrs, $scope, $animate) { 16314 var form = this, 16315 parentForm = element.parent().controller('form') || nullFormCtrl, 16316 invalidCount = 0, // used to easily determine if we are valid 16317 errors = form.$error = {}, 16318 controls = []; 16319 16320 // init state 16321 form.$name = attrs.name || attrs.ngForm; 16322 form.$dirty = false; 16323 form.$pristine = true; 16324 form.$valid = true; 16325 form.$invalid = false; 16326 16327 parentForm.$addControl(form); 16328 16329 // Setup initial state of the control 16330 element.addClass(PRISTINE_CLASS); 16331 toggleValidCss(true); 16332 16333 // convenience method for easy toggling of classes 16334 function toggleValidCss(isValid, validationErrorKey) { 16335 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 16336 $animate.setClass(element, 16337 (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey, 16338 (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); 16339 } 16340 16341 /** 16342 * @ngdoc method 16343 * @name form.FormController#$addControl 16344 * 16345 * @description 16346 * Register a control with the form. 16347 * 16348 * Input elements using ngModelController do this automatically when they are linked. 16349 */ 16350 form.$addControl = function(control) { 16351 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 16352 // and not added to the scope. Now we throw an error. 16353 assertNotHasOwnProperty(control.$name, 'input'); 16354 controls.push(control); 16355 16356 if (control.$name) { 16357 form[control.$name] = control; 16358 } 16359 }; 16360 16361 /** 16362 * @ngdoc method 16363 * @name form.FormController#$removeControl 16364 * 16365 * @description 16366 * Deregister a control from the form. 16367 * 16368 * Input elements using ngModelController do this automatically when they are destroyed. 16369 */ 16370 form.$removeControl = function(control) { 16371 if (control.$name && form[control.$name] === control) { 16372 delete form[control.$name]; 16373 } 16374 forEach(errors, function(queue, validationToken) { 16375 form.$setValidity(validationToken, true, control); 16376 }); 16377 16378 arrayRemove(controls, control); 16379 }; 16380 16381 /** 16382 * @ngdoc method 16383 * @name form.FormController#$setValidity 16384 * 16385 * @description 16386 * Sets the validity of a form control. 16387 * 16388 * This method will also propagate to parent forms. 16389 */ 16390 form.$setValidity = function(validationToken, isValid, control) { 16391 var queue = errors[validationToken]; 16392 16393 if (isValid) { 16394 if (queue) { 16395 arrayRemove(queue, control); 16396 if (!queue.length) { 16397 invalidCount--; 16398 if (!invalidCount) { 16399 toggleValidCss(isValid); 16400 form.$valid = true; 16401 form.$invalid = false; 16402 } 16403 errors[validationToken] = false; 16404 toggleValidCss(true, validationToken); 16405 parentForm.$setValidity(validationToken, true, form); 16406 } 16407 } 16408 16409 } else { 16410 if (!invalidCount) { 16411 toggleValidCss(isValid); 16412 } 16413 if (queue) { 16414 if (includes(queue, control)) return; 16415 } else { 16416 errors[validationToken] = queue = []; 16417 invalidCount++; 16418 toggleValidCss(false, validationToken); 16419 parentForm.$setValidity(validationToken, false, form); 16420 } 16421 queue.push(control); 16422 16423 form.$valid = false;
16424 form.$invalid = true; 16425 } 16426 }; 16427 16428 /** 16429 * @ngdoc method 16430 * @name form.FormController#$setDirty 16431 * 16432 * @description 16433 * Sets the form to a dirty state. 16434 * 16435 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 16436 * state (ng-dirty class). This method will also propagate to parent forms. 16437 */ 16438 form.$setDirty = function() { 16439 $animate.removeClass(element, PRISTINE_CLASS); 16440 $animate.addClass(element, DIRTY_CLASS); 16441 form.$dirty = true; 16442 form.$pristine = false; 16443 parentForm.$setDirty(); 16444 }; 16445 16446 /** 16447 * @ngdoc method 16448 * @name form.FormController#$setPristine 16449 * 16450 * @description 16451 * Sets the form to its pristine state. 16452 * 16453 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 16454 * state (ng-pristine class). This method will also propagate to all the controls contained 16455 * in this form. 16456 * 16457 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 16458 * saving or resetting it. 16459 */ 16460 form.$setPristine = function () { 16461 $animate.removeClass(element, DIRTY_CLASS); 16462 $animate.addClass(element, PRISTINE_CLASS); 16463 form.$dirty = false; 16464 form.$pristine = true; 16465 forEach(controls, function(control) { 16466 control.$setPristine(); 16467 }); 16468 }; 16469} 16470 16471 16472/** 16473 * @ngdoc directive 16474 * @name ngForm 16475 * @restrict EAC 16476 * 16477 * @description 16478 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 16479 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 16480 * sub-group of controls needs to be determined. 16481 *
16482 * Note: the purpose of `ngForm` is to group controls, 16483 * but not to be a replacement for the `<form>` tag with all of its capabilities 16484 * (e.g. posting to the server, ...). 16485 * 16486 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 16487 * related scope, under this name. 16488 * 16489 */ 16490 16491 /** 16492 * @ngdoc directive 16493 * @name form 16494 * @restrict E 16495 * 16496 * @description 16497 * Directive that instantiates 16498 * {@link form.FormController FormController}. 16499 * 16500 * If the `name` attribute is specified, the form controller is published onto the current scope under 16501 * this name. 16502 * 16503 * # Alias: {@link ng.directive:ngForm `ngForm`} 16504 * 16505 * In Angular forms can be nested. This means that the outer form is valid when all of the child 16506 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 16507 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to 16508 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when 16509 * using Angular validation directives in forms that are dynamically generated using the 16510 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` 16511 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an 16512 * `ngForm` directive and nest these in an outer `form` element. 16513 * 16514 * 16515 * # CSS classes 16516 * - `ng-valid` is set if the form is valid. 16517 * - `ng-invalid` is set if the form is invalid. 16518 * - `ng-pristine` is set if the form is pristine. 16519 * - `ng-dirty` is set if the form is dirty. 16520 * 16521 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 16522 * 16523 * 16524 * # Submitting a form and preventing the default action 16525 * 16526 * Since the role of forms in client-side Angular applications is different than in classical 16527 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 16528 * page reload that sends the data to the server. Instead some javascript logic should be triggered 16529 * to handle the form submission in an application-specific way. 16530 * 16531 * For this reason, Angular prevents the default action (form submission to the server) unless the 16532 * `<form>` element has an `action` attribute specified. 16533 * 16534 * You can use one of the following two ways to specify what javascript method should be called when 16535 * a form is submitted: 16536 * 16537 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 16538 * - {@link ng.directive:ngClick ngClick} directive on the first 16539 * button or input field of type submit (input[type=submit]) 16540 * 16541 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 16542 * or {@link ng.directive:ngClick ngClick} directives. 16543 * This is because of the following form submission rules in the HTML specification: 16544 * 16545 * - If a form has only one input field then hitting enter in this field triggers form submit 16546 * (`ngSubmit`) 16547 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 16548 * doesn't trigger submit 16549 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 16550 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 16551 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 16552 * 16553 * 16554 * ## Animation Hooks 16555 * 16556 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 16557 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 16558 * other validations that are performed within the form. Animations in ngForm are similar to how 16559 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 16560 * as JS animations. 16561 * 16562 * The following example shows a simple way to utilize CSS transitions to style a form element 16563 * that has been rendered as invalid after it has been validated: 16564 * 16565 * <pre> 16566 * //be sure to include ngAnimate as a module to hook into more 16567 * //advanced animations 16568 * .my-form { 16569 * transition:0.5s linear all; 16570 * background: white; 16571 * } 16572 * .my-form.ng-invalid { 16573 * background: red; 16574 * color:white; 16575 * } 16576 * </pre> 16577 * 16578 * @example 16579 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 16580 <file name="index.html"> 16581 <script> 16582 angular.module('formExample', []) 16583 .controller('FormController', ['$scope', function($scope) { 16584 $scope.userType = 'guest'; 16585 }]); 16586 </script> 16587 <style> 16588 .my-form { 16589 -webkit-transition:all linear 0.5s; 16590 transition:all linear 0.5s; 16591 background: transparent; 16592 } 16593 .my-form.ng-invalid { 16594 background: red; 16595 } 16596 </style> 16597 <form name="myForm" ng-controller="FormController" class="my-form"> 16598 userType: <input name="input" ng-model="userType" required>
16599 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 16600 <tt>userType = {{userType}}</tt><br> 16601 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br> 16602 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br> 16603 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 16604 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 16605 </form> 16606 </file> 16607 <file name="protractor.js" type="protractor"> 16608 it('should initialize to model', function() { 16609 var userType = element(by.binding('userType')); 16610 var valid = element(by.binding('myForm.input.$valid')); 16611 16612 expect(userType.getText()).toContain('guest'); 16613 expect(valid.getText()).toContain('true'); 16614 }); 16615 16616 it('should be invalid if empty', function() { 16617 var userType = element(by.binding('userType')); 16618 var valid = element(by.binding('myForm.input.$valid')); 16619 var userInput = element(by.model('userType')); 16620 16621 userInput.clear(); 16622 userInput.sendKeys(''); 16623 16624 expect(userType.getText()).toEqual('userType ='); 16625 expect(valid.getText()).toContain('false'); 16626 }); 16627 </file> 16628 </example> 16629 * 16630 * @param {string=} name Name of the form. If specified, the form controller will be published into 16631 * related scope, under this name. 16632 */ 16633var formDirectiveFactory = function(isNgForm) { 16634 return ['$timeout', function($timeout) { 16635 var formDirective = { 16636 name: 'form', 16637 restrict: isNgForm ? 'EAC' : 'E', 16638 controller: FormController, 16639 compile: function() { 16640 return { 16641 pre: function(scope, formElement, attr, controller) { 16642 if (!attr.action) { 16643 // we can't use jq events because if a form is destroyed during submission the default 16644 // action is not prevented. see #1238 16645 // 16646 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 16647 // page reload if the form was destroyed by submission of the form via a click handler 16648 // on a button in the form. Looks like an IE9 specific bug. 16649 var preventDefaultListener = function(event) { 16650 event.preventDefault 16651 ? event.preventDefault() 16652 : event.returnValue = false; // IE 16653 }; 16654 16655 addEventListenerFn(formElement[0], 'submit', preventDefaultListener); 16656 16657 // unregister the preventDefault listener so that we don't not leak memory but in a 16658 // way that will achieve the prevention of the default action. 16659 formElement.on('$destroy', function() { 16660 $timeout(function() { 16661 removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); 16662 }, 0, false); 16663 }); 16664 } 16665 16666 var parentFormCtrl = formElement.parent().controller('form'), 16667 alias = attr.name || attr.ngForm; 16668 16669 if (alias) { 16670 setter(scope, alias, controller, alias); 16671 } 16672 if (parentFormCtrl) { 16673 formElement.on('$destroy', function() { 16674 parentFormCtrl.$removeControl(controller); 16675 if (alias) { 16676 setter(scope, alias, undefined, alias); 16677 } 16678 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 16679 }); 16680 } 16681 } 16682 }; 16683 } 16684 }; 16685 16686 return formDirective; 16687 }]; 16688}; 16689 16690var formDirective = formDirectiveFactory(); 16691var ngFormDirective = formDirectiveFactory(true); 16692 16693/* global VALID_CLASS: true, 16694 INVALID_CLASS: true, 16695 PRISTINE_CLASS: true, 16696 DIRTY_CLASS: true 16697*/ 16698 16699var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 16700var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 16701var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; 16702 16703var inputType = { 16704 16705 /** 16706 * @ngdoc input 16707 * @name input[text] 16708 * 16709 * @description 16710 * Standard HTML text input with angular data binding, inherited by most of the `input` elements. 16711 * 16712 * *NOTE* Not every feature offered is available for all input types. 16713 * 16714 * @param {string} ngModel Assignable angular expression to data-bind to. 16715 * @param {string=} name Property name of the form under which the control is published. 16716 * @param {string=} required Adds `required` validation error key if the value is not entered. 16717 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16718 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16719 * `required` when you want to data-bind to the `required` attribute. 16720 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16721 * minlength. 16722 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16723 * maxlength. 16724 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16725 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16726 * patterns defined as scope expressions. 16727 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16728 * interaction with the input element. 16729 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 16730 * This parameter is ignored for input[type=password] controls, which will never trim the 16731 * input. 16732 * 16733 * @example 16734 <example name="text-input-directive" module="textInputExample"> 16735 <file name="index.html"> 16736 <script> 16737 angular.module('textInputExample', [])
16738 .controller('ExampleController', ['$scope', function($scope) { 16739 $scope.text = 'guest'; 16740 $scope.word = /^\s*\w*\s*$/; 16741 }]); 16742 </script> 16743 <form name="myForm" ng-controller="ExampleController"> 16744 Single word: <input type="text" name="input" ng-model="text" 16745 ng-pattern="word" required ng-trim="false"> 16746 <span class="error" ng-show="myForm.input.$error.required"> 16747 Required!</span> 16748 <span class="error" ng-show="myForm.input.$error.pattern"> 16749 Single word only!</span> 16750 16751 <tt>text = {{text}}</tt><br/> 16752 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16753 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16754 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16755 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16756 </form> 16757 </file> 16758 <file name="protractor.js" type="protractor"> 16759 var text = element(by.binding('text')); 16760 var valid = element(by.binding('myForm.input.$valid')); 16761 var input = element(by.model('text')); 16762 16763 it('should initialize to model', function() { 16764 expect(text.getText()).toContain('guest'); 16765 expect(valid.getText()).toContain('true'); 16766 }); 16767 16768 it('should be invalid if empty', function() { 16769 input.clear(); 16770 input.sendKeys(''); 16771 16772 expect(text.getText()).toEqual('text ='); 16773 expect(valid.getText()).toContain('false'); 16774 }); 16775 16776 it('should be invalid if multi word', function() { 16777 input.clear(); 16778 input.sendKeys('hello world'); 16779 16780 expect(valid.getText()).toContain('false'); 16781 }); 16782 </file> 16783 </example> 16784 */ 16785 'text': textInputType, 16786 16787 16788 /** 16789 * @ngdoc input 16790 * @name input[number] 16791 * 16792 * @description 16793 * Text input with number validation and transformation. Sets the `number` validation 16794 * error if not a valid number. 16795 * 16796 * @param {string} ngModel Assignable angular expression to data-bind to. 16797 * @param {string=} name Property name of the form under which the control is published. 16798 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 16799 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 16800 * @param {string=} required Sets `required` validation error key if the value is not entered. 16801 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16802 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16803 * `required` when you want to data-bind to the `required` attribute. 16804 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16805 * minlength. 16806 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16807 * maxlength. 16808 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16809 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16810 * patterns defined as scope expressions. 16811 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16812 * interaction with the input element. 16813 * 16814 * @example 16815 <example name="number-input-directive" module="numberExample"> 16816 <file name="index.html"> 16817 <script> 16818 angular.module('numberExample', []) 16819 .controller('ExampleController', ['$scope', function($scope) { 16820 $scope.value = 12; 16821 }]); 16822 </script>
16823 <form name="myForm" ng-controller="ExampleController"> 16824 Number: <input type="number" name="input" ng-model="value" 16825 min="0" max="99" required> 16826 <span class="error" ng-show="myForm.input.$error.required"> 16827 Required!</span> 16828 <span class="error" ng-show="myForm.input.$error.number"> 16829 Not valid number!</span> 16830 <tt>value = {{value}}</tt><br/> 16831 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16832 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16833 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16834 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16835 </form> 16836 </file> 16837 <file name="protractor.js" type="protractor"> 16838 var value = element(by.binding('value')); 16839 var valid = element(by.binding('myForm.input.$valid')); 16840 var input = element(by.model('value')); 16841 16842 it('should initialize to model', function() { 16843 expect(value.getText()).toContain('12'); 16844 expect(valid.getText()).toContain('true'); 16845 }); 16846 16847 it('should be invalid if empty', function() { 16848 input.clear(); 16849 input.sendKeys(''); 16850 expect(value.getText()).toEqual('value ='); 16851 expect(valid.getText()).toContain('false'); 16852 }); 16853 16854 it('should be invalid if over max', function() { 16855 input.clear(); 16856 input.sendKeys('123'); 16857 expect(value.getText()).toEqual('value ='); 16858 expect(valid.getText()).toContain('false'); 16859 }); 16860 </file> 16861 </example> 16862 */ 16863 'number': numberInputType, 16864 16865 16866 /** 16867 * @ngdoc input 16868 * @name input[url] 16869 * 16870 * @description 16871 * Text input with URL validation. Sets the `url` validation error key if the content is not a 16872 * valid URL. 16873 * 16874 * @param {string} ngModel Assignable angular expression to data-bind to. 16875 * @param {string=} name Property name of the form under which the control is published. 16876 * @param {string=} required Sets `required` validation error key if the value is not entered. 16877 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16878 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16879 * `required` when you want to data-bind to the `required` attribute. 16880 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16881 * minlength. 16882 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16883 * maxlength. 16884 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16885 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16886 * patterns defined as scope expressions. 16887 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16888 * interaction with the input element. 16889 * 16890 * @example 16891 <example name="url-input-directive" module="urlExample"> 16892 <file name="index.html"> 16893 <script> 16894 angular.module('urlExample', []) 16895 .controller('ExampleController', ['$scope', function($scope) { 16896 $scope.text = 'http://google.com'; 16897 }]); 16898 </script>
16899 <form name="myForm" ng-controller="ExampleController"> 16900 URL: <input type="url" name="input" ng-model="text" required> 16901 <span class="error" ng-show="myForm.input.$error.required"> 16902 Required!</span> 16903 <span class="error" ng-show="myForm.input.$error.url"> 16904 Not valid url!</span> 16905 <tt>text = {{text}}</tt><br/> 16906 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16907 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16908 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16909 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16910 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 16911 </form> 16912 </file> 16913 <file name="protractor.js" type="protractor"> 16914 var text = element(by.binding('text')); 16915 var valid = element(by.binding('myForm.input.$valid')); 16916 var input = element(by.model('text')); 16917 16918 it('should initialize to model', function() { 16919 expect(text.getText()).toContain('http://google.com'); 16920 expect(valid.getText()).toContain('true'); 16921 }); 16922 16923 it('should be invalid if empty', function() { 16924 input.clear(); 16925 input.sendKeys(''); 16926 16927 expect(text.getText()).toEqual('text ='); 16928 expect(valid.getText()).toContain('false'); 16929 }); 16930 16931 it('should be invalid if not url', function() { 16932 input.clear(); 16933 input.sendKeys('box'); 16934 16935 expect(valid.getText()).toContain('false'); 16936 }); 16937 </file> 16938 </example> 16939 */ 16940 'url': urlInputType, 16941 16942 16943 /** 16944 * @ngdoc input 16945 * @name input[email] 16946 * 16947 * @description 16948 * Text input with email validation. Sets the `email` validation error key if not a valid email 16949 * address. 16950 * 16951 * @param {string} ngModel Assignable angular expression to data-bind to. 16952 * @param {string=} name Property name of the form under which the control is published. 16953 * @param {string=} required Sets `required` validation error key if the value is not entered. 16954 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16955 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16956 * `required` when you want to data-bind to the `required` attribute. 16957 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16958 * minlength. 16959 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16960 * maxlength. 16961 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16962 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16963 * patterns defined as scope expressions. 16964 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16965 * interaction with the input element. 16966 * 16967 * @example 16968 <example name="email-input-directive" module="emailExample"> 16969 <file name="index.html"> 16970 <script> 16971 angular.module('emailExample', []) 16972 .controller('ExampleController', ['$scope', function($scope) { 16973 $scope.text = '[email protected]'; 16974 }]); 16975 </script>
16976 <form name="myForm" ng-controller="ExampleController"> 16977 Email: <input type="email" name="input" ng-model="text" required> 16978 <span class="error" ng-show="myForm.input.$error.required"> 16979 Required!</span> 16980 <span class="error" ng-show="myForm.input.$error.email"> 16981 Not valid email!</span> 16982 <tt>text = {{text}}</tt><br/> 16983 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16984 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16985 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16986 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16987 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 16988 </form> 16989 </file> 16990 <file name="protractor.js" type="protractor"> 16991 var text = element(by.binding('text')); 16992 var valid = element(by.binding('myForm.input.$valid')); 16993 var input = element(by.model('text')); 16994 16995 it('should initialize to model', function() { 16996 expect(text.getText()).toContain('[email protected]'); 16997 expect(valid.getText()).toContain('true'); 16998 }); 16999 17000 it('should be invalid if empty', function() { 17001 input.clear(); 17002 input.sendKeys(''); 17003 expect(text.getText()).toEqual('text ='); 17004 expect(valid.getText()).toContain('false'); 17005 }); 17006 17007 it('should be invalid if not email', function() { 17008 input.clear(); 17009 input.sendKeys('xxx'); 17010 17011 expect(valid.getText()).toContain('false'); 17012 }); 17013 </file> 17014 </example> 17015 */ 17016 'email': emailInputType, 17017 17018 17019 /** 17020 * @ngdoc input 17021 * @name input[radio] 17022 * 17023 * @description 17024 * HTML radio button. 17025 * 17026 * @param {string} ngModel Assignable angular expression to data-bind to. 17027 * @param {string} value The value to which the expression should be set when selected. 17028 * @param {string=} name Property name of the form under which the control is published. 17029 * @param {string=} ngChange Angular expression to be executed when input changes due to user 17030 * interaction with the input element. 17031 * @param {string} ngValue Angular expression which sets the value to which the expression should 17032 * be set when selected. 17033 * 17034 * @example 17035 <example name="radio-input-directive" module="radioExample"> 17036 <file name="index.html"> 17037 <script> 17038 angular.module('radioExample', []) 17039 .controller('ExampleController', ['$scope', function($scope) { 17040 $scope.color = 'blue'; 17041 $scope.specialValue = { 17042 "id": "12345", 17043 "value": "green" 17044 }; 17045 }]); 17046 </script>
17047 <form name="myForm" ng-controller="ExampleController"> 17048 <input type="radio" ng-model="color" value="red"> Red <br/> 17049 <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/> 17050 <input type="radio" ng-model="color" value="blue"> Blue <br/> 17051 <tt>color = {{color | json}}</tt><br/> 17052 </form> 17053 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 17054 </file> 17055 <file name="protractor.js" type="protractor"> 17056 it('should change state', function() { 17057 var color = element(by.binding('color')); 17058 17059 expect(color.getText()).toContain('blue'); 17060 17061 element.all(by.model('color')).get(0).click(); 17062 17063 expect(color.getText()).toContain('red'); 17064 }); 17065 </file> 17066 </example> 17067 */ 17068 'radio': radioInputType, 17069 17070 17071 /** 17072 * @ngdoc input 17073 * @name input[checkbox] 17074 * 17075 * @description 17076 * HTML checkbox. 17077 * 17078 * @param {string} ngModel Assignable angular expression to data-bind to. 17079 * @param {string=} name Property name of the form under which the control is published. 17080 * @param {string=} ngTrueValue The value to which the expression should be set when selected. 17081 * @param {string=} ngFalseValue The value to which the expression should be set when not selected. 17082 * @param {string=} ngChange Angular expression to be executed when input changes due to user 17083 * interaction with the input element. 17084 * 17085 * @example 17086 <example name="checkbox-input-directive" module="checkboxExample"> 17087 <file name="index.html"> 17088 <script> 17089 angular.module('checkboxExample', []) 17090 .controller('ExampleController', ['$scope', function($scope) { 17091 $scope.value1 = true; 17092 $scope.value2 = 'YES' 17093 }]); 17094 </script>
17095 <form name="myForm" ng-controller="ExampleController"> 17096 Value1: <input type="checkbox" ng-model="value1"> <br/> 17097 Value2: <input type="checkbox" ng-model="value2" 17098 ng-true-value="YES" ng-false-value="NO"> <br/> 17099 <tt>value1 = {{value1}}</tt><br/> 17100 <tt>value2 = {{value2}}</tt><br/> 17101 </form> 17102 </file> 17103 <file name="protractor.js" type="protractor"> 17104 it('should change state', function() { 17105 var value1 = element(by.binding('value1')); 17106 var value2 = element(by.binding('value2')); 17107 17108 expect(value1.getText()).toContain('true'); 17109 expect(value2.getText()).toContain('YES'); 17110 17111 element(by.model('value1')).click(); 17112 element(by.model('value2')).click(); 17113 17114 expect(value1.getText()).toContain('false'); 17115 expect(value2.getText()).toContain('NO'); 17116 }); 17117 </file> 17118 </example> 17119 */ 17120 'checkbox': checkboxInputType, 17121 17122 'hidden': noop, 17123 'button': noop, 17124 'submit': noop, 17125 'reset': noop, 17126 'file': noop 17127}; 17128 17129// A helper function to call $setValidity and return the value / undefined, 17130// a pattern that is repeated a lot in the input validation logic. 17131function validate(ctrl, validatorName, validity, value){ 17132 ctrl.$setValidity(validatorName, validity); 17133 return validity ? value : undefined; 17134} 17135 17136function testFlags(validity, flags) { 17137 var i, flag; 17138 if (flags) { 17139 for (i=0; i<flags.length; ++i) { 17140 flag = flags[i]; 17141 if (validity[flag]) { 17142 return true; 17143 } 17144 } 17145 } 17146 return false; 17147} 17148 17149// Pass validity so that behaviour can be mocked easier. 17150function addNativeHtml5Validators(ctrl, validatorName, badFlags, ignoreFlags, validity) { 17151 if (isObject(validity)) { 17152 ctrl.$$hasNativeValidators = true; 17153 var validator = function(value) { 17154 // Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can 17155 // perform the required validation) 17156 if (!ctrl.$error[validatorName] && 17157 !testFlags(validity, ignoreFlags) && 17158 testFlags(validity, badFlags)) { 17159 ctrl.$setValidity(validatorName, false); 17160 return; 17161 } 17162 return value; 17163 }; 17164 ctrl.$parsers.push(validator); 17165 } 17166} 17167 17168function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 17169 var validity = element.prop(VALIDITY_STATE_PROPERTY); 17170 var placeholder = element[0].placeholder, noevent = {}; 17171 var type = lowercase(element[0].type); 17172 ctrl.$$validityState = validity; 17173 17174 // In composition mode, users are still inputing intermediate text buffer, 17175 // hold the listener until composition is done. 17176 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 17177 if (!$sniffer.android) { 17178 var composing = false; 17179 17180 element.on('compositionstart', function(data) { 17181 composing = true; 17182 }); 17183 17184 element.on('compositionend', function() { 17185 composing = false; 17186 listener(); 17187 }); 17188 } 17189 17190 var listener = function(ev) { 17191 if (composing) return; 17192 var value = element.val(); 17193 17194 // IE (11 and under) seem to emit an 'input' event if the placeholder value changes. 17195 // We don't want to dirty the value when this happens, so we abort here. Unfortunately, 17196 // IE also sends input events for other non-input-related things, (such as focusing on a 17197 // form control), so this change is not entirely enough to solve this. 17198 if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) { 17199 placeholder = element[0].placeholder; 17200 return; 17201 } 17202 17203 // By default we will trim the value 17204 // If the attribute ng-trim exists we will avoid trimming 17205 // If input type is 'password', the value is never trimmed 17206 if (type !== 'password' && (toBoolean(attr.ngTrim || 'T'))) { 17207 value = trim(value); 17208 } 17209 17210 // If a control is suffering from bad input, browsers discard its value, so it may be 17211 // necessary to revalidate even if the control's value is the same empty value twice in 17212 // a row. 17213 var revalidate = validity && ctrl.$$hasNativeValidators; 17214 if (ctrl.$viewValue !== value || (value === '' && revalidate)) { 17215 if (scope.$root.$$phase) { 17216 ctrl.$setViewValue(value); 17217 } else { 17218 scope.$apply(function() { 17219 ctrl.$setViewValue(value); 17220 }); 17221 } 17222 } 17223 }; 17224 17225 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 17226 // input event on backspace, delete or cut 17227 if ($sniffer.hasEvent('input')) { 17228 element.on('input', listener); 17229 } else { 17230 var timeout; 17231 17232 var deferListener = function() { 17233 if (!timeout) { 17234 timeout = $browser.defer(function() { 17235 listener(); 17236 timeout = null; 17237 }); 17238 } 17239 }; 17240 17241 element.on('keydown', function(event) { 17242 var key = event.keyCode; 17243 17244 // ignore 17245 // command modifiers arrows 17246 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 17247 17248 deferListener(); 17249 }); 17250 17251 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 17252 if ($sniffer.hasEvent('paste')) { 17253 element.on('paste cut', deferListener); 17254 } 17255 } 17256 17257 // if user paste into input using mouse on older browser 17258 // or form autocomplete on newer browser, we need "change" event to catch it 17259 element.on('change', listener); 17260 17261 ctrl.$render = function() { 17262 element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); 17263 }; 17264 17265 // pattern validator 17266 var pattern = attr.ngPattern, 17267 patternValidator, 17268 match; 17269 17270 if (pattern) {
17271 var validateRegex = function(regexp, value) { 17272 return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value); 17273 }; 17274 match = pattern.match(/^\/(.*)\/([gim]*)$/); 17275 if (match) { 17276 pattern = new RegExp(match[1], match[2]); 17277 patternValidator = function(value) { 17278 return validateRegex(pattern, value); 17279 }; 17280 } else { 17281 patternValidator = function(value) { 17282 var patternObj = scope.$eval(pattern); 17283 17284 if (!patternObj || !patternObj.test) { 17285 throw minErr('ngPattern')('noregexp', 17286 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, 17287 patternObj, startingTag(element)); 17288 } 17289 return validateRegex(patternObj, value); 17290 }; 17291 } 17292 17293 ctrl.$formatters.push(patternValidator); 17294 ctrl.$parsers.push(patternValidator); 17295 } 17296 17297 // min length validator 17298 if (attr.ngMinlength) { 17299 var minlength = int(attr.ngMinlength); 17300 var minLengthValidator = function(value) { 17301 return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value); 17302 }; 17303 17304 ctrl.$parsers.push(minLengthValidator); 17305 ctrl.$formatters.push(minLengthValidator); 17306 } 17307 17308 // max length validator 17309 if (attr.ngMaxlength) { 17310 var maxlength = int(attr.ngMaxlength); 17311 var maxLengthValidator = function(value) { 17312 return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value); 17313 }; 17314 17315 ctrl.$parsers.push(maxLengthValidator); 17316 ctrl.$formatters.push(maxLengthValidator); 17317 } 17318} 17319 17320var numberBadFlags = ['badInput']; 17321 17322function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 17323 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 17324 17325 ctrl.$parsers.push(function(value) { 17326 var empty = ctrl.$isEmpty(value); 17327 if (empty || NUMBER_REGEXP.test(value)) { 17328 ctrl.$setValidity('number', true); 17329 return value === '' ? null : (empty ? value : parseFloat(value)); 17330 } else { 17331 ctrl.$setValidity('number', false); 17332 return undefined; 17333 } 17334 }); 17335 17336 addNativeHtml5Validators(ctrl, 'number', numberBadFlags, null, ctrl.$$validityState); 17337 17338 ctrl.$formatters.push(function(value) { 17339 return ctrl.$isEmpty(value) ? '' : '' + value; 17340 }); 17341 17342 if (attr.min) { 17343 var minValidator = function(value) { 17344 var min = parseFloat(attr.min); 17345 return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); 17346 }; 17347 17348 ctrl.$parsers.push(minValidator); 17349 ctrl.$formatters.push(minValidator); 17350 } 17351 17352 if (attr.max) { 17353 var maxValidator = function(value) { 17354 var max = parseFloat(attr.max); 17355 return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); 17356 }; 17357 17358 ctrl.$parsers.push(maxValidator); 17359 ctrl.$formatters.push(maxValidator); 17360 } 17361 17362 ctrl.$formatters.push(function(value) { 17363 return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); 17364 }); 17365} 17366 17367function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 17368 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 17369 17370 var urlValidator = function(value) { 17371 return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); 17372 }; 17373 17374 ctrl.$formatters.push(urlValidator); 17375 ctrl.$parsers.push(urlValidator); 17376} 17377 17378function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 17379 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 17380 17381 var emailValidator = function(value) { 17382 return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); 17383 }; 17384 17385 ctrl.$formatters.push(emailValidator); 17386 ctrl.$parsers.push(emailValidator); 17387} 17388 17389function radioInputType(scope, element, attr, ctrl) { 17390 // make the name unique, if not defined 17391 if (isUndefined(attr.name)) { 17392 element.attr('name', nextUid()); 17393 } 17394 17395 element.on('click', function() { 17396 if (element[0].checked) { 17397 scope.$apply(function() { 17398 ctrl.$setViewValue(attr.value); 17399 }); 17400 } 17401 }); 17402 17403 ctrl.$render = function() { 17404 var value = attr.value; 17405 element[0].checked = (value == ctrl.$viewValue); 17406 }; 17407 17408 attr.$observe('value', ctrl.$render); 17409} 17410 17411function checkboxInputType(scope, element, attr, ctrl) { 17412 var trueValue = attr.ngTrueValue, 17413 falseValue = attr.ngFalseValue; 17414 17415 if (!isString(trueValue)) trueValue = true; 17416 if (!isString(falseValue)) falseValue = false;
17417 17418 element.on('click', function() { 17419 scope.$apply(function() { 17420 ctrl.$setViewValue(element[0].checked); 17421 }); 17422 }); 17423 17424 ctrl.$render = function() { 17425 element[0].checked = ctrl.$viewValue; 17426 }; 17427 17428 // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. 17429 ctrl.$isEmpty = function(value) { 17430 return value !== trueValue; 17431 }; 17432 17433 ctrl.$formatters.push(function(value) { 17434 return value === trueValue; 17435 }); 17436 17437 ctrl.$parsers.push(function(value) { 17438 return value ? trueValue : falseValue; 17439 }); 17440} 17441 17442 17443/** 17444 * @ngdoc directive 17445 * @name textarea 17446 * @restrict E 17447 * 17448 * @description 17449 * HTML textarea element control with angular data-binding. The data-binding and validation 17450 * properties of this element are exactly the same as those of the 17451 * {@link ng.directive:input input element}. 17452 * 17453 * @param {string} ngModel Assignable angular expression to data-bind to. 17454 * @param {string=} name Property name of the form under which the control is published. 17455 * @param {string=} required Sets `required` validation error key if the value is not entered. 17456 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 17457 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 17458 * `required` when you want to data-bind to the `required` attribute. 17459 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 17460 * minlength. 17461 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 17462 * maxlength. 17463 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 17464 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 17465 * patterns defined as scope expressions. 17466 * @param {string=} ngChange Angular expression to be executed when input changes due to user 17467 * interaction with the input element. 17468 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 17469 */ 17470 17471 17472/** 17473 * @ngdoc directive 17474 * @name input 17475 * @restrict E 17476 * 17477 * @description 17478 * HTML input element control with angular data-binding. Input control follows HTML5 input types 17479 * and polyfills the HTML5 validation behavior for older browsers. 17480 * 17481 * *NOTE* Not every feature offered is available for all input types. 17482 * 17483 * @param {string} ngModel Assignable angular expression to data-bind to. 17484 * @param {string=} name Property name of the form under which the control is published. 17485 * @param {string=} required Sets `required` validation error key if the value is not entered. 17486 * @param {boolean=} ngRequired Sets `required` attribute if set to true 17487 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 17488 * minlength. 17489 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 17490 * maxlength. 17491 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 17492 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 17493 * patterns defined as scope expressions. 17494 * @param {string=} ngChange Angular expression to be executed when input changes due to user 17495 * interaction with the input element. 17496 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 17497 * This parameter is ignored for input[type=password] controls, which will never trim the 17498 * input. 17499 * 17500 * @example 17501 <example name="input-directive" module="inputExample"> 17502 <file name="index.html"> 17503 <script> 17504 angular.module('inputExample', []) 17505 .controller('ExampleController', ['$scope', function($scope) { 17506 $scope.user = {name: 'guest', last: 'visitor'}; 17507 }]); 17508 </script>
17509 <div ng-controller="ExampleController"> 17510 <form name="myForm"> 17511 User name: <input type="text" name="userName" ng-model="user.name" required> 17512 <span class="error" ng-show="myForm.userName.$error.required"> 17513 Required!</span><br> 17514 Last name: <input type="text" name="lastName" ng-model="user.last" 17515 ng-minlength="3" ng-maxlength="10"> 17516 <span class="error" ng-show="myForm.lastName.$error.minlength"> 17517 Too short!</span> 17518 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 17519 Too long!</span><br> 17520 </form> 17521 <hr> 17522 <tt>user = {{user}}</tt><br/> 17523 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br> 17524 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br> 17525 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br> 17526 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br> 17527 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 17528 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 17529 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br> 17530 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br> 17531 </div> 17532 </file> 17533 <file name="protractor.js" type="protractor"> 17534 var user = element(by.binding('{{user}}
vendor: 2,296 bytes, lines 17534-17588
17534')); 17535 var userNameValid = element(by.binding('myForm.userName.$valid')); 17536 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 17537 var lastNameError = element(by.binding('myForm.lastName.$error')); 17538 var formValid = element(by.binding('myForm.$valid')); 17539 var userNameInput = element(by.model('user.name')); 17540 var userLastInput = element(by.model('user.last')); 17541 17542 it('should initialize to model', function() { 17543 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 17544 expect(userNameValid.getText()).toContain('true'); 17545 expect(formValid.getText()).toContain('true'); 17546 }); 17547 17548 it('should be invalid if empty when required', function() { 17549 userNameInput.clear(); 17550 userNameInput.sendKeys(''); 17551 17552 expect(user.getText()).toContain('{"last":"visitor"}'); 17553 expect(userNameValid.getText()).toContain('false'); 17554 expect(formValid.getText()).toContain('false'); 17555 }); 17556 17557 it('should be valid if empty when min length is set', function() { 17558 userLastInput.clear(); 17559 userLastInput.sendKeys(''); 17560 17561 expect(user.getText()).toContain('{"name":"guest","last":""}'); 17562 expect(lastNameValid.getText()).toContain('true'); 17563 expect(formValid.getText()).toContain('true'); 17564 }); 17565 17566 it('should be invalid if less than required min length', function() { 17567 userLastInput.clear(); 17568 userLastInput.sendKeys('xx'); 17569 17570 expect(user.getText()).toContain('{"name":"guest"}'); 17571 expect(lastNameValid.getText()).toContain('false'); 17572 expect(lastNameError.getText()).toContain('minlength'); 17573 expect(formValid.getText()).toContain('false'); 17574 }); 17575 17576 it('should be invalid if longer than max length', function() { 17577 userLastInput.clear(); 17578 userLastInput.sendKeys('some ridiculously long name'); 17579 17580 expect(user.getText()).toContain('{"name":"guest"}'); 17581 expect(lastNameValid.getText()).toContain('false'); 17582 expect(lastNameError.getText()).toContain('maxlength'); 17583 expect(formValid.getText()).toContain('false'); 17584 }); 17585 </file> 17586 </example> 17587 */ 17588var inputDirective = ['$browser', '$sniffer',
17588function($browser, $sniffer) { 17589 return { 17590 restrict: 'E', 17591 require: '?ngModel', 17592 link: function(scope, element, attr, ctrl) { 17593 if (ctrl) { 17594 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, 17595 $browser); 17596 } 17597 } 17598 }; 17599}]; 17600 17601var VALID_CLASS = 'ng-valid', 17602 INVALID_CLASS = 'ng-invalid', 17603 PRISTINE_CLASS = 'ng-pristine', 17604 DIRTY_CLASS = 'ng-dirty'; 17605 17606/** 17607 * @ngdoc type 17608 * @name ngModel.NgModelController 17609 * 17610 * @property {string} $viewValue Actual string value in the view. 17611 * @property {*} $modelValue The value in the model, that the control is bound to. 17612 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 17613 the control reads value from the DOM. Each function is called, in turn, passing the value 17614 through to the next. The last return value is used to populate the model. 17615 Used to sanitize / convert the value as well as validation. For validation, 17616 the parsers should update the validity state using 17617 {@link ngModel.NgModelController#$setValidity $setValidity()}, 17618 and return `undefined` for invalid values. 17619 17620 * 17621 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 17622 the model value changes. Each function is called, in turn, passing the value through to the 17623 next. Used to format / convert values for display in the control and validation. 17624 * ```js 17625 * function formatter(value) { 17626 * if (value) { 17627 * return value.toUpperCase(); 17628 * } 17629 * } 17630 * ngModel.$formatters.push(formatter); 17631 * ``` 17632 * 17633 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 17634 * view value has changed. It is called with no arguments, and its return value is ignored. 17635 * This can be used in place of additional $watches against the model value. 17636 * 17637 * @property {Object} $error An object hash with all errors as keys. 17638 * 17639 * @property {boolean} $pristine True if user has not interacted with the control yet. 17640 * @property {boolean} $dirty True if user has already interacted with the control. 17641 * @property {boolean} $valid True if there is no error. 17642 * @property {boolean} $invalid True if at least one error on the control. 17643 * 17644 * @description 17645 * 17646 * `NgModelController` provides API for the `ng-model` directive. The controller contains 17647 * services for data-binding, validation, CSS updates, and value formatting and parsing. It 17648 * purposefully does not contain any logic which deals with DOM rendering or listening to 17649 * DOM events. Such DOM related logic should be provided by other directives which make use of 17650 * `NgModelController` for data-binding. 17651 * 17652 * ## Custom Control Example 17653 * This example shows how to use `NgModelController` with a custom control to achieve 17654 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 17655 * collaborate together to achieve the desired result. 17656 * 17657 * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element 17658 * contents be edited in place by the user. This will not work on older browsers. 17659 * 17660 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 17661 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 17662 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 17663 * that content using the `$sce` service. 17664 * 17665 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 17666 <file name="style.css"> 17667 [contenteditable] { 17668 border: 1px solid black; 17669 background-color: white; 17670 min-height: 20px; 17671 } 17672 17673 .ng-invalid { 17674 border: 1px solid red; 17675 } 17676 17677 </file> 17678 <file name="script.js"> 17679 angular.module('customControl', ['ngSanitize']). 17680 directive('contenteditable', ['$sce', function($sce) { 17681 return { 17682 restrict: 'A', // only activate on element attribute 17683 require: '?ngModel', // get a hold of NgModelController 17684 link: function(scope, element, attrs, ngModel) { 17685 if(!ngModel) return; // do nothing if no ng-model 17686 17687 // Specify how UI should be updated 17688 ngModel.$render = function() { 17689 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 17690 }; 17691 17692 // Listen for change events to enable binding 17693 element.on('blur keyup change', function() { 17694 scope.$evalAsync(read); 17695 }); 17696 read(); // initialize 17697 17698 // Write data to the model 17699 function read() { 17700 var html = element.html(); 17701 // When we clear the content editable the browser leaves a <br> behind
17702 // If strip-br attribute is provided then we strip this out 17703 if( attrs.stripBr && html == '<br>' ) { 17704 html = ''; 17705 } 17706 ngModel.$setViewValue(html); 17707 } 17708 } 17709 }; 17710 }]); 17711 </file> 17712 <file name="index.html"> 17713 <form name="myForm"> 17714 <div contenteditable 17715 name="myWidget" ng-model="userContent" 17716 strip-br="true" 17717 required>Change me!</div> 17718 <span ng-show="myForm.myWidget.$error.required">Required!</span> 17719 <hr> 17720 <textarea ng-model="userContent"></textarea> 17721 </form> 17722 </file> 17723 <file name="protractor.js" type="protractor"> 17724 it('should data-bind and become invalid', function() { 17725 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 17726 // SafariDriver can't handle contenteditable 17727 // and Firefox driver can't clear contenteditables very well 17728 return; 17729 } 17730 var contentEditable = element(by.css('[contenteditable]')); 17731 var content = 'Change me!'; 17732 17733 expect(contentEditable.getText()).toEqual(content); 17734 17735 contentEditable.clear(); 17736 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 17737 expect(contentEditable.getText()).toEqual(''); 17738 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 17739 }); 17740 </file> 17741 * </example> 17742 * 17743 * 17744 */ 17745var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', 17746 function($scope, $exceptionHandler, $attr, $element, $parse, $animate) { 17747 this.$viewValue = Number.NaN; 17748 this.$modelValue = Number.NaN; 17749 this.$parsers = []; 17750 this.$formatters = []; 17751 this.$viewChangeListeners = []; 17752 this.$pristine = true; 17753 this.$dirty = false; 17754 this.$valid = true; 17755 this.$invalid = false; 17756 this.$name = $attr.name; 17757 17758 var ngModelGet = $parse($attr.ngModel), 17759 ngModelSet = ngModelGet.assign; 17760 17761 if (!ngModelSet) { 17762 throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 17763 $attr.ngModel, startingTag($element)); 17764 } 17765 17766 /** 17767 * @ngdoc method 17768 * @name ngModel.NgModelController#$render 17769 * 17770 * @description 17771 * Called when the view needs to be updated. It is expected that the user of the ng-model 17772 * directive will implement this method. 17773 */ 17774 this.$render = noop; 17775 17776 /** 17777 * @ngdoc method 17778 * @name ngModel.NgModelController#$isEmpty 17779 * 17780 * @description 17781 * This is called when we need to determine if the value of the input is empty. 17782 * 17783 * For instance, the required directive does this to work out if the input has data or not. 17784 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 17785 * 17786 * You can override this for input directives whose concept of being empty is different to the 17787 * default. The `checkboxInputType` directive does this because in its case a value of `false` 17788 * implies empty. 17789 * 17790 * @param {*} value Reference to check. 17791 * @returns {boolean} True if `value` is empty. 17792 */ 17793 this.$isEmpty = function(value) { 17794 return isUndefined(value) || value === '' || value === null || value !== value; 17795 }; 17796 17797 var parentForm = $element.inheritedData('$formController') || nullFormCtrl, 17798 invalidCount = 0, // used to easily determine if we are valid 17799 $error = this.$error = {}; // keep invalid keys here 17800 17801 17802 // Setup initial state of the control 17803 $element.addClass(PRISTINE_CLASS); 17804 toggleValidCss(true); 17805 17806 // convenience method for easy toggling of classes 17807 function toggleValidCss(isValid, validationErrorKey) { 17808 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 17809 $animate.removeClass($element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); 17810 $animate.addClass($element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 17811 } 17812 17813 /** 17814 * @ngdoc method 17815 * @name ngModel.NgModelController#$setValidity 17816 * 17817 * @description 17818 * Change the validity state, and notifies the form when the control changes validity. (i.e. it 17819 * does not notify form if given validator is already marked as invali
17819d). 17820 * 17821 * This method should be called by validators - i.e. the parser or formatter functions. 17822 * 17823 * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign 17824 * to `$error[validationErrorKey]=!isValid` so that it is available for data-binding. 17825 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 17826 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 17827 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 17828 * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). 17829 */ 17830 this.$setValidity = function(validationErrorKey, isValid) { 17831 // Purposeful use of ! here to cast isValid to boolean in case it is undefined 17832 // jshint -W018 17833 if ($error[validationErrorKey] === !isValid) return; 17834 // jshint +W018 17835 17836 if (isValid) { 17837 if ($error[validationErrorKey]) invalidCount--; 17838 if (!invalidCount) { 17839 toggleValidCss(true); 17840 this.$valid = true; 17841 this.$invalid = false; 17842 } 17843 } else { 17844 toggleValidCss(false); 17845 this.$invalid = true; 17846 this.$valid = false; 17847 invalidCount++; 17848 } 17849 17850 $error[validationErrorKey] = !isValid; 17851 toggleValidCss(isValid, validationErrorKey); 17852 17853 parentForm.$setValidity(validationErrorKey, isValid, this); 17854 }; 17855 17856 /** 17857 * @ngdoc method 17858 * @name ngModel.NgModelController#$setPristine 17859 * 17860 * @description 17861 * Sets the control to its pristine state. 17862 * 17863 * This method can be called to remove the 'ng-dirty' class and set the control to its pristine 17864 * state (ng-pristine class). 17865 */ 17866 this.$setPristine = function () { 17867 this.$dirty = false; 17868 this.$pristine = true; 17869 $animate.removeClass($element, DIRTY_CLASS); 17870 $animate.addClass($element, PRISTINE_CLASS); 17871 }; 17872 17873 /** 17874 * @ngdoc method 17875 * @name ngModel.NgModelController#$setViewValue 17876 * 17877 * @description 17878 * Update the view value. 17879 * 17880 * This method should be called when the view value changes, typically from within a DOM event handler. 17881 * For example {@link ng.directive:input input} and 17882 * {@link ng.directive:select select} directives call it. 17883 * 17884 * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, 17885 * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to 17886 * `$modelValue` and the **expression** specified in the `ng-model` attribute. 17887 * 17888 * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. 17889 * 17890 * Note that calling this function does not trigger a `$digest`. 17891 * 17892 * @param {string} value Value from the view. 17893 */ 17894 this.$setViewValue = function(value) { 17895 this.$viewValue = value; 17896 17897 // change to dirty 17898 if (this.$pristine) { 17899 this.$dirty = true; 17900 this.$pristine = false; 17901 $animate.removeClass($element, PRISTINE_CLASS); 17902 $animate.addClass($element, DIRTY_CLASS); 17903 parentForm.$setDirty(); 17904 } 17905 17906 forEach(this.$parsers, function(fn) { 17907 value = fn(value); 17908 }); 17909 17910 if (this.$modelValue !== value) { 17911 this.$modelValue = value; 17912 ngModelSet($scope, value); 17913 forEach(this.$viewChangeListeners, function(listener) { 17914 try { 17915 listener(); 17916 } catch(e) { 17917 $exceptionHandler(e); 17918 } 17919 }); 17920 } 17921 }; 17922 17923 // model -> value 17924 var ctrl = this; 17925 17926 $scope.$watch(function ngModelWatch() { 17927 var value = ngModelGet($scope); 17928 17929 // if scope model value and ngModel value are out of sync 17930 if (ctrl.$modelValue !== value) { 17931 17932 var formatters = ctrl.$formatters, 17933 idx = formatters.length; 17934 17935 ctrl.$modelValue = value; 17936 while(idx--) { 17937 value = formatters[idx](value); 17938 } 17939 17940 if (ctrl.$viewValue !== value) { 17941 ctrl.$viewValue = value; 17942 ctrl.$render(); 17943 } 17944 } 17945 17946 return value; 17947 }); 17948}]; 17949 17950 17951/** 17952 * @ngdoc directive 17953 * @name ngModel 17954 * 17955 * @element input 17956 * 17957 * @description 17958 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 17959 * property on the scope using {@link ngModel.NgModelController NgModelController}, 17960 * which is created and exposed by this directive. 17961 * 17962 * `ngModel` is responsible for: 17963 * 17964 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 17965 * require. 17966 * - Providing validation behavior (i.e. required, number, email, url). 17967 * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). 17968 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`) including animations. 17969 * - Registering the control with its parent {@link ng.directive:form form}. 17970 *
17971 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 17972 * current scope. If the property doesn't already exist on this scope, it will be created 17973 * implicitly and added to the scope. 17974 * 17975 * For best practices on using `ngModel`, see: 17976 * 17977 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 17978 * 17979 * For basic examples, how to use `ngModel`, see: 17980 * 17981 * - {@link ng.directive:input input} 17982 * - {@link input[text] text} 17983 * - {@link input[checkbox] checkbox} 17984 * - {@link input[radio] radio} 17985 * - {@link input[number] number} 17986 * - {@link input[email] email} 17987 * - {@link input[url] url} 17988 * - {@link ng.directive:select select} 17989 * - {@link ng.directive:textarea textarea} 17990 * 17991 * # CSS classes 17992 * The following CSS classes are added and removed on the associated input/select/textarea element 17993 * depending on the validity of the model. 17994 * 17995 * - `ng-valid` is set if the model is valid. 17996 * - `ng-invalid` is set if the model is invalid. 17997 * - `ng-pristine` is set if the model is pristine. 17998 * - `ng-dirty` is set if the model is dirty. 17999 * 18000 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 18001 * 18002 * ## Animation Hooks 18003 * 18004 * Animations within models are triggered when any of the associated CSS classes are added and removed 18005 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, 18006 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 18007 * The animations that are triggered within ngModel are similar to how they work in ngClass and 18008 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 18009 * 18010 * The following example shows a simple way to utilize CSS transitions to style an input element 18011 * that has been rendered as invalid after it has been validated: 18012 * 18013 * <pre> 18014 * //be sure to include ngAnimate as a module to hook into more 18015 * //advanced animations 18016 * .my-input { 18017 * transition:0.5s linear all; 18018 * background: white; 18019 * } 18020 * .my-input.ng-invalid { 18021 * background: red; 18022 * color:white; 18023 * } 18024 * </pre> 18025 * 18026 * @example 18027 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> 18028 <file name="index.html"> 18029 <script> 18030 angular.module('inputExample', []) 18031 .controller('ExampleController', ['$scope', function($scope) { 18032 $scope.val = '1'; 18033 }]); 18034 </script> 18035 <style> 18036 .my-input { 18037 -webkit-transition:all linear 0.5s; 18038 transition:all linear 0.5s; 18039 background: transparent; 18040 } 18041 .my-input.ng-invalid { 18042 color:white; 18043 background: red; 18044 } 18045 </style> 18046 Update input to see transitions when valid/invalid. 18047 Integer is a valid value.
18048 <form name="testForm" ng-controller="ExampleController"> 18049 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" /> 18050 </form> 18051 </file> 18052 * </example> 18053 */ 18054var ngModelDirective = function() { 18055 return { 18056 require: ['ngModel', '^?form'], 18057 controller: NgModelController, 18058 link: function(scope, element, attr, ctrls) { 18059 // notify others, especially parent forms 18060 18061 var modelCtrl = ctrls[0], 18062 formCtrl = ctrls[1] || nullFormCtrl; 18063 18064 formCtrl.$addControl(modelCtrl); 18065 18066 scope.$on('$destroy', function() { 18067 formCtrl.$removeControl(modelCtrl); 18068 }); 18069 } 18070 }; 18071}; 18072 18073 18074/** 18075 * @ngdoc directive 18076 * @name ngChange 18077 * 18078 * @description 18079 * Evaluate the given expression when the user changes the input. 18080 * The expression is evaluated immediately, unlike the JavaScript onchange event 18081 * which only triggers at the end of a change (usually, when the user leaves the 18082 * form element or presses the return key). 18083 * The expression is not evaluated when the value change is coming from the model. 18084 * 18085 * Note, this directive requires `ngModel` to be present. 18086 * 18087 * @element input 18088 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 18089 * in input value. 18090 * 18091 * @example 18092 * <example name="ngChange-directive" module="changeExample"> 18093 * <file name="index.html"> 18094 * <script> 18095 * angular.module('changeExample', []) 18096 * .controller('ExampleController', ['$scope', function($scope) { 18097 * $scope.counter = 0; 18098 * $scope.change = function() { 18099 * $scope.counter++; 18100 * }; 18101 * }]); 18102 * </script>
18103 * <div ng-controller="ExampleController"> 18104 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 18105 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 18106 * <label for="ng-change-example2">Confirmed</label><br /> 18107 * <tt>debug = {{confirmed}}</tt><br/> 18108 * <tt>counter = {{counter}}</tt><br/> 18109 * </div> 18110 * </file> 18111 * <file name="protractor.js" type="protractor"> 18112 * var counter = element(by.binding('counter')); 18113 * var debug = element(by.binding('confirmed')); 18114 * 18115 * it('should evaluate the expression if changing from view', function() { 18116 * expect(counter.getText()).toContain('0'); 18117 * 18118 * element(by.id('ng-change-example1')).click(); 18119 * 18120 * expect(counter.getText()).toContain('1'); 18121 * expect(debug.getText()).toContain('true'); 18122 * }); 18123 * 18124 * it('should not evaluate the expression if changing from model', function() { 18125 * element(by.id('ng-change-example2')).click(); 18126 18127 * expect(counter.getText()).toContain('0'); 18128 * expect(debug.getText()).toContain('true'); 18129 * }); 18130 * </file> 18131 * </example> 18132 */ 18133var ngChangeDirective = valueFn({ 18134 require: 'ngModel', 18135 link: function(scope, element, attr, ctrl) { 18136 ctrl.$viewChangeListeners.push(function() { 18137 scope.$eval(attr.ngChange); 18138 }); 18139 } 18140}); 18141 18142 18143var requiredDirective = function() { 18144 return { 18145 require: '?ngModel', 18146 link: function(scope, elm, attr, ctrl) { 18147 if (!ctrl) return; 18148 attr.required = true; // force truthy in case we are on non input element 18149 18150 var validator = function(value) { 18151 if (attr.required && ctrl.$isEmpty(value)) { 18152 ctrl.$setValidity('required', false); 18153 return; 18154 } else { 18155 ctrl.$setValidity('required', true); 18156 return value; 18157 } 18158 }; 18159 18160 ctrl.$formatters.push(validator); 18161 ctrl.$parsers.unshift(validator); 18162 18163 attr.$observe('required', function() { 18164 validator(ctrl.$viewValue); 18165 }); 18166 } 18167 }; 18168}; 18169 18170 18171/** 18172 * @ngdoc directive 18173 * @name ngList 18174 * 18175 * @description 18176 * Text input that converts between a delimited string and an array of strings. The delimiter 18177 * can be a fixed string (by default a comma) or a regular expression. 18178 * 18179 * @element input 18180 * @param {string=} ngList optional delimiter that should be used to split the value. If 18181 * specified in form `/something/` then the value will be converted into a regular expression. 18182 * 18183 * @example 18184 <example name="ngList-directive" module="listExample"> 18185 <file name="index.html"> 18186 <script> 18187 angular.module('listExample', []) 18188 .controller('ExampleController', ['$scope', function($scope) { 18189 $scope.names = ['igor', 'misko', 'vojta']; 18190 }]); 18191 </script>
18192 <form name="myForm" ng-controller="ExampleController"> 18193 List: <input name="namesInput" ng-model="names" ng-list required> 18194 <span class="error" ng-show="myForm.namesInput.$error.required"> 18195 Required!</span> 18196 <br> 18197 <tt>names = {{names}}</tt><br/> 18198 <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 18199 <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 18200 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 18201 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 18202 </form> 18203 </file> 18204 <file name="protractor.js" type="protractor"> 18205 var listInput = element(by.model('names')); 18206 var names = element(by.binding('{{names}}')); 18207 var valid = element(by.binding('myForm.namesInput.$valid')); 18208 var error = element(by.css('span.error')); 18209 18210 it('should initialize to model', function() { 18211 expect(names.getText()).toContain('["igor","misko","vojta"]'); 18212 expect(valid.getText()).toContain('true'); 18213 expect(error.getCssValue('display')).toBe('none'); 18214 }); 18215 18216 it('should be invalid if empty', function() { 18217 listInput.clear(); 18218 listInput.sendKeys(''); 18219 18220 expect(names.getText()).toContain(''); 18221 expect(valid.getText()).toContain('false'); 18222 expect(error.getCssValue('display')).not.toBe('none'); }); 18223 </file> 18224 </example> 18225 */ 18226var ngListDirective = function() { 18227 return { 18228 require: 'ngModel', 18229 link: function(scope, element, attr, ctrl) { 18230 var match = /\/(.*)\//.exec(attr.ngList), 18231 separator = match && new RegExp(match[1]) || attr.ngList || ','; 18232 18233 var parse = function(viewValue) { 18234 // If the viewValue is invalid (say required but empty) it will be `undefined` 18235 if (isUndefined(viewValue)) return; 18236 18237 var list = []; 18238 18239 if (viewValue) { 18240 forEach(viewValue.split(separator), function(value) { 18241 if (value) list.push(trim(value)); 18242 }); 18243 } 18244 18245 return list; 18246 }; 18247 18248 ctrl.$parsers.push(parse); 18249 ctrl.$formatters.push(function(value) { 18250 if (isArray(value)) { 18251 return value.join(', '); 18252 } 18253 18254 return undefined; 18255 }); 18256 18257 // Override the standard $isEmpty because an empty array means the input is empty. 18258 ctrl.$isEmpty = function(value) { 18259 return !value || !value.length; 18260 }; 18261 } 18262 }; 18263}; 18264 18265 18266var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 18267/** 18268 * @ngdoc directive 18269 * @name ngValue 18270 * 18271 * @description 18272 * Binds the given expression to the value of `input[select]` or `input[radio]`, so 18273 * that when the element is selected, the `ngModel` of that element is set to the 18274 * bound value. 18275 * 18276 * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as 18277 * shown below. 18278 * 18279 * @element input 18280 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 18281 * of the `input` element 18282 * 18283 * @example 18284 <example name="ngValue-directive" module="valueExample"> 18285 <file name="index.html"> 18286 <script> 18287 angular.module('valueExample', []) 18288 .controller('ExampleController', ['$scope', function($scope) { 18289 $scope.names = ['pizza', 'unicorns', 'robots']; 18290 $scope.my = { favorite: 'unicorns' }; 18291 }]); 18292 </script>
18293 <form ng-controller="ExampleController"> 18294 <h2>Which is your favorite?</h2> 18295 <label ng-repeat="name in names" for="{{name}}"> 18296 {{name}} 18297 <input type="radio" 18298 ng-model="my.favorite" 18299 ng-value="name" 18300 id="{{name}}" 18301 name="favorite"> 18302 </label> 18303 <div>You chose {{my.favorite}}</div> 18304 </form> 18305 </file> 18306 <file name="protractor.js" type="protractor"> 18307 var favorite = element(by.binding('my.favorite')); 18308 18309 it('should initialize to model', function() { 18310 expect(favorite.getText()).toContain('unicorns'); 18311 }); 18312 it('should bind the values to the inputs', function() { 18313 element.all(by.model('my.favorite')).get(0).click(); 18314 expect(favorite.getText()).toContain('pizza'); 18315 }); 18316 </file> 18317 </example> 18318 */ 18319var ngValueDirective = function() { 18320 return { 18321 priority: 100, 18322 compile: function(tpl, tplAttr) { 18323 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 18324 return function ngValueConstantLink(scope, elm, attr) { 18325 attr.$set('value', scope.$eval(attr.ngValue)); 18326 }; 18327 } else { 18328 return function ngValueLink(scope, elm, attr) { 18329 scope.$watch(attr.ngValue, function valueWatchAction(value) { 18330 attr.$set('value', value); 18331 }); 18332 }; 18333 } 18334 } 18335 }; 18336}; 18337 18338/** 18339 * @ngdoc directive 18340 * @name ngBind 18341 * @restrict AC 18342 * 18343 * @description 18344 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 18345 * with the value of a given expression, and to update the text content when the value of that 18346 * expression changes. 18347 * 18348 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 18349 * `{{ expression }}` which is similar but less verbose. 18350 * 18351 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 18352 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 18353 * element attribute, it makes the bindings invisible to the user while the page is loading. 18354 * 18355 * An alternative solution to this problem would be using the 18356 * {@link ng.directive:ngCloak ngCloak} directive. 18357 * 18358 * 18359 * @element ANY 18360 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 18361 * 18362 * @example 18363 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 18364 <example module="bindExample"> 18365 <file name="index.html"> 18366 <script> 18367 angular.module('bindExample', []) 18368 .controller('ExampleController', ['$scope', function($scope) { 18369 $scope.name = 'Whirled'; 18370 }]); 18371 </script>
18372 <div ng-controller="ExampleController"> 18373 Enter name: <input type="text" ng-model="name"><br> 18374 Hello <span ng-bind="name"></span>! 18375 </div> 18376 </file> 18377 <file name="protractor.js" type="protractor"> 18378 it('should check ng-bind', function() { 18379 var nameInput = element(by.model('name')); 18380 18381 expect(element(by.binding('name')).getText()).toBe('Whirled'); 18382 nameInput.clear(); 18383 nameInput.sendKeys('world'); 18384 expect(element(by.binding('name')).getText()).toBe('world'); 18385 }); 18386 </file> 18387 </example> 18388 */ 18389var ngBindDirective = ngDirective({ 18390 compile: function(templateElement) { 18391 templateElement.addClass('ng-binding'); 18392 return function (scope, element, attr) { 18393 element.data('$binding', attr.ngBind); 18394 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 18395 // We are purposefully using == here rather than === because we want to 18396 // catch when value is "null or undefined" 18397 // jshint -W041 18398 element.text(value == undefined ? '' : value); 18399 }); 18400 }; 18401 } 18402}); 18403 18404 18405/** 18406 * @ngdoc directive 18407 * @name ngBindTemplate 18408 * 18409 * @description 18410 * The `ngBindTemplate` directive specifies that the element 18411 * text content should be replaced with the interpolation of the template 18412 * in the `ngBindTemplate` attribute. 18413 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 18414 * expressions. This directive is needed since some HTML elements 18415 * (such as TITLE and OPTION) cannot contain SPAN elements. 18416 * 18417 * @element ANY 18418 * @param {string} ngBindTemplate template of form 18419 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 18420 * 18421 * @example 18422 * Try it here: enter text in text box and watch the greeting change. 18423 <example module="bindExample"> 18424 <file name="index.html"> 18425 <script> 18426 angular.module('bindExample', []) 18427 .controller('ExampleController', ['$scope', function ($scope) { 18428 $scope.salutation = 'Hello'; 18429 $scope.name = 'World'; 18430 }]); 18431 </script>
18432 <div ng-controller="ExampleController"> 18433 Salutation: <input type="text" ng-model="salutation"><br> 18434 Name: <input type="text" ng-model="name"><br> 18435 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 18436 </div> 18437 </file> 18438 <file name="protractor.js" type="protractor"> 18439 it('should check ng-bind', function() { 18440 var salutationElem = element(by.binding('salutation')); 18441 var salutationInput = element(by.model('salutation')); 18442 var nameInput = element(by.model('name')); 18443 18444 expect(salutationElem.getText()).toBe('Hello World!'); 18445 18446 salutationInput.clear(); 18447 salutationInput.sendKeys('Greetings'); 18448 nameInput.clear(); 18449 nameInput.sendKeys('user'); 18450 18451 expect(salutationElem.getText()).toBe('Greetings user!'); 18452 }); 18453 </file> 18454 </example> 18455 */ 18456var ngBindTemplateDirective = ['$interpolate', function($interpolate) { 18457 return function(scope, element, attr) { 18458 // TODO: move this to scenario runner 18459 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 18460 element.addClass('ng-binding').data('$binding', interpolateFn); 18461 attr.$observe('ngBindTemplate', function(value) { 18462 element.text(value); 18463 }); 18464 }; 18465}]; 18466 18467 18468/** 18469 * @ngdoc directive 18470 * @name ngBindHtml 18471 * 18472 * @description 18473 * Creates a binding that will innerHTML the result of evaluating the `expression` into the current 18474 * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link 18475 * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` 18476 * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in 18477 * core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to 18478 * include "angular-sanitize.js" in your application. 18479 * 18480 * You may also bypass sanitization for values you know are safe. To do so, bind to 18481 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 18482 * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. 18483 * 18484 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 18485 * will have an exception (instead of an exploit.) 18486 * 18487 * @element ANY 18488 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 18489 * 18490 * @example 18491 18492 <example module="bindHtmlExample" deps="angular-sanitize.js"> 18493 <file name="index.html">
18494 <div ng-controller="ExampleController"> 18495 <p ng-bind-html="myHTML"></p> 18496 </div> 18497 </file> 18498 18499 <file name="script.js"> 18500 angular.module('bindHtmlExample', ['ngSanitize']) 18501 .controller('ExampleController', ['$scope', function($scope) { 18502 $scope.myHTML = 18503 'I am an <code>HTML</code>string with ' + 18504 '<a href="#">links!</a> and other <em>stuff</em>'; 18505 }]); 18506 </file> 18507 18508 <file name="protractor.js" type="protractor"> 18509 it('should check ng-bind-html', function() { 18510 expect(element(by.binding('myHTML')).getText()).toBe( 18511 'I am an HTMLstring with links! and other stuff'); 18512 }); 18513 </file> 18514 </example> 18515 */ 18516var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { 18517 return { 18518 compile: function (tElement) { 18519 tElement.addClass('ng-binding'); 18520 18521 return function (scope, element, attr) { 18522 element.data('$binding', attr.ngBindHtml); 18523 18524 var parsed = $parse(attr.ngBindHtml); 18525 18526 function getStringValue() { 18527 return (parsed(scope) || '').toString(); 18528 } 18529 18530 scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { 18531 element.html($sce.getTrustedHtml(parsed(scope)) || ''); 18532 }); 18533 }; 18534 } 18535 }; 18536}]; 18537 18538function classDirective(name, selector) { 18539 name = 'ngClass' + name; 18540 return ['$animate', function($animate) { 18541 return { 18542 restrict: 'AC', 18543 link: function(scope, element, attr) { 18544 var oldVal; 18545 18546 scope.$watch(attr[name], ngClassWatchAction, true); 18547 18548 attr.$observe('class', function(value) { 18549 ngClassWatchAction(scope.$eval(attr[name])); 18550 }); 18551 18552 18553 if (name !== 'ngClass') { 18554 scope.$watch('$index', function($index, old$index) { 18555 // jshint bitwise: false 18556 var mod = $index & 1; 18557 if (mod !== (old$index & 1)) { 18558 var classes = arrayClasses(scope.$eval(attr[name])); 18559 mod === selector ? 18560 addClasses(classes) : 18561 removeClasses(classes); 18562 } 18563 }); 18564 } 18565 18566 function addClasses(classes) { 18567 var newClasses = digestClassCounts(classes, 1); 18568 attr.$addClass(newClasses); 18569 } 18570 18571 function removeClasses(classes) { 18572 var newClasses = digestClassCounts(classes, -1); 18573 attr.$removeClass(newClasses); 18574 } 18575 18576 function digestClassCounts (classes, count) { 18577 var classCounts = element.data('$classCounts') || {}; 18578 var classesToUpdate = []; 18579 forEach(classes, function (className) { 18580 if (count > 0 || classCounts[className]) { 18581 classCounts[className] = (classCounts[className] || 0) + count; 18582 if (classCounts[className] === +(count > 0)) { 18583 classesToUpdate.push(className); 18584 } 18585 } 18586 }); 18587 element.data('$classCounts', classCounts); 18588 return classesToUpdate.join(' '); 18589 } 18590 18591 function updateClasses (oldClasses, newClasses) { 18592 var toAdd = arrayDifference(newClasses, oldClasses); 18593 var toRemove = arrayDifference(oldClasses, newClasses); 18594 toRemove = digestClassCounts(toRemove, -1); 18595 toAdd = digestClassCounts(toAdd, 1); 18596 18597 if (toAdd.length === 0) { 18598 $animate.removeClass(element, toRemove); 18599 } else if (toRemove.length === 0) { 18600 $animate.addClass(element, toAdd); 18601 } else { 18602 $animate.setClass(element, toAdd, toRemove); 18603 } 18604 } 18605 18606 function ngClassWatchAction(newVal) { 18607 if (selector === true || scope.$index % 2 === selector) { 18608 var newClasses = arrayClasses(newVal || []); 18609 if (!oldVal) { 18610 addClasses(newClasses); 18611 } else if (!equals(newVal,oldVal)) { 18612 var oldClasses = arrayClasses(oldVal); 18613 updateClasses(oldClasses, newClasses); 18614 } 18615 } 18616 oldVal = shallowCopy(newVal); 18617 } 18618 } 18619 }; 18620 18621 function arrayDifference(tokens1, tokens2) { 18622 var values = []; 18623 18624 outer: 18625 for(var i = 0; i < tokens1.length; i++) { 18626 var token = tokens1[i]; 18627 for(var j = 0; j < tokens2.length; j++) { 18628 if(token == tokens2[j]) continue outer; 18629 } 18630 values.push(token); 18631 } 18632 return values; 18633 } 18634
18635 function arrayClasses (classVal) { 18636 if (isArray(classVal)) { 18637 return classVal; 18638 } else if (isString(classVal)) { 18639 return classVal.split(' '); 18640 } else if (isObject(classVal)) { 18641 var classes = [], i = 0; 18642 forEach(classVal, function(v, k) { 18643 if (v) { 18644 classes = classes.concat(k.split(' ')); 18645 } 18646 }); 18647 return classes; 18648 } 18649 return classVal; 18650 } 18651 }]; 18652} 18653 18654/** 18655 * @ngdoc directive 18656 * @name ngClass 18657 * @restrict AC 18658 * 18659 * @description 18660 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 18661 * an expression that represents all classes to be added. 18662 * 18663 * The directive operates in three different ways, depending on which of three types the expression 18664 * evaluates to: 18665 * 18666 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 18667 * names. 18668 * 18669 * 2. If the expression evaluates to an array, each element of the array should be a string that is 18670 * one or more space-delimited class names. 18671 * 18672 * 3. If the expression evaluates to an object, then for each key-value pair of the 18673 * object with a truthy value the corresponding key is used as a class name. 18674 * 18675 * The directive won't add duplicate classes if a particular class was already set. 18676 * 18677 * When the expression changes, the previously added classes are removed and only then the 18678 * new classes are added. 18679 * 18680 * @animations 18681 * add - happens just before the class is applied to the element 18682 * remove - happens just before the class is removed from the element 18683 * 18684 * @element ANY 18685 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 18686 * of the evaluation can be a string representing space delimited class 18687 * names, an array, or a map of class names to boolean values. In the case of a map, the 18688 * names of the properties whose values are truthy will be added as css classes to the 18689 * element. 18690 * 18691 * @example Example that demonstrates basic bindings via ngClass directive. 18692 <example> 18693 <file name="index.html"> 18694 <p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p> 18695 <input type="checkbox" ng-model="deleted"> deleted (apply "strike" class)<br> 18696 <input type="checkbox" ng-model="important"> important (apply "bold" class)<br> 18697 <input type="checkbox" ng-model="error"> error (apply "red" class) 18698 <hr> 18699 <p ng-class="style">Using String Syntax</p> 18700 <input type="text" ng-model="style" placeholder="Type: bold strike red"> 18701 <hr> 18702 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 18703 <input ng-model="style1" placeholder="Type: bold, strike or red"><br> 18704 <input ng-model="style2" placeholder="Type: bold, strike or red"><br> 18705 <input ng-model="style3" placeholder="Type: bold, strike or red"><br> 18706 </file> 18707 <file name="style.css"> 18708 .strike { 18709 text-decoration: line-through; 18710 } 18711 .bold { 18712 font-weight: bold; 18713 } 18714 .red { 18715 color: red; 18716 } 18717 </file> 18718 <file name="protractor.js" type="protractor"> 18719 var ps = element.all(by.css('p')); 18720 18721 it('should let you toggle the class', function() { 18722 18723 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 18724 expect(ps.first().getAttribute('class')).not.toMatch(/red/); 18725 18726 element(by.model('important')).click(); 18727 expect(ps.first().getAttribute('class')).toMatch(/bold/); 18728 18729 element(by.model('error')).click(); 18730 expect(ps.first().getAttribute('class')).toMatch(/red/); 18731 }); 18732 18733 it('should let you toggle string example', function() { 18734 expect(ps.get(1).getAttribute('class')).toBe(''); 18735 element(by.model('style')).clear(); 18736 element(by.model('style')).sendKeys('red'); 18737 expect(ps.get(1).getAttribute('class')).toBe('red'); 18738 }); 18739 18740 it('array example should have 3 classes', function() { 18741 expect(ps.last().getAttribute('class')).toBe(''); 18742 element(by.model('style1')).sendKeys('bold');
18743 element(by.model('style2')).sendKeys('strike'); 18744 element(by.model('style3')).sendKeys('red'); 18745 expect(ps.last().getAttribute('class')).toBe('bold strike red'); 18746 }); 18747 </file> 18748 </example> 18749 18750 ## Animations 18751 18752 The example below demonstrates how to perform animations using ngClass. 18753 18754 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 18755 <file name="index.html"> 18756 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 18757 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 18758 <br> 18759 <span class="base-class" ng-class="myVar">Sample Text</span> 18760 </file> 18761 <file name="style.css"> 18762 .base-class { 18763 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18764 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18765 } 18766 18767 .base-class.my-class { 18768 color: red; 18769 font-size:3em; 18770 } 18771 </file> 18772 <file name="protractor.js" type="protractor"> 18773 it('should check ng-class', function() { 18774 expect(element(by.css('.base-class')).getAttribute('class')).not. 18775 toMatch(/my-class/); 18776 18777 element(by.id('setbtn')).click(); 18778 18779 expect(element(by.css('.base-class')).getAttribute('class')). 18780 toMatch(/my-class/); 18781 18782 element(by.id('clearbtn')).click(); 18783 18784 expect(element(by.css('.base-class')).getAttribute('class')).not. 18785 toMatch(/my-class/); 18786 }); 18787 </file> 18788 </example> 18789 18790 18791 ## ngClass and pre-existing CSS3 Transitions/Animations 18792 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 18793 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 18794 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 18795 to view the step by step details of {@link ngAnimate.$animate#addclass $animate.addClass} and 18796 {@link ngAnimate.$animate#removeclass $animate.removeClass}. 18797 */ 18798var ngClassDirective = classDirective('', true); 18799 18800/** 18801 * @ngdoc directive 18802 * @name ngClassOdd 18803 * @restrict AC 18804 * 18805 * @description 18806 * The `ngClassOdd` and `ngClassEven` directives work exactly as 18807 * {@link ng.directive:ngClass ngClass}, except they work in 18808 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 18809 * 18810 * This directive can be applied only within the scope of an 18811 * {@link ng.directive:ngRepeat ngRepeat}. 18812 * 18813 * @element ANY 18814 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 18815 * of the evaluation can be a string representing space delimited class names or an array. 18816 * 18817 * @example 18818 <example> 18819 <file name="index.html"> 18820 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 18821 <li ng-repeat="name in names"> 18822 <span ng-class-odd="'odd'" ng-class-even="'even'"> 18823 {{name}} 18824 </span> 18825 </li> 18826 </ol> 18827 </file> 18828 <file name="style.css"> 18829 .odd { 18830 color: red; 18831 } 18832 .even { 18833 color: blue; 18834 } 18835 </file> 18836 <file name="protractor.js" type="protractor"> 18837 it('should check ng-class-odd and ng-class-even', function() { 18838 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 18839 toMatch(/odd/); 18840 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 18841 toMatch(/even/); 18842 }); 18843 </file> 18844 </example> 18845 */ 18846var ngClassOddDirective = classDirective('Odd', 0); 18847 18848/** 18849 * @ngdoc directive 18850 * @name ngClassEven 18851 * @restrict AC 18852 * 18853 * @description 18854 * The `ngClassOdd` and `ngClassEven` directives work exactly as 18855 * {@link ng.directive:ngClass ngClass}, except they work in 18856 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 18857 * 18858 * This directive can be applied only within the scope of an 18859 * {@link ng.directive:ngRepeat ngRepeat}. 18860 * 18861 * @element ANY 18862 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 18863 * result of the evaluation can be a string representing space delimited class names or an array.
18864 * 18865 * @example 18866 <example> 18867 <file name="index.html"> 18868 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 18869 <li ng-repeat="name in names"> 18870 <span ng-class-odd="'odd'" ng-class-even="'even'"> 18871 {{name}} 18872 </span> 18873 </li> 18874 </ol> 18875 </file> 18876 <file name="style.css"> 18877 .odd { 18878 color: red; 18879 } 18880 .even { 18881 color: blue; 18882 } 18883 </file> 18884 <file name="protractor.js" type="protractor"> 18885 it('should check ng-class-odd and ng-class-even', function() { 18886 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 18887 toMatch(/odd/); 18888 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 18889 toMatch(/even/); 18890 }); 18891 </file> 18892 </example> 18893 */ 18894var ngClassEvenDirective = classDirective('Even', 1); 18895 18896/** 18897 * @ngdoc directive 18898 * @name ngCloak 18899 * @restrict AC 18900 * 18901 * @description 18902 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 18903 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 18904 * directive to avoid the undesirable flicker effect caused by the html template display. 18905 * 18906 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 18907 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 18908 * of the browser view. 18909 * 18910 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 18911 * `angular.min.js`. 18912 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 18913 * 18914 * ```css 18915 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 18916 * display: none !important; 18917 * } 18918 * ``` 18919 * 18920 * When this css rule is loaded by the browser, all html elements (including their children) that 18921 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 18922 * during the compilation of the template it deletes the `ngCloak` element attribute, making 18923 * the compiled element visible. 18924 * 18925 * For the best result, the `angular.js` script must be loaded in the head section of the html 18926 * document; alternatively, the css rule above must be included in the external stylesheet of the 18927 * application. 18928 * 18929 * Legacy browsers, like IE7, do not provide attribute selector support (a
18929dded in CSS 2.1) so they 18930 * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css 18931 * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. 18932 * 18933 * @element ANY 18934 * 18935 * @example 18936 <example> 18937 <file name="index.html"> 18938 <div id="template1" ng-cloak>{{ 'hello' }}</div> 18939 <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div> 18940 </file> 18941 <file name="protractor.js" type="protractor"> 18942 it('should remove the template directive and css class', function() { 18943 expect($('#template1').getAttribute('ng-cloak')). 18944 toBeNull(); 18945 expect($('#template2').getAttribute('ng-cloak')). 18946 toBeNull(); 18947 }); 18948 </file> 18949 </example> 18950 * 18951 */ 18952var ngCloakDirective = ngDirective({ 18953 compile: function(element, attr) { 18954 attr.$set('ngCloak', undefined); 18955 element.removeClass('ng-cloak'); 18956 } 18957}); 18958 18959/** 18960 * @ngdoc directive 18961 * @name ngController 18962 * 18963 * @description 18964 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 18965 * supports the principles behind the Model-View-Controller design pattern. 18966 * 18967 * MVC components in angular: 18968 * 18969 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 18970 * are accessed through bindings. 18971 * * View â The template (HTML with data bindings) that is rendered into the View. 18972 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 18973 * logic behind the application to decorate the scope with functions and values 18974 * 18975 * Note that you can also attach controllers to the DOM by declaring it in a route definition 18976 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 18977 * again using `ng-controller` in the template itself. This will cause the controller to be attached 18978 * and executed twice. 18979 * 18980 * @element ANY 18981 * @scope 18982 * @priority 500 18983 * @param {expression} ngController Name of a globally accessible constructor function or an 18984 * {@link guide/expression expression} that on the current scope evaluates to a 18985 * constructor function. The controller instance can be published into a scope property 18986 * by specifying `as propertyName`. 18987 * 18988 * @example 18989 * Here is a simple form for editing user contact information. Adding, removing, clearing, and 18990 * greeting are methods declared on the controller (see source tab). These methods can 18991 * easily be called from the angular markup. Any changes to the data are automatically reflected 18992 * in the View without the need for a manual update. 18993 * 18994 * Two different declaration styles are included below: 18995 * 18996 * * one binds methods and properties directly onto the controller using `this`: 18997 * `ng-controller="SettingsController1 as settings"` 18998 * * one injects `$scope` into the controller: 18999 * `ng-controller="SettingsController2"` 19000 * 19001 * The second option is more common in the Angular community, and is generally used in boilerplates 19002 * and in this guide. However, there are advantages to binding properties directly to the controller 19003 * and avoiding scope. 19004 * 19005 * * Using `controller as` makes it obvious which controller you are accessing in the template when 19006 * multiple controllers apply to an element. 19007 * * If you are writing your controllers as classes you have easier access to the properties and 19008 * methods, which will appear on the scope, from inside the controller code. 19009 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 19010 * inheritance masking primitives. 19011 * 19012 * This example demonstrates the `controller as` syntax. 19013 * 19014 * <example name="ngControllerAs" module="controllerAsExample"> 19015 * <file name="index.html"> 19016 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 19017 * Name: <input type="text" ng-model="settings.name"/> 19018 * [ <a href="" ng-click="settings.greet()">greet</a> ]<br/> 19019 * Contact: 19020 * <ul> 19021 * <li ng-repeat="contact in settings.contacts"> 19022 * <select ng-model="contact.type"> 19023 * <option>phone</option> 19024 * <option>email</option> 19025 * </select> 19026 * <input type="text" ng-model="contact.value"/> 19027 * [ <a href="" ng-click="settings.clearContact(contact)">clear</a> 19028 * | <a href="" ng-click="settings.removeContact(contact)">X</a> ] 19029 * </li> 19030 * <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li> 19031 * </ul> 19032 * </div> 19033 * </file> 19034 * <file name="app.js">
19035 * angular.module('controllerAsExample', []) 19036 * .controller('SettingsController1', SettingsController1); 19037 * 19038 * function SettingsController1() { 19039 * this.name = "John Smith"; 19040 * this.contacts = [ 19041 * {type: 'phone', value: '408 555 1212'}, 19042 * {type: 'email', value: '[email protected]'} ]; 19043 * } 19044 * 19045 * SettingsController1.prototype.greet = function() { 19046 * alert(this.name); 19047 * }; 19048 * 19049 * SettingsController1.prototype.addContact = function() { 19050 * this.contacts.push({type: 'email', value: '[email protected]'}); 19051 * }; 19052 * 19053 * SettingsController1.prototype.removeContact = function(contactToRemove) { 19054 * var index = this.contacts.indexOf(contactToRemove); 19055 * this.contacts.splice(index, 1); 19056 * }; 19057 * 19058 * SettingsController1.prototype.clearContact = function(contact) { 19059 * contact.type = 'phone'; 19060 * contact.value = ''; 19061 * }; 19062 * </file> 19063 * <file name="protractor.js" type="protractor"> 19064 * it('should check controller as', function() { 19065 * var container = element(by.id('ctrl-as-exmpl')); 19066 * expect(container.element(by.model('settings.name')) 19067 * .getAttribute('value')).toBe('John Smith'); 19068 * 19069 * var firstRepeat = 19070 * container.element(by.repeater('contact in settings.contacts').row(0)); 19071 * var secondRepeat = 19072 * container.element(by.repeater('contact in settings.contacts').row(1)); 19073 * 19074 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 19075 * .toBe('408 555 1212'); 19076 * 19077 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 19078 * .toBe('[email protected]'); 19079 * 19080 * firstRepeat.element(by.linkText('clear')).click(); 19081 * 19082 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 19083 * .toBe(''); 19084 * 19085 * container.element(by.linkText('add')).click(); 19086 * 19087 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 19088 * .element(by.model('contact.value')) 19089 * .getAttribute('value')) 19090 * .toBe('[email protected]'); 19091 * }); 19092 * </file> 19093 * </example> 19094 * 19095 * This example demonstrates the "attach to `$scope`" style of controller. 19096 * 19097 * <example name="ngController" module="controllerExample"> 19098 * <file name="index.html"> 19099 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 19100 * Name: <input type="text" ng-model="name"/> 19101 * [ <a href="" ng-click="greet()">greet</a> ]<br/> 19102 * Contact: 19103 * <ul> 19104 * <li ng-repeat="contact in contacts"> 19105 * <select ng-model="contact.type"> 19106 * <option>phone</option> 19107 * <option>email</option> 19108 * </select> 19109 * <input type="text" ng-model="contact.value"/> 19110 * [ <a href="" ng-click="clearContact(contact)">clear</a> 19111 * | <a href="" ng-click="removeContact(contact)">X</a> ] 19112 * </li> 19113 * <li>[ <a href="" ng-click="addContact()">add</a> ]</li> 19114 * </ul> 19115 * </div> 19116 * </file> 19117 * <file name="app.js"> 19118 * angular.module('controllerExample', []) 19119 * .controller('SettingsController2', ['$scope', SettingsController2]); 19120 * 19121 * function SettingsController2($scope) { 19122 * $scope.name = "John Smith"; 19123 * $scope.contacts = [ 19124 * {type:'phone', value:'408 555 1212'}, 19125 * {type:'email', value:'[email protected]'} ]; 19126 * 19127 * $scope.greet = function() { 19128 * alert($scope.name); 19129 * }; 19130 * 19131 * $scope.addContact = function() { 19132 * $scope.contacts.push({type:'email', value:'[email protected]'}); 19133 * }; 19134 * 19135 * $scope.removeContact = function(contactToRemove) { 19136 * var index = $scope.contacts.indexOf(contactToRemove); 19137 * $scope.contacts.splice(index, 1); 19138 * }; 19139 * 19140 * $scope.clearContact = function(contact) { 19141 * contact.type = 'phone'; 19142 * contact.value = ''; 19143 * }; 19144 * } 19145 * </file> 19146 * <file name="protractor.js" type="protractor"> 19147 * it('should check controller', function() { 19148 * var container = element(by.id('ctrl-exmpl')); 19149 * 19150 * expect(container.element(by.model('name')) 19151 * .getAttribute('value')).toBe('John Smith'); 19152 * 19153 * var firstRepeat = 19154 * container.element(by.repeater('contact in contacts').row(0)); 19155 * var secondRepeat = 19156 * container.element(by.repeater('contact in contacts').row(1)); 19157 * 19158 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 19159 * .toBe('408 555 1212'); 19160 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 19161 * .toBe('[email protected]'); 19162 * 19163 * firstRepeat.element(by.linkText('clear')).click(); 19164 * 19165 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 19166 * .toBe(''); 19167 * 19168 * container.element(by.linkText('add')).click(); 19169 * 19170 * expect(container.element(by.repeater('contact in contacts').row(2)) 19171 * .element(by.model('contact.value')) 19172 * .getAttribute('value')) 19173 * .toBe('[email protected]'); 19174 * }); 19175 * </file> 19176 *</example> 19177 19178 */ 19179var ngControllerDirective = [function() { 19180 return { 19181 scope: true, 19182 controller: '@', 19183 priority: 500 19184 }; 19185}]; 19186 19187/** 19188 * @ngdoc directive 19189 * @name ngCsp 19190 * 19191 * @element html 19192 * @description 19193 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. 19194 * 19195 * This is necessary when developing things like Google Chrome Extensions. 19196 * 19197 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). 19198 * For Angular to be CSP compatible there are only two things that we need to do differently: 19199 * 19200 * - don't use `Function` constructor to generate optimized value getters 19201 * - don't inject custom stylesheet into the document 19202 * 19203 * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` 19204 * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will
19205 * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will 19206 * be raised. 19207 * 19208 * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically 19209 * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). 19210 * To make those directives work in CSP mode, include the `angular-csp.css` manually. 19211 * 19212 * Angular tries to autodetect if CSP is active and automatically turn on the CSP-safe mode. This 19213 * autodetection however triggers a CSP error to be logged in the console: 19214 * 19215 * ``` 19216 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 19217 * script in the following Content Security Policy directive: "default-src 'self'". Note that 19218 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 19219 * ``` 19220 * 19221 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 19222 * directive on the root element of the application or on the `angular.js` script tag, whichever 19223 * appears first in the html document. 19224 * 19225 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 19226 * 19227 * @example 19228 * This example shows how to apply the `ngCsp` directive to the `html` tag. 19229 ```html 19230 <!doctype html> 19231 <html ng-app ng-csp> 19232 ... 19233 ... 19234 </html> 19235 ``` 19236 */ 19237 19238// ngCsp is not implemented as a proper directive any more, because we need it be processed while we 19239// bootstrap the system (before $parse is instantiated), for this reason we just have 19240// the csp.isActive() fn that looks for ng-csp attribute anywhere in the current doc 19241 19242/** 19243 * @ngdoc directive 19244 * @name ngClick 19245 * 19246 * @description 19247 * The ngClick directive allows you to specify custom behavior when 19248 * an element is clicked. 19249 * 19250 * @element ANY 19251 * @priority 0 19252 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 19253 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 19254 * 19255 * @example 19256 <example> 19257 <file name="index.html"> 19258 <button ng-click="count = count + 1" ng-init="count=0"> 19259 Increment 19260 </button> 19261 <span> 19262 count: {{count}} 19263 <span> 19264 </file> 19265 <file name="protractor.js" type="protractor"> 19266 it('should check ng-click', function() { 19267 expect(element(by.binding('count')).getText()).toMatch('0'); 19268 element(by.css('button')).click(); 19269 expect(element(by.binding('count')).getText()).toMatch('1'); 19270 }); 19271 </file> 19272 </example> 19273 */ 19274/* 19275 * A collection of directives that allows creation of custom event handlers that are defined as 19276 * angular expressions and are compiled and executed within the current scope. 19277 */ 19278var ngEventDirectives = {}; 19279 19280// For events that might fire synchronously during DOM manipulation 19281// we need to execute their event handlers asynchronously using $evalAsync, 19282// so that they are not executed in an inconsistent state. 19283var forceAsyncEvents = { 19284 'blur': true, 19285 'focus': true 19286}; 19287forEach( 19288 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 19289 function(eventName) { 19290 var directiveName = directiveNormalize('ng-' + eventName); 19291 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 19292 return { 19293 compile: function($element, attr) { 19294 // We expose the powerful $event object on the scope that provides access to the Window, 19295 // etc. that isn't protected by the fast paths in $parse. We explicitly request better 19296 // checks at the cost of speed since event handler expressions are not executed as 19297 // frequently as regular change detection. 19298 var fn = $parse(attr[directiveName], /* expensiveChecks */ true); 19299 return function ngEventHandler(scope, element) { 19300 element.on(eventName, function(event) { 19301 var callback = function() { 19302 fn(scope, {$event:event}); 19303 }; 19304 if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 19305 scope.$evalAsync(callback); 19306 } else { 19307 scope.$apply(callback); 19308 } 19309 }); 19310 }; 19311 } 19312 }; 19313 }]; 19314 } 19315); 19316 19317/** 19318 * @ngdoc directive 19319 * @name ngDblclick 19320 * 19321 * @description 19322 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 19323 * 19324 * @element ANY 19325 * @priority 0 19326 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 19327 * a dblclick. (The Event object is available as `$event`) 19328 * 19329 * @example 19330 <example> 19331 <file name="index.html"> 19332 <button ng-dblclick="count = count + 1" ng-init="count=0"> 19333 Increment (on double click) 19334 </button> 19335 count: {{count}} 19336 </file> 19337 </example> 19338 */ 19339 19340 19341/** 19342 * @ngdoc directive 19343 * @name ngMousedown 19344 * 19345 * @description 19346 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 19347 * 19348 * @element ANY 19349 * @priority 0 19350 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 19351 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 19352 * 19353 * @example 19354 <example> 19355 <file name="index.html"> 19356 <button ng-mousedown="count = count + 1" ng-init="count=0"> 19357 Increment (on mouse down) 19358 </button> 19359 count: {{count}} 19360 </file> 19361 </example> 19362 */ 19363 19364 19365/** 19366 * @ngdoc directive 19367 * @name ngMouseup 19368 * 19369 * @description 19370 * Specify custom behavior on mouseup event. 19371 * 19372 * @element ANY 19373 * @priority 0 19374 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 19375 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 19376 * 19377 * @example 19378 <example> 19379 <file name="index.html"> 19380 <button ng-mouseup="count = count + 1" ng-init="count=0"> 19381 Increment (on mouse up) 19382 </button> 19383 count: {{count}} 19384 </file> 19385 </example> 19386 */ 19387 19388/** 19389 * @ngdoc directive 19390 * @name ngMouseover 19391 * 19392 * @description 19393 * Specify custom behavior on mouseover event. 19394 * 19395 * @element ANY 19396 * @priority 0 19397 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 19398 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 19399 * 19400 * @example 19401 <example> 19402 <file name="index.html"> 19403 <button ng-mouseover="count = count + 1" ng-init="count=0"> 19404 Increment (when mouse is over) 19405 </button> 19406 count: {{count}} 19407 </file> 19408 </example> 19409 */ 19410 19411 19412/** 19413 * @ngdoc directive 19414 * @name ngMouseenter 19415 * 19416 * @description 19417 * Specify custom behavior on mouseenter event. 19418 * 19419 * @element ANY 19420 * @priority 0 19421 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 19422 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 19423 * 19424 * @example 19425 <example> 19426 <file name="index.html"> 19427 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 19428 Increment (when mouse enters) 19429 </button> 19430 count: {{count}} 19431 </file> 19432 </example> 19433 */ 19434 19435 19436/** 19437 * @ngdoc directive 19438 * @name ngMouseleave 19439 * 19440 * @description 19441 * Specify custom behavior on mouseleave event. 19442 * 19443 * @element ANY 19444 * @priority 0 19445 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 19446 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 19447 * 19448 * @example 19449 <example> 19450 <file name="index.html"> 19451 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 19452 Increment (when mouse leaves) 19453 </button> 19454 count: {{count}} 19455 </file> 19456 </example> 19457 */ 19458 19459 19460/** 19461 * @ngdoc directive 19462 * @name ngMousemove 19463 * 19464 * @description 19465 * Specify custom behavior on mousemove event. 19466 * 19467 * @element ANY 19468 * @priority 0 19469 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 19470 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 19471 * 19472 * @example 19473 <example> 19474 <file name="index.html"> 19475 <button ng-mousemove="count = count + 1" ng-init="count=0"> 19476 Increment (when mouse moves) 19477 </button> 19478 count: {{count}} 19479 </file> 19480 </example> 19481 */ 19482 19483 19484/** 19485 * @ngdoc directive 19486 * @name ngKeydown 19487 * 19488 * @description 19489 * Specify custom behavior on keydown event. 19490 * 19491 * @element ANY 19492 * @priority 0 19493 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 19494 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 19495 * 19496 * @example 19497 <example> 19498 <file name="index.html"> 19499 <input ng-keydown="count = count + 1" ng-init="count=0"> 19500 key down count: {{count}} 19501 </file> 19502 </example> 19503 */ 19504 19505 19506/** 19507 * @ngdoc directive 19508 * @name ngKeyup 19509 * 19510 * @description 19511 * Specify custom behavior on keyup event. 19512 * 19513 * @element ANY 19514 * @priority 0 19515 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 19516 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 19517 * 19518 * @example 19519 <example> 19520 <file name="index.html"> 19521 <p>Typing in the input box below updates the key count</p> 19522 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 19523 19524 <p>Typing in the input box below updates the keycode</p> 19525 <input ng-keyup="event=$event"> 19526 <p>event keyCode: {{ event.keyCode }}</p> 19527 <p>event altKey: {{ event.altKey }}</p> 19528 </file> 19529 </example> 19530 */ 19531 19532 19533/** 19534 * @ngdoc directive 19535 * @name ngKeypress 19536 * 19537 * @description 19538 * Specify custom behavior on keypress event. 19539 * 19540 * @element ANY 19541 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 19542 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 19543 * and can be interrogated for keyCode, altKey, etc.) 19544 * 19545 * @example 19546 <example> 19547 <file name="index.html"> 19548 <input ng-keypress="count = count + 1" ng-init="count=0"> 19549 key press count: {{count}} 19550 </file> 19551 </example> 19552 */ 19553 19554 19555/** 19556 * @ngdoc directive 19557 * @name ngSubmit 19558 * 19559 * @description 19560 * Enables binding angular expressions to onsubmit events. 19561 * 19562 * Additionally it prevents the default action (which for form means
19562sending the request to the 19563 * server and reloading the current page), but only if the form does not contain `action`, 19564 * `data-action`, or `x-action` attributes. 19565 * 19566 * <div class="alert alert-warning"> 19567 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 19568 * `ngSubmit` handlers together. See the 19569 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 19570 * for a detailed discussion of when `ngSubmit` may be triggered. 19571 * </div> 19572 * 19573 * @element form 19574 * @priority 0 19575 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 19576 * ({@link guide/expression#-event- Event object is available as `$event`}) 19577 * 19578 * @example 19579 <example module="submitExample"> 19580 <file name="index.html"> 19581 <script> 19582 angular.module('submitExample', []) 19583 .controller('ExampleController', ['$scope', function($scope) { 19584 $scope.list = []; 19585 $scope.text = 'hello'; 19586 $scope.submit = function() { 19587 if ($scope.text) { 19588 $scope.list.push(this.text); 19589 $scope.text = ''; 19590 } 19591 }; 19592 }]); 19593 </script>
19594 <form ng-submit="submit()" ng-controller="ExampleController"> 19595 Enter text and hit enter: 19596 <input type="text" ng-model="text" name="text" /> 19597 <input type="submit" id="submit" value="Submit" /> 19598 <pre>list={{list}}</pre> 19599 </form> 19600 </file> 19601 <file name="protractor.js" type="protractor"> 19602 it('should check ng-submit', function() { 19603 expect(element(by.binding('list')).getText()).toBe('list=[]'); 19604 element(by.css('#submit')).click(); 19605 expect(element(by.binding('list')).getText()).toContain('hello'); 19606 expect(element(by.model('text')).getAttribute('value')).toBe(''); 19607 }); 19608 it('should ignore empty strings', function() { 19609 expect(element(by.binding('list')).getText()).toBe('list=[]'); 19610 element(by.css('#submit')).click(); 19611 element(by.css('#submit')).click(); 19612 expect(element(by.binding('list')).getText()).toContain('hello'); 19613 }); 19614 </file> 19615 </example> 19616 */ 19617 19618/** 19619 * @ngdoc directive 19620 * @name ngFocus 19621 * 19622 * @description 19623 * Specify custom behavior on focus event. 19624 * 19625 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 19626 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 19627 * during an `$apply` to ensure a consistent state. 19628 * 19629 * @element window, input, select, textarea, a 19630 * @priority 0 19631 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 19632 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 19633 * 19634 * @example 19635 * See {@link ng.directive:ngClick ngClick} 19636 */ 19637 19638/** 19639 * @ngdoc directive 19640 * @name ngBlur 19641 * 19642 * @description 19643 * Specify custom behavior on blur event. 19644 * 19645 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 19646 * an element has lost focus. 19647 * 19648 * Note: As the `blur` event is executed synchronously also during DOM manipulations 19649 * (e.g. removing a focussed input), 19650 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 19651 * during an `$apply` to ensure a consistent state. 19652 * 19653 * @element window, input, select, textarea, a 19654 * @priority 0 19655 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 19656 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 19657 * 19658 * @example 19659 * See {@link ng.directive:ngClick ngClick} 19660 */ 19661 19662/** 19663 * @ngdoc directive 19664 * @name ngCopy 19665 * 19666 * @description 19667 * Specify custom behavior on copy event. 19668 * 19669 * @element window, input, select, textarea, a 19670 * @priority 0 19671 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 19672 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 19673 * 19674 * @example 19675 <example> 19676 <file name="index.html"> 19677 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 19678 copied: {{copied}} 19679 </file> 19680 </example> 19681 */ 19682 19683/** 19684 * @ngdoc directive 19685 * @name ngCut 19686 * 19687 * @description 19688 * Specify custom behavior on cut event. 19689 * 19690 * @element window, input, select, textarea, a 19691 * @priority 0 19692 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 19693 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 19694 * 19695 * @example 19696 <example> 19697 <file name="index.html"> 19698 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 19699 cut: {{cut}} 19700 </file> 19701 </example> 19702 */ 19703 19704/** 19705 * @ngdoc directive 19706 * @name ngPaste 19707 * 19708 * @description 19709 * Specify custom behavior on paste event. 19710 * 19711 * @element window, input, select, textarea, a 19712 * @priority 0 19713 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 19714 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 19715 * 19716 * @example 19717 <example> 19718 <file name="index.html"> 19719 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 19720 pasted: {{paste}} 19721 </file> 19722 </example> 19723 */ 19724 19725/** 19726 * @ngdoc directive 19727 * @name ngIf 19728 * @restrict A 19729 * 19730 * @description 19731 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 19732 * {expression}. If the expression assigned to `ngIf` evaluates to a false 19733 * value then the element is removed from the DOM, otherwise a clone of the 19734 * element is reinserted into the DOM. 19735 * 19736 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 19737 * element in the DOM rather than changing its visibility via the `display` css property. A common 19738 * case when this difference is significant is when using css selectors that rely on an element's 19739 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
19739s. 19740 * 19741 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 19742 * is created when the element is restored. The scope created within `ngIf` inherits from 19743 * its parent scope using 19744 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 19745 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 19746 * a javascript primitive defined in the parent scope. In this case any modifications made to the 19747 * variable within the child scope will override (hide) the value in the parent scope. 19748 * 19749 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 19750 * is if an element's class attribute is directly modified after it's compiled, using something like 19751 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 19752 * the added class will be lost because the original compiled state is used to regenerate the element. 19753 * 19754 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 19755 * and `leave` effects. 19756 * 19757 * @animations 19758 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container 19759 * leave - happens just before the `ngIf` contents are removed from the DOM 19760 * 19761 * @element ANY 19762 * @scope 19763 * @priority 600 19764 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 19765 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 19766 * element is added to the DOM tree. 19767 * 19768 * @example 19769 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 19770 <file name="index.html"> 19771 Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/> 19772 Show when checked: 19773 <span ng-if="checked" class="animate-if"> 19774 I'm removed when the checkbox is unchecked. 19775 </span> 19776 </file> 19777 <file name="animations.css"> 19778 .animate-if { 19779 background:white; 19780 border:1px solid black; 19781 padding:10px; 19782 } 19783 19784 .animate-if.ng-enter, .animate-if.ng-leave { 19785 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19786 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19787 } 19788 19789 .animate-if.ng-enter, 19790 .animate-if.ng-leave.ng-leave-active { 19791 opacity:0; 19792 } 19793 19794 .animate-if.ng-leave, 19795 .animate-if.ng-enter.ng-enter-active { 19796 opacity:1; 19797 } 19798 </file> 19799 </example> 19800 */ 19801var ngIfDirective = ['$animate', function($animate) { 19802 return { 19803 transclude: 'element', 19804 priority: 600, 19805 terminal: true, 19806 restrict: 'A', 19807 $$tlb: true, 19808 link: function ($scope, $element, $attr, ctrl, $transclude) { 19809 var block, childScope, previousElements; 19810 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 19811 19812 if (toBoolean(value)) { 19813 if (!childScope) { 19814 childScope = $scope.$new(); 19815 $transclude(childScope, function (clone) { 19816 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 19817 // Note: We only need the first/last node of the cloned nodes. 19818 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 19819 // by a directive with templateUrl when its template arrives. 19820 block = { 19821 clone: clone 19822 }; 19823 $animate.enter(clone, $element.parent(), $element); 19824 }); 19825 } 19826 } else { 19827 if(previousElements) { 19828 previousElements.remove(); 19829 previousElements = null; 19830 } 19831 if(childScope) { 19832 childScope.$destroy(); 19833 childScope = null; 19834 } 19835 if(block) { 19836 previousElements = getBlockElements(block.clone); 19837 $animate.leave(previousElements, function() { 19838 previousElements = null; 19839 }); 19840 block = null; 19841 } 19842 } 19843 }); 19844 } 19845 }; 19846}]; 19847 19848/** 19849 * @ngdoc directive 19850 * @name ngInclude 19851 * @restrict ECA 19852 * 19853 * @description 19854 * Fetches, compiles and includes an external HTML fragment. 19855 * 19856 * By default, the template URL is restricted to the same domain and protocol as the 19857 * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 19858 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 19859 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 19860 * [wrap them](ng.$sce#trustAsResourceUrl) as trusted values. Refer to Angular's {@link 19861 * ng.$sce Strict Contextual Escaping}. 19862 * 19863 * In addition, the browser's 19864 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 19865 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 19866 * policy may further restrict whether the template is successfully loaded. 19867 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 19868 * access on some browsers. 19869 * 19870 * @animations 19871 * enter - animation is used to bring new content into the browser. 19872 * leave - animation is used to animate existing content away. 19873 * 19874 * The enter and leave animation occur concurrently. 19875 * 19876 * @scope 19877 * @priority 400 19878 * 19879 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 19880 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 19881 * @param {string=} onload Expression to evaluate when a new partial is loaded. 19882 * 19883 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 19884 * $anchorScroll} to scroll the viewport after the content is loaded. 19885 * 19886 * - If the attribute is not set, disable scrolling. 19887 * - If the attribute is set without value, enable scrolling. 19888 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 19889 * 19890 * @example 19891 <example module="includeExample" deps="angular-animate.js" animations="true"> 19892 <file name="index.html">
19893 <div ng-controller="ExampleController"> 19894 <select ng-model="template" ng-options="t.name for t in templates"> 19895 <option value="">(blank)</option> 19896 </select> 19897 url of the template: <code>{{template.url}}</code> 19898 <hr/> 19899 <div class="slide-animate-container"> 19900 <div class="slide-animate" ng-include="template.url"></div> 19901 </div> 19902 </div> 19903 </file> 19904 <file name="script.js"> 19905 angular.module('includeExample', ['ngAnimate']) 19906 .controller('ExampleController', ['$scope', function($scope) { 19907 $scope.templates = 19908 [ { name: 'template1.html', url: 'template1.html'}, 19909 { name: 'template2.html', url: 'template2.html'} ]; 19910 $scope.template = $scope.templates[0]; 19911 }]); 19912 </file> 19913 <file name="template1.html"> 19914 Content of template1.html 19915 </file> 19916 <file name="template2.html"> 19917 Content of template2.html 19918 </file> 19919 <file name="animations.css"> 19920 .slide-animate-container { 19921 position:relative; 19922 background:white; 19923 border:1px solid black; 19924 height:40px; 19925 overflow:hidden; 19926 } 19927 19928 .slide-animate { 19929 padding:10px; 19930 } 19931 19932 .slide-animate.ng-enter, .slide-animate.ng-leave { 19933 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19934 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19935 19936 position:absolute; 19937 top:0; 19938 left:0; 19939 right:0; 19940 bottom:0; 19941 display:block; 19942 padding:10px; 19943 } 19944 19945 .slide-animate.ng-enter { 19946 top:-50px; 19947 } 19948 .slide-animate.ng-enter.ng-enter-active { 19949 top:0; 19950 } 19951 19952 .slide-animate.ng-leave { 19953 top:0; 19954 } 19955 .slide-animate.ng-leave.ng-leave-active { 19956 top:50px; 19957 } 19958 </file> 19959 <file name="protractor.js" type="protractor"> 19960 var templateSelect = element(by.model('template')); 19961 var includeElem = element(by.css('[ng-include]')); 19962 19963 it('should load template1.html', function() { 19964 expect(includeElem.getText()).toMatch(/Content of template1.html/); 19965 }); 19966 19967 it('should load template2.html', function() { 19968 if (browser.params.browser == 'firefox') { 19969 // Firefox can't handle using selects 19970 // See https://github.com/angular/protractor/issues/480 19971 return; 19972 } 19973 templateSelect.click(); 19974 templateSelect.all(by.css('option')).get(2).click(); 19975 expect(includeElem.getText()).toMatch(/Content of template2.html/); 19976 }); 19977 19978 it('should change to blank', function() { 19979 if (browser.params.browser == 'firefox') { 19980 // Firefox can't handle using selects 19981 return; 19982 } 19983 templateSelect.click(); 19984 templateSelect.all(by.css('option')).get(0).click(); 19985 expect(includeElem.isPresent()).toBe(false); 19986 }); 19987 </file> 19988 </example> 19989 */ 19990 19991 19992/** 19993 * @ngdoc event 19994 * @name ngInclude#$includeContentRequested 19995 * @eventType emit on the scope ngInclude was declared in 19996 * @description 19997 * Emitted every time the ngInclude content is requested. 19998 */ 19999 20000 20001/** 20002 * @ngdoc event 20003 * @name ngInclude#$includeContentLoaded 20004 * @eventType emit on the current ngInclude scope 20005 * @description 20006 * Emitted every time the ngInclude content is reloaded. 20007 */ 20008var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce', 20009 function($http, $templateCache, $anchorScroll, $animate, $sce) { 20010 return { 20011 restrict: 'ECA', 20012 priority: 400, 20013 terminal: true, 20014 transclude: 'element', 20015 controller: angular.noop, 20016 compile: function(element, attr) { 20017 var srcExp = attr.ngInclude || attr.src, 20018 onloadExp = attr.onload || '', 20019 autoScrollExp = attr.autoscroll; 20020 20021 return function(scope, $element, $attr, ctrl, $transclude) { 20022 var changeCounter = 0, 20023 currentScope, 20024 previousElement, 20025 currentElement; 20026 20027 var cleanupLastIncludeContent = function() { 20028 if(previousElement) { 20029 previousElement.remove(); 20030 previousElement = null; 20031 } 20032 if(currentScope) { 20033 currentScope.$destroy(); 20034 currentScope = null; 20035 } 20036 if(currentElement) { 20037 $animate.leave(currentElement, function() { 20038 previousElement = null; 20039 }); 20040 previousElement = currentElement; 20041 currentElement = null; 20042 } 20043 }; 20044 20045 scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { 20046 var afterAnimation = function() { 20047 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 20048 $anchorScroll(); 20049 } 20050 }; 20051 var thisChangeId = ++changeCounter; 20052 20053 if (src) { 20054 $http.get(src, {cache: $templateCache}).success(function(response) { 20055 if (thisChangeId !== changeCounter) return; 20056 var newScope = scope.$new(); 20057 ctrl.template = response; 20058 20059 // Note: This will also link all children of ng-include that were contained in the original 20060 // html. If that content contains controllers, ... they could pollute/change the scope. 20061 // However, using ng-include on an element with additional content does not make sense... 20062 // Note: We can't remove them in the cloneAttchFn of $transclude as that 20063 // function is called before linking the content, which would apply child 20064 // directives to non existing elements. 20065 var clone = $transclude(newScope, function(clone) { 20066 cleanupLastIncludeContent(); 20067 $animate.enter(clone, null, $element, afterAnimation); 20068 }); 20069 20070 currentScope = newScope; 20071 currentElement = clone; 20072 20073 currentScope.$emit('$includeContentLoaded'); 20074 scope.$eval(onloadExp); 20075 }).error(function() { 20076 if (thisChangeId === changeCounter) cleanupLastIncludeContent(); 20077 }); 20078 scope.$emit('$includeContentRequested'); 20079 } else { 20080 cleanupLastIncludeContent(); 20081 ctrl.template = null; 20082 } 20083 }); 20084 }; 20085 } 20086 }; 20087}]; 20088 20089// This directive is called during the $transclude call of the first `ngInclude` directive. 20090// It will replace and compile the content of the element with the loaded template. 20091// We need this directive so that the element content is already filled when 20092// the link function of another directive on the same element as ngInclude 20093// is called. 20094var ngIncludeFillContentDirective = ['$compile', 20095 function($compile) { 20096 return { 20097 restrict: 'ECA', 20098 priority: -400, 20099 require: 'ngInclude', 20100 link: function(scope, $element, $attr, ctrl) { 20101 $element.html(ctrl.template); 20102 $compile($element.contents())(scope); 20103 } 20104 }; 20105 }]; 20106 20107/** 20108 * @ngdoc directive 20109 * @name ngInit 20110 * @restrict AC 20111 * 20112 * @description 20113 * The `ngInit` directive allows you to evaluate an expression in the 20114 * current scope. 20115 * 20116 * <div class="alert alert-error"> 20117 * The only appropriate use of `ngInit` is for aliasing special properties of 20118 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you 20119 * should use {@link guide/controller controllers} rather than `ngInit` 20120 * to initialize values on a scope. 20121 * </div> 20122 * <div class="alert alert-warning"> 20123 * **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
20124 * sure you have parenthesis for correct precedence: 20125 * <pre class="prettyprint"> 20126 * <div ng-init="test1 = (data | orderBy:'name')"></div> 20127 * </pre> 20128 * </div> 20129 * 20130 * @priority 450 20131 * 20132 * @element ANY 20133 * @param {expression} ngInit {@link guide/expression Expression} to eval. 20134 * 20135 * @example 20136 <example module="initExample"> 20137 <file name="index.html"> 20138 <script> 20139 angular.module('initExample', []) 20140 .controller('ExampleController', ['$scope', function($scope) { 20141 $scope.list = [['a', 'b'], ['c', 'd']]; 20142 }]); 20143 </script> 20144 <div ng-controller="ExampleController"> 20145 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 20146 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 20147 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 20148 </div> 20149 </div> 20150 </div> 20151 </file> 20152 <file name="protractor.js" type="protractor"> 20153 it('should alias index positions', function() { 20154 var elements = element.all(by.css('.example-init')); 20155 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 20156 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 20157 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 20158 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 20159 }); 20160 </file> 20161 </example> 20162 */ 20163var ngInitDirective = ngDirective({ 20164 priority: 450, 20165 compile: function() { 20166 return { 20167 pre: function(scope, element, attrs) { 20168 scope.$eval(attrs.ngInit); 20169 } 20170 }; 20171 } 20172}); 20173 20174/** 20175 * @ngdoc directive 20176 * @name ngNonBindable 20177 * @restrict AC 20178 * @priority 1000 20179 * 20180 * @description 20181 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 20182 * DOM element. This is useful if the element contains what appears to be Angular directives and 20183 * bindings but which should be ignored by Angular. This could be the case if you have a site that 20184 * displays snippets of code, for instance. 20185 * 20186 * @element ANY 20187 * 20188 * @example 20189 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 20190 * but the one wrapped in `ngNonBindable` is left alone. 20191 * 20192 * @example 20193 <example> 20194 <file name="index.html"> 20195 <div>Normal: {{1 + 2}}</div> 20196 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 20197 </file> 20198 <file name="protractor.js" type="protractor"> 20199 it('should check ng-non-bindable', function() { 20200 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 20201 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 20202 }); 20203 </file> 20204 </example> 20205 */ 20206var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 20207 20208/** 20209 * @ngdoc directive 20210 * @name ngPluralize 20211 * @restrict EA 20212 * 20213 * @description 20214 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 20215 * These rules are bundled with angular.js, but can be overridden 20216 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 20217 * by specifying the mappings between 20218 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 20219 * and the strings to be displayed. 20220 * 20221 * # Plural categories and explicit number rules 20222 * There are two 20223 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 20224 * in Angular's default en-US locale: "one" and "other". 20225 * 20226 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 20227 * any number that is not 1), an explicit number rule can only match one number. For example, the 20228 * explicit number rule for "3" matches the number 3. There are examples of plural categories 20229 * and explicit number rules throughout the rest of this documentation. 20230 * 20231 * # Configuring ngPluralize 20232 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 20233 * You can also provide an optional attribute, `offset`. 20234 * 20235 * The value of the `count` attribute can be either a string or an {@link guide/expression 20236 * Angular expression}; these are evaluated on the current scope for its bound value. 20237 * 20238 * The `when` attribute specifies the mappings between plural categories and the actual 20239 * string to be displayed. The value of the attribute should be a JSON object. 20240 * 20241 * The following example shows how to configure ngPluralize: 20242 * 20243 * ```html 20244 * <ng-pluralize count="personCount"
20245 when="{'0': 'Nobody is viewing.', 20246 * 'one': '1 person is viewing.', 20247 * 'other': '{} people are viewing.'}"> 20248 * </ng-pluralize> 20249 *``` 20250 * 20251 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 20252 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 20253 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 20254 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 20255 * show "a dozen people are viewing". 20256 * 20257 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 20258 * into pluralized strings. In the previous example, Angular will replace `{}` with 20259 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 20260 * for <span ng-non-bindable>{{numberExpression}}</span>. 20261 * 20262 * # Configuring ngPluralize with offset 20263 * The `offset` attribute allows further customization of pluralized text, which can result in 20264 * a better user experience. For example, instead of the message "4 people are viewing this document", 20265 * you might display "John, Kate and 2 others are viewing this document". 20266 * The offset attribute allows you to offset a number by any desired value. 20267 * Let's take a look at an example: 20268 * 20269 * ```html 20270 * <ng-pluralize count="personCount" offset=2 20271 * when="{'0': 'Nobody is viewing.', 20272 * '1': '{{person1}} is viewing.', 20273 * '2': '{{person1}} and {{person2}} are viewing.', 20274 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 20275 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 20276 * </ng-pluralize> 20277 * ``` 20278 * 20279 * Notice that we are still using two plural categories(one, other), but we added 20280 * three explicit number rules 0, 1 and 2. 20281 * When one person, perhaps John, views the document, "John is viewing" will be shown. 20282 * When three people view the document, no explicit number rule is found, so 20283 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 20284 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 20285 * is shown. 20286 * 20287 * Note that when you specify offsets, you must provide explicit number rules for 20288 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 20289 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 20290 * plural categories "one" and "other". 20291 * 20292 * @param {string|expression} count The variable to be bound to. 20293 * @param {string} when The mapping between plural category to its corresponding strings. 20294 * @param {number=} offset Offset to deduct from the total number. 20295 * 20296 * @example 20297 <example module="pluralizeExample"> 20298 <file name="index.html"> 20299 <script> 20300 angular.module('pluralizeExample', []) 20301 .controller('ExampleController', ['$scope', function($scope) { 20302 $scope.person1 = 'Igor'; 20303 $scope.person2 = 'Misko'; 20304 $scope.personCount = 1; 20305 }]); 20306 </script>
20307 <div ng-controller="ExampleController"> 20308 Person 1:<input type="text" ng-model="person1" value="Igor" /><br/> 20309 Person 2:<input type="text" ng-model="person2" value="Misko" /><br/> 20310 Number of People:<input type="text" ng-model="personCount" value="1" /><br/> 20311 20312 <!--- Example with simple pluralization rules for en locale ---> 20313 Without Offset: 20314 <ng-pluralize count="personCount" 20315 when="{'0': 'Nobody is viewing.', 20316 'one': '1 person is viewing.', 20317 'other': '{} people are viewing.'}"> 20318 </ng-pluralize><br> 20319 20320 <!--- Example with offset ---> 20321 With Offset(2): 20322 <ng-pluralize count="personCount" offset=2 20323 when="{'0': 'Nobody is viewing.', 20324 '1': '{{person1}} is viewing.', 20325 '2': '{{person1}} and {{person2}} are viewing.', 20326 'one': '{{person1}}, {{person2}} and one other person are viewing.', 20327 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 20328 </ng-pluralize> 20329 </div> 20330 </file> 20331 <file name="protractor.js" type="protractor"> 20332 it('should show correct pluralized string', function() { 20333 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 20334 var withOffset = element.all(by.css('ng-pluralize')).get(1); 20335 var countInput = element(by.model('personCount')); 20336 20337 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 20338 expect(withOffset.getText()).toEqual('Igor is viewing.'); 20339 20340 countInput.clear(); 20341 countInput.sendKeys('0'); 20342 20343 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 20344 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 20345 20346 countInput.clear(); 20347 countInput.sendKeys('2'); 20348 20349 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 20350 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 20351 20352 countInput.clear(); 20353 countInput.sendKeys('3'); 20354 20355 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 20356 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 20357 20358 countInput.clear(); 20359 countInput.sendKeys('4'); 20360 20361 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 20362 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 20363 }); 20364 it('should show data-bound names', function() { 20365 var withOffset = element.all(by.css('ng-pluralize')).get(1); 20366 var personCount = element(by.model('personCount')); 20367 var person1 = element(by.model('person1')); 20368 var person2 = element(by.model('person2')); 20369 personCount.clear(); 20370 personCount.sendKeys('4'); 20371 person1.clear(); 20372 person1.sendKeys('Di'); 20373 person2.clear(); 20374 person2.sendKeys('Vojta'); 20375 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 20376 }); 20377 </file> 20378 </example> 20379 */ 20380var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { 20381 var BRACE = /{}/g; 20382 return { 20383 restrict: 'EA', 20384 link: function(scope, element, attr) { 20385 var numberExp = attr.count, 20386 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 20387 offset = attr.offset || 0, 20388 whens = scope.$eval(whenExp) || {}, 20389 whensExpFns = {}, 20390 startSymbol = $interpolate.startSymbol(), 20391 endSymbol = $interpolate.endSymbol(), 20392 isWhen = /^when(Minus)?(.+)$/; 20393 20394 forEach(attr, function(expression, attributeName) { 20395 if (isWhen.test(attributeName)) { 20396 whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = 20397 element.attr(attr.$attr[attributeName]); 20398 } 20399 }); 20400 forEach(whens, function(expression, key) { 20401 whensExpFns[key] = 20402 $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + 20403 offset + endSymbol)); 20404 }); 20405 20406 scope.$watch(function ngPluralizeWatch() {
20407 var value = parseFloat(scope.$eval(numberExp)); 20408 20409 if (!isNaN(value)) { 20410 //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, 20411 //check it against pluralization rules in $locale service 20412 if (!(value in whens)) value = $locale.pluralCat(value - offset); 20413 return whensExpFns[value](scope, element, true); 20414 } else { 20415 return ''; 20416 } 20417 }, function ngPluralizeWatchAction(newVal) { 20418 element.text(newVal); 20419 }); 20420 } 20421 }; 20422}]; 20423 20424/** 20425 * @ngdoc directive 20426 * @name ngRepeat 20427 * 20428 * @description 20429 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 20430 * instance gets its own scope, where the given loop variable is set to the current collection item, 20431 * and `$index` is set to the item index or key. 20432 * 20433 * Special properties are exposed on the local scope of each template instance, including: 20434 * 20435 * | Variable | Type | Details | 20436 * |-----------|-----------------|-----------------------------------------------------------------------------| 20437 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 20438 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 20439 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 20440 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 20441 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 20442 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 20443 * 20444 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 20445 * This may be useful when, for instance, nesting ngRepeats. 20446 * 20447 * # Special repeat start and end points 20448 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 20449 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 20450 * 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) 20451 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 20452 * 20453 * The example below makes use of this feature: 20454 * ```html 20455 * <header ng-repeat-start="item in items"> 20456 * Header {{ item }} 20457 * </header> 20458 * <div class="body"> 20459 * Body {{ item }} 20460 * </div> 20461 * <footer ng-repeat-end> 20462 * Footer {{ item }} 20463 * </footer> 20464 * ``` 20465 * 20466 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 20467 * ```html 20468 * <header> 20469 * Header A 20470 * </header> 20471 * <div class="body"> 20472 * Body A 20473 * </div> 20474 * <footer> 20475 * Footer A 20476 * </footer> 20477 * <header> 20478 * Header B 20479 * </header> 20480 * <div class="body"> 20481 * Body B 20482 * </div> 20483 * <footer> 20484 * Footer B 20485 * </footer> 20486 * ``` 20487 * 20488 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 20489 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 20490 * 20491 * @animations 20492 * **.enter** - when a new item is added to the list or when an item is revealed after a filter 20493 * 20494 * **.leave** - when an item is removed from the list or when an item is filtered out 20495 * 20496 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 20497 * 20498 * @element ANY 20499 * @scope 20500 * @priority 1000 20501 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 20502 * formats are currently supported: 20503 * 20504 * * `variable in expression` â where variable is the user defined loop variable and `expression` 20505 * is a scope expression giving the collection to enumerate. 20506 * 20507 * For example: `album in artist.albums`. 20508 * 20509 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 20510 * and `expression` is the scope expression giving the collection to enumerate. 20511 * 20512 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 20513 * 20514 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking function 20515 * which can be used to associate the objects in the collection with the DOM elements. If no tracking function 20516 * is specified the ng-repeat associates elements by identity in the collection. It is an error to have 20517 * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are 20518 * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, 20519 * before specifying a tracking expression. 20520 * 20521 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 20522 * will be associated by item identity in the array. 20523 * 20524 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 20525 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 20526 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 20527 * element in the same way in the DOM. 20528 * 20529 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 20530 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 20531 * property is same. 20532 * 20533 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 20534 * to items in conjunction with a tracking expression. 20535 * 20536 * @example 20537 * This example initializes the scope to a list of names and 20538 * then uses `ngRepeat` to display every person: 20539 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20540 <file name="index.html"> 20541 <div ng-init="friends = [ 20542 {name:'John', age:25, gender:'boy'}, 20543 {name:'Jessie', age:30, gender:'girl'}, 20544 {name:'Johanna', age:28, gender:'girl'}, 20545 {name:'Joy', age:15, gender:'girl'}, 20546 {name:'Mary', age:28, gender:'girl'}, 20547 {name:'Peter', age:95, gender:'boy'}, 20548 {name:'Sebastian', age:50, gender:'boy'}, 20549 {name:'Erika', age:27, gender:'girl'}, 20550 {name:'Patrick', age:40, gender:'boy'}, 20551 {name:'Samantha', age:60, gender:'girl'} 20552 ]"> 20553 I have {{friends.length}} friends. They are: 20554 <input type="search" ng-model="q" placeholder="filter friends..." /> 20555 <ul class="example-animate-container"> 20556 <li class="animate-repeat" ng-repeat="friend in friends | filter:q"> 20557 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 20558 </li> 20559 </ul> 20560 </div> 20561 </file> 20562 <file name="animations.css"> 20563 .example-animate-container { 20564 background:white; 20565 border:1px solid black; 20566 list-style:none; 20567 margin:0; 20568 padding:0 10px; 20569 } 20570 20571 .animate-repeat { 20572 line-height:40px; 20573 list-style:none; 20574 box-sizing:border-box; 20575 } 20576 20577 .animate-repeat.ng-move, 20578 .animate-repeat.ng-enter, 20579 .animate-repeat.ng-leave { 20580 -webkit-transition:all linear 0.5s; 20581 transition:all linear 0.5s; 20582 } 20583 20584 .animate-repeat.ng-leave.ng-leave-active, 20585 .animate-repeat.ng-move, 20586 .animate-repeat.ng-enter { 20587 opacity:0; 20588 max-height:0; 20589 } 20590 20591 .animate-repeat.ng-leave,
20592 .animate-repeat.ng-move.ng-move-active, 20593 .animate-repeat.ng-enter.ng-enter-active { 20594 opacity:1; 20595 max-height:40px; 20596 } 20597 </file> 20598 <file name="protractor.js" type="protractor"> 20599 var friends = element.all(by.repeater('friend in friends')); 20600 20601 it('should render initial data set', function() { 20602 expect(friends.count()).toBe(10); 20603 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 20604 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 20605 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 20606 expect(element(by.binding('friends.length')).getText()) 20607 .toMatch("I have 10 friends. They are:"); 20608 }); 20609 20610 it('should update repeater when filter predicate changes', function() { 20611 expect(friends.count()).toBe(10); 20612 20613 element(by.model('q')).sendKeys('ma'); 20614 20615 expect(friends.count()).toBe(2); 20616 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 20617 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 20618 }); 20619 </file> 20620 </example> 20621 */ 20622var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 20623 var NG_REMOVED = '$$NG_REMOVED'; 20624 var ngRepeatMinErr = minErr('ngRepeat'); 20625 return { 20626 transclude: 'element', 20627 priority: 1000, 20628 terminal: true, 20629 $$tlb: true, 20630 link: function($scope, $element, $attr, ctrl, $transclude){ 20631 var expression = $attr.ngRepeat; 20632 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), 20633 trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, 20634 lhs, rhs, valueIdentifier, keyIdentifier, 20635 hashFnLocals = {$id: hashKey}; 20636 20637 if (!match) { 20638 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 20639 expression); 20640 } 20641 20642 lhs = match[1]; 20643 rhs = match[2]; 20644 trackByExp = match[3]; 20645 20646 if (trackByExp) { 20647 trackByExpGetter = $parse(trackByExp); 20648 trackByIdExpFn = function(key, value, index) { 20649 // assign key, value, and $index to the locals so that they can be used in hash functions 20650 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 20651 hashFnLocals[valueIdentifier] = value; 20652 hashFnLocals.$index = index; 20653 return trackByExpGetter($scope, hashFnLocals); 20654 }; 20655 } else { 20656 trackByIdArrayFn = function(key, value) { 20657 return hashKey(value); 20658 }; 20659 trackByIdObjFn = function(key) { 20660 return key; 20661 }; 20662 } 20663 20664 match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); 20665 if (!match) { 20666 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 20667 lhs); 20668 } 20669 valueIdentifier = match[3] || match[1]; 20670 keyIdentifier = match[2]; 20671 20672 // Store a list of elements from previous run. This is a hash where key is the item from the 20673 // iterator, and the value is objects with following properties. 20674 // - scope: bound scope 20675 // - element: previous element. 20676 // - index: position 20677 var lastBlockMap = {}; 20678 20679 //watch props 20680 $scope.$watchCollection(rhs, function ngRepeatAction(collection){ 20681 var index, length, 20682 previousNode = $element[0], // current position of the node 20683 nextNode, 20684 // Same as lastBlockMap but it has the current state. It will become the 20685 // lastBlockMap on the next iteration. 20686 nextBlockMap = {}, 20687 arrayLength, 20688 childScope, 20689 key, value, // key/value of iteration 20690 trackById, 20691 trackByIdFn, 20692 collectionKeys, 20693 block, // last object information {scope, element, id} 20694 nextBlockOrder = [], 20695 elementsToRemove; 20696 20697 20698 if (isArrayLike(collection)) { 20699 collectionKeys = collection; 20700 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 20701 } else { 20702 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 20703 // if object, extract keys, sort them and use to determine order of iteration over obj props 20704 collectionKeys = []; 20705 for (key in collection) { 20706 if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { 20707 collectionKeys.push(key); 20708 } 20709 } 20710 collectionKeys.sort(); 20711 } 20712 20713 arrayLength = collectionKeys.length; 20714 20715 // locate existing items 20716 length = nextBlockOrder.length = collectionKeys.length; 20717 for(index = 0; index < length; index++) { 20718 key = (collection === collectionKeys) ? index : collectionKeys[index]; 20719 value = collection[key]; 20720 trackById = trackByIdFn(key, value, index); 20721 assertNotHasOwnProperty(trackById, '`track by` id'); 20722 if(lastBlockMap.hasOwnProperty(trackById)) { 20723 block = lastBlockMap[trackById]; 20724 delete lastBlockMap[trackById]; 20725 nextBlockMap[trackById] = block; 20726 nextBlockOrder[index] = block; 20727 } else if (nextBlockMap.hasOwnProperty(trackById)) { 20728 // restore lastBlockMap
20729 forEach(nextBlockOrder, function(block) { 20730 if (block && block.scope) lastBlockMap[block.id] = block; 20731 }); 20732 // This is a duplicate and we need to throw an error 20733 throw ngRepeatMinErr('dupes', 20734 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", 20735 expression, trackById, toJson(value)); 20736 } else { 20737 // new never before seen block 20738 nextBlockOrder[index] = { id: trackById }; 20739 nextBlockMap[trackById] = false; 20740 } 20741 } 20742 20743 // remove existing items 20744 for (key in lastBlockMap) { 20745 // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn 20746 if (lastBlockMap.hasOwnProperty(key)) { 20747 block = lastBlockMap[key]; 20748 elementsToRemove = getBlockElements(block.clone); 20749 $animate.leave(elementsToRemove); 20750 forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); 20751 block.scope.$destroy(); 20752 } 20753 } 20754 20755 // we are not using forEach for perf reasons (trying to avoid #call) 20756 for (index = 0, length = collectionKeys.length; index < length; index++) { 20757 key = (collection === collectionKeys) ? index : collectionKeys[index]; 20758 value = collection[key]; 20759 block = nextBlockOrder[index]; 20760 if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]); 20761 20762 if (block.scope) { 20763 // if we have already seen this object, then we need to reuse the 20764 // associated scope/element 20765 childScope = block.scope; 20766 20767 nextNode = previousNode; 20768 do { 20769 nextNode = nextNode.nextSibling; 20770 } while(nextNode && nextNode[NG_REMOVED]); 20771 20772 if (getBlockStart(block) != nextNode) { 20773 // existing item which got moved 20774 $animate.move(getBlockElements(block.clone), null, jqLite(previousNode)); 20775 } 20776 previousNode = getBlockEnd(block); 20777 } else { 20778 // new item which we don't know about 20779 childScope = $scope.$new(); 20780 } 20781 20782 childScope[valueIdentifier] = value; 20783 if (keyIdentifier) childScope[keyIdentifier] = key; 20784 childScope.$index = index; 20785 childScope.$first = (index === 0); 20786 childScope.$last = (index === (arrayLength - 1)); 20787 childScope.$middle = !(childScope.$first || childScope.$last); 20788 // jshint bitwise: false 20789 childScope.$odd = !(childScope.$even = (index&1) === 0); 20790 // jshint bitwise: true 20791 20792 if (!block.scope) { 20793 $transclude(childScope, function(clone) { 20794 clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); 20795 $animate.enter(clone, null, jqLite(previousNode)); 20796 previousNode = clone; 20797 block.scope = childScope; 20798 // Note: We only need the first/last node of the cloned nodes. 20799 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 20800 // by a directive with templateUrl when its template arrives. 20801 block.clone = clone; 20802 nextBlockMap[block.id] = block; 20803 }); 20804 } 20805 } 20806 lastBlockMap = nextBlockMap; 20807 }); 20808 } 20809 }; 20810 20811 function getBlockStart(block) { 20812 return block.clone[0]; 20813 } 20814 20815 function getBlockEnd(block) { 20816 return block.clone[block.clone.length - 1]; 20817 } 20818}]; 20819 20820/** 20821 * @ngdoc directive 20822 * @name ngShow 20823 * 20824 * @description 20825 * The `ngShow` directive shows or hides the given HTML element based on the expression 20826 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding 20827 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 20828 * in AngularJS and sets the display style to none (using an !important flag). 20829 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 20830 * 20831 * ```html 20832 * <!-- when $scope.myValue is truthy (element is visible) --> 20833 * <div ng-show="myValue"></div> 20834 * 20835 * <!-- when $scope.myValue is falsy (element is hidden) --> 20836 * <div ng-show="myValue" class="ng-hide"></div> 20837 * ``` 20838 * 20839 * When the `ngShow` expression evaluates to false then the `.ng-hide` CSS class is added to the class attribute 20840 * on the element causing it to become hidden. When true, the `.ng-hide` CSS class is removed 20841 * from the element causing the element not to appear hidden. 20842 * 20843 * <div class="alert alert-warning"> 20844 * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):<br /> 20845 * "f" / "0" / "false" / "no" / "n" / "[]" 20846 * </div> 20847 * 20848 * ## Why is !important used? 20849 * 20850 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 20851 * can be easily overridden by heavier selectors. For example, something as simple 20852 * as changing the display style on a HTML list item would make hidden elements appear visible.
20853 * This also becomes a bigger issue when dealing with CSS frameworks. 20854 * 20855 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 20856 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 20857 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 20858 * 20859 * ### Overriding `.ng-hide` 20860 * 20861 * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change 20862 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 20863 * class in CSS: 20864 * 20865 * ```css 20866 * .ng-hide { 20867 * //this is just another form of hiding an element 20868 * display:block!important; 20869 * position:absolute; 20870 * top:-9999px; 20871 * left:-9999px; 20872 * } 20873 * ``` 20874 * 20875 * By default you don't need to override in CSS anything and the animations will work around the display style. 20876 * 20877 * ## A note about animations with `ngShow` 20878 * 20879 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 20880 * is true and false. This system works like the animation system present with ngClass except that 20881 * you must also include the !important flag to override the display property 20882 * so that you can perform an animation when the element is hidden during the time of the animation. 20883 * 20884 * ```css 20885 * // 20886 * //a working example can be found at the bottom of this page 20887 * // 20888 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 20889 * transition:0.5s linear all; 20890 * } 20891 * 20892 * .my-element.ng-hide-add { ... } 20893 * .my-element.ng-hide-add.ng-hide-add-active { ... } 20894 * .my-element.ng-hide-remove { ... } 20895 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 20896 * ``` 20897 * 20898 * 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 20899 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 20900 * 20901 * @animations 20902 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible 20903 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden 20904 * 20905 * @element ANY 20906 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 20907 * then the element is shown or hidden respectively. 20908 * 20909 * @example 20910 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20911 <file name="index.html"> 20912 Click me: <input type="checkbox" ng-model="checked"><br/> 20913 <div> 20914 Show: 20915 <div class="check-element animate-show" ng-show="checked"> 20916 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 20917 </div> 20918 </div> 20919 <div> 20920 Hide: 20921 <div class="check-element animate-show" ng-hide="checked"> 20922 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 20923 </div> 20924 </div> 20925 </file> 20926 <file name="glyphicons.css"> 20927 @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); 20928 </file> 20929 <file name="animations.css"> 20930 .animate-show { 20931 -webkit-transition:all linear 0.5s; 20932 transition:all linear 0.5s; 20933 line-height:20px; 20934 opacity:1; 20935 padding:10px; 20936 border:1px solid black; 20937 background:white; 20938 } 20939 20940 .animate-show.ng-hide { 20941 line-height:0; 20942 opacity:0; 20943 padding:0 10px; 20944 } 20945 20946 .check-element { 20947 padding:10px; 20948 border:1px solid black; 20949 background:white; 20950 } 20951 </file> 20952 <file name="protractor.js" type="protractor"> 20953 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 20954 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 20955 20956 it('should check ng-show / ng-hide', function() { 20957 expect(thumbsUp.isDisplayed()).toBeFalsy(); 20958 expect(thumbsDown.isDisplayed()).toBeTruthy(); 20959
20960 element(by.model('checked')).click(); 20961 20962 expect(thumbsUp.isDisplayed()).toBeTruthy(); 20963 expect(thumbsDown.isDisplayed()).toBeFalsy(); 20964 }); 20965 </file> 20966 </example> 20967 */ 20968var ngShowDirective = ['$animate', function($animate) { 20969 return function(scope, element, attr) { 20970 scope.$watch(attr.ngShow, function ngShowWatchAction(value){ 20971 $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); 20972 }); 20973 }; 20974}]; 20975 20976 20977/** 20978 * @ngdoc directive 20979 * @name ngHide 20980 * 20981 * @description 20982 * The `ngHide` directive shows or hides the given HTML element based on the expression 20983 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding 20984 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 20985 * in AngularJS and sets the display style to none (using an !important flag). 20986 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 20987 * 20988 * ```html 20989 * <!-- when $scope.myValue is truthy (element is hidden) --> 20990 * <div ng-hide="myValue" class="ng-hide"></div> 20991 * 20992 * <!-- when $scope.myValue is falsy (element is visible) --> 20993 * <div ng-hide="myValue"></div> 20994 * ``` 20995 * 20996 * When the `.ngHide` expression evaluates to true then the `.ng-hide` CSS class is added to the class attribute 20997 * on the element causing it to become hidden. When false, the `.ng-hide` CSS class is removed 20998 * from the element causing the element not to appear hidden. 20999 * 21000 * <div class="alert alert-warning"> 21001 * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):<br /> 21002 * "f" / "0" / "false" / "no" / "n" / "[]" 21003 * </div> 21004 * 21005 * ## Why is !important used? 21006 * 21007 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 21008 * can be easily overridden by heavier selectors. For example, something as simple 21009 * as changing the display style on a HTML list item would make hidden elements appear visible. 21010 * This also becomes a bigger issue when dealing with CSS frameworks. 21011 * 21012 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 21013 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 21014 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 21015 * 21016 * ### Overriding `.ng-hide` 21017 * 21018 * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change 21019 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 21020 * class in CSS: 21021 * 21022 * ```css 21023 * .ng-hide { 21024 * //this is just another form of hiding an element 21025 * display:block!important; 21026 * position:absolute; 21027 * top:-9999px; 21028 * left:-9999px; 21029 * } 21030 * ``` 21031 * 21032 * By default you don't need to override in CSS anything and the animations will work around the display style. 21033 * 21034 * ## A note about animations with `ngHide` 21035 * 21036 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 21037 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 21038 * CSS class is added and removed for you instead of your own CSS class. 21039 * 21040 * ```css 21041 * // 21042 * //a working example can be found at the bottom of this page 21043 * // 21044 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 21045 * transition:0.5s linear all; 21046 * } 21047 * 21048 * .my-element.ng-hide-add { ... } 21049 * .my-element.ng-hide-add.ng-hide-add-active { ... } 21050 * .my-element.ng-hide-remove { ... } 21051 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 21052 * ``` 21053 * 21054 * 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 21055 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 21056 * 21057 * @animations 21058 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden 21059 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible 21060 * 21061 * @element ANY 21062 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 21063 * the element is shown or hidden respectively. 21064 * 21065 * @example 21066 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 21067 <file name="index.html"> 21068 Click me: <input type="checkbox" ng-model="checked"><br/> 21069 <div> 21070 Show: 21071 <div class="check-element animate-hide" ng-show="checked">
21072 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 21073 </div> 21074 </div> 21075 <div> 21076 Hide: 21077 <div class="check-element animate-hide" ng-hide="checked"> 21078 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 21079 </div> 21080 </div> 21081 </file> 21082 <file name="glyphicons.css"> 21083 @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); 21084 </file> 21085 <file name="animations.css"> 21086 .animate-hide { 21087 -webkit-transition:all linear 0.5s; 21088 transition:all linear 0.5s; 21089 line-height:20px; 21090 opacity:1; 21091 padding:10px; 21092 border:1px solid black; 21093 background:white; 21094 } 21095 21096 .animate-hide.ng-hide { 21097 line-height:0; 21098 opacity:0; 21099 padding:0 10px; 21100 } 21101 21102 .check-element { 21103 padding:10px; 21104 border:1px solid black; 21105 background:white; 21106 } 21107 </file> 21108 <file name="protractor.js" type="protractor"> 21109 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 21110 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 21111 21112 it('should check ng-show / ng-hide', function() { 21113 expect(thumbsUp.isDisplayed()).toBeFalsy(); 21114 expect(thumbsDown.isDisplayed()).toBeTruthy(); 21115 21116 element(by.model('checked')).click(); 21117 21118 expect(thumbsUp.isDisplayed()).toBeTruthy(); 21119 expect(thumbsDown.isDisplayed()).toBeFalsy(); 21120 }); 21121 </file> 21122 </example> 21123 */ 21124var ngHideDirective = ['$animate', function($animate) { 21125 return function(scope, element, attr) { 21126 scope.$watch(attr.ngHide, function ngHideWatchAction(value){ 21127 $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); 21128 }); 21129 }; 21130}]; 21131 21132/** 21133 * @ngdoc directive 21134 * @name ngStyle 21135 * @restrict AC 21136 * 21137 * @description 21138 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 21139 * 21140 * @element ANY 21141 * @param {expression} ngStyle 21142 * 21143 * {@link guide/expression Expression} which evals to an 21144 * object whose keys are CSS style names and values are corresponding values for those CSS 21145 * keys. 21146 * 21147 * Since some CSS style names are not valid keys for an object, they must be quoted. 21148 * See the 'background-color' style in the example below. 21149 * 21150 * @example 21151 <example> 21152 <file name="index.html"> 21153 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 21154 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 21155 <input type="button" value="clear" ng-click="myStyle={}"> 21156 <br/> 21157 <span ng-style="myStyle">Sample Text</span> 21158 <pre>myStyle={{myStyle}}</pre> 21159 </file> 21160 <file name="style.css"> 21161 span { 21162 color: black; 21163 } 21164 </file> 21165 <file name="protractor.js" type="protractor"> 21166 var colorSpan = element(by.css('span')); 21167 21168 it('should check ng-style', function() { 21169 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 21170 element(by.css('input[value=\'set color\']')).click(); 21171 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 21172 element(by.css('input[value=clear]')).click(); 21173 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 21174 }); 21175 </file> 21176 </example> 21177 */ 21178var ngStyleDirective = ngDirective(function(scope, element, attr) { 21179 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 21180 if (oldStyles && (newStyles !== oldStyles)) { 21181 forEach(oldStyles, function(val, style) { element.css(style, '');}); 21182 } 21183 if (newStyles) element.css(newStyles); 21184 }, true); 21185}); 21186 21187/** 21188 * @ngdoc directive 21189 * @name ngSwitch 21190 * @restrict EA 21191 * 21192 * @description 21193 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 21194 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 21195 * as specified in the template. 21196 * 21197 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 21198 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 21199 * matches the value obtained from the evaluated expression. In other words, you define a container element 21200 * (where you place the directive), place an expression on the **`on="..."` attribute** 21201 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 21202 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 21203 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 21204 * attribute is displayed. 21205 * 21206 * <div class="alert alert-info"> 21207 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 21208 * as literal string values to match against. 21209 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
21209omeVal"` not against the 21210 * value of the expression `$scope.someVal`. 21211 * </div> 21212 21213 * @animations 21214 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 21215 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 21216 * 21217 * @usage 21218 * 21219 * ``` 21220 * <ANY ng-switch="expression"> 21221 * <ANY ng-switch-when="matchValue1">...</ANY> 21222 * <ANY ng-switch-when="matchValue2">...</ANY> 21223 * <ANY ng-switch-default>...</ANY> 21224 * </ANY> 21225 * ``` 21226 * 21227 * 21228 * @scope 21229 * @priority 800 21230 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>. 21231 * On child elements add: 21232 * 21233 * * `ngSwitchWhen`: the case statement to match against. If match then this 21234 * case will be displayed. If the same match appears multiple times, all the 21235 * elements will be displayed. 21236 * * `ngSwitchDefault`: the default case when no other case match. If there 21237 * are multiple default cases, all of them will be displayed when no other 21238 * case match. 21239 * 21240 * 21241 * @example 21242 <example module="switchExample" deps="angular-animate.js" animations="true"> 21243 <file name="index.html">
21244 <div ng-controller="ExampleController"> 21245 <select ng-model="selection" ng-options="item for item in items"> 21246 </select> 21247 <tt>selection={{selection}}</tt> 21248 <hr/> 21249 <div class="animate-switch-container" 21250 ng-switch on="selection"> 21251 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 21252 <div class="animate-switch" ng-switch-when="home">Home Span</div> 21253 <div class="animate-switch" ng-switch-default>default</div> 21254 </div> 21255 </div> 21256 </file> 21257 <file name="script.js"> 21258 angular.module('switchExample', ['ngAnimate']) 21259 .controller('ExampleController', ['$scope', function($scope) { 21260 $scope.items = ['settings', 'home', 'other']; 21261 $scope.selection = $scope.items[0]; 21262 }]); 21263 </file> 21264 <file name="animations.css"> 21265 .animate-switch-container { 21266 position:relative; 21267 background:white; 21268 border:1px solid black; 21269 height:40px; 21270 overflow:hidden; 21271 } 21272 21273 .animate-switch { 21274 padding:10px; 21275 } 21276 21277 .animate-switch.ng-animate { 21278 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 21279 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 21280 21281 position:absolute; 21282 top:0; 21283 left:0; 21284 right:0; 21285 bottom:0; 21286 } 21287 21288 .animate-switch.ng-leave.ng-leave-active, 21289 .animate-switch.ng-enter { 21290 top:-50px; 21291 } 21292 .animate-switch.ng-leave, 21293 .animate-switch.ng-enter.ng-enter-active { 21294 top:0; 21295 } 21296 </file> 21297 <file name="protractor.js" type="protractor"> 21298 var switchElem = element(by.css('[ng-switch]')); 21299 var select = element(by.model('selection')); 21300 21301 it('should start in settings', function() { 21302 expect(switchElem.getText()).toMatch(/Settings Div/); 21303 }); 21304 it('should change to home', function() { 21305 select.all(by.css('option')).get(1).click(); 21306 expect(switchElem.getText()).toMatch(/Home Span/); 21307 }); 21308 it('should select default', function() { 21309 select.all(by.css('option')).get(2).click(); 21310 expect(switchElem.getText()).toMatch(/default/); 21311 }); 21312 </file> 21313 </example> 21314 */ 21315var ngSwitchDirective = ['$animate', function($animate) { 21316 return { 21317 restrict: 'EA', 21318 require: 'ngSwitch', 21319 21320 // asks for $scope to fool the BC controller module 21321 controller: ['$scope', function ngSwitchController() { 21322 this.cases = {}; 21323 }], 21324 link: function(scope, element, attr, ngSwitchController) { 21325 var watchExpr = attr.ngSwitch || attr.on, 21326 selectedTranscludes = [], 21327 selectedElements = [], 21328 previousElements = [], 21329 selectedScopes = []; 21330 21331 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 21332 var i, ii; 21333 for (i = 0, ii = previousElements.length; i < ii; ++i) { 21334 previousElements[i].remove(); 21335 } 21336 previousElements.length = 0; 21337 21338 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 21339 var selected = selectedElements[i]; 21340 selectedScopes[i].$destroy(); 21341 previousElements[i] = selected; 21342 $animate.leave(selected, function() { 21343 previousElements.splice(i, 1); 21344 }); 21345 } 21346 21347 selectedElements.length = 0; 21348 selectedScopes.length = 0; 21349 21350 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 21351 scope.$eval(attr.change); 21352 forEach(selectedTranscludes, function(selectedTransclude) { 21353 var selectedScope = scope.$new(); 21354 selectedScopes.push(selectedScope); 21355 selectedTransclude.transclude(selectedScope, function(caseElement) { 21356 var anchor = selectedTransclude.element; 21357 21358 selectedElements.push(caseElement); 21359 $animate.enter(caseElement, anchor.parent(), anchor); 21360 }); 21361 }); 21362 } 21363 }); 21364 } 21365 }; 21366}]; 21367 21368var ngSwitchWhenDirective = ngDirective({ 21369 transclude: 'element', 21370 priority: 800, 21371 require: '^ngSwitch', 21372 link: function(scope, element, attrs, ctrl, $transclude) { 21373 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 21374 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 21375 } 21376}); 21377 21378var ngSwitchDefaultDirective = ngDirective({ 21379 transclude: 'element', 21380 priority: 800, 21381 require: '^ngSwitch', 21382 link: function(scope, element, attr, ctrl, $transclude) { 21383 ctrl.cases['?'] = (ctrl.cases['?'] || []); 21384 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 21385 } 21386}); 21387 21388/** 21389 * @ngdoc directive 21390 * @name ngTransclude 21391 * @restrict AC 21392 * 21393 * @description 21394 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 21395 * 21396 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 21397 * 21398 * @element ANY 21399 * 21400 * @example 21401 <example module="transcludeExample"> 21402 <file name="index.html"> 21403 <script> 21404 angular.module('transcludeExample', []) 21405 .directive('pane', function(){ 21406 return { 21407 restrict: 'E', 21408 transclude: true, 21409 scope: { title:'@' }, 21410 template: '<div style="border: 1px solid black;">
21410' + 21411 '<div style="background-color: gray">{{title}}</div>' + 21412 '<div ng-transclude></div>' + 21413 '</div>' 21414 }; 21415 }) 21416 .controller('ExampleController', ['$scope', function($scope) { 21417 $scope.title = 'Lorem Ipsum'; 21418 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 21419 }]); 21420 </script> 21421 <div ng-controller="ExampleController"> 21422 <input ng-model="title"><br> 21423 <textarea ng-model="text"></textarea> <br/> 21424 <pane title="{{title}}">{{text}}</pane> 21425 </div> 21426 </file> 21427 <file name="protractor.js" type="protractor"> 21428 it('should have transcluded', function() { 21429 var titleElement = element(by.model('title')); 21430 titleElement.clear(); 21431 titleElement.sendKeys('TITLE'); 21432 var textElement = element(by.model('text')); 21433 textElement.clear(); 21434 textElement.sendKeys('TEXT'); 21435 expect(element(by.binding('title')).getText()).toEqual('TITLE'); 21436 expect(element(by.binding('text')).getText()).toEqual('TEXT'); 21437 }); 21438 </file> 21439 </example> 21440 * 21441 */ 21442var ngTranscludeDirective = ngDirective({ 21443 link: function($scope, $element, $attrs, controller, $transclude) { 21444 if (!$transclude) { 21445 throw minErr('ngTransclude')('orphan', 21446 'Illegal use of ngTransclude directive in the template! ' + 21447 'No parent directive that requires a transclusion found. ' + 21448 'Element: {0}', 21449 startingTag($element)); 21450 } 21451 21452 $transclude(function(clone) { 21453 $element.empty(); 21454 $element.append(clone); 21455 }); 21456 } 21457}); 21458 21459/** 21460 * @ngdoc directive 21461 * @name script 21462 * @restrict E 21463 * 21464 * @description 21465 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 21466 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 21467 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 21468 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 21469 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 21470 * 21471 * @param {string} type Must be set to `'text/ng-template'`. 21472 * @param {string} id Cache name of the template. 21473 * 21474 * @example 21475 <example> 21476 <file name="index.html"> 21477 <script type="text/ng-template" id="/tpl.html"> 21478 Content of the template. 21479 </script> 21480 21481 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 21482 <div id="tpl-content" ng-include src="currentTpl"></div> 21483 </file> 21484 <file name="protractor.js" type="protractor"> 21485 it('should load template defined inside script tag', function() { 21486 element(by.css('#tpl-link')).click(); 21487 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 21488 }); 21489 </file> 21490 </example> 21491 */ 21492var scriptDirective = ['$templateCache', function($templateCache) { 21493 return { 21494 restrict: 'E', 21495 terminal: true, 21496 compile: function(element, attr) { 21497 if (attr.type == 'text/ng-template') { 21498 var templateUrl = attr.id, 21499 text = element[0].text; 21500 21501 $templateCache.put(templateUrl, text); 21502 } 21503 } 21504 }; 21505}]; 21506 21507var ngOptionsMinErr = minErr('ngOptions'); 21508/** 21509 * @ngdoc directive 21510 * @name select 21511 * @restrict E 21512 * 21513 * @description 21514 * HTML `SELECT` element with angular data-binding. 21515 * 21516 * # `ngOptions` 21517 * 21518 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 21519 * elements for the `<select>` element using the array or object obtained by evaluating the 21520 * `ngOptions` comprehension_expression. 21521 * 21522 * When an item in the `<select>` menu is selected, the array element or object property 21523 * represented by the selected option will be bound to the model identified by the `ngModel` 21524 * directive. 21525 * 21526 * <div class="alert alert-warning"> 21527 * **Note:** `ngModel` compares by reference, not value. This is important when binding to an 21528 * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). 21529 * </div> 21530 * 21531 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 21532 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 21533 * option. See example below for demonstration. 21534 * 21535 * <div class="alert alert-warning"> 21536 * **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead 21537 * of {@link ng.directive:ngRepeat ngRepeat} when you want the 21538 * `select` model to be bound to a non-string value. This is because an option element can only 21539 * be bound to string values at present. 21540 * </div> 21541 * 21542 * @param {string} ngModel Assignable angular expression to data-bind to. 21543 * @param {string=} name Property name of the form under which the control is published. 21544 * @param {string=} required The control is considered valid only if value is entered. 21545 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21546 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21547 * `required` when you want to data-bind to the `required` attribute. 21548 * @param {comprehension_expression=} ngOptions in one of the following forms: 21549 * 21550 * * for array data sources: 21551 * * `label` **`for`** `value` **`in`** `array` 21552 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 21553 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 21554 * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 21555 * * for object data sources:
21556 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 21557 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 21558 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 21559 * * `select` **`as`** `label` **`group by`** `group` 21560 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 21561 * 21562 * Where: 21563 * 21564 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 21565 * * `value`: local variable which will refer to each item in the `array` or each property value 21566 * of `object` during iteration. 21567 * * `key`: local variable which will refer to a property name in `object` during iteration. 21568 * * `label`: The result of this expression will be the label for `<option>` element. The 21569 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 21570 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 21571 * element. If not specified, `select` expression will default to `value`. 21572 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 21573 * DOM element. 21574 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 21575 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 21576 * `value` variable (e.g. `value.propertyName`). 21577 * 21578 * @example 21579 <example module="selectExample"> 21580 <file name="index.html"> 21581 <script> 21582 angular.module('selectExample', []) 21583 .controller('ExampleController', ['$scope', function($scope) { 21584 $scope.colors = [ 21585 {name:'black', shade:'dark'}, 21586 {name:'white', shade:'light'}, 21587 {name:'red', shade:'dark'}, 21588 {name:'blue', shade:'dark'}, 21589 {name:'yellow', shade:'light'} 21590 ]; 21591 $scope.myColor = $scope.colors[2]; // red 21592 }]); 21593 </script>
21594 <div ng-controller="ExampleController"> 21595 <ul> 21596 <li ng-repeat="color in colors"> 21597 Name: <input ng-model="color.name"> 21598 [<a href ng-click="colors.splice($index, 1)">X</a>] 21599 </li> 21600 <li> 21601 [<a href ng-click="colors.push({})">add</a>] 21602 </li> 21603 </ul> 21604 <hr/> 21605 Color (null not allowed): 21606 <select ng-model="myColor" ng-options="color.name for color in colors"></select><br> 21607 21608 Color (null allowed): 21609 <span class="nullable"> 21610 <select ng-model="myColor" ng-options="color.name for color in colors"> 21611 <option value="">-- choose color --</option> 21612 </select> 21613 </span><br/> 21614 21615 Color grouped by shade: 21616 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 21617 </select><br/> 21618 21619 21620 Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br> 21621 <hr/> 21622 Currently selected: {{ {selected_color:myColor} }} 21623 <div style="border:solid 1px black; height:20px" 21624 ng-style="{'background-color':myColor.name}"> 21625 </div> 21626 </div> 21627 </file> 21628 <file name="protractor.js" type="protractor"> 21629 it('should check ng-options', function() { 21630 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 21631 element.all(by.model('myColor')).first().click(); 21632 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 21633 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 21634 element(by.css('.nullable select[ng-model="myColor"]')).click(); 21635 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 21636 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 21637 }); 21638 </file> 21639 </example> 21640 */ 21641 21642var ngOptionsDirective = valueFn({ terminal: true }); 21643// jshint maxlen: false 21644var selectDirective = ['$compile', '$parse', function($compile, $parse) { 21645 //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 21646 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]+?))?$/, 21647 nullModelCtrl = {$setViewValue: noop}; 21648// jshint maxlen: 100 21649 21650 return { 21651 restrict: 'E', 21652 require: ['select', '?ngModel'], 21653 controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 21654 var self = this, 21655 optionsMap = {}, 21656 ngModelCtrl = nullModelCtrl, 21657 nullOption, 21658 unknownOption; 21659 21660 21661 self.databound = $attrs.ngModel; 21662 21663 21664 self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { 21665 ngModelCtrl = ngModelCtrl_; 21666 nullOption = nullOption_; 21667 unknownOption = unknownOption_; 21668 }; 21669 21670 21671 self.addOption = function(value) { 21672 assertNotHasOwnProperty(value, '"option value"'); 21673 optionsMap[value] = true; 21674 21675 if (ngModelCtrl.$viewValue == value) { 21676 $element.val(value); 21677 if (unknownOption.parent()) unknownOption.remove(); 21678 } 21679 }; 21680 21681 21682 self.removeOption = function(value) { 21683 if (this.hasOption(value)) { 21684 delete optionsMap[value]; 21685 if (ngModelCtrl.$viewValue == value) { 21686 this.renderUnknownOption(value); 21687 } 21688 } 21689 }; 21690 21691 21692 self.renderUnknownOption = function(val) { 21693 var unknownVal = '? ' + hashKey(val) + ' ?'; 21694 unknownOption.val(unknownVal); 21695 $element.prepend(unknownOption); 21696 $element.val(unknownVal); 21697 unknownOption.prop('selected', true); // needed for IE 21698 }; 21699 21700 21701 self.hasOption = function(value) { 21702 return optionsMap.hasOwnProperty(value); 21703 }; 21704 21705 $scope.$on('$destroy', function() { 21706 // disable unknown option so that we don't do work when the whole select is being destroyed 21707 self.renderUnknownOption = noop; 21708 }); 21709 }], 21710 21711 link: function(scope, element, attr, ctrls) { 21712 // if ngModel is not defined, we don't need to do anything 21713 if (!ctrls[1]) return; 21714 21715 var selectCtrl = ctrls[0], 21716 ngModelCtrl = ctrls[1], 21717 multiple = attr.multiple, 21718 optionsExp = attr.ngOptions, 21719 nullOption = false, // if false, user will not be able to select it (used by ngOptions) 21720 emptyOption, 21721 // we can't just jqLite('<option>') since jqLite is not smart enough 21722 // to create it in <select> and IE barfs otherwise. 21723 optionTemplate = jqLite(document.createElement('option')), 21724 optGroupTemplate =jqLite(document.createElement('optgroup')), 21725 unknownOption = optionTemplate.clone(); 21726 21727 // find "null" option 21728 for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) { 21729 if (children[i].value === '') { 21730 emptyOption = nullOption = children.eq(i); 21731 break; 21732 } 21733 } 21734 21735 selectCtrl.init(ngModelCtrl, nullOption, unknownOption); 21736 21737 // required validator 21738 if (multiple) { 21739 ngModelCtrl.$isEmpty = function(value) { 21740 return !value || value.length === 0; 21741 }; 21742 } 21743 21744 if (optionsExp) setupAsOptions(scope, element, ngModelCtrl); 21745 else if (multiple) setupAsMultiple(scope, element, ngModelCtrl); 21746 else setupAsSingle(scope, element, ngModelCtrl, selectCtrl); 21747 21748
21749 //////////////////////////// 21750 21751 21752 21753 function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) { 21754 ngModelCtrl.$render = function() { 21755 var viewValue = ngModelCtrl.$viewValue; 21756 21757 if (selectCtrl.hasOption(viewValue)) { 21758 if (unknownOption.parent()) unknownOption.remove(); 21759 selectElement.val(viewValue); 21760 if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy 21761 } else { 21762 if (isUndefined(viewValue) && emptyOption) { 21763 selectElement.val(''); 21764 } else { 21765 selectCtrl.renderUnknownOption(viewValue); 21766 } 21767 } 21768 }; 21769 21770 selectElement.on('change', function() { 21771 scope.$apply(function() { 21772 if (unknownOption.parent()) unknownOption.remove(); 21773 ngModelCtrl.$setViewValue(selectElement.val()); 21774 }); 21775 }); 21776 } 21777 21778 function setupAsMultiple(scope, selectElement, ctrl) { 21779 var lastView; 21780 ctrl.$render = function() { 21781 var items = new HashMap(ctrl.$viewValue); 21782 forEach(selectElement.find('option'), function(option) { 21783 option.selected = isDefined(items.get(option.value)); 21784 }); 21785 }; 21786 21787 // we have to do it on each watch since ngModel watches reference, but 21788 // we need to work of an array, so we need to see if anything was inserted/removed 21789 scope.$watch(function selectMultipleWatch() { 21790 if (!equals(lastView, ctrl.$viewValue)) { 21791 lastView = shallowCopy(ctrl.$viewValue); 21792 ctrl.$render(); 21793 } 21794 }); 21795 21796 selectElement.on('change', function() { 21797 scope.$apply(function() { 21798 var array = []; 21799 forEach(selectElement.find('option'), function(option) { 21800 if (option.selected) { 21801 array.push(option.value); 21802 } 21803 }); 21804 ctrl.$setViewValue(array); 21805 }); 21806 }); 21807 } 21808 21809 function setupAsOptions(scope, selectElement, ctrl) { 21810 var match; 21811 21812 if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) { 21813 throw ngOptionsMinErr('iexp', 21814 "Expected expression in form of " + 21815 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 21816 " but got '{0}'. Element: {1}", 21817 optionsExp, startingTag(selectElement)); 21818 } 21819 21820 var displayFn = $parse(match[2] || match[1]), 21821 valueName = match[4] || match[6], 21822 keyName = match[5], 21823 groupByFn = $parse(match[3] || ''), 21824 valueFn = $parse(match[2] ? match[1] : valueName), 21825 valuesFn = $parse(match[7]), 21826 track = match[8], 21827 trackFn = track ? $parse(match[8]) : null, 21828 // This is an array of array of existing option groups in DOM. 21829 // We try to reuse these if possible 21830 // - optionGroupsCache[0] is the options with no option group 21831 // - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element 21832 optionGroupsCache = [[{element: selectElement, label:''}]]; 21833 21834 if (nullOption) { 21835 // compile the element since there might be bindings in it 21836 $compile(nullOption)(scope); 21837 21838 // remove the class, which is added automatically because we recompile the element and it 21839 // becomes the compilation root 21840 nullOption.removeClass('ng-scope'); 21841 21842 // we need to remove it before calling selectElement.empty() because otherwise IE will 21843 // remove the label from the element. wtf? 21844 nullOption.remove(); 21845 } 21846 21847 // clear contents, we'll add what's needed based on the model 21848 selectElement.empty(); 21849 21850 selectElement.on('change', function() { 21851 scope.$apply(function() { 21852 var optionGroup, 21853 collection = valuesFn(scope) || [], 21854 locals = {}, 21855 key, value, optionElement, index, groupIndex, length, groupLength, trackIndex; 21856 21857 if (multiple) { 21858 value = []; 21859 for (groupIndex = 0, groupLength = optionGroupsCache.length; 21860 groupIndex < groupLength; 21861 groupIndex++) { 21862 // list of options for that group. (first item has the parent) 21863 optionGroup = optionGroupsCache[groupIndex]; 21864 21865 for(index = 1, length = optionGroup.length; index < length; index++) { 21866 if ((optionElement = optionGroup[index].element)[0].selected) { 21867 key = optionElement.val(); 21868 if (keyName) locals[keyName] = key; 21869 if (trackFn) { 21870 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 21871 locals[valueName] = collection[trackIndex]; 21872 if (trackFn(scope, locals) == key) break; 21873 } 21874 } else { 21875 locals[valueName] = collection[key]; 21876 } 21877 value.push(valueFn(scope, locals)); 21878 } 21879 } 21880 } 21881 } else { 21882 key = selectElement.val(); 21883 if (key == '?') { 21884 value = undefined; 21885 } else if (key === ''){ 21886 value = null; 21887 } else { 21888 if (trackFn) { 21889 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 21890 locals[valueName] = collection[trackIndex]; 21891 if (trackFn(scope, locals) == key) { 21892 value = valueFn(scope, locals); 21893 break; 21894 } 21895 } 21896 } else { 21897 locals[valueName] = collection[key]; 21898 if (keyName) locals[keyName] = key; 21899 value = valueFn(scope, locals); 21900 } 21901 } 21902 } 21903 ctrl.$setViewValue(value); 21904 render(); 21905 }); 21906 }); 21907 21908 ctrl.$render = render; 21909 21910 scope.$watchCollection(valuesFn, render); 21911 scope.$watchCollection(function () { 21912 var locals = {}, 21913 values = valuesFn(scope); 21914 if (values) { 21915 var toDisplay = new Array(values.length); 21916 for (var i = 0, ii = values.length; i < ii; i++) { 21917 locals[valueName] = values[i]; 21918 toDisplay[i] = displayFn(scope, locals); 21919 } 21920 return toDisplay; 21921 } 21922 }, render); 21923 21924 if ( multiple ) { 21925 scope.$watchCollection(function() { return ctrl.$modelValue; }, render); 21926 } 21927 21928 function getSelectedSet() { 21929 var selectedSet = false;
21930 if (multiple) { 21931 var modelValue = ctrl.$modelValue; 21932 if (trackFn && isArray(modelValue)) { 21933 selectedSet = new HashMap([]); 21934 var locals = {}; 21935 for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) { 21936 locals[valueName] = modelValue[trackIndex]; 21937 selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]); 21938 } 21939 } else { 21940 selectedSet = new HashMap(modelValue); 21941 } 21942 } 21943 return selectedSet; 21944 } 21945 21946 21947 function render() { 21948 // Temporary location for the option groups before we render them 21949 var optionGroups = {'':[]}, 21950 optionGroupNames = [''], 21951 optionGroupName, 21952 optionGroup, 21953 option, 21954 existingParent, existingOptions, existingOption, 21955 modelValue = ctrl.$modelValue, 21956 values = valuesFn(scope) || [], 21957 keys = keyName ? sortedKeys(values) : values, 21958 key, 21959 groupLength, length, 21960 groupIndex, index, 21961 locals = {}, 21962 selected, 21963 selectedSet = getSelectedSet(), 21964 lastElement, 21965 element, 21966 label; 21967 21968 21969 // We now build up the list of options we need (we merge later) 21970 for (index = 0; length = keys.length, index < length; index++) { 21971 21972 key = index; 21973 if (keyName) { 21974 key = keys[index]; 21975 if ( key.charAt(0) === '$' ) continue; 21976 locals[keyName] = key; 21977 } 21978 21979 locals[valueName] = values[key]; 21980 21981 optionGroupName = groupByFn(scope, locals) || ''; 21982 if (!(optionGroup = optionGroups[optionGroupName])) { 21983 optionGroup = optionGroups[optionGroupName] = []; 21984 optionGroupNames.push(optionGroupName); 21985 } 21986 if (multiple) { 21987 selected = isDefined( 21988 selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals)) 21989 ); 21990 } else { 21991 if (trackFn) { 21992 var modelCast = {}; 21993 modelCast[valueName] = modelValue; 21994 selected = trackFn(scope, modelCast) === trackFn(scope, locals); 21995 } else { 21996 selected = modelValue === valueFn(scope, locals); 21997 } 21998 selectedSet = selectedSet || selected; // see if at least one item is selected 21999 } 22000 label = displayFn(scope, locals); // what will be seen by the user 22001 22002 // doing displayFn(scope, locals) || '' overwrites zero values 22003 label = isDefined(label) ? label : ''; 22004 optionGroup.push({ 22005 // either the index into array or key from object 22006 id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index), 22007 label: label, 22008 selected: selected // determine if we should be selected 22009 }); 22010 } 22011 if (!multiple) { 22012 if (nullOption || modelValue === null) { 22013 // insert null option if we have a placeholder, or the model is null 22014 optionGroups[''].unshift({id:'', label:'', selected:!selectedSet}); 22015 } else if (!selectedSet) { 22016 // option could not be found, we have to insert the undefined item 22017 optionGroups[''].unshift({id:'?', label:'', selected:true}); 22018 } 22019 } 22020 22021 // Now we need to update the list of DOM nodes to match the optionGroups we computed above 22022 for (groupIndex = 0, groupLength = optionGroupNames.length; 22023 groupIndex < groupLength; 22024 groupIndex++) { 22025 // current option group name or '' if no group 22026 optionGroupName = optionGroupNames[groupIndex]; 22027 22028 // list of options for that group. (first item has the parent) 22029 optionGroup = optionGroups[optionGroupName]; 22030 22031 if (optionGroupsCache.length <= groupIndex) { 22032 // we need to grow the optionGroups 22033 existingParent = { 22034 element: optGroupTemplate.clone().attr('label', optionGroupName), 22035 label: optionGroup.label 22036 }; 22037 existingOptions = [existingParent]; 22038 optionGroupsCache.push(existingOptions); 22039 selectElement.append(existingParent.element); 22040 } else { 22041 existingOptions = optionGroupsCache[groupIndex]; 22042 existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element 22043 22044 // update the OPTGROUP label if not the same. 22045 if (existingParent.label != optionGroupName) { 22046 existingParent.element.attr('label', existingParent.label = optionGroupName); 22047 } 22048 } 22049 22050 lastElement = null; // start at the beginning 22051 for(index = 0, length = optionGroup.length; index < length; index++) { 22052 option = optionGroup[index]; 22053 if ((existingOption = existingOptions[index+1])) { 22054 // reuse elements 22055 lastElement = existingOption.element; 22056 if (existingOption.label !== option.label) { 22057 lastElement.text(existingOption.label = option.label); 22058 lastElement.prop('label', existingOption.label); 22059 } 22060 if (existingOption.id !== option.id) { 22061 lastElement.val(existingOption.id = option.id); 22062 } 22063 // lastElement.prop('selected') provided by jQuery has side-effects 22064 if (lastElement[0].selected !== option.selected) {
22065 lastElement.prop('selected', (existingOption.selected = option.selected)); 22066 if (msie) { 22067 // See #7692 22068 // The selected item wouldn't visually update on IE without this. 22069 // Tested on Win7: IE9, IE10 and IE11. Future IEs should be tested as well 22070 lastElement.prop('selected', existingOption.selected); 22071 } 22072 } 22073 } else { 22074 // grow elements 22075 22076 // if it's a null option 22077 if (option.id === '' && nullOption) { 22078 // put back the pre-compiled element 22079 element = nullOption; 22080 } else { 22081 // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but 22082 // in this version of jQuery on some browser the .text() returns a string 22083 // rather then the element. 22084 (element = optionTemplate.clone()) 22085 .val(option.id) 22086 .prop('selected', option.selected) 22087 .attr('selected', option.selected) 22088 .prop('label', option.label) 22089 .text(option.label); 22090 } 22091 22092 existingOptions.push(existingOption = { 22093 element: element, 22094 label: option.label, 22095 id: option.id, 22096 selected: option.selected 22097 }); 22098 selectCtrl.addOption(option.label, element); 22099 if (lastElement) { 22100 lastElement.after(element); 22101 } else { 22102 existingParent.element.append(element); 22103 } 22104 lastElement = element; 22105 } 22106 } 22107 // remove any excessive OPTIONs in a group 22108 index++; // increment since the existingOptions[0] is parent element not OPTION 22109 while(existingOptions.length > index) { 22110 option = existingOptions.pop(); 22111 selectCtrl.removeOption(option.label); 22112 option.element.remove(); 22113 } 22114 } 22115 // remove any excessive OPTGROUPs from select 22116 while(optionGroupsCache.length > groupIndex) { 22117 optionGroupsCache.pop()[0].element.remove(); 22118 } 22119 } 22120 } 22121 } 22122 }; 22123}]; 22124 22125var optionDirective = ['$interpolate', function($interpolate) { 22126 var nullSelectCtrl = { 22127 addOption: noop, 22128 removeOption: noop 22129 }; 22130 22131 return { 22132 restrict: 'E', 22133 priority: 100, 22134 compile: function(element, attr) { 22135 if (isUndefined(attr.value)) { 22136 var interpolateFn = $interpolate(element.text(), true); 22137 if (!interpolateFn) { 22138 attr.$set('value', element.text()); 22139 } 22140 } 22141 22142 return function (scope, element, attr) { 22143 var selectCtrlName = '$selectController', 22144 parent = element.parent(), 22145 selectCtrl = parent.data(selectCtrlName) || 22146 parent.parent().data(selectCtrlName); // in case we are in optgroup 22147 22148 if (selectCtrl && selectCtrl.databound) { 22149 // For some reason Opera defaults to true and if not overridden this messes up the repeater. 22150 // We don't want the view to drive the initialization of the model anyway. 22151 element.prop('selected', false); 22152 } else { 22153 selectCtrl = nullSelectCtrl; 22154 } 22155 22156 if (interpolateFn) { 22157 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 22158 attr.$set('value', newVal); 22159 if (newVal !== oldVal) selectCtrl.removeOption(oldVal); 22160 selectCtrl.addOption(newVal); 22161 }); 22162 } else { 22163 selectCtrl.addOption(attr.value); 22164 } 22165 22166 element.on('$destroy', function() { 22167 selectCtrl.removeOption(attr.value); 22168 }); 22169 }; 22170 } 22171 }; 22172}]; 22173 22174var styleDirective = valueFn({ 22175 restrict: 'E', 22176 terminal: true 22177}); 22178 22179 if (window.angular.bootstrap) { 22180 //AngularJS is already loaded, so we can return here... 22181 console.log('WARNING: Tried to load angular more than once.'); 22182 return; 22183 } 22184 22185 //try to bind to jquery now so that one can write angular.element().read() 22186 //but we will rebind on bootstrap again. 22187 bindJQuery(); 22188 22189 publishExternalAPI(angular); 22190 22191 jqLite(document).ready(function() { 22192 angularInit(document, bootstrap); 22193 }); 22194 22195})(window, document); 22196 22197!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.