1/** 2 * @license AngularJS v1.2.19 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.19/' + 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 84 -angular, 85 -msie, 86 -jqLite, 87 -jQuery, 88 -slice, 89 -push, 90 -toString, 91 -ngMinErr, 92 -angularModule, 93 -nodeName_, 94 -uid, 95 -VALIDITY_STATE_PROPERTY, 96 97 -lowercase, 98 -uppercase, 99 -manualLowercase, 100 -manualUppercase, 101 -nodeName_, 102 -isArrayLike, 103 -forEach, 104 -sortedKeys, 105 -forEachSorted, 106 -reverseParams, 107 -nextUid, 108 -setHashKey, 109 -extend, 110 -int, 111 -inherit, 112 -noop, 113 -identity, 114 -valueFn, 115 -isUndefined, 116 -isDefined, 117 -isObject, 118 -isString, 119 -isNumber, 120 -isDate, 121 -isArray, 122 -isFunction, 123 -isRegExp, 124 -isWindow, 125 -isScope, 126 -isFile, 127 -isBlob, 128 -isBoolean, 129 -trim, 130 -isElement, 131 -makeMap, 132 -map, 133 -size, 134 -includes, 135 -indexOf, 136 -arrayRemove, 137 -isLeafNode, 138 -copy, 139 -shallowCopy, 140 -equals, 141 -csp, 142 -concat, 143 -sliceArgs, 144 -bind, 145 -toJsonReplacer, 146 -toJson, 147 -fromJson, 148 -toBoolean, 149 -startingTag, 150 -tryDecodeURIComponent, 151 -parseKeyValue, 152 -toKeyValue, 153 -encodeUriSegment, 154 -encodeUriQuery, 155 -angularInit, 156 -bootstrap, 157 -snake_case, 158 -bindJQuery, 159 -assertArg, 160 -assertArgFn,
161 -assertNotHasOwnProperty, 162 -getter, 163 -getBlockElements, 164 -hasOwnProperty, 165 166*/ 167 168//////////////////////////////////// 169 170/** 171 * @ngdoc module 172 * @name ng 173 * @module ng 174 * @description 175 * 176 * # ng (core module) 177 * The ng module is loaded by default when an AngularJS application is started. The module itself 178 * contains the essential components for an AngularJS application to function. The table below 179 * lists a high level breakdown of each of the services/factories, filters, directives and testing 180 * components available within this core module. 181 * 182 * <div doc-module-components="ng"></div> 183 */ 184 185// The name of a form control's ValidityState property. 186// This is used so that it's possible for internal tests to create mock ValidityStates. 187var VALIDITY_STATE_PROPERTY = 'validity'; 188 189/** 190 * @ngdoc function 191 * @name angular.lowercase 192 * @module ng 193 * @kind function 194 * 195 * @description Converts the specified string to lowercase. 196 * @param {string} string String to be converted to lowercase. 197 * @returns {string} Lowercased string. 198 */ 199var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; 200var hasOwnProperty = Object.prototype.hasOwnProperty; 201 202/** 203 * @ngdoc function 204 * @name angular.uppercase 205 * @module ng 206 * @kind function 207 * 208 * @description Converts the specified string to uppercase. 209 * @param {string} string String to be converted to uppercase. 210 * @returns {string} Uppercased string. 211 */ 212var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; 213 214 215var manualLowercase = function(s) { 216 /* jshint bitwise: false */ 217 return isString(s) 218 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 219 : s; 220}; 221var manualUppercase = function(s) { 222 /* jshint bitwise: false */ 223 return isString(s) 224 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 225 : s; 226}; 227 228 229// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 230// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 231// with correct but slower alternatives. 232if ('i' !== 'I'.toLowerCase()) { 233 lowercase = manualLowercase; 234 uppercase = manualUppercase; 235} 236 237 238var /** holds major version number for IE or NaN for real browsers */ 239 msie, 240 jqLite, // delay binding since jQuery could be loaded after us. 241 jQuery, // delay binding 242 slice = [].slice, 243 push = [].push, 244 toString = Object.prototype.toString, 245 ngMinErr = minErr('ng'), 246 247 /** @name angular */ 248 angular = window.angular || (window.angular = {}), 249 angularModule, 250 nodeName_, 251 uid = ['0', '0', '0']; 252 253/** 254 * IE 11 changed the format of the UserAgent string. 255 * See http://msdn.microsoft.com/en-us/library/ms537503.aspx 256 */ 257msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); 258if (isNaN(msie)) { 259 msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); 260} 261 262 263/** 264 * @private 265 * @param {*} obj 266 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 267 * String ...) 268 */ 269function isArrayLike(obj) { 270 if (obj == null || isWindow(obj)) { 271 return false; 272 } 273 274 var length = obj.length; 275 276 if (obj.nodeType === 1 && length) { 277 return true; 278 } 279 280 return isString(obj) || isArray(obj) || length === 0 || 281 typeof length === 'number' && length > 0 && (length - 1) in obj; 282} 283 284/** 285 * @ngdoc function 286 * @name angular.forEach 287 * @module ng 288 * @kind function 289 * 290 * @description 291 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 292 * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` 293 * is the value of an object property or an array element and `key` is the object property key or 294 * array element index. Specifying a `context` for the function is optional. 295 * 296 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 297 * using the `hasOwnProperty` method. 298 * 299 ```js 300 var values = {name: 'misko', gender: 'male'}; 301 var log = []; 302 angular.forEach(values, function(value, key) { 303 this.push(key + ': ' + value); 304 }, log); 305 expect(log).toEqual(['name: misko', 'gender: male']); 306 ``` 307 * 308 * @param {Object|Array} obj Object to iterate over. 309 * @param {Function} iterator Iterator function. 310 * @param {Object=} context Object to become context (`this`) for the iterator function. 311 * @returns {Object|Array} Reference to `obj`. 312 */ 313function forEach(obj, iterator, context) { 314 var key; 315 if (obj) { 316 if (isFunction(obj)) { 317 for (key in obj) { 318 // Need to check if hasOwnProperty exists, 319 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 320 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 321 iterator.call(context, obj[key], key); 322 } 323 } 324 }
324 else if (obj.forEach && obj.forEach !== forEach) { 325 obj.forEach(iterator, context); 326 } else if (isArrayLike(obj)) { 327 for (key = 0; key < obj.length; key++) 328 iterator.call(context, obj[key], key); 329 } else { 330 for (key in obj) { 331 if (obj.hasOwnProperty(key)) { 332 iterator.call(context, obj[key], key); 333 } 334 } 335 } 336 } 337 return obj; 338} 339 340function sortedKeys(obj) { 341 var keys = []; 342 for (var key in obj) { 343 if (obj.hasOwnProperty(key)) { 344 keys.push(key); 345 } 346 } 347 return keys.sort(); 348} 349 350function forEachSorted(obj, iterator, context) { 351 var keys = sortedKeys(obj); 352 for ( var i = 0; i < keys.length; i++) { 353 iterator.call(context, obj[keys[i]], keys[i]); 354 } 355 return keys; 356} 357 358 359/** 360 * when using forEach the params are value, key, but it is often useful to have key, value. 361 * @param {function(string, *)} iteratorFn 362 * @returns {function(*, string)} 363 */ 364function reverseParams(iteratorFn) { 365 return function(value, key) { iteratorFn(key, value); }; 366} 367 368/** 369 * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric 370 * characters such as '012ABC'. The reason why we are not using simply a number counter is that 371 * the number string gets longer over time, and it can also overflow, where as the nextId 372 * will grow much slower, it is a string, and it will never overflow. 373 * 374 * @returns {string} an unique alpha-numeric string 375 */ 376function nextUid() { 377 var index = uid.length; 378 var digit; 379 380 while(index) { 381 index--;
382 digit = uid[index].charCodeAt(0); 383 if (digit == 57 /*'9'*/) { 384 uid[index] = 'A'; 385 return uid.join(''); 386 } 387 if (digit == 90 /*'Z'*/) { 388 uid[index] = '0'; 389 } else { 390 uid[index] = String.fromCharCode(digit + 1); 391 return uid.join(''); 392 } 393 } 394 uid.unshift('0'); 395 return uid.join(''); 396} 397 398 399/** 400 * Set or clear the hashkey for an object. 401 * @param obj object 402 * @param h the hashkey (!truthy to delete the hashkey) 403 */ 404function setHashKey(obj, h) { 405 if (h) { 406 obj.$$hashKey = h; 407 } 408 else { 409 delete obj.$$hashKey; 410 } 411} 412 413/** 414 * @ngdoc function 415 * @name angular.extend 416 * @module ng 417 * @kind function 418 * 419 * @description 420 * Extends the destination object `dst` by copying all of the properties from the `src` object(s) 421 * to `dst`. You can specify multiple `src` objects. 422 * 423 * @param {Object} dst Destination object. 424 * @param {...Object} src Source object(s). 425 * @returns {Object} Reference to `dst`. 426 */ 427function extend(dst) { 428 var h = dst.$$hashKey; 429 forEach(arguments, function(obj) { 430 if (obj !== dst) { 431 forEach(obj, function(value, key) { 432 dst[key] = value; 433 }); 434 } 435 }); 436 437 setHashKey(dst,h); 438 return dst; 439} 440 441function int(str) { 442 return parseInt(str, 10); 443} 444 445 446function inherit(parent, extra) { 447 return extend(new (extend(function() {}, {prototype:parent}))(), extra); 448} 449 450/** 451 * @ngdoc function 452 * @name angular.noop 453 * @module ng 454 * @kind function 455 * 456 * @description 457 * A function that performs no operations. This function can be useful when writing code in the 458 * functional style. 459 ```js 460 function foo(callback) { 461 var result = calculateResult(); 462 (callback || angular.noop)(result); 463 } 464 ``` 465 */ 466function noop() {} 467noop.$inject = []; 468 469 470/** 471 * @ngdoc function 472 * @name angular.identity 473 * @module ng 474 * @kind function 475 * 476 * @description 477 * A function that returns its first argument. This function is useful when writing code in the 478 * functional style. 479 * 480 ```js 481 function transformer(transformationFn, value) { 482 return (transformationFn || angular.identity)(value); 483 }; 484 ``` 485 */ 486function identity($) {return $;} 487identity.$inject = []; 488 489 490function valueFn(value) {return function() {return value;};} 491 492/** 493 * @ngdoc function 494 * @name angular.isUndefined 495 * @module ng 496 * @kind function 497 * 498 * @description 499 * Determines if a reference is undefined. 500 * 501 * @param {*} value Reference to check. 502 * @returns {boolean} True if `value` is undefined. 503 */ 504function isUndefined(value){return typeof value === 'undefined';} 505 506 507/** 508 * @ngdoc function 509 * @name angular.isDefined 510 * @module ng 511 * @kind function 512 * 513 * @description 514 * Determines if a reference is defined. 515 * 516 * @param {*} value Reference to check. 517 * @returns {boolean} True if `value` is defined. 518 */ 519function isDefined(value){return typeof value !== 'undefined';} 520 521 522/** 523 * @ngdoc function 524 * @name angular.isObject 525 * @module ng 526 * @kind function 527 * 528 * @description 529 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 530 * considered to be objects. Note that JavaScript arrays are objects. 531 * 532 * @param {*} value Reference to check. 533 * @returns {boolean} True if `value` is an `Object` but not `null`. 534 */ 535function isObject(value){return value != null && typeof value === 'object';} 536 537 538/** 539 * @ngdoc function 540 * @name angular.isString 541 * @module ng 542 * @kind function 543 * 544 * @description 545 * Determines if a reference is a `String`. 546 * 547 * @param {*} value Reference to check. 548 * @returns {boolean} True if `value` is a `String`. 549 */ 550function isString(value){return typeof value === 'string';} 551 552 553/** 554 * @ngdoc function 555 * @name angular.isNumber 556 * @module ng 557 * @kind function 558 * 559 * @description 560 * Determines if a reference is a `Number`. 561 * 562 * @param {*} value Reference to check. 563 * @returns {boolean} True if `value` is a `Number`. 564 */ 565function isNumber(value){return typeof value === 'number';} 566 567 568/** 569 * @ngdoc function 570 * @name angular.isDate 571 * @module ng 572 * @kind function 573 * 574 * @description 575 * Determines if a value is a date. 576 * 577 * @param {*} value Reference to check. 578 * @returns {boolean} True if `value` is a `Date`. 579 */ 580function isDate(value) { 581 return toString.call(value) === '[object Date]'; 582} 583 584 585/** 586 * @ngdoc function 587 * @name angular.isArray 588 * @module ng 589 * @kind function 590 * 591 * @description 592 * Determines if a reference is an `Array`. 593 * 594 * @param {*} value Reference to check. 595 * @returns {boolean} True if `value` is an `Array`. 596 */ 597var isArray = (function() { 598 if (!isFunction(Array.isArray)) { 599 return function(value) { 600 return toString.call(value) === '[object Array]'; 601 }; 602 } 603 return Array.isArray; 604})(); 605 606/** 607 * @ngdoc function 608 * @name angular.isFunction 609 * @module ng 610 * @kind function 611 * 612 * @description 613 * Determines if a reference is a `Function`. 614 * 615 * @param {*} value Reference to check. 616 * @returns {boolean} True if `value` is a `Function`. 617 */ 618function isFunction(value){return typeof value === 'function';} 619 620 621/** 622 * Determines if a value is a regular expression object. 623 * 624 * @private 625 * @param {*} value Reference to check. 626 * @returns {boolean} True if `value` is a `RegExp`. 627 */ 628function isRegExp(value) { 629 return toString.call(value) === '[object RegExp]'; 630} 631 632 633/** 634 * Checks if `obj` is a window object. 635 * 636 * @private 637 * @param {*} obj Object to check 638 * @returns {boolean} True if `obj` is a window obj. 639 */ 640function isWindow(obj) { 641 return obj && obj.document && obj.location && obj.alert && obj.setInterval; 642} 643 644 645function isScope(obj) { 646 return obj && obj.$evalAsync && obj.$watch; 647} 648 649 650function isFile(obj) { 651 return toString.call(obj) === '[object File]'; 652} 653 654 655function isBlob(obj) { 656 return toString.call(obj) === '[object Blob]'; 657} 658 659 660function isBoolean(value) { 661 return typeof value === 'boolean'; 662} 663 664 665var trim = (function() { 666 // native trim is way faster: http://jsperf.com/angular-trim-test 667 // but IE doesn't have it... :-( 668 // TODO: we should move this into IE/ES5 polyfill 669 if (!String.prototype.trim) { 670 return function(value) { 671 return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value; 672 }; 673 } 674 return function(value) { 675 return isString(value) ? value.trim() : value; 676 }; 677})(); 678 679 680/** 681 * @ngdoc function 682 * @name angular.isElement 683 * @module ng 684 * @kind function 685 * 686 * @description 687 * Determines if a reference is a DOM element (or wrapped jQuery element). 688 * 689 * @param {*} value Reference to check. 690 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 691 */ 692function isElement(node) { 693 return !!(node && 694 (node.nodeName // we are a direct element 695 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 696} 697 698/** 699 * @param str 'key1,key2,...' 700 * @returns {object} in the form of {key1:true, key2:true, ...} 701 */ 702function makeMap(str) { 703 var obj = {}, items = str.split(","), i; 704 for ( i = 0; i < items.length; i++ ) 705 obj[ items[i] ] = true; 706 return obj;
707} 708 709 710if (msie < 9) { 711 nodeName_ = function(element) { 712 element = element.nodeName ? element : element[0]; 713 return (element.scopeName && element.scopeName != 'HTML') 714 ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; 715 }; 716} else { 717 nodeName_ = function(element) { 718 return element.nodeName ? element.nodeName : element[0].nodeName; 719 }; 720} 721 722 723function map(obj, iterator, context) { 724 var results = []; 725 forEach(obj, function(value, index, list) { 726 results.push(iterator.call(context, value, index, list)); 727 }); 728 return results; 729} 730 731 732/** 733 * @description 734 * Determines the number of elements in an array, the number of properties an object has, or 735 * the length of a string. 736 * 737 * Note: This function is used to augment the Object type in Angular expressions. See 738 * {@link angular.Object} for more information about Angular arrays. 739 * 740 * @param {Object|Array|string} obj Object, array, or string to inspect. 741 * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object 742 * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. 743 */ 744function size(obj, ownPropsOnly) { 745 var count = 0, key; 746 747 if (isArray(obj) || isString(obj)) { 748 return obj.length; 749 } else if (isObject(obj)) { 750 for (key in obj) 751 if (!ownPropsOnly || obj.hasOwnProperty(key)) 752 count++; 753 } 754 755 return count; 756} 757 758 759function includes(array, obj) { 760 return indexOf(array, obj) != -1; 761} 762 763function indexOf(array, obj) { 764 if (array.indexOf) return array.indexOf(obj); 765 766 for (var i = 0; i < array.length; i++) { 767 if (obj === array[i]) return i; 768 } 769 return -1; 770} 771 772function arrayRemove(array, value) { 773 var index = indexOf(array, value); 774 if (index >=0) 775 array.splice(index, 1); 776 return value; 777} 778 779function isLeafNode (node) { 780 if (node) { 781 switch (node.nodeName) { 782 case "OPTION": 783 case "PRE": 784 case "TITLE": 785 return true; 786 } 787 } 788 return false; 789} 790 791/** 792 * @ngdoc function 793 * @name angular.copy 794 * @module ng 795 * @kind function 796 * 797 * @description 798 * Creates a deep copy of `source`, which should be an object or an array. 799 * 800 * * If no destination is supplied, a copy of the object or array is created. 801 * * If a destination is provided, all of its elements (for array) or properties (for objects) 802 * are deleted and then all elements/properties from the source are copied to it. 803 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 804 * * If `source` is identical to 'destination' an exception will be thrown. 805 * 806 * @param {*} source The source that will be used to make a copy. 807 * Can be any type, including primitives, `null`, and `undefined`. 808 * @param {(Object|Array)=} destination Destination into which the source is copied. If 809 * provided, must be of the same type as `source`. 810 * @returns {*} The copy or updated `destination`, if `destination` was specified. 811 * 812 * @example 813 <example> 814 <file name="index.html"> 815 <div ng-controller="Controller"> 816 <form novalidate class="simple-form"> 817 Name: <input type="text" ng-model="user.name" /><br /> 818 E-mail: <input type="email" ng-model="user.email" /><br /> 819 Gender: <input type="radio" ng-model="user.gender" value="male" />male 820 <input type="radio" ng-model="user.gender" value="female" />female<br /> 821 <button ng-click="reset()">RESET</button> 822 <button ng-click="update(user)">SAVE</button> 823 </form> 824 <pre>form = {{user | json}}</pre> 825 <pre>master = {{master | json}}</pre> 826 </div> 827 828 <script> 829 function Controller($scope) { 830 $scope.master= {}; 831 832 $scope.update = function(user) { 833 // Example with 1 argument 834 $scope.master= angular.copy(user); 835 }; 836 837 $scope.reset = function() { 838 // Example with 2 arguments 839 angular.copy($scope.master, $scope.user); 840 }; 841 842 $scope.reset(); 843 } 844 </script> 845 </file> 846 </example> 847 */ 848function copy(source, destination, stackSource, stackDest) { 849 if (isWindow(source) || isScope(source)) { 850 throw ngMinErr('cpws',
851 "Can't copy! Making copies of Window or Scope instances is not supported."); 852 } 853 854 if (!destination) { 855 destination = source; 856 if (source) { 857 if (isArray(source)) { 858 destination = copy(source, [], stackSource, stackDest); 859 } else if (isDate(source)) { 860 destination = new Date(source.getTime()); 861 } else if (isRegExp(source)) { 862 destination = new RegExp(source.source); 863 } else if (isObject(source)) { 864 destination = copy(source, {}, stackSource, stackDest); 865 } 866 } 867 } else { 868 if (source === destination) throw ngMinErr('cpi', 869 "Can't copy! Source and destination are identical."); 870 871 stackSource = stackSource || []; 872 stackDest = stackDest || []; 873 874 if (isObject(source)) { 875 var index = indexOf(stackSource, source); 876 if (index !== -1) return stackDest[index]; 877 878 stackSource.push(source); 879 stackDest.push(destination); 880 } 881 882 var result; 883 if (isArray(source)) { 884 destination.length = 0; 885 for ( var i = 0; i < source.length; i++) { 886 result = copy(source[i], null, stackSource, stackDest); 887 if (isObject(source[i])) { 888 stackSource.push(source[i]); 889 stackDest.push(result); 890 } 891 destination.push(result); 892 } 893 } else { 894 var h = destination.$$hashKey; 895 forEach(destination, function(value, key) { 896 delete destination[key]; 897 }); 898 for ( var key in source) { 899 result = copy(source[key], null, stackSource, stackDest); 900 if (isObject(source[key])) { 901 stackSource.push(source[key]); 902 stackDest.push(result); 903 } 904 destination[key] = result; 905 } 906 setHashKey(destination,h); 907 } 908 909 } 910 return destination; 911} 912 913/** 914 * Creates a shallow copy of an object, an array or a primitive 915 */ 916function shallowCopy(src, dst) { 917 if (isArray(src)) { 918 dst = dst || []; 919 920 for ( var i = 0; i < src.length; i++) { 921 dst[i] = src[i];
922 } 923 } else if (isObject(src)) { 924 dst = dst || {}; 925 926 for (var key in src) { 927 if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { 928 dst[key] = src[key]; 929 } 930 } 931 } 932 933 return dst || src; 934} 935 936 937/** 938 * @ngdoc function 939 * @name angular.equals 940 * @module ng 941 * @kind function 942 * 943 * @description 944 * Determines if two objects or two values are equivalent. Supports value types, regular 945 * expressions, arrays and objects. 946 * 947 * Two objects or values are considered equivalent if at least one of the following is true: 948 * 949 * * Both objects or values pass `===` comparison. 950 * * Both objects or values are of the same type and all of their properties are equal by 951 * comparing them with `angular.equals`. 952 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 953 * * Both values represent the same regular expression (In JavaScript, 954 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 955 * representation matches). 956 * 957 * During a property comparison, properties of `function` type and properties with names 958 * that begin with `$` are ignored. 959 * 960 * Scope and DOMWindow objects are being compared only by identify (`===`). 961 * 962 * @param {*} o1 Object or value to compare. 963 * @param {*} o2 Object or value to compare. 964 * @returns {boolean} True if arguments are equal. 965 */ 966function equals(o1, o2) { 967 if (o1 === o2) return true; 968 if (o1 === null || o2 === null) return false; 969 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 970 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 971 if (t1 == t2) { 972 if (t1 == 'object') { 973 if (isArray(o1)) { 974 if (!isArray(o2)) return false; 975 if ((length = o1.length) == o2.length) { 976 for(key=0; key<length; key++) { 977 if (!equals(o1[key], o2[key])) return false; 978 } 979 return true; 980 } 981 } else if (isDate(o1)) { 982 return isDate(o2) && o1.getTime() == o2.getTime(); 983 } else if (isRegExp(o1) && isRegExp(o2)) { 984 return o1.toString() == o2.toString(); 985 } else { 986 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false; 987 keySet = {}; 988 for(key in o1) { 989 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 990 if (!equals(o1[key], o2[key])) return false; 991 keySet[key] = true; 992 } 993 for(key in o2) { 994 if (!keySet.hasOwnProperty(key) && 995 key.charAt(0) !== '$' && 996 o2[key] !== undefined && 997 !isFunction(o2[key])) return false; 998 } 999 return true; 1000 } 1001 } 1002 } 1003 return false; 1004} 1005 1006 1007function csp() { 1008 return (document.securityPolicy && document.securityPolicy.isActive) || 1009 (document.querySelector && 1010 !!(document.querySelector('[ng-csp]') || document.querySelector('[data-ng-csp]'))); 1011} 1012 1013 1014function concat(array1, array2, index) { 1015 return array1.concat(slice.call(array2, index)); 1016} 1017 1018function sliceArgs(args, startIndex) { 1019 return slice.call(args, startIndex || 0); 1020} 1021 1022 1023/* jshint -W101 */ 1024/** 1025 * @ngdoc function 1026 * @name angular.bind 1027 * @module ng 1028 * @kind function 1029 * 1030 * @description 1031 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1032 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1033 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1034 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1035 * 1036 * @param {Object} self Context which `fn` should be evaluated in. 1037 * @param {function()} fn Function to be bound. 1038 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1039 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1040 */ 1041/* jshint +W101 */ 1042function bind(self, fn) { 1043 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1044 if (isFunction(fn) && !(fn instanceof RegExp)) { 1045 return curryArgs.length 1046 ? function() { 1047 return arguments.length 1048 ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) 1049 : fn.apply(self, curryArgs); 1050 } 1051 : function() { 1052 return arguments.length 1053 ? fn.apply(self, arguments) 1054 : fn.call(self); 1055 }; 1056 } else { 1057 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1058 return fn; 1059 } 1060} 1061 1062 1063function toJsonReplacer(key, value) { 1064 var val = value; 1065 1066 if (typeof key === 'string' && key.charAt(0) === '$') { 1067 val = undefined; 1068 } else if (isWindow(value)) { 1069 val = '$WINDOW'; 1070 } else if (value && document === value) { 1071 val = '$DOCUMENT'; 1072 } else if (isScope(value)) { 1073 val = '$SCOPE'; 1074 } 1075 1076 return val; 1077} 1078 1079 1080/** 1081 * @ngdoc function 1082 * @name angular.toJson 1083 * @module ng 1084 * @kind function 1085 * 1086 * @description 1087 * Serializes input into a JSON-formatted string. Properties with leading $ characters will be 1088 * stripped since angular uses this notation internally. 1089 * 1090 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1091 * @param {boolean=}
1091 pretty If set to true, the JSON output will contain newlines and whitespace. 1092 * @returns {string|undefined} JSON-ified string representing `obj`. 1093 */ 1094function toJson(obj, pretty) { 1095 if (typeof obj === 'undefined') return undefined; 1096 return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); 1097} 1098 1099 1100/** 1101 * @ngdoc function 1102 * @name angular.fromJson 1103 * @module ng 1104 * @kind function 1105 * 1106 * @description 1107 * Deserializes a JSON string. 1108 * 1109 * @param {string} json JSON string to deserialize. 1110 * @returns {Object|Array|string|number} Deserialized thingy. 1111 */ 1112function fromJson(json) { 1113 return isString(json) 1114 ? JSON.parse(json) 1115 : json; 1116} 1117 1118 1119function toBoolean(value) { 1120 if (typeof value === 'function') { 1121 value = true; 1122 } else if (value && value.length !== 0) { 1123 var v = lowercase("" + value); 1124 value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); 1125 } else { 1126 value = false; 1127 } 1128 return value; 1129} 1130 1131/** 1132 * @returns {string} Returns the string representation of the element. 1133 */ 1134function startingTag(element) { 1135 element = jqLite(element).clone(); 1136 try { 1137 // turns out IE does not let you set .html() on elements which 1138 // are not allowed to have children. So we just ignore it. 1139 element.empty(); 1140 } catch(e) {} 1141 // As Per DOM Standards 1142 var TEXT_NODE = 3; 1143 var elemHtml = jqLite('<div>').append(element).html(); 1144 try { 1145 return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : 1146 elemHtml. 1147 match(/^(<[^>]+>)/)[1]. 1148 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1149 } catch(e) { 1150 return lowercase(elemHtml); 1151 } 1152 1153} 1154 1155 1156///////////////////////////////////////////////// 1157 1158/** 1159 * Tries to decode the URI component without throwing an exception. 1160 * 1161 * @private 1162 * @param str value potential URI component to check. 1163 * @returns {boolean} True if `value` can be decoded 1164 * with the decodeURIComponent function. 1165 */ 1166function tryDecodeURIComponent(value) { 1167 try { 1168 return decodeURIComponent(value); 1169 } catch(e) { 1170 // Ignore any invalid uri component 1171 } 1172} 1173 1174 1175/** 1176 * Parses an escaped url query string into key-value pairs. 1177 * @returns {Object.<string,boolean|Array>} 1178 */ 1179function parseKeyValue(/**string*/keyValue) { 1180 var obj = {}, key_value, key; 1181 forEach((keyValue || "").split('&'), function(keyValue) { 1182 if ( keyValue ) { 1183 key_value = keyValue.split('='); 1184 key = tryDecodeURIComponent(key_value[0]); 1185 if ( isDefined(key) ) { 1186 var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; 1187 if (!obj[key]) { 1188 obj[key] = val; 1189 } else if(isArray(obj[key])) { 1190 obj[key].push(val); 1191 } else { 1192 obj[key] = [obj[key],val]; 1193 } 1194 } 1195 } 1196 }); 1197 return obj; 1198} 1199 1200function toKeyValue(obj) { 1201 var parts = []; 1202 forEach(obj, function(value, key) { 1203 if (isArray(value)) { 1204 forEach(value, function(arrayValue) { 1205 parts.push(encodeUriQuery(key, true) + 1206 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1207 }); 1208 } else { 1209 parts.push(encodeUriQuery(key, true) + 1210 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1211 } 1212 }); 1213 return parts.length ? parts.join('&') : ''; 1214} 1215 1216 1217/** 1218 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1219 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1220 * segments: 1221 * segment = *pchar 1222 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1223 * pct-encoded = "%" HEXDIG HEXDIG 1224 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1225 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1226 * / "*" / "+" / "," / ";" / "=" 1227 */ 1228function encodeUriSegment(val) { 1229 return encodeUriQuery(val, true). 1230 replace(/%26/gi, '&'). 1231 replace(/%3D/gi, '='). 1232 replace(/%2B/gi, '+'); 1233} 1234 1235 1236/** 1237 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1238 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1239 * encoded per http://tools.ietf.org/html/rfc3986: 1240 * query = *( pchar / "/" / "?" ) 1241 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1242 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1243 * pct-encoded = "%" HEXDIG HEXDIG 1244 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1245 * / "*" / "+" / "," / ";" / "=" 1246 */ 1247function encodeUriQuery(val, pctEncodeSpaces) { 1248 return encodeURIComponent(val). 1249 replace(/%40/gi, '@'). 1250 replace(/%3A/gi, ':'). 1251 replace(/%24/g, '$'). 1252 replace(/%2C/gi, ','). 1253 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1254} 1255 1256 1257/** 1258 * @ngdoc directive 1259 * @name ngApp 1260 * @module ng 1261 * 1262 * @element ANY 1263 * @param {angular.Module} ngApp an optional application 1264 * {@link angular.module module} name to load. 1265 * 1266 * @description 1267 * 1268 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1269 * designates the **root element** of the application and is typically placed near the root element 1270 * of the page - e.g. on the `<body>` or `<html>` tags. 1271 * 1272 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngAp
1272p` 1273 * found in the document will be used to define the root element to auto-bootstrap as an 1274 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1275 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1276 * 1277 * You can specify an **AngularJS module** to be used as the root module for the application. This 1278 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and 1279 * should contain the application code needed or have dependencies on other modules that will 1280 * contain the code. See {@link angular.module} for more information. 1281 * 1282 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1283 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1284 * would not be resolved to `3`. 1285 * 1286 * `ngApp` is the easiest, and most common, way to bootstrap an application. 1287 * 1288 <example module="ngAppDemo"> 1289 <file name="index.html"> 1290 <div ng-controller="ngAppDemoController"> 1291 I can add: {{a}} + {{b}} = {{ a+b }} 1292 </div> 1293 </file> 1294 <file name="script.js"> 1295 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1296 $scope.a = 1; 1297 $scope.b = 2; 1298 }); 1299 </file> 1300 </example> 1301 * 1302 */ 1303function angularInit(element, bootstrap) { 1304 var elements = [element], 1305 appElement, 1306 module, 1307 names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], 1308 NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; 1309 1310 function append(element) { 1311 element && elements.push(element); 1312 } 1313 1314 forEach(names, function(name) { 1315 names[name] = true; 1316 append(document.getElementById(name)); 1317 name = name.replace(':', '\\:'); 1318 if (element.querySelectorAll) { 1319 forEach(element.querySelectorAll('.' + name), append); 1320 forEach(element.querySelectorAll('.' + name + '\\:'), append); 1321 forEach(element.querySelectorAll('[' + name + ']'), append); 1322 } 1323 }); 1324 1325 forEach(elements, function(element) { 1326 if (!appElement) { 1327 var className = ' ' + element.className + ' '; 1328 var match = NG_APP_CLASS_REGEXP.exec(className); 1329 if (match) { 1330 appElement = element; 1331 module = (match[2] || '').replace(/\s+/g, ','); 1332 } else { 1333 forEach(element.attributes, function(attr) { 1334 if (!appElement && names[attr.name]) { 1335 appElement = element; 1336 module = attr.value; 1337 } 1338 }); 1339 } 1340 } 1341 }); 1342 if (appElement) { 1343 bootstrap(appElement, module ? [module] : []); 1344 } 1345} 1346 1347/** 1348 * @ngdoc function 1349 * @name angular.bootstrap 1350 * @module ng 1351 * @description 1352 * Use this function to manually start up angular application. 1353 * 1354 * See: {@link guide/bootstrap Bootstrap} 1355 * 1356 * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. 1357 * They must use {@link ng.directive:ngApp ngApp}. 1358 * 1359 * Angular will detect if it has been loaded into the browser more than once and only allow the 1360 * first loaded script to be bootstrapped and will report a warning to the browser console for 1361 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1362 * multiple instances of Angular try to work on the DOM. 1363 * 1364 * <example name="multi-bootstrap" module="multi-bootstrap"> 1365 * <file name="index.html"> 1366 * <script src="../../../angular.js"></script> 1367 * <div ng-controller="BrokenTable"> 1368 * <table> 1369 * <tr> 1370 * <th ng-repeat="heading in headings">{{heading}}</th> 1371 * </tr> 1372 * <tr ng-repeat="filling in fillings"> 1373 * <td ng-repeat="fill in filling">{{fill}}</td> 1374 * </tr> 1375 * </table> 1376 * </div> 1377 * </file> 1378 * <file name="controller.js"> 1379 * var app = angular.module('multi-bootstrap', []) 1380 * 1381 * .controller('BrokenTable', function($scope) { 1382 * $scope.headings = ['One', 'Two', 'Three']; 1383 * $scope.fillings = [[1, 2, 3], ['A', 'B', 'C'], [7, 8, 9]]; 1384 * }); 1385 * </file> 1386 * <file name="protractor.js" type="protractor"> 1387 * it('should only insert one table cell for each item in $scope.fillings', function() { 1388 * expect(element.all(by.css('td')).count()) 1389 * .toBe(9); 1390 * }); 1391 * </file> 1392 * </example> 1393 * 1394 * @param {DOMElement} element DOM element which is the root of angular application. 1395 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1396 * Each item in the array should be the name of a predefined module or a (DI annotated) 1397 * function that will be invoked by the injector as a run block. 1398 * See: {@link angular.module modules} 1399 * @returns {auto.$injector} Returns the newly created injector for this app. 1400 */ 1401function bootstrap(element, modules) { 1402 var doBootstrap = function() { 1403 element = jqLite(element); 1404 1405 if (element.injector()) { 1406 var tag = (element[0] === document) ? 'document' : startingTag(element); 1407 throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); 1408 } 1409 1410 modules = modules || []; 1411 modules.unshift(['$provide', function($provide) { 1412 $provide.value('$rootElement', element); 1413 }]); 1414 modules.unshift('ng'); 1415 var injector = createInjector(modules); 1416 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', 1417 function(scope, element, compile, injector, animate) { 1418 scope.$apply(function() { 1419 element.data('$injector', injector); 1420 compile(element)(scope); 1421 }); 1422 }] 1423 ); 1424 return injector; 1425 }; 1426 1427 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1428 1429 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1430 return doBootstrap(); 1431 } 1432 1433 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1434 angular.resumeBootstrap = function(extraModules) {
1435 forEach(extraModules, function(module) { 1436 modules.push(module); 1437 }); 1438 doBootstrap(); 1439 }; 1440} 1441 1442var SNAKE_CASE_REGEXP = /[A-Z]/g; 1443function snake_case(name, separator) { 1444 separator = separator || '_'; 1445 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1446 return (pos ? separator : '') + letter.toLowerCase(); 1447 }); 1448} 1449 1450function bindJQuery() { 1451 // bind to jQuery if present; 1452 jQuery = window.jQuery; 1453 // Use jQuery if it exists with proper functionality, otherwise default to us. 1454 // Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support. 1455 if (jQuery && jQuery.fn.on) { 1456 jqLite = jQuery; 1457 extend(jQuery.fn, { 1458 scope: JQLitePrototype.scope, 1459 isolateScope: JQLitePrototype.isolateScope, 1460 controller: JQLitePrototype.controller, 1461 injector: JQLitePrototype.injector, 1462 inheritedData: JQLitePrototype.inheritedData 1463 }); 1464 // Method signature: 1465 // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) 1466 jqLitePatchJQueryRemove('remove', true, true, false); 1467 jqLitePatchJQueryRemove('empty', false, false, false); 1468 jqLitePatchJQueryRemove('html', false, false, true); 1469 } else { 1470 jqLite = JQLite; 1471 } 1472 angular.element = jqLite; 1473} 1474 1475/** 1476 * throw error if the argument is falsy. 1477 */ 1478function assertArg(arg, name, reason) { 1479 if (!arg) { 1480 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1481 } 1482 return arg; 1483} 1484 1485function assertArgFn(arg, name, acceptArrayAnnotation) { 1486 if (acceptArrayAnnotation && isArray(arg)) { 1487 arg = arg[arg.length - 1]; 1488 } 1489 1490 assertArg(isFunction(arg), name, 'not a function, got ' + 1491 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1492 return arg; 1493} 1494 1495/** 1496 * throw error if the name given is hasOwnProperty 1497 * @param {String} name the name to test 1498 * @param {String} context the context in which the name is used, such as module or directive 1499 */ 1500function assertNotHasOwnProperty(name, context) { 1501 if (name === 'hasOwnProperty') { 1502 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1503 } 1504} 1505 1506/** 1507 * Return the value accessible from the object by path. Any undefined traversals are ignored 1508 * @param {Object} obj starting object 1509 * @param {String} path path to traverse 1510 * @param {boolean} [bindFnToScope=true] 1511 * @returns {Object} value as accessible by path 1512 */ 1513//TODO(misko): this function needs to be removed 1514function getter(obj, path, bindFnToScope) { 1515 if (!path) return obj; 1516 var keys = path.split('.'); 1517 var key; 1518 var lastInstance = obj; 1519 var len = keys.length; 1520 1521 for (var i = 0; i < len; i++) { 1522 key = keys[i]; 1523 if (obj) { 1524 obj = (lastInstance = obj)[key]; 1525 } 1526 } 1527 if (!bindFnToScope && isFunction(obj)) { 1528 return bind(lastInstance, obj); 1529 } 1530 return obj; 1531} 1532 1533/** 1534 * Return the DOM siblings between the first and last node in the given array. 1535 * @param {Array} array like object 1536 * @returns {DOMElement}
1536 object containing the elements 1537 */ 1538function getBlockElements(nodes) { 1539 var startNode = nodes[0], 1540 endNode = nodes[nodes.length - 1]; 1541 if (startNode === endNode) { 1542 return jqLite(startNode); 1543 } 1544 1545 var element = startNode; 1546 var elements = [element]; 1547 1548 do { 1549 element = element.nextSibling; 1550 if (!element) break; 1551 elements.push(element); 1552 } while (element !== endNode); 1553 1554 return jqLite(elements); 1555} 1556 1557/** 1558 * @ngdoc type 1559 * @name angular.Module 1560 * @module ng 1561 * @description 1562 * 1563 * Interface for configuring angular {@link angular.module modules}. 1564 */ 1565 1566function setupModuleLoader(window) { 1567 1568 var $injectorMinErr = minErr('$injector'); 1569 var ngMinErr = minErr('ng'); 1570 1571 function ensure(obj, name, factory) { 1572 return obj[name] || (obj[name] = factory()); 1573 } 1574 1575 var angular = ensure(window, 'angular', Object); 1576 1577 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1578 angular.$$minErr = angular.$$minErr || minErr; 1579 1580 return ensure(angular, 'module', function() { 1581 /** @type {Object.<string, angular.Module>} */ 1582 var modules = {}; 1583 1584 /** 1585 * @ngdoc function 1586 * @name angular.module 1587 * @module ng 1588 * @description 1589 * 1590 * The `angular.module` is a global place for creating, registering and retrieving Angular 1591 * modules. 1592 * All modules (angular core or 3rd party) that should be available to an application must be 1593 * registered using this mechanism. 1594 * 1595 * When passed two or more arguments, a new module is created. If passed only one argument, an 1596 * existing module (the name passed as the first argument to `module`) is retrieved. 1597 * 1598 * 1599 * # Module 1600 * 1601 * A module is a collection of services, directives, controllers, filters, and configuration information. 1602 * `angular.module` is used to configure the {@link auto.$injector $injector}. 1603 * 1604 * ```js 1605 * // Create a new module 1606 * var myModule = angular.module('myModule', []); 1607 * 1608 * // register a new service 1609 * myModule.value('appName', 'MyCoolApp'); 1610 * 1611 * // configure existing services inside initialization blocks. 1612 * myModule.config(['$locationProvider', function($locationProvider) { 1613 * // Configure existing providers 1614 * $locationProvider.hashPrefix('!'); 1615 * }]); 1616 * ``` 1617 * 1618 * Then you can create an injector and load your modules like this: 1619 * 1620 * ```js 1621 * var injector = angular.injector(['ng', 'myModule']) 1622 * ``` 1623 * 1624 * However it's more likely that you'll just use 1625 * {@link ng.directive:ngApp ngApp} or 1626 * {@link angular.bootstrap} to simplify this process for you. 1627 * 1628 * @param {!string} name The name of the module to create or retrieve. 1629 * @param {!Array.<string>=} requires If specified then new module is being created. If 1630 * unspecified then the module is being retrieved for further configuration. 1631 * @param {Function=} configFn Optional configuration function for the module. Same as 1632 * {@link angular.Module#config Module#config()}. 1633 * @returns {module} new module with the {@link angular.Module} api. 1634 */ 1635 return function module(name, requires, configFn) { 1636 var assertNotHasOwnProperty = function(name, context) { 1637 if (name === 'hasOwnProperty') { 1638 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 1639 } 1640 }; 1641 1642 assertNotHasOwnProperty(name, 'module'); 1643 if (requires && modules.hasOwnProperty(name)) { 1644 modules[name] = null; 1645 } 1646 return ensure(modules, name, function() { 1647 if (!requires) { 1648 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 1649 "the module name or forgot to load it. If registering a module ensure that you " + 1650 "specify the dependencies as the second argument.", name); 1651 } 1652 1653 /** @type {!Array.<Array.<*>>} */ 1654 var invokeQueue = []; 1655
1656 /** @type {!Array.<Function>} */ 1657 var runBlocks = []; 1658 1659 var config = invokeLater('$injector', 'invoke'); 1660 1661 /** @type {angular.Module} */ 1662 var moduleInstance = { 1663 // Private state 1664 _invokeQueue: invokeQueue, 1665 _runBlocks: runBlocks, 1666 1667 /** 1668 * @ngdoc property 1669 * @name angular.Module#requires 1670 * @module ng 1671 * @returns {Array.<string>} List of module names which must be loaded before this module. 1672 * @description 1673 * Holds the list of modules which the injector will load before the current module is 1674 * loaded. 1675 */ 1676 requires: requires, 1677 1678 /** 1679 * @ngdoc property 1680 * @name angular.Module#name 1681 * @module ng 1682 * @returns {string} Name of the module. 1683 * @description 1684 */ 1685 name: name, 1686 1687 1688 /** 1689 * @ngdoc method 1690 * @name angular.Module#provider 1691 * @module ng 1692 * @param {string} name service name 1693 * @param {Function} providerType Construction function for creating new instance of the 1694 * service. 1695 * @description 1696 * See {@link auto.$provide#provider $provide.provider()}. 1697 */ 1698 provider: invokeLater('$provide', 'provider'), 1699 1700 /** 1701 * @ngdoc method 1702 * @name angular.Module#factory 1703 * @module ng 1704 * @param {string} name service name 1705 * @param {Function} providerFunction Function for creating new instance of the service. 1706 * @description 1707 * See {@link auto.$provide#factory $provide.factory()}. 1708 */ 1709 factory: invokeLater('$provide', 'factory'), 1710 1711 /** 1712 * @ngdoc method 1713 * @name angular.Module#service 1714 * @module ng 1715 * @param {string} name service name 1716 * @param {Function} constructor A constructor function that will be instantiated. 1717 * @description 1718 * See {@link auto.$provide#service $provide.service()}. 1719 */ 1720 service: invokeLater('$provide', 'service'), 1721 1722 /** 1723 * @ngdoc method 1724 * @name angular.Module#value 1725 * @module ng 1726 * @param {string} name service name 1727 * @param {*} object Service instance object. 1728 * @description 1729 * See {@link auto.$provide#value $provide.value()}. 1730 */ 1731 value: invokeLater('$provide', 'value'), 1732 1733 /** 1734 * @ngdoc method 1735 * @name angular.Module#constant 1736 * @module ng 1737 * @param {string} name constant name 1738 * @param {*} object Constant value. 1739 * @description 1740 * Because the constant are fixed, they get applied before other provide methods. 1741 * See {@link auto.$provide#constant $provide.constant()}. 1742 */ 1743 constant: invokeLater('$provide', 'constant', 'unshift'), 1744 1745 /** 1746 * @ngdoc method 1747 * @name angular.Module#animation 1748 * @module ng 1749 * @param {string} name animation name 1750 * @param {Function} animationFactory Factory function for creating new instance of an 1751 * animation. 1752 * @description 1753 * 1754 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 1755 * 1756 * 1757 * Defines an animation hook that can be later used with 1758 * {@link ngAnimate.$animate $animate} service and directives that use this service. 1759 * 1760 * ```js 1761 * module.animation('.animation-name', function($inject1, $inject2) { 1762 * return { 1763 * eventName : function(element, done) { 1764 * //code to run the animation 1765 * //once complete, then run done() 1766 * return function cancellationFunction(element) { 1767 * //code to cancel the animation 1768 * } 1769 * } 1770 * } 1771 * }) 1772 * ``` 1773 * 1774 * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and 1775 * {@link ngAnimate ngAnimate module} for more information. 1776 */ 1777 animation: invokeLater('$animateProvider', 'register'), 1778 1779 /** 1780 * @ngdoc method 1781 * @name angular.Module#filter 1782 * @module ng 1783 * @param {string} name Filter name. 1784 * @param {Function} filterFactory Factory function for creating new instance of filter.
1785 * @description 1786 * See {@link ng.$filterProvider#register $filterProvider.register()}. 1787 */ 1788 filter: invokeLater('$filterProvider', 'register'), 1789 1790 /** 1791 * @ngdoc method 1792 * @name angular.Module#controller 1793 * @module ng 1794 * @param {string|Object} name Controller name, or an object map of controllers where the 1795 * keys are the names and the values are the constructors. 1796 * @param {Function} constructor Controller constructor function. 1797 * @description 1798 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 1799 */ 1800 controller: invokeLater('$controllerProvider', 'register'), 1801 1802 /** 1803 * @ngdoc method 1804 * @name angular.Module#directive 1805 * @module ng 1806 * @param {string|Object} name Directive name, or an object map of directives where the 1807 * keys are the names and the values are the factories. 1808 * @param {Function} directiveFactory Factory function for creating new instance of 1809 * directives. 1810 * @description 1811 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 1812 */ 1813 directive: invokeLater('$compileProvider', 'directive'), 1814 1815 /** 1816 * @ngdoc method 1817 * @name angular.Module#config 1818 * @module ng 1819 * @param {Function} configFn Execute this function on module load. Useful for service 1820 * configuration. 1821 * @description 1822 * Use this method to register work which needs to be performed on module loading. 1823 * For more about how to configure services, see 1824 * {@link providers#providers_provider-recipe Provider Recipe}. 1825 */ 1826 config: config, 1827 1828 /** 1829 * @ngdoc method 1830 * @name angular.Module#run 1831 * @module ng 1832 * @param {Function} initializationFn Execute this function after injector creation. 1833 * Useful for application initialization. 1834 * @description 1835 * Use this method to register work which should be performed when the injector is done 1836 * loading all modules. 1837 */ 1838 run: function(block) { 1839 runBlocks.push(block); 1840 return this; 1841 } 1842 }; 1843 1844 if (configFn) { 1845 config(configFn); 1846 } 1847 1848 return moduleInstance; 1849 1850 /** 1851 * @param {string} provider 1852 * @param {string} method 1853 * @param {String=} insertMethod 1854 * @returns {angular.Module} 1855 */ 1856 function invokeLater(provider, method, insertMethod) { 1857 return function() { 1858 invokeQueue[insertMethod || 'push']([provider, method, arguments]); 1859 return moduleInstance; 1860 }; 1861 } 1862 }); 1863 }; 1864 }); 1865 1866} 1867 1868/* global 1869 angularModule: true, 1870 version: true, 1871 1872 $LocaleProvider, 1873 $CompileProvider, 1874 1875 htmlAnchorDirective, 1876 inputDirective, 1877 inputDirective, 1878 formDirective, 1879 scriptDirective, 1880 selectDirective, 1881 styleDirective, 1882 optionDirective, 1883 ngBindDirective, 1884 ngBindHtmlDirective, 1885 ngBindTemplateDirective, 1886 ngClassDirective, 1887 ngClassEvenDirective, 1888 ngClassOddDirective, 1889 ngCspDirective, 1890 ngCloakDirective, 1891 ngControllerDirective, 1892 ngFormDirective, 1893 ngHideDirective, 1894 ngIfDirective, 1895 ngIncludeDirective, 1896 ngIncludeFillContentDirective, 1897 ngInitDirective, 1898 ngNonBindableDirective, 1899 ngPluralizeDirective, 1900 ngRepeatDirective, 1901 ngShowDirective, 1902 ngStyleDirective, 1903 ngSwitchDirective, 1904 ngSwitchWhenDirective, 1905 ngSwitchDefaultDirective, 1906 ngOptionsDirective, 1907 ngTranscludeDirective, 1908 ngModelDirective, 1909 ngListDirective, 1910 ngChangeDirective, 1911 requiredDirective, 1912 requiredDirective, 1913 ngValueDirective, 1914 ngAttributeAliasDirectives, 1915 ngEventDirectives, 1916 1917 $AnchorScrollProvider, 1918 $AnimateProvider, 1919 $BrowserProvider,
1920 $CacheFactoryProvider, 1921 $ControllerProvider, 1922 $DocumentProvider, 1923 $ExceptionHandlerProvider, 1924 $FilterProvider, 1925 $InterpolateProvider, 1926 $IntervalProvider, 1927 $HttpProvider, 1928 $HttpBackendProvider, 1929 $LocationProvider, 1930 $LogProvider, 1931 $ParseProvider, 1932 $RootScopeProvider, 1933 $QProvider, 1934 $$SanitizeUriProvider, 1935 $SceProvider, 1936 $SceDelegateProvider, 1937 $SnifferProvider, 1938 $TemplateCacheProvider, 1939 $TimeoutProvider, 1940 $$RAFProvider, 1941 $$AsyncCallbackProvider, 1942 $WindowProvider 1943*/ 1944 1945 1946/** 1947 * @ngdoc object 1948 * @name angular.version 1949 * @module ng 1950 * @description 1951 * An object that contains information about the current AngularJS version. This object has the 1952 * following properties: 1953 * 1954 * - `full` â `{string}` â Full version string, such as "0.9.18". 1955 * - `major` â `{number}` â Major version number, such as "0". 1956 * - `minor` â `{number}` â Minor version number, such as "9". 1957 * - `dot` â `{number}` â Dot version number, such as "18". 1958 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 1959 */ 1960var version = { 1961 full: '1.2.19', // all of these placeholder strings will be replaced by grunt's 1962 major: 1, // package task 1963 minor: 2, 1964 dot: 19, 1965 codeName: 'precognitive-flashbacks' 1966}; 1967 1968 1969function publishExternalAPI(angular){ 1970 extend(angular, { 1971 'bootstrap': bootstrap, 1972 'copy': copy, 1973 'extend': extend, 1974 'equals': equals, 1975 'element': jqLite, 1976 'forEach': forEach, 1977 'injector': createInjector, 1978 'noop':noop, 1979 'bind':bind, 1980 'toJson': toJson, 1981 'fromJson': fromJson, 1982 'identity':identity, 1983 'isUndefined': isUndefined, 1984 'isDefined': isDefined, 1985 'isString': isString, 1986 'isFunction': isFunction, 1987 'isObject': isObject, 1988 'isNumber': isNumber, 1989 'isElement': isElement, 1990 'isArray': isArray, 1991 'version': version, 1992 'isDate': isDate, 1993 'lowercase': lowercase, 1994 'uppercase': uppercase, 1995 'callbacks': {counter: 0}, 1996 '$$minErr': minErr, 1997 '$$csp': csp 1998 }); 1999 2000 angularModule = setupModuleLoader(window); 2001 try { 2002 angularModule('ngLocale'); 2003 } catch (e) { 2004 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 2005 } 2006 2007 angularModule('ng', ['ngLocale'], ['$provide', 2008 function ngModule($provide) { 2009 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2010 $provide.provider({ 2011 $$sanitizeUri: $$SanitizeUriProvider 2012 }); 2013 $provide.provider('$compile', $CompileProvider). 2014 directive({ 2015 a: htmlAnchorDirective, 2016 input: inputDirective, 2017 textarea: inputDirective, 2018 form: formDirective, 2019 script: scriptDirective, 2020 select: selectDirective, 2021 style: styleDirective, 2022 option: optionDirective, 2023 ngBind: ngBindDirective, 2024 ngBindHtml: ngBindHtmlDirective, 2025 ngBindTemplate: ngBindTemplateDirective, 2026 ngClass: ngClassDirective, 2027 ngClassEven: ngClassEvenDirective, 2028 ngClassOdd: ngClassOddDirective, 2029 ngCloak: ngCloakDirective, 2030 ngController: ngControllerDirective, 2031 ngForm: ngFormDirective, 2032 ngHide: ngHideDirective, 2033 ngIf: ngIfDirective, 2034 ngInclude: ngIncludeDirective, 2035 ngInit: ngInitDirective, 2036 ngNonBindable: ngNonBindableDirective, 2037 ngPluralize: ngPluralizeDirective, 2038 ngRepeat: ngRepeatDirective, 2039 ngShow: ngShowDirective, 2040 ngStyle: ngStyleDirective, 2041 ngSwitch: ngSwitchDirective, 2042 ngSwitchWhen: ngSwitchWhenDirective, 2043 ngSwitchDefault: ngSwitchDefaultDirective, 2044 ngOptions: ngOptionsDirective, 2045 ngTransclude: ngTranscludeDirective, 2046 ngModel: ngModelDirective, 2047 ngList: ngListDirective, 2048 ngChange: ngChangeDirective, 2049 required: requiredDirective, 2050 ngRequired: requiredDirective, 2051 ngValue: ngValueDirective 2052 }). 2053 directive({ 2054 ngInclude: ngIncludeFillContentDirective 2055 }). 2056 directive(ngAttributeAliasDirectives). 2057 directive(ngEventDirectives); 2058 $provide.provider({ 2059 $anchorScroll: $AnchorScrollProvider, 2060 $animate: $AnimateProvider, 2061 $browser: $BrowserProvider,
2062 $cacheFactory: $CacheFactoryProvider, 2063 $controller: $ControllerProvider, 2064 $document: $DocumentProvider, 2065 $exceptionHandler: $ExceptionHandlerProvider, 2066 $filter: $FilterProvider, 2067 $interpolate: $InterpolateProvider, 2068 $interval: $IntervalProvider, 2069 $http: $HttpProvider, 2070 $httpBackend: $HttpBackendProvider, 2071 $location: $LocationProvider, 2072 $log: $LogProvider, 2073 $parse: $ParseProvider, 2074 $rootScope: $RootScopeProvider, 2075 $q: $QProvider, 2076 $sce: $SceProvider, 2077 $sceDelegate: $SceDelegateProvider, 2078 $sniffer: $SnifferProvider, 2079 $templateCache: $TemplateCacheProvider, 2080 $timeout: $TimeoutProvider, 2081 $window: $WindowProvider, 2082 $$rAF: $$RAFProvider, 2083 $$asyncCallback : $$AsyncCallbackProvider 2084 }); 2085 } 2086 ]); 2087} 2088 2089/* global 2090 2091 -JQLitePrototype, 2092 -addEventListenerFn, 2093 -removeEventListenerFn, 2094 -BOOLEAN_ATTR 2095*/ 2096 2097////////////////////////////////// 2098//JQLite 2099////////////////////////////////// 2100 2101/** 2102 * @ngdoc function 2103 * @name angular.element 2104 * @module ng 2105 * @kind function 2106 * 2107 * @description 2108 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2109 * 2110 * If jQuery is available, `angular.element` is an alias for the 2111 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2112 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." 2113 * 2114 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows 2115 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most 2116 * commonly needed functionality with the goal of having a very small footprint.</div> 2117 * 2118 * To use jQuery, simply load it before `DOMContentLoaded` event fired. 2119 * 2120 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or 2121 * jqLite; they are never raw DOM references.</div> 2122 * 2123 * ## Angular's jqLite 2124 * jqLite provides only the following jQuery methods: 2125 * 2126 * - [`addClass()`](http://api.jquery.com/addClass/) 2127 * - [`after()`](http://api.jquery.com/after/) 2128 * - [`append()`](http://api.jquery.com/append/) 2129 * - [`attr()`](http://api.jquery.com/attr/) 2130 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2131 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2132 * - [`clone()`](http://api.jquery.com/clone/) 2133 * - [`contents()`](http://api.jquery.com/contents/) 2134 * - [`css()`](http://api.jquery.com/css/) 2135 * - [`data()`](http://api.jquery.com/data/) 2136 * - [`empty()`](http://api.jquery.com/empty/) 2137 * - [`eq()`](http://api.jquery.com/eq/) 2138 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2139 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2140 * - [`html()`](http://api.jquery.com/html/) 2141 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2142 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2143 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors 2144 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2145 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2146 * - [`prepend()`](http://api.jquery.com/prepend/) 2147 * - [`prop()`](http://api.jquery.com/prop/) 2148 * - [`ready()`](http://api.jquery.com/ready/) 2149 * - [`remove()`](http://api.jquery.com/remove/) 2150 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2151 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2152 * - [`removeData()`](http://api.jquery.com/removeData/) 2153 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2154 * - [`text()`](http://api.jquery.com/text/) 2155 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2156 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2157 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces 2158 * - [`val()`](http://api.jquery.com/val/) 2159 * - [`wrap()`](http://api.jquery.com/wrap/) 2160 * 2161 * ## jQuery/jqLite Extras 2162 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2163 * 2164 * ### Events 2165 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
2165pis and fires this event 2166 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2167 * element before it is removed. 2168 * 2169 * ### Methods 2170 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2171 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2172 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2173 * `'ngModel'`). 2174 * - `injector()` - retrieves the injector of the current element or its parent. 2175 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2176 * element or its parent. 2177 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2178 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 2179 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2180 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2181 * parent element is reached. 2182 * 2183 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2184 * @returns {Object} jQuery object. 2185 */ 2186 2187JQLite.expando = 'ng339'; 2188 2189var jqCache = JQLite.cache = {}, 2190 jqId = 1, 2191 addEventListenerFn = (window.document.addEventListener 2192 ? function(element, type, fn) {element.addEventListener(type, fn, false);} 2193 : function(element, type, fn) {element.attachEvent('on' + type, fn);}), 2194 removeEventListenerFn = (window.document.removeEventListener 2195 ? function(element, type, fn) {element.removeEventListener(type, fn, false); } 2196 : function(element, type, fn) {element.detachEvent('on' + type, fn); }); 2197 2198/* 2199 * !!! This is an undocumented "private" function !!! 2200 */ 2201var jqData = JQLite._data = function(node) { 2202 //jQuery always returns an object on cache miss 2203 return this.cache[node[this.expando]] || {}; 2204}; 2205 2206function jqNextId() { return ++jqId; } 2207 2208 2209var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2210var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2211var jqLiteMinErr = minErr('jqLite'); 2212 2213/** 2214 * Converts snake_case to camelCase. 2215 * Also there is special case for Moz prefix starting with upper case letter. 2216 * @param name Name to normalize 2217 */ 2218function camelCase(name) { 2219 return name. 2220 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2221 return offset ? letter.toUpperCase() : letter; 2222 }). 2223 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2224} 2225 2226///////////////////////////////////////////// 2227// jQuery mutation patch 2228// 2229// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a 2230// $destroy event on all DOM nodes being removed. 2231// 2232///////////////////////////////////////////// 2233 2234function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { 2235 var originalJqFn = jQuery.fn[name]; 2236 originalJqFn = originalJqFn.$original || originalJqFn; 2237 removePatch.$original = originalJqFn; 2238 jQuery.fn[name] = removePatch; 2239 2240 function removePatch(param) { 2241 // jshint -W040 2242 var list = filterElems && param ? [this.filter(param)] : [this], 2243 fireEvent = dispatchThis, 2244 set, setIndex, setLength, 2245 element, childIndex, childLength, children; 2246 2247 if (!getterIfNoArguments || param != null) { 2248 while(list.length) { 2249 set = list.shift(); 2250 for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { 2251 element = jqLite(set[setIndex]); 2252 if (fireEvent) { 2253 element.triggerHandler('$destroy'); 2254 } else { 2255 fireEvent = !fireEvent; 2256 } 2257 for(childIndex = 0, childLength = (children = element.children()).length; 2258 childIndex < childLength; 2259 childIndex++) { 2260 list.push(jQuery(children[childIndex])); 2261 } 2262 } 2263 } 2264 } 2265 return originalJqFn.apply(this, arguments); 2266 } 2267} 2268 2269var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/;
2270var HTML_REGEXP = /<|&#?\w+;/; 2271var TAG_NAME_REGEXP = /<([\w:]+)/; 2272var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; 2273 2274var wrapMap = { 2275 'option': [1, '<select multiple="multiple">', '</select>'], 2276 2277 'thead': [1, '<table>', '</table>'], 2278 'col': [2, '<table><colgroup>', '</colgroup></table>'], 2279 'tr': [2, '<table><tbody>', '</tbody></table>'], 2280 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2281 '_default': [0, "", ""] 2282}; 2283 2284wrapMap.optgroup = wrapMap.option; 2285wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2286wrapMap.th = wrapMap.td; 2287 2288function jqLiteIsTextNode(html) { 2289 return !HTML_REGEXP.test(html); 2290} 2291 2292function jqLiteBuildFragment(html, context) { 2293 var elem, tmp, tag, wrap, 2294 fragment = context.createDocumentFragment(), 2295 nodes = [], i, j, jj; 2296 2297 if (jqLiteIsTextNode(html)) { 2298 // Convert non-html into a text node 2299 nodes.push(context.createTextNode(html)); 2300 } else { 2301 tmp = fragment.appendChild(context.createElement('div')); 2302 // Convert html into DOM nodes 2303 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2304 wrap = wrapMap[tag] || wrapMap._default; 2305 tmp.innerHTML = '<div> </div>' + 2306 wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2307 tmp.removeChild(tmp.firstChild); 2308 2309 // Descend through wrappers to the right content 2310 i = wrap[0]; 2311 while (i--) { 2312 tmp = tmp.lastChild; 2313 } 2314 2315 for (j=0, jj=tmp.childNodes.length; j<jj; ++j) nodes.push(tmp.childNodes[j]); 2316 2317 tmp = fragment.firstChild; 2318 tmp.textContent = ""; 2319 } 2320 2321 // Remove wrapper from fragment 2322 fragment.textContent = ""; 2323 fragment.innerHTML = ""; // Clear inner HTML 2324 return nodes; 2325} 2326 2327function jqLiteParseHTML(html, context) { 2328 context = context || document; 2329 var parsed; 2330 2331 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2332 return [context.createElement(parsed[1])]; 2333 } 2334 2335 return jqLiteBuildFragment(html, context); 2336} 2337 2338///////////////////////////////////////////// 2339function JQLite(element) { 2340 if (element instanceof JQLite) { 2341 return element; 2342 } 2343 if (isString(element)) { 2344 element = trim(element); 2345 } 2346 if (!(this instanceof JQLite)) { 2347 if (isString(element) && element.charAt(0) != '<') { 2348 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2349 } 2350 return new JQLite(element); 2351 } 2352 2353 if (isString(element)) { 2354 jqLiteAddNodes(this, jqLiteParseHTML(element)); 2355 var fragment = jqLite(document.createDocumentFragment()); 2356 fragment.append(this); 2357 } else { 2358 jqLiteAddNodes(this, element); 2359 } 2360} 2361 2362function jqLiteClone(element) { 2363 return element.cloneNode(true); 2364} 2365 2366function jqLiteDealoc(element){ 2367 jqLiteRemoveData(element); 2368 for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { 2369 jqLiteDealoc(children[i]); 2370 } 2371} 2372 2373function jqLiteOff(element, type, fn, unsupported) {
2374 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2375 2376 var events = jqLiteExpandoStore(element, 'events'), 2377 handle = jqLiteExpandoStore(element, 'handle'); 2378 2379 if (!handle) return; //no listeners registered 2380 2381 if (isUndefined(type)) { 2382 forEach(events, function(eventHandler, type) { 2383 removeEventListenerFn(element, type, eventHandler); 2384 delete events[type]; 2385 }); 2386 } else { 2387 forEach(type.split(' '), function(type) { 2388 if (isUndefined(fn)) { 2389 removeEventListenerFn(element, type, events[type]); 2390 delete events[type]; 2391 } else { 2392 arrayRemove(events[type] || [], fn); 2393 } 2394 }); 2395 } 2396} 2397 2398function jqLiteRemoveData(element, name) { 2399 var expandoId = element.ng339, 2400 expandoStore = jqCache[expandoId]; 2401 2402 if (expandoStore) { 2403 if (name) { 2404 delete jqCache[expandoId].data[name]; 2405 return; 2406 } 2407 2408 if (expandoStore.handle) { 2409 expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); 2410 jqLiteOff(element); 2411 } 2412 delete jqCache[expandoId]; 2413 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 2414 } 2415} 2416 2417function jqLiteExpandoStore(element, key, value) { 2418 var expandoId = element.ng339, 2419 expandoStore = jqCache[expandoId || -1]; 2420 2421 if (isDefined(value)) { 2422 if (!expandoStore) { 2423 element.ng339 = expandoId = jqNextId(); 2424 expandoStore = jqCache[expandoId] = {}; 2425 } 2426 expandoStore[key] = value; 2427 } else { 2428 return expandoStore && expandoStore[key]; 2429 } 2430} 2431 2432function jqLiteData(element, key, value) { 2433 var data = jqLiteExpandoStore(element, 'data'), 2434 isSetter = isDefined(value), 2435 keyDefined = !isSetter && isDefined(key), 2436 isSimpleGetter = keyDefined && !isObject(key); 2437 2438 if (!data && !isSimpleGetter) { 2439 jqLiteExpandoStore(element, 'data', data = {}); 2440 } 2441 2442 if (isSetter) { 2443 data[key] = value; 2444 } else { 2445 if (keyDefined) { 2446 if (isSimpleGetter) { 2447 // don't create data in this case. 2448 return data && data[key]; 2449 } else { 2450 extend(data, key); 2451 } 2452 } else { 2453 return data; 2454 } 2455 } 2456} 2457 2458function jqLiteHasClass(element, selector) { 2459 if (!element.getAttribute) return false; 2460 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2461 indexOf( " " + selector + " " ) > -1); 2462} 2463 2464function jqLiteRemoveClass(element, cssClasses) { 2465 if (cssClasses && element.setAttribute) { 2466 forEach(cssClasses.split(' '), function(cssClass) { 2467 element.setAttribute('class', trim( 2468 (" " + (element.getAttribute('class') || '') + " ") 2469 .replace(/[\n\t]/g, " ") 2470 .replace(" " + trim(cssClass) + " ", " ")) 2471 ); 2472 }); 2473 } 2474} 2475 2476function jqLiteAddClass(element, cssClasses) { 2477 if (cssClasses && element.setAttribute) { 2478 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 2479 .replace(/[\n\t]/g, " "); 2480 2481 forEach(cssClasses.split(' '), function(cssClass) { 2482 cssClass = trim(cssClass); 2483 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 2484 existingClasses += cssClass + ' '; 2485 } 2486 }); 2487 2488 element.setAttribute('class', trim(existingClasses)); 2489 } 2490} 2491 2492function jqLiteAddNodes(root, elements) { 2493 if (elements) { 2494 elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) 2495 ? elements 2496 : [ elements ]; 2497 for(var i=0; i < elements.length; i++) { 2498 root.push(elements[i]); 2499 } 2500 } 2501} 2502 2503function jqLiteController(element, name) { 2504 return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); 2505} 2506 2507function jqLiteInheritedData(element, name, value) { 2508 element = jqLite(element); 2509 2510 // if element is the document object work with the html element instead 2511 // this makes $(document).scope() possible 2512 if(element[0].nodeType == 9) { 2513 element = element.find('html'); 2514 } 2515 var names = isArray(name) ? name : [name]; 2516 2517 while (element.length) { 2518 var node = element[0]; 2519 for (var i = 0, ii = names.length; i < ii; i++) { 2520 if ((value = element.data(names[i])) !== undefined) return value; 2521 } 2522 2523 // If dealing with a document fragment node with a host element, and no parent, use the host 2524 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 2525 // to lookup parent controllers. 2526 element = jqLite(node.parentNode || (node.nodeType === 11 && node.host)); 2527 } 2528} 2529 2530function jqLiteEmpty(element) { 2531 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2532 jqLiteDealoc(childNodes[i]); 2533 } 2534 while (element.firstChild) { 2535 element.removeChild(element.firstChild); 2536 } 2537} 2538 2539////////////////////////////////////////// 2540// Functions which are declared directly.
2541////////////////////////////////////////// 2542var JQLitePrototype = JQLite.prototype = { 2543 ready: function(fn) { 2544 var fired = false; 2545 2546 function trigger() { 2547 if (fired) return; 2548 fired = true; 2549 fn(); 2550 } 2551 2552 // check if document already is loaded 2553 if (document.readyState === 'complete'){ 2554 setTimeout(trigger); 2555 } else { 2556 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 2557 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 2558 // jshint -W064 2559 JQLite(window).on('load', trigger); // fallback to window.onload for others 2560 // jshint +W064 2561 } 2562 }, 2563 toString: function() { 2564 var value = []; 2565 forEach(this, function(e){ value.push('' + e);}); 2566 return '[' + value.join(', ') + ']'; 2567 }, 2568 2569 eq: function(index) { 2570 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 2571 }, 2572 2573 length: 0, 2574 push: push, 2575 sort: [].sort, 2576 splice: [].splice 2577}; 2578 2579////////////////////////////////////////// 2580// Functions iterating getter/setters. 2581// these functions return self on setter and 2582// value on get. 2583////////////////////////////////////////// 2584var BOOLEAN_ATTR = {}; 2585forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 2586 BOOLEAN_ATTR[lowercase(value)] = value; 2587}); 2588var BOOLEAN_ELEMENTS = {}; 2589forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 2590 BOOLEAN_ELEMENTS[uppercase(value)] = true; 2591}); 2592 2593function getBooleanAttrName(element, name) { 2594 // check dom last since we will most likely fail on name 2595 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 2596 2597 // booleanAttr is here twice to minimize DOM access 2598 return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; 2599} 2600 2601forEach({ 2602 data: jqLiteData, 2603 inheritedData: jqLiteInheritedData, 2604 2605 scope: function(element) { 2606 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2607 return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 2608 }, 2609 2610 isolateScope: function(element) { 2611 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2612 return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); 2613 }, 2614 2615 controller: jqLiteController, 2616 2617 injector: function(element) { 2618 return jqLiteInheritedData(element, '$injector'); 2619 }, 2620 2621 removeAttr: function(element,name) { 2622 element.removeAttribute(name); 2623 }, 2624 2625 hasClass: jqLiteHasClass, 2626 2627 css: function(element, name, value) { 2628 name = camelCase(name); 2629 2630 if (isDefined(value)) { 2631 element.style[name] = value; 2632 } else { 2633 var val; 2634 2635 if (msie <= 8) { 2636 // this is some IE specific weirdness that jQuery 1.6.4 does not sure why 2637 val = element.currentStyle && element.currentStyle[name]; 2638 if (val === '') val = 'auto'; 2639 } 2640 2641 val = val || element.style[name]; 2642 2643 if (msie <= 8) { 2644 // jquery weirdness :-/ 2645 val = (val === '') ? undefined : val; 2646 } 2647 2648 return val; 2649 } 2650 }, 2651 2652 attr: function(element, name, value){ 2653 var lowercasedName = lowercase(name); 2654 if (BOOLEAN_ATTR[lowercasedName]) { 2655 if (isDefined(value)) { 2656 if (!!value) { 2657 element[name] = true; 2658 element.setAttribute(name, lowercasedName); 2659 } else { 2660 element[name] = false; 2661 element.removeAttribute(lowercasedName); 2662 } 2663 } else { 2664 return (element[name] || 2665 (element.attributes.getNamedItem(name)|| noop).specified) 2666 ? lowercasedName 2667 : undefined; 2668 } 2669 } else if (isDefined(value)) { 2670 element.setAttribute(name, value); 2671 } else if (element.getAttribute) { 2672 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 2673 // some elements (e.g. Document) don't have get attribute, so return undefined 2674 var ret = element.getAttribute(name, 2); 2675 // normalize non-existing attributes to undefined (as jQuery) 2676 return ret === null ? undefined : ret; 2677 } 2678 }, 2679 2680 prop: function(element, name, value) { 2681 if (isDefined(value)) { 2682 element[name] = value; 2683 } else { 2684 return element[name]; 2685 } 2686 }, 2687 2688 text: (function() { 2689 var NODE_TYPE_TEXT_PROPERTY = []; 2690 if (msie < 9) { 2691 NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ 2692 NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ 2693 } else { 2694 NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ 2695 NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ 2696 } 2697 getText.$dv = ''; 2698 return getText; 2699 2700 function getText(element, value) { 2701 var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; 2702 if (isUndefined(value)) { 2703 return textProp ? element[textProp] : ''; 2704 } 2705 element[textProp] = value; 2706 } 2707 })(), 2708 2709 val: function(element, value) { 2710 if (isUndefined(value)) { 2711 if (nodeName_(element) === 'SELECT' && element.multiple) { 2712 var result = [];
2713 forEach(element.options, function (option) { 2714 if (option.selected) { 2715 result.push(option.value || option.text); 2716 } 2717 }); 2718 return result.length === 0 ? null : result; 2719 } 2720 return element.value; 2721 } 2722 element.value = value; 2723 }, 2724 2725 html: function(element, value) { 2726 if (isUndefined(value)) { 2727 return element.innerHTML; 2728 } 2729 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2730 jqLiteDealoc(childNodes[i]); 2731 } 2732 element.innerHTML = value; 2733 }, 2734 2735 empty: jqLiteEmpty 2736}, function(fn, name){ 2737 /** 2738 * Properties: writes return selection, reads return first value 2739 */ 2740 JQLite.prototype[name] = function(arg1, arg2) { 2741 var i, key; 2742 var nodeCount = this.length; 2743 2744 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 2745 // in a way that survives minification. 2746 // jqLiteEmpty takes no arguments but is a setter. 2747 if (fn !== jqLiteEmpty && 2748 (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { 2749 if (isObject(arg1)) { 2750 2751 // we are a write, but the object properties are the key/values 2752 for (i = 0; i < nodeCount; i++) { 2753 if (fn === jqLiteData) { 2754 // data() takes the whole object in jQuery 2755 fn(this[i], arg1); 2756 } else { 2757 for (key in arg1) { 2758 fn(this[i], key, arg1[key]); 2759 } 2760 } 2761 } 2762 // return self for chaining 2763 return this; 2764 } else { 2765 // we are a read, so read the first child. 2766 // TODO: do we still need this? 2767 var value = fn.$dv; 2768 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 2769 var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount; 2770 for (var j = 0; j < jj; j++) { 2771 var nodeValue = fn(this[j], arg1, arg2); 2772 value = value ? value + nodeValue : nodeValue; 2773 } 2774 return value; 2775 } 2776 } else { 2777 // we are a write, so apply to all children 2778 for (i = 0; i < nodeCount; i++) { 2779 fn(this[i], arg1, arg2); 2780 } 2781 // return self for chaining 2782 return this; 2783 } 2784 }; 2785}); 2786 2787function createEventHandler(element, events) { 2788 var eventHandler = function (event, type) { 2789 if (!event.preventDefault) { 2790 event.preventDefault = function() { 2791 event.returnValue = false; //ie 2792 }; 2793 } 2794 2795 if (!event.stopPropagation) { 2796 event.stopPropagation = function() { 2797 event.cancelBubble = true; //ie 2798 }; 2799 } 2800 2801 if (!event.target) { 2802 event.target = event.srcElement || document; 2803 } 2804 2805 if (isUndefined(event.defaultPrevented)) { 2806 var prevent = event.preventDefault; 2807 event.preventDefault = function() { 2808 event.defaultPrevented = true; 2809 prevent.call(event); 2810 }; 2811 event.defaultPrevented = false; 2812 } 2813 2814 event.isDefaultPrevented = function() { 2815 return event.defaultPrevented || event.returnValue === false; 2816 }; 2817 2818 // Copy event handlers in case event handlers array is modified during execution. 2819 var eventHandlersCopy = shallowCopy(events[type || event.type] || []); 2820 2821 forEach(eventHandlersCopy, function(fn) { 2822 fn.call(element, event); 2823 }); 2824 2825 // Remove monkey-patched methods (IE), 2826 // as they would cause memory leaks in IE8. 2827 if (msie <= 8) { 2828 // IE7/8 does not allow to delete property on native object 2829 event.preventDefault = null; 2830 event.stopPropagation = null; 2831 event.isDefaultPrevented = null; 2832 } else { 2833 // It shouldn't affect normal browsers (native methods are defined on prototype). 2834 delete event.preventDefault; 2835 delete event.stopPropagation; 2836 delete event.isDefaultPrevented; 2837 } 2838 }; 2839 eventHandler.elem = element; 2840 return eventHandler; 2841} 2842 2843////////////////////////////////////////// 2844// Functions iterating traversal.
2845// These functions chain results into a single 2846// selector. 2847////////////////////////////////////////// 2848forEach({ 2849 removeData: jqLiteRemoveData, 2850 2851 dealoc: jqLiteDealoc, 2852 2853 on: function onFn(element, type, fn, unsupported){ 2854 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 2855 2856 var events = jqLiteExpandoStore(element, 'events'), 2857 handle = jqLiteExpandoStore(element, 'handle'); 2858 2859 if (!events) jqLiteExpandoStore(element, 'events', events = {}); 2860 if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); 2861 2862 forEach(type.split(' '), function(type){ 2863 var eventFns = events[type]; 2864 2865 if (!eventFns) { 2866 if (type == 'mouseenter' || type == 'mouseleave') { 2867 var contains = document.body.contains || document.body.compareDocumentPosition ? 2868 function( a, b ) { 2869 // jshint bitwise: false 2870 var adown = a.nodeType === 9 ? a.documentElement : a, 2871 bup = b && b.parentNode; 2872 return a === bup || !!( bup && bup.nodeType === 1 && ( 2873 adown.contains ? 2874 adown.contains( bup ) : 2875 a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 2876 )); 2877 } : 2878 function( a, b ) { 2879 if ( b ) { 2880 while ( (b = b.parentNode) ) { 2881 if ( b === a ) { 2882 return true; 2883 } 2884 } 2885 } 2886 return false; 2887 }; 2888 2889 events[type] = []; 2890 2891 // Refer to jQuery's implementation of mouseenter & mouseleave 2892 // Read about mouseenter and mouseleave: 2893 // http://www.quirksmode.org/js/events_mouse.html#link8 2894 var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; 2895 2896 onFn(element, eventmap[type], function(event) { 2897 var target = this, related = event.relatedTarget; 2898 // For mousenter/leave call the handler if related is outside the target. 2899 // NB: No relatedTarget if the mouse left/entered the browser window 2900 if ( !related || (related !== target && !contains(target, related)) ){ 2901 handle(event, type); 2902 } 2903 }); 2904 2905 } else { 2906 addEventListenerFn(element, type, handle); 2907 events[type] = []; 2908 } 2909 eventFns = events[type]; 2910 } 2911 eventFns.push(fn); 2912 }); 2913 }, 2914 2915 off: jqLiteOff, 2916 2917 one: function(element, type, fn) { 2918 element = jqLite(element); 2919 2920 //add the listener twice so that when it is called 2921 //you can remove the original function and still be 2922 //able to call element.off(ev, fn) normally 2923 element.on(type, function onFn() { 2924 element.off(type, fn); 2925 element.off(type, onFn); 2926 }); 2927 element.on(type, fn); 2928 }, 2929 2930 replaceWith: function(element, replaceNode) { 2931 var index, parent = element.parentNode; 2932 jqLiteDealoc(element); 2933 forEach(new JQLite(replaceNode), function(node){ 2934 if (index) { 2935 parent.insertBefore(node, index.nextSibling); 2936 } else { 2937 parent.replaceChild(node, element); 2938 } 2939 index = node; 2940 }); 2941 }, 2942 2943 children: function(element) { 2944 var children = []; 2945 forEach(element.childNodes, function(element){ 2946 if (element.nodeType === 1) 2947 children.push(element); 2948 }); 2949 return children; 2950 }, 2951 2952 contents: function(element) { 2953 return element.contentDocument || element.childNodes || []; 2954 }, 2955 2956 append: function(element, node) { 2957 forEach(new JQLite(node), function(child){ 2958 if (element.nodeType === 1 || element.nodeType === 11) { 2959 element.appendChild(child); 2960 } 2961 }); 2962 }, 2963 2964 prepend: function(element, node) { 2965 if (element.nodeType === 1) { 2966 var index = element.firstChild; 2967 forEach(new JQLite(node), function(child){ 2968 element.insertBefore(child, index); 2969 }); 2970 } 2971 }, 2972 2973 wrap: function(element, wrapNode) { 2974 wrapNode = jqLite(wrapNode)[0]; 2975 var parent = element.parentNode; 2976 if (parent) { 2977 parent.replaceChild(wrapNode, element); 2978 } 2979 wrapNode.appendChild(element); 2980 }, 2981 2982 remove: function(element) { 2983 jqLiteDealoc(element); 2984 var parent = element.parentNode; 2985 if (parent) parent.removeChild(element); 2986 }, 2987 2988 after: function(element, newElement) { 2989 var index = element, parent = element.parentNode;
2990 forEach(new JQLite(newElement), function(node){ 2991 parent.insertBefore(node, index.nextSibling); 2992 index = node; 2993 }); 2994 }, 2995 2996 addClass: jqLiteAddClass, 2997 removeClass: jqLiteRemoveClass, 2998 2999 toggleClass: function(element, selector, condition) { 3000 if (selector) { 3001 forEach(selector.split(' '), function(className){ 3002 var classCondition = condition; 3003 if (isUndefined(classCondition)) { 3004 classCondition = !jqLiteHasClass(element, className); 3005 } 3006 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 3007 }); 3008 } 3009 }, 3010 3011 parent: function(element) { 3012 var parent = element.parentNode; 3013 return parent && parent.nodeType !== 11 ? parent : null; 3014 }, 3015 3016 next: function(element) { 3017 if (element.nextElementSibling) { 3018 return element.nextElementSibling; 3019 } 3020 3021 // IE8 doesn't have nextElementSibling 3022 var elm = element.nextSibling; 3023 while (elm != null && elm.nodeType !== 1) { 3024 elm = elm.nextSibling; 3025 } 3026 return elm; 3027 }, 3028 3029 find: function(element, selector) { 3030 if (element.getElementsByTagName) { 3031 return element.getElementsByTagName(selector); 3032 } else { 3033 return []; 3034 } 3035 }, 3036 3037 clone: jqLiteClone, 3038 3039 triggerHandler: function(element, eventName, eventData) { 3040 var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; 3041 3042 eventData = eventData || []; 3043 3044 var event = [{ 3045 preventDefault: noop, 3046 stopPropagation: noop 3047 }]; 3048 3049 forEach(eventFns, function(fn) { 3050 fn.apply(element, event.concat(eventData)); 3051 }); 3052 } 3053}, function(fn, name){ 3054 /** 3055 * chaining functions 3056 */ 3057 JQLite.prototype[name] = function(arg1, arg2, arg3) { 3058 var value; 3059 for(var i=0; i < this.length; i++) { 3060 if (isUndefined(value)) { 3061 value = fn(this[i], arg1, arg2, arg3); 3062 if (isDefined(value)) { 3063 // any function which returns a value needs to be wrapped 3064 value = jqLite(value); 3065 } 3066 } else { 3067 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3068 } 3069 } 3070 return isDefined(value) ? value : this; 3071 }; 3072 3073 // bind legacy bind/unbind to on/off 3074 JQLite.prototype.bind = JQLite.prototype.on; 3075 JQLite.prototype.unbind = JQLite.prototype.off; 3076}); 3077 3078/** 3079 * Computes a hash of an 'obj'. 3080 * Hash of a: 3081 * string is string 3082 * number is number as string 3083 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3084 * that is also assigned to the $$hashKey property of the object. 3085 * 3086 * @param obj 3087 * @returns {string} hash string such that the same input will have the same hash string. 3088 * The resulting string key is in 'type:hashKey' format. 3089 */ 3090function hashKey(obj, nextUidFn) { 3091 var objType = typeof obj, 3092 key; 3093 3094 if (objType == 'function' || (objType == 'object' && obj !== null)) { 3095 if (typeof (key = obj.$$hashKey) == 'function') { 3096 // must invoke on object to keep the right this 3097 key = obj.$$hashKey(); 3098 } else if (key === undefined) { 3099 key = obj.$$hashKey = (nextUidFn || nextUid)(); 3100 } 3101 } else { 3102 key = obj; 3103 } 3104 3105 return objType + ':' + key; 3106} 3107 3108/** 3109 * HashMap which can use objects as keys 3110 */ 3111function HashMap(array, isolatedUid) { 3112 if (isolatedUid) { 3113 var uid = 0; 3114 this.nextUid = function() { 3115 return ++uid; 3116 }; 3117 } 3118 forEach(array, this.put, this); 3119} 3120HashMap.prototype = { 3121 /** 3122 * Store key value pair 3123 * @param key key to store can be any type 3124 * @param value value to store can be any type 3125 */ 3126 put: function(key, value) { 3127 this[hashKey(key, this.nextUid)] = value; 3128 }, 3129 3130 /** 3131 * @param key 3132 * @returns {Object} the value for the key 3133 */ 3134 get: function(key) { 3135 return this[hashKey(key, this.nextUid)]; 3136 }, 3137 3138 /** 3139 * Remove the key/value pair 3140 * @param key 3141 */ 3142 remove: function(key) { 3143 var value = this[key = hashKey(key, this.nextUid)]; 3144 delete this[key]; 3145 return value; 3146 } 3147}; 3148 3149/** 3150 * @ngdoc function 3151 * @module ng 3152 * @name angular.injector 3153 * @kind function 3154 * 3155 * @description 3156 * Creates an injector function that can be used for retrieving services as well as for 3157 * dependency injection (see {@link guide/di dependency injection}). 3158 * 3159 3160 * @param {Array.<string|Function>}
3160 modules A list of module functions or their aliases. See 3161 * {@link angular.module}. The `ng` module must be explicitly added. 3162 * @returns {function()} Injector function. See {@link auto.$injector $injector}. 3163 * 3164 * @example 3165 * Typical usage 3166 * ```js 3167 * // create an injector 3168 * var $injector = angular.injector(['ng']); 3169 * 3170 * // use the injector to kick off your application 3171 * // use the type inference to auto inject arguments, or use implicit injection 3172 * $injector.invoke(function($rootScope, $compile, $document){ 3173 * $compile($document)($rootScope); 3174 * $rootScope.$digest(); 3175 * }); 3176 * ``` 3177 * 3178 * Sometimes you want to get access to the injector of a currently running Angular app 3179 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3180 * application has been bootstrapped. You can do this using the extra `injector()` added 3181 * to JQuery/jqLite elements. See {@link angular.element}. 3182 * 3183 * *This is fairly rare but could be the case if a third party library is injecting the 3184 * markup.* 3185 * 3186 * In the following example a new block of HTML containing a `ng-controller` 3187 * directive is added to the end of the document body by JQuery. We then compile and link 3188 * it into the current AngularJS scope. 3189 * 3190 * ```js 3191 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3192 * $(document.body).append($div); 3193 * 3194 * angular.element(document).injector().invoke(function($compile) { 3195 * var scope = angular.element($div).scope(); 3196 * $compile($div)(scope); 3197 * }); 3198 * ``` 3199 */ 3200 3201 3202/** 3203 * @ngdoc module 3204 * @name auto 3205 * @description 3206 * 3207 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3208 */ 3209 3210var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; 3211var FN_ARG_SPLIT = /,/; 3212var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3213var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3214var $injectorMinErr = minErr('$injector'); 3215function annotate(fn) { 3216 var $inject, 3217 fnText, 3218 argDecl, 3219 last; 3220 3221 if (typeof fn === 'function') { 3222 if (!($inject = fn.$inject)) { 3223 $inject = []; 3224 if (fn.length) { 3225 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3226 argDecl = fnText.match(FN_ARGS); 3227 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ 3228 arg.replace(FN_ARG, function(all, underscore, name){ 3229 $inject.push(name); 3230 }); 3231 }); 3232 } 3233 fn.$inject = $inject; 3234 } 3235 } else if (isArray(fn)) { 3236 last = fn.length - 1; 3237 assertArgFn(fn[last], 'fn'); 3238 $inject = fn.slice(0, last); 3239 } else { 3240 assertArgFn(fn, 'fn', true); 3241 } 3242 return $inject; 3243} 3244 3245/////////////////////////////////////// 3246 3247/** 3248 * @ngdoc service 3249 * @name $injector 3250 * @kind function 3251 * 3252 * @description 3253 * 3254 * `$injector` is used to retrieve object instances as defined by 3255 * {@link auto.$provide provider}, instantiate types, invoke methods, 3256 * and load modules. 3257 * 3258 * The following always holds true: 3259 * 3260 * ```js 3261 * var $injector = angular.injector(); 3262 * expect($injector.get('$injector')).toBe($injector); 3263 * expect($injector.invoke(function($injector){ 3264 * return $injector; 3265 * }).toBe($injector); 3266 * ``` 3267 * 3268 * # Injection Function Annotation 3269 * 3270 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3271 * following are all valid ways of annotating function with injection arguments and are equivalent. 3272 * 3273 * ```js 3274 * // inferred (only works if code not minified/obfuscated) 3275 * $injector.invoke(function(serviceA){}); 3276 * 3277 * // annotated 3278 * function explicit(serviceA) {}; 3279 * explicit.$inject = ['serviceA']; 3280 * $injector.invoke(explicit); 3281 * 3282 * // inline 3283 * $injector.invoke(['serviceA', function(serviceA){}]); 3284 * ``` 3285 * 3286 * ## Inference 3287 * 3288 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3289 * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with 3290 * minification, and obfuscation tools since these tools change the argument names. 3291 * 3292 * ## `$inject` Annotation 3293 * By adding an `$inject` property onto a function the injection parameters can be specified. 3294 * 3295 * ## Inline 3296 * As an array of injection names, where the last item in the array is the function to call. 3297 */ 3298 3299/** 3300 * @ngdoc method 3301 * @name $injector#get 3302 * 3303 * @description 3304 * Return an instance of the service. 3305 * 3306 * @param {string} name The name of the instance to retrieve. 3307 * @return {*} The instance. 3308 */ 3309 3310/** 3311 * @ngdoc method 3312 * @name $injector#invoke 3313 * 3314 * @description 3315 * Invoke the method and supply the method arguments from the `$injector`. 3316 * 3317 * @param {!Function} fn The function to invoke. Function parameters are injected according to the 3318 * {@link guide/di $inject Annotation} rules. 3319 * @param {Object=} self The `this` for the invoked method. 3320 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3321 * object first, before the `$injector` is consulted. 3322 * @returns {*} the value returned by the invoked `fn` function. 3323 */ 3324 3325/** 3326 * @ngdoc method 3327 * @name $injector#has 3328 * 3329 * @description 3330 * Allows the user to query if the particular service exists. 3331 * 3332 * @param {string} Name of the service to query. 3333 * @returns {boolean} returns true if injector has given service. 3334 */ 3335 3336/** 3337 * @ngdoc method 3338 * @name $injector#instantiate 3339 * @description 3340 * Create a new instance of JS type. The method takes a constructor function, invokes the new 3341 * operator, and supplies all of the arguments to the constructor function as specified by the 3342 * constructor annotation. 3343 * 3344 * @param {Function} Type Annotated constructor function. 3345 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3346 * object first, before the `$injector` is consulted. 3347 * @returns {Object} new instance of `Type`. 3348 */ 3349 3350/** 3351 * @ngdoc method 3352 * @name $injector#annotate 3353 * 3354 * @description 3355 * Returns an array of service names which the function is requesting for injection. This API is 3356 * used by the injector to determine which services need to be injected into the function when the 3357 * function is invoked. There are three ways in which the function can be annotated with the needed 3358 * dependencies. 3359 * 3360 * # Argument names 3361 * 3362 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3363 * by converting the function into a string using `toString()` method and extracting the argument 3364 * names. 3365 * ```js 3366 * // Given 3367 * function MyController($scope, $route) { 3368 * // ... 3369 * } 3370 * 3371 * // Then 3372 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3373 * ``` 3374 * 3375 * This method does not work with code minification / obfuscation. For this reason the following 3376 * annotation strategies are supported. 3377 * 3378 * # The `$inject` property 3379 * 3380 * If a function has an `$inject` property and its value is an array of strings, then the strings 3381 * represent names of services to be injected into the function. 3382 * ```js 3383 * // Given 3384 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 3385 * // ... 3386 * } 3387 * // Define function dependencies 3388 * MyController['$inject'] = ['$scope', '$route']; 3389 * 3390 * // Then 3391 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3392 * ``` 3393 * 3394 * # The array notation 3395 * 3396 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 3397 * is very inconvenient. In these situations using the array notation to specify the dependencies in 3398 * a way that survives minification is a better choice: 3399 * 3400 * ```js 3401 * // We wish to write this (not minification / obfuscation safe) 3402 * injector.invoke(function($compile, $rootScope) { 3403 * // ... 3404 * }); 3405 * 3406 * // We are forced to write break inlining 3407 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 3408 * // ... 3409 * }; 3410 * tmpFn.$inject = ['$compile', '$rootScope']; 3411 * injector.invoke(tmpFn); 3412 * 3413 * // To better support inline function the inline annotation is supported 3414 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 3415 * // ... 3416 * }]); 3417 * 3418 * // Therefore 3419 * expect(injector.annotate( 3420 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 3421 * ).toEqual(['$compile', '$rootScope']); 3422 * ``` 3423 * 3424 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 3425 * be retrieved as described above. 3426 * 3427 * @returns {Array.<string>} The names of the services which the function requires. 3428 */ 3429 3430 3431 3432 3433/** 3434 * @ngdoc service 3435 * @name $provide 3436 * 3437 * @description 3438 * 3439 * The {@link auto.$provide $provide} service has a number of methods for registering components 3440 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 3441 * {@link angular.Module}. 3442 * 3443 * An Angular **service** is a singleton object created by a **service factory**. These **service 3444 * factories** are functions which, in turn, are created by a **service provider**. 3445 * The **service providers** are constructor functions. When instantiated they must contain a 3446 * property called `$get`, which holds the **service factory** function. 3447 * 3448 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 3449 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 3450 * function to get the instance of the **service**. 3451 * 3452 * Often services have no configuration options and there is no need to add methods to the service 3453 * provider. The provider will be no more than a constructor function with a `$get` property. For 3454 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 3455 * services without specifying a provider. 3456 * 3457 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 3458 * {@link auto.$injector $injector} 3459 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 3460 * providers and services. 3461 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 3462 * services, not providers. 3463 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 3464 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 3465 * given factory function. 3466 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 3467 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 3468 * a new object using the given constructor function. 3469 * 3470 * See the individual methods for more information and examples. 3471 */ 3472 3473/** 3474 * @ngdoc method 3475 * @name $provide#provider 3476 * @description 3477 * 3478 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 3479 * are constructor functions, whose instances are responsible for "providing" a factory for a 3480 * service. 3481 * 3482 * Service provider names start with the name of the service they provide followed by `Provider`. 3483 * For example, the {@link ng.$log $log} service has a provider called 3484 * {@link ng.$logProvider $logProvider}. 3485 * 3486 * Service provider objects can have additional methods which allow configuration of the provider 3487 * and its service. Importantly, you can configure what kind of service is created by the `$get` 3488 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 3489 * method {@link ng.$logProvider#debugEnabled debugEnabled} 3490 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 3491 * console or not. 3492 * 3493 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 3494 'Provider'` key. 3495 * @param {(Object|function())} provider If the provider is: 3496 * 3497 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 3498 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 3499 * - `Constructor`: a new instance of the provider will be created using 3500 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 3501 * 3502 * @returns {Object} registered provider instance 3503 3504 * @example 3505 * 3506 * The following example shows how to create a simple event tracking service and register it using 3507 * {@link auto.$provide#provider $provide.provider()}. 3508 * 3509 * ```js 3510 * // Define the eventTracker provider 3511 * function EventTrackerProvider() { 3512 * var trackingUrl = '/track'; 3513 * 3514 * // A provider method for configuring where the tracked events should been saved 3515 * this.setTrackingUrl = function(url) { 3516 * trackingUrl = url; 3517 * }; 3518 * 3519 * // The service factory function 3520 * this.$get = ['$http', function($http) { 3521 * var trackedEvents = {}; 3522 * return { 3523 * // Call this to track an event 3524 * event: function(event) { 3525 * var count = trackedEvents[event] || 0; 3526 * count += 1; 3527 * trackedEvents[event] = count; 3528 * return count; 3529 * }, 3530 * // Call this to save the tracked events to the trackingUrl 3531 * save: function() { 3532 * $http.post(trackingUrl, trackedEvents); 3533 * } 3534 * }; 3535 * }]; 3536 * } 3537 * 3538 * describe('eventTracker', function() { 3539 * var postSpy; 3540 * 3541 * beforeEach(module(function($provide) { 3542 * // Register the eventTracker provider 3543 * $provide.provider('eventTracker', EventTrackerProvider); 3544 * })); 3545 * 3546 * beforeEach(module(function(eventTrackerProvider) { 3547 * // Configure eventTracker provider 3548 * eventTrackerProvider.setTrackingUrl('/custom-track'); 3549 * })); 3550 * 3551 * it('tracks events', inject(function(eventTracker) { 3552 * expect(eventTracker.event('login')).toEqual(1); 3553 * expect(eventTracker.event('login')).toEqual(2); 3554 * })); 3555 * 3556 * it('saves to the tracking url', inject(function(eventTracker, $http) { 3557 * postSpy = spyOn($http, 'post'); 3558 * eventTracker.event('login'); 3559 * eventTracker.save(); 3560 * expect(postSpy).toHaveBeenCalled(); 3561 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 3562 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 3563 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 3564 * })); 3565 * }); 3566 * ``` 3567 */ 3568 3569/** 3570 * @ngdoc method 3571 * @name $provide#factory 3572 * @description 3573 * 3574 * Register a **service factory**, which will be called to return the service instance. 3575 * This is short for registering a service where its provider consists of only a `$get` property, 3576 * which is the given service factory function. 3577 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 3578 * configure your service in a provider. 3579 * 3580 * @param {string} name The name of the instance. 3581 * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand 3582 * for `$provide.provider(name, {$get: $getFn})`. 3583 * @returns {Object} registered provider instance 3584 * 3585 * @example 3586 * Here is an example of registering a service 3587 * ```js 3588 * $provide.factory('ping', ['$http', function($http) { 3589 * return function ping() { 3590 * return $http.send('/ping'); 3591 * }; 3592 * }]); 3593 * ``` 3594 * You would then inject and use this service like this: 3595 * ```js 3596 * someModule.controller('Ctrl', ['ping', function(ping) { 3597 * ping(); 3598 * }]); 3599 * ``` 3600 */ 3601 3602 3603/** 3604 * @ngdoc method 3605 * @name $provide#service 3606 * @description 3607 * 3608 * Register a **service constructor**, which will be invoked with `new` to create the service 3609 * instance. 3610 * This is short for registering a service where its provider's `$get` property is the service 3611 * constructor function that will be used to instantiate the service instance. 3612 * 3613 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 3614 * as a type/class. 3615 * 3616 * @param {string} name The name of the instance. 3617 * @param {Function} constructor A class (constructor function) that will be instantiated. 3618 * @returns {Object} registered provider instance 3619 * 3620 * @example 3621 * Here is an example of registering a service using 3622 * {@link auto.$provide#service $provide.service(class)}. 3623 * ```js 3624 * var Ping = function($http) { 3625 * this.$http = $http; 3626 * }; 3627 * 3628 * Ping.$inject = ['$http']; 3629 * 3630 * Ping.prototype.send = function() { 3631 * return this.$http.get('/ping'); 3632 * }; 3633 * $provide.service('ping', Ping); 3634 * ``` 3635 * You would then inject and use this service like this: 3636 * ```js 3637 * someModule.controller('Ctrl', ['ping', function(ping) { 3638 * ping.send(); 3639 * }]); 3640 * ``` 3641 */ 3642 3643 3644/** 3645 * @ngdoc method 3646 * @name $provide#value 3647 * @description 3648 * 3649 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 3650 * number, an array, an object or a function. This is short for registering a service where its 3651 * provider's `$get` property is a factory function that takes no arguments and returns the **value 3652 * service**. 3653 * 3654 * Value services are similar to constant services, except that they cannot be injected into a 3655 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 3656 * an Angular 3657 * {@link auto.$provide#decorator decorator}. 3658 * 3659 * @param {string} name The name of the instance. 3660 * @param {*} value The value. 3661 * @returns {Object} registered provider instance 3662 * 3663 * @example 3664 * Here are some examples of creating value services. 3665 * ```js 3666 * $provide.value('ADMIN_USER', 'admin'); 3667 *
3668 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 3669 * 3670 * $provide.value('halfOf', function(value) { 3671 * return value / 2; 3672 * }); 3673 * ``` 3674 */ 3675 3676 3677/** 3678 * @ngdoc method 3679 * @name $provide#constant 3680 * @description 3681 * 3682 * Register a **constant service**, such as a string, a number, an array, an object or a function, 3683 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be 3684 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 3685 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 3686 * 3687 * @param {string} name The name of the constant. 3688 * @param {*} value The constant value. 3689 * @returns {Object} registered instance 3690 * 3691 * @example 3692 * Here a some examples of creating constants: 3693 * ```js 3694 * $provide.constant('SHARD_HEIGHT', 306); 3695 * 3696 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 3697 * 3698 * $provide.constant('double', function(value) { 3699 * return value * 2; 3700 * }); 3701 * ``` 3702 */ 3703 3704 3705/** 3706 * @ngdoc method 3707 * @name $provide#decorator 3708 * @description 3709 * 3710 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 3711 * intercepts the creation of a service, allowing it to override or modify the behaviour of the 3712 * service. The object returned by the decorator may be the original service, or a new service 3713 * object which replaces or wraps and delegates to the original service. 3714 * 3715 * @param {string} name The name of the service to decorate. 3716 * @param {function()} decorator This function will be invoked when the service needs to be 3717 * instantiated and should return the decorated service instance. The function is called using 3718 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 3719 * Local injection arguments: 3720 * 3721 * * `$delegate` - The original service instance, which can be monkey patched, configured, 3722 * decorated or delegated to. 3723 * 3724 * @example 3725 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 3726 * calls to {@link ng.$log#error $log.warn()}. 3727 * ```js 3728 * $provide.decorator('$log', ['$delegate', function($delegate) { 3729 * $delegate.warn = $delegate.error; 3730 * return $delegate; 3731 * }]); 3732 * ``` 3733 */ 3734 3735 3736function createInjector(modulesToLoad) { 3737 var INSTANTIATING = {}, 3738 providerSuffix = 'Provider', 3739 path = [], 3740 loadedModules = new HashMap([], true), 3741 providerCache = { 3742 $provide: { 3743 provider: supportObject(provider), 3744 factory: supportObject(factory), 3745 service: supportObject(service), 3746 value: supportObject(value), 3747 constant: supportObject(constant), 3748 decorator: decorator 3749 } 3750 }, 3751 providerInjector = (providerCache.$injector = 3752 createInternalInjector(providerCache, function() { 3753 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 3754 })), 3755 instanceCache = {}, 3756 instanceInjector = (instanceCache.$injector = 3757 createInternalInjector(instanceCache, function(servicename) { 3758 var provider = providerInjector.get(servicename + providerSuffix); 3759 return instanceInjector.invoke(provider.$get, provider); 3760 })); 3761 3762 3763 forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); 3764 3765 return instanceInjector; 3766 3767 //////////////////////////////////// 3768 // $provider 3769 //////////////////////////////////// 3770 3771 function supportObject(delegate) { 3772 return function(key, value) { 3773 if (isObject(key)) { 3774 forEach(key, reverseParams(delegate)); 3775 } else { 3776 return delegate(key, value); 3777 } 3778 }; 3779 } 3780 3781 function provider(name, provider_) { 3782 assertNotHasOwnProperty(name, 'service'); 3783 if (isFunction(provider_) || isArray(provider_)) { 3784 provider_ = providerInjector.instantiate(provider_); 3785 } 3786 if (!provider_.$get) { 3787 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 3788 } 3789 return providerCache[name + providerSuffix] = provider_; 3790 } 3791 3792 function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } 3793 3794 function service(name, constructor) { 3795 return factory(name, ['$injector', function($injector) { 3796 return $injector.instantiate(constructor); 3797 }]); 3798 } 3799 3800 function value(name, val) { return factory(name, valueFn(val)); } 3801 3802 function constant(name, value) { 3803 assertNotHasOwnProperty(name, 'constant'); 3804 providerCache[name] = value; 3805 instanceCache[name] = value; 3806 } 3807 3808 function decorator(serviceName, decorFn) { 3809 var origProvider = providerInjector.get(serviceName + providerSuffix), 3810 orig$get = origProvider.$get; 3811 3812 origProvider.$get = function() { 3813 var origInstance = instanceInjector.invoke(orig$get, origProvider); 3814 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 3815 }; 3816 } 3817 3818 //////////////////////////////////// 3819 // Module Loading 3820 //////////////////////////////////// 3821 function loadModules(modulesToLoad){ 3822 var runBlocks = [], moduleFn, invokeQueue, i, ii;
3823 forEach(modulesToLoad, function(module) { 3824 if (loadedModules.get(module)) return; 3825 loadedModules.put(module, true); 3826 3827 try { 3828 if (isString(module)) { 3829 moduleFn = angularModule(module); 3830 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 3831 3832 for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { 3833 var invokeArgs = invokeQueue[i], 3834 provider = providerInjector.get(invokeArgs[0]); 3835 3836 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 3837 } 3838 } else if (isFunction(module)) { 3839 runBlocks.push(providerInjector.invoke(module)); 3840 } else if (isArray(module)) { 3841 runBlocks.push(providerInjector.invoke(module)); 3842 } else { 3843 assertArgFn(module, 'module'); 3844 } 3845 } catch (e) { 3846 if (isArray(module)) { 3847 module = module[module.length - 1]; 3848 } 3849 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 3850 // Safari & FF's stack traces don't contain error.message content 3851 // unlike those of Chrome and IE 3852 // So if stack doesn't contain message, we create a new string that contains both. 3853 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 3854 /* jshint -W022 */ 3855 e = e.message + '\n' + e.stack; 3856 } 3857 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 3858 module, e.stack || e.message || e); 3859 } 3860 }); 3861 return runBlocks; 3862 } 3863 3864 //////////////////////////////////// 3865 // internal Injector 3866 //////////////////////////////////// 3867 3868 function createInternalInjector(cache, factory) { 3869 3870 function getService(serviceName) { 3871 if (cache.hasOwnProperty(serviceName)) { 3872 if (cache[serviceName] === INSTANTIATING) { 3873 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 3874 serviceName + ' <- ' + path.join(' <- ')); 3875 } 3876 return cache[serviceName]; 3877 } else { 3878 try { 3879 path.unshift(serviceName); 3880 cache[serviceName] = INSTANTIATING; 3881 return cache[serviceName] = factory(serviceName); 3882 } catch (err) { 3883 if (cache[serviceName] === INSTANTIATING) { 3884 delete cache[serviceName]; 3885 } 3886 throw err; 3887 } finally { 3888 path.shift(); 3889 } 3890 } 3891 } 3892 3893 function invoke(fn, self, locals){ 3894 var args = [], 3895 $inject = annotate(fn), 3896 length, i, 3897 key; 3898 3899 for(i = 0, length = $inject.length; i < length; i++) { 3900 key = $inject[i]; 3901 if (typeof key !== 'string') { 3902 throw $injectorMinErr('itkn', 3903 'Incorrect injection token! Expected service name as string, got {0}', key); 3904 } 3905 args.push( 3906 locals && locals.hasOwnProperty(key) 3907 ? locals[key] 3908 : getService(key) 3909 ); 3910 } 3911 if (isArray(fn)) { 3912 fn = fn[length]; 3913 } 3914 3915 // http://jsperf.com/angularjs-invoke-apply-vs-switch 3916 // #5388 3917 return fn.apply(self, args); 3918 } 3919 3920 function instantiate(Type, locals) { 3921 var Constructor = function() {}, 3922 instance, returnedValue; 3923 3924 // Check if Type is annotated and use just the given function at n-1 as parameter 3925 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 3926 Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; 3927 instance = new Constructor(); 3928 returnedValue = invoke(Type, instance, locals); 3929 3930 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 3931 } 3932 3933 return { 3934 invoke: invoke, 3935 instantiate: instantiate, 3936 get: getService, 3937 annotate: annotate, 3938 has: function(name) { 3939 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 3940 } 3941 }; 3942 } 3943} 3944 3945/** 3946 * @ngdoc service 3947 * @name $anchorScroll 3948 * @kind function 3949 * @requires $window 3950 * @requires $location 3951 * @requires $rootScope 3952 * 3953 * @description 3954 * When called, it checks current value of `$location.hash()` and scrolls to the related element, 3955 * according to rules specified in 3956 * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). 3957 * 3958 * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. 3959 * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. 3960 * 3961 * @example 3962 <example> 3963 <file name="index.html"> 3964 <div id="scrollArea" ng-controller="ScrollCtrl"> 3965 <a ng-click="gotoBottom()">Go to bottom</a> 3966 <a id="bottom"></a> You're at the bottom! 3967 </div> 3968 </file> 3969 <file name="script.js"> 3970 function ScrollCtrl($scope, $location, $anchorScroll) { 3971 $scope.gotoBottom = function (){ 3972 // set the location.hash to the id of
3973 // the element you wish to scroll to. 3974 $location.hash('bottom'); 3975 3976 // call $anchorScroll() 3977 $anchorScroll(); 3978 }; 3979 } 3980 </file> 3981 <file name="style.css"> 3982 #scrollArea { 3983 height: 350px; 3984 overflow: auto; 3985 } 3986 3987 #bottom { 3988 display: block; 3989 margin-top: 2000px; 3990 } 3991 </file> 3992 </example> 3993 */ 3994function $AnchorScrollProvider() { 3995 3996 var autoScrollingEnabled = true; 3997 3998 this.disableAutoScrolling = function() { 3999 autoScrollingEnabled = false; 4000 }; 4001 4002 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 4003 var document = $window.document; 4004 4005 // helper function to get first anchor from a NodeList 4006 // can't use filter.filter, as it accepts only instances of Array 4007 // and IE can't convert NodeList to an array using [].slice 4008 // TODO(vojta): use filter if we change it to accept lists as well 4009 function getFirstAnchor(list) { 4010 var result = null; 4011 forEach(list, function(element) { 4012 if (!result && lowercase(element.nodeName) === 'a') result = element; 4013 }); 4014 return result; 4015 } 4016 4017 function scroll() { 4018 var hash = $location.hash(), elm; 4019 4020 // empty hash, scroll to the top of the page 4021 if (!hash) $window.scrollTo(0, 0); 4022 4023 // element with given id 4024 else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); 4025 4026 // first anchor with given name :-D 4027 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); 4028 4029 // no element and hash == 'top', scroll to the top of the page 4030 else if (hash === 'top') $window.scrollTo(0, 0); 4031 } 4032 4033 // does not scroll when user clicks on anchor link that is currently on 4034 // (no url change, no $location.hash() change), browser native does scroll 4035 if (autoScrollingEnabled) { 4036 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 4037 function autoScrollWatchAction() { 4038 $rootScope.$evalAsync(scroll); 4039 }); 4040 } 4041 4042 return scroll; 4043 }]; 4044} 4045 4046var $animateMinErr = minErr('$animate'); 4047 4048/** 4049 * @ngdoc provider 4050 * @name $animateProvider 4051 * 4052 * @description 4053 * Default implementation of $animate that doesn't perform any animations, instead just 4054 * synchronously performs DOM 4055 * updates and calls done() callbacks. 4056 * 4057 * In order to enable animations the ngAnimate module has to be loaded. 4058 * 4059 * To see the functional implementation check out src/ngAnimate/animate.js 4060 */ 4061var $AnimateProvider = ['$provide', function($provide) { 4062 4063 4064 this.$$selectors = {}; 4065 4066 4067 /** 4068 * @ngdoc method 4069 * @name $animateProvider#register 4070 * 4071 * @description 4072 * Registers a new injectable animation factory function. The factory function produces the 4073 * animation object which contains callback functions for each event that is expected to be 4074 * animated. 4075 * 4076 * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` 4077 * must be called once the element animation is complete. If a function is returned then the 4078 * animation service will use this function to cancel the animation whenever a cancel event is 4079 * triggered. 4080 * 4081 * 4082 * ```js 4083 * return { 4084 * eventFn : function(element, done) { 4085 * //code to run the animation 4086 * //once complete, then run done() 4087 * return function cancellationFunction() { 4088 * //code to cancel the animation 4089 * } 4090 * } 4091 * } 4092 * ``` 4093 * 4094 * @param {string} name The name of the animation. 4095 * @param {Function} factory The factory function that will be executed to return the animation 4096 * object. 4097 */ 4098 this.register = function(name, factory) { 4099 var key = name + '-animation'; 4100 if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', 4101 "Expecting class selector starting with '.' got '{0}'.", name); 4102 this.$$selectors[name.substr(1)] = key; 4103 $provide.factory(key, factory); 4104 }; 4105 4106 /** 4107 * @ngdoc method 4108 * @name $animateProvider#classNameFilter 4109 * 4110 * @description 4111 * Sets and/or returns the CSS class regular expression that is checked when performing 4112 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 4113 * therefore enable $animate to attempt to perform an animation on any element. 4114 * When setting the classNameFilter value, animations will only be performed on elements 4115 * that successfully match the filter expression. This in turn can boost performance 4116 * for low-powered devices as well as applications containing a lot of structural operations. 4117 * @param {RegExp=} expression The className expression which will be checked against all animations 4118 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 4119 */ 4120 this.classNameFilter = function(expression) { 4121 if(arguments.length === 1) { 4122 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 4123 } 4124 return this.$$classNameFilter; 4125 }; 4126 4127 this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) { 4128 4129 function async(fn) { 4130 fn && $$asyncCallback(fn); 4131 } 4132 4133 /** 4134 * 4135 * @ngdoc service 4136 * @name $animate
4137 * @description The $animate service provides rudimentary DOM manipulation functions to 4138 * insert, remove and move elements within the DOM, as well as adding and removing classes. 4139 * This service is the core service used by the ngAnimate $animator service which provides 4140 * high-level animation hooks for CSS and JavaScript. 4141 * 4142 * $animate is available in the AngularJS core, however, the ngAnimate module must be included 4143 * to enable full out animation support. Otherwise, $animate will only perform simple DOM 4144 * manipulation operations. 4145 * 4146 * To learn more about enabling animation support, click here to visit the {@link ngAnimate 4147 * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service 4148 * page}. 4149 */ 4150 return { 4151 4152 /** 4153 * 4154 * @ngdoc method 4155 * @name $animate#enter 4156 * @kind function 4157 * @description Inserts the element into the DOM either after the `after` element or within 4158 * the `parent` element. Once complete, the done() callback will be fired (if provided). 4159 * @param {DOMElement} element the element which will be inserted into the DOM 4160 * @param {DOMElement} parent the parent element which will append the element as 4161 * a child (if the after element is not present) 4162 * @param {DOMElement} after the sibling element which will append the element 4163 * after itself 4164 * @param {Function=} done callback function that will be called after the element has been 4165 * inserted into the DOM 4166 */ 4167 enter : function(element, parent, after, done) { 4168 if (after) { 4169 after.after(element); 4170 } else { 4171 if (!parent || !parent[0]) { 4172 parent = after.parent(); 4173 } 4174 parent.append(element); 4175 } 4176 async(done); 4177 }, 4178 4179 /** 4180 * 4181 * @ngdoc method 4182 * @name $animate#leave 4183 * @kind function 4184 * @description Removes the element from the DOM. Once complete, the done() callback will be 4185 * fired (if provided). 4186 * @param {DOMElement} element the element which will be removed from the DOM 4187 * @param {Function=} done callback function that will be called after the element has been 4188 * removed from the DOM 4189 */ 4190 leave : function(element, done) { 4191 element.remove(); 4192 async(done); 4193 }, 4194 4195 /** 4196 * 4197 * @ngdoc method 4198 * @name $animate#move 4199 * @kind function 4200 * @description Moves the position of the provided element within the DOM to be placed 4201 * either after the `after` element or inside of the `parent` element. Once complete, the 4202 * done() callback will be fired (if provided). 4203 * 4204 * @param {DOMElement} element the element which will be moved around within the 4205 * DOM 4206 * @param {DOMElement} parent the parent element where the element will be 4207 * inserted into (if the after element is not present) 4208 * @param {DOMElement} after the sibling element where the element will be 4209 * positioned next to 4210 * @param {Function=} done the callback function (if provided) that will be fired after the 4211 * element has been moved to its new position 4212 */ 4213 move : function(element, parent, after, done) { 4214 // Do not remove element before insert. Removing will cause data associated with the 4215 // element to be dropped. Insert will implicitly do the remove. 4216 this.enter(element, parent, after, done); 4217 }, 4218 4219 /** 4220 * 4221 * @ngdoc method 4222 * @name $animate#addClass 4223 * @kind function 4224 * @description Adds the provided className CSS class value to the provided element. Once 4225 * complete, the done() callback will be fired (if provided). 4226 * @param {DOMElement} element the element which will have the className value 4227 * added to it 4228 * @param {string} className the CSS class which will be added to the element 4229 * @param {Function=} done the callback function (if provided) that will be fired after the 4230 * className value has been added to the element 4231 */ 4232 addClass : function(element, className, done) { 4233 className = isString(className) ? 4234 className : 4235 isArray(className) ? className.join(' ') : '';
4236 forEach(element, function (element) { 4237 jqLiteAddClass(element, className); 4238 }); 4239 async(done); 4240 }, 4241 4242 /** 4243 * 4244 * @ngdoc method 4245 * @name $animate#removeClass 4246 * @kind function 4247 * @description Removes the provided className CSS class value from the provided element. 4248 * Once complete, the done() callback will be fired (if provided). 4249 * @param {DOMElement} element the element which will have the className value 4250 * removed from it 4251 * @param {string} className the CSS class which will be removed from the element 4252 * @param {Function=} done the callback function (if provided) that will be fired after the 4253 * className value has been removed from the element 4254 */ 4255 removeClass : function(element, className, done) { 4256 className = isString(className) ? 4257 className : 4258 isArray(className) ? className.join(' ') : ''; 4259 forEach(element, function (element) { 4260 jqLiteRemoveClass(element, className); 4261 }); 4262 async(done); 4263 }, 4264 4265 /** 4266 * 4267 * @ngdoc method 4268 * @name $animate#setClass 4269 * @kind function 4270 * @description Adds and/or removes the given CSS classes to and from the element. 4271 * Once complete, the done() callback will be fired (if provided). 4272 * @param {DOMElement} element the element which will have its CSS classes changed 4273 * removed from it 4274 * @param {string} add the CSS classes which will be added to the element 4275 * @param {string} remove the CSS class which will be removed from the element 4276 * @param {Function=} done the callback function (if provided) that will be fired after the 4277 * CSS classes have been set on the element 4278 */ 4279 setClass : function(element, add, remove, done) { 4280 forEach(element, function (element) { 4281 jqLiteAddClass(element, add); 4282 jqLiteRemoveClass(element, remove); 4283 }); 4284 async(done); 4285 }, 4286 4287 enabled : noop 4288 }; 4289 }]; 4290}]; 4291 4292function $$AsyncCallbackProvider(){ 4293 this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { 4294 return $$rAF.supported 4295 ? function(fn) { return $$rAF(fn); } 4296 : function(fn) { 4297 return $timeout(fn, 0, false); 4298 }; 4299 }]; 4300} 4301 4302/** 4303 * ! This is a private undocumented service ! 4304 * 4305 * @name $browser 4306 * @requires $log 4307 * @description 4308 * This object has two goals: 4309 * 4310 * - hide all the global state in the browser caused by the window object 4311 * - abstract away all the browser specific features and inconsistencies 4312 * 4313 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 4314 * service, which can be used for convenient testing of the application without the interaction with 4315 * the real browser apis. 4316 */ 4317/** 4318 * @param {object} window The global window object. 4319 * @param {object} document jQuery wrapped document. 4320 * @param {function()} XHR XMLHttpRequest constructor. 4321 * @param {object} $log console.log or an object with the same interface. 4322 * @param {object} $sniffer $sniffer service 4323 */ 4324function Browser(window, document, $log, $sniffer) { 4325 var self = this, 4326 rawDocument = document[0], 4327 location = window.location, 4328 history = window.history, 4329 setTimeout = window.setTimeout, 4330 clearTimeout = window.clearTimeout, 4331 pendingDeferIds = {}; 4332 4333 self.isMock = false; 4334 4335 var outstandingRequestCount = 0; 4336 var outstandingRequestCallbacks = []; 4337 4338 // TODO(vojta): remove this temporary api 4339 self.$$completeOutstandingRequest = completeOutstandingRequest; 4340 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 4341 4342 /** 4343 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 4344 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 4345 */ 4346 function completeOutstandingRequest(fn) { 4347 try { 4348 fn.apply(null, sliceArgs(arguments, 1)); 4349 } finally { 4350 outstandingRequestCount--; 4351 if (outstandingRequestCount === 0) { 4352 while(outstandingRequestCallbacks.length) { 4353 try { 4354 outstandingRequestCallbacks.pop()(); 4355 } catch (e) { 4356 $log.error(e); 4357 } 4358 } 4359 } 4360 } 4361 } 4362 4363 /** 4364 * @private 4365 * Note: this method is used only by scenario runner 4366 * TODO(vojta): prefix this method with $$ ? 4367 * @param {function()} callback Function that will be called when no outstanding request 4368 */ 4369 self.notifyWhenNoOutstandingRequests = function(callback) { 4370 // force browser to execute all pollFns - this is needed so that cookies and other pollers fire 4371 // at some deterministic time in respect to the test runner's actions. Leaving things up to the 4372 // regular poller would result in flaky tests.
4373 forEach(pollFns, function(pollFn){ pollFn(); }); 4374 4375 if (outstandingRequestCount === 0) { 4376 callback(); 4377 } else { 4378 outstandingRequestCallbacks.push(callback); 4379 } 4380 }; 4381 4382 ////////////////////////////////////////////////////////////// 4383 // Poll Watcher API 4384 ////////////////////////////////////////////////////////////// 4385 var pollFns = [], 4386 pollTimeout; 4387 4388 /** 4389 * @name $browser#addPollFn 4390 * 4391 * @param {function()} fn Poll function to add 4392 * 4393 * @description 4394 * Adds a function to the list of functions that poller periodically executes, 4395 * and starts polling if not started yet. 4396 * 4397 * @returns {function()} the added function 4398 */ 4399 self.addPollFn = function(fn) { 4400 if (isUndefined(pollTimeout)) startPoller(100, setTimeout); 4401 pollFns.push(fn); 4402 return fn; 4403 }; 4404 4405 /** 4406 * @param {number} interval How often should browser call poll functions (ms) 4407 * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. 4408 * 4409 * @description 4410 * Configures the poller to run in the specified intervals, using the specified 4411 * setTimeout fn and kicks it off. 4412 */ 4413 function startPoller(interval, setTimeout) { 4414 (function check() { 4415 forEach(pollFns, function(pollFn){ pollFn(); }); 4416 pollTimeout = setTimeout(check, interval); 4417 })(); 4418 } 4419 4420 ////////////////////////////////////////////////////////////// 4421 // URL API 4422 ////////////////////////////////////////////////////////////// 4423 4424 var lastBrowserUrl = location.href, 4425 baseElement = document.find('base'), 4426 newLocation = null; 4427 4428 /** 4429 * @name $browser#url 4430 * 4431 * @description 4432 * GETTER: 4433 * Without any argument, this method just returns current value of location.href. 4434 * 4435 * SETTER: 4436 * With at least one argument, this method sets url to new value. 4437 * If html5 history api supported, pushState/replaceState is used, otherwise 4438 * location.href/location.replace is used. 4439 * Returns its own instance to allow chaining 4440 * 4441 * NOTE: this api is intended for use only by the $location service. Please use the 4442 * {@link ng.$location $location service} to change url. 4443 * 4444 * @param {string} url New url (when used as setter) 4445 * @param {boolean=} replace Should new url replace current history record ? 4446 */ 4447 self.url = function(url, replace) { 4448 // Android Browser BFCache causes location, history reference to become stale. 4449 if (location !== window.location) location = window.location; 4450 if (history !== window.history) history = window.history; 4451 4452 // setter 4453 if (url) { 4454 if (lastBrowserUrl == url) return; 4455 lastBrowserUrl = url; 4456 if ($sniffer.history) { 4457 if (replace) history.replaceState(null, '', url); 4458 else { 4459 history.pushState(null, '', url); 4460 // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 4461 baseElement.attr('href', baseElement.attr('href')); 4462 } 4463 } else { 4464 newLocation = url; 4465 if (replace) { 4466 location.replace(url); 4467 } else { 4468 location.href = url; 4469 } 4470 } 4471 return self; 4472 // getter 4473 } else { 4474 // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href 4475 // methods not updating location.href synchronously. 4476 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 4477 return newLocation || location.href.replace(/%27/g,"'"); 4478 } 4479 }; 4480 4481 var urlChangeListeners = [], 4482 urlChangeInit = false; 4483 4484 function fireUrlChange() { 4485 newLocation = null; 4486 if (lastBrowserUrl == self.url()) return; 4487 4488 lastBrowserUrl = self.url(); 4489 forEach(urlChangeListeners, function(listener) { 4490 listener(self.url()); 4491 }); 4492 } 4493 4494 /** 4495 * @name $browser#onUrlChange 4496 * 4497 * @description 4498 * Register callback function that will be called, when url changes. 4499 * 4500 * It's only called when the url is changed from outside of angular: 4501 * - user types different url into address bar 4502 * - user clicks on history (forward/back) button 4503 * - user clicks on a link 4504 * 4505 * It's not called when url is changed by $browser.url() method 4506 * 4507 * The listener gets called with new url as parameter. 4508 * 4509 * NOTE: this api is intended for use only by the $location service. Please use the 4510 * {@link ng.$location $location service} to monitor url changes in angular apps. 4511 * 4512 * @param {function(string)} listener Listener function to be called when url changes. 4513 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 4514 */ 4515 self.onUrlChange = function(callback) { 4516 // TODO(vojta): refactor to use node's syntax for events 4517 if (!urlChangeInit) { 4518 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 4519 // don't fire popstate when user change the address bar and don't fire hashchange when url 4520 // changed by push/replaceState 4521 4522 // html5 history api - popstate event 4523 if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); 4524 // hashchange event 4525 if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); 4526 // polling 4527 else self.addPollFn(fireUrlChange); 4528 4529 urlChangeInit = true; 4530 } 4531 4532 urlChangeListeners.push(callback); 4533 return callback; 4534 }; 4535 4536 ////////////////////////////////////////////////////////////// 4537 // Misc API 4538 ////////////////////////////////////////////////////////////// 4539 4540 /** 4541 * @name $browser#baseHref 4542 * 4543 * @description 4544 * Returns current <base href> 4545 * (always relative - without domain) 4546 * 4547 * @returns {string} The current base href 4548 */ 4549 self.baseHref = function() { 4550 var href = baseElement.attr('href'); 4551 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 4552 }; 4553 4554 ////////////////////////////////////////////////////////////// 4555 // Cookies API 4556 ////////////////////////////////////////////////////////////// 4557 var lastCookies = {}; 4558 var lastCookieString = ''; 4559 var cookiePath = self.baseHref(); 4560 4561 /** 4562 * @name $browser#cookies 4563 * 4564 * @param {string=} name Cookie name 4565 * @param {string=} value Cookie value 4566 * 4567 * @description 4568 * The cookies method provides a 'private' low level access to browser cookies. 4569 * It is not meant to be used directly, use the $cookie service instead. 4570 * 4571 * The return values vary depending on the arguments that the method was called with as follows: 4572 * 4573 * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify 4574 * it 4575 * - cookies(name, value) -> set name to value, if value is undefined delete the cookie 4576 * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that 4577 * way) 4578 * 4579 * @returns {Object} Hash of all cookies (if called without any parameter) 4580 */ 4581 self.cookies = function(name, value) { 4582 /* global escape: false, unescape: false */ 4583 var cookieLength, cookieArray, cookie, i, index; 4584 4585 if (name) { 4586 if (value === undefined) { 4587 rawDocument.cookie = escape(name) + "=;
4587path=" + cookiePath + 4588 ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; 4589 } else { 4590 if (isString(value)) { 4591 cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + 4592 ';path=' + cookiePath).length + 1; 4593 4594 // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: 4595 // - 300 cookies 4596 // - 20 cookies per unique domain 4597 // - 4096 bytes per cookie 4598 if (cookieLength > 4096) { 4599 $log.warn("Cookie '"+ name + 4600 "' possibly not set or overflowed because it was too large ("+ 4601 cookieLength + " > 4096 bytes)!"); 4602 } 4603 } 4604 } 4605 } else { 4606 if (rawDocument.cookie !== lastCookieString) { 4607 lastCookieString = rawDocument.cookie; 4608 cookieArray = lastCookieString.split("; "); 4609 lastCookies = {}; 4610 4611 for (i = 0; i < cookieArray.length; i++) { 4612 cookie = cookieArray[i]; 4613 index = cookie.indexOf('='); 4614 if (index > 0) { //ignore nameless cookies 4615 name = unescape(cookie.substring(0, index)); 4616 // the first value that is seen for a cookie is the most 4617 // specific one. values for the same cookie name that 4618 // follow are for less specific paths. 4619 if (lastCookies[name] === undefined) { 4620 lastCookies[name] = unescape(cookie.substring(index + 1)); 4621 } 4622 } 4623 } 4624 } 4625 return lastCookies; 4626 } 4627 }; 4628 4629 4630 /** 4631 * @name $browser#defer 4632 * @param {function()} fn A function, who's execution should be deferred. 4633 * @param {number=} [delay=0] of milliseconds to defer the function execution. 4634 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 4635 * 4636 * @description 4637 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 4638 * 4639 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 4640 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 4641 * via `$browser.defer.flush()`. 4642 * 4643 */ 4644 self.defer = function(fn, delay) { 4645 var timeoutId; 4646 outstandingRequestCount++; 4647 timeoutId = setTimeout(function() { 4648 delete pendingDeferIds[timeoutId]; 4649 completeOutstandingRequest(fn); 4650 }, delay || 0); 4651 pendingDeferIds[timeoutId] = true; 4652 return timeoutId; 4653 }; 4654 4655 4656 /** 4657 * @name $browser#defer.cancel 4658 * 4659 * @description 4660 * Cancels a deferred task identified with `deferId`. 4661 * 4662 * @param {*} deferId Token returned by the `$browser.defer` function. 4663 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 4664 * canceled. 4665 */ 4666 self.defer.cancel = function(deferId) { 4667 if (pendingDeferIds[deferId]) { 4668 delete pendingDeferIds[deferId]; 4669 clearTimeout(deferId); 4670 completeOutstandingRequest(noop); 4671 return true; 4672 } 4673 return false; 4674 }; 4675 4676} 4677 4678function $BrowserProvider(){ 4679 this.$get = ['$window', '$log', '$sniffer', '$document', 4680 function( $window, $log, $sniffer, $document){ 4681 return new Browser($window, $document, $log, $sniffer); 4682 }]; 4683} 4684 4685/** 4686 * @ngdoc service 4687 * @name $cacheFactory 4688 * 4689 * @description 4690 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 4691 * them. 4692 * 4693 * ```js 4694 * 4695 * var cache = $cacheFactory('cacheId'); 4696 * expect($cacheFactory.get('cacheId')).toBe(cache); 4697 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 4698 * 4699 * cache.put("key", "value"); 4700 * cache.put("another key", "another value"); 4701 * 4702 * // We've specified no options on creation 4703 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 4704 * 4705 * ``` 4706 * 4707 * 4708 * @param {string} cacheId Name or id of the newly created cache. 4709 * @param {object=} options Options object that specifies the cache behavior. Properties: 4710 * 4711 * - `{number=}` `capacity` â turns the cache into LRU cache. 4712 * 4713 * @returns {object} Newly created cache object with the following set of methods: 4714 * 4715 * - `{object}` `info()` â Returns id, size, and options of cache. 4716 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 4717 * it. 4718 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 4719 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 4720 * - `{void}` `removeAll()` â Removes all cached values. 4721 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 4722 * 4723 * @example 4724 <example module="cacheExampleApp"> 4725 <file name="index.html"> 4726 <div ng-controller="CacheController"> 4727 <input ng-model="newCacheKey" placeholder="Key"> 4728 <input ng-model="newCacheValue" placeholder="Value"> 4729 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 4730 4731 <p ng-if="keys.length">Cached Values</p> 4732 <div ng-repeat="key in keys">
4733 <span ng-bind="key"></span> 4734 <span>: </span> 4735 <b ng-bind="cache.get(key)"></b> 4736 </div> 4737 4738 <p>Cache Info</p> 4739 <div ng-repeat="(key, value) in cache.info()"> 4740 <span ng-bind="key"></span> 4741 <span>: </span> 4742 <b ng-bind="value"></b> 4743 </div> 4744 </div> 4745 </file> 4746 <file name="script.js"> 4747 angular.module('cacheExampleApp', []). 4748 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 4749 $scope.keys = []; 4750 $scope.cache = $cacheFactory('cacheId'); 4751 $scope.put = function(key, value) { 4752 $scope.cache.put(key, value); 4753 $scope.keys.push(key); 4754 }; 4755 }]); 4756 </file> 4757 <file name="style.css"> 4758 p { 4759 margin: 10px 0 3px; 4760 } 4761 </file> 4762 </example> 4763 */ 4764function $CacheFactoryProvider() { 4765 4766 this.$get = function() { 4767 var caches = {}; 4768 4769 function cacheFactory(cacheId, options) { 4770 if (cacheId in caches) { 4771 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 4772 } 4773 4774 var size = 0, 4775 stats = extend({}, options, {id: cacheId}), 4776 data = {}, 4777 capacity = (options && options.capacity) || Number.MAX_VALUE, 4778 lruHash = {}, 4779 freshEnd = null, 4780 staleEnd = null; 4781 4782 /** 4783 * @ngdoc type 4784 * @name $cacheFactory.Cache 4785 * 4786 * @description 4787 * A cache object used to store and retrieve data, primarily used by 4788 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 4789 * templates and other data. 4790 * 4791 * ```js 4792 * angular.module('superCache') 4793 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 4794 * return $cacheFactory('super-cache'); 4795 * }]); 4796 * ``` 4797 * 4798 * Example test: 4799 * 4800 * ```js 4801 * it('should behave like a cache', inject(function(superCache) { 4802 * superCache.put('key', 'value'); 4803 * superCache.put('another key', 'another value'); 4804 * 4805 * expect(superCache.info()).toEqual({ 4806 * id: 'super-cache', 4807 * size: 2 4808 * }); 4809 * 4810 * superCache.remove('another key'); 4811 * expect(superCache.get('another key')).toBeUndefined(); 4812 * 4813 * superCache.removeAll(); 4814 * expect(superCache.info()).toEqual({ 4815 * id: 'super-cache', 4816 * size: 0 4817 * }); 4818 * })); 4819 * ``` 4820 */ 4821 return caches[cacheId] = { 4822 4823 /** 4824 * @ngdoc method 4825 * @name $cacheFactory.Cache#put 4826 * @kind function 4827 * 4828 * @description 4829 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 4830 * retrieved later, and incrementing the size of the cache if the key was not already 4831 * present in the cache. If behaving like an LRU cache, it will also remove stale 4832 * entries from the set. 4833 * 4834 * It will not insert undefined values into the cache. 4835 * 4836 * @param {string} key the key under which the cached data is stored. 4837 * @param {*} value the value to store alongside the key. If it is undefined, the key 4838 * will not be stored. 4839 * @returns {*} the value stored. 4840 */ 4841 put: function(key, value) { 4842 if (capacity < Number.MAX_VALUE) { 4843 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 4844 4845 refresh(lruEntry); 4846 } 4847 4848 if (isUndefined(value)) return; 4849 if (!(key in data)) size++; 4850 data[key] = value; 4851 4852 if (size > capacity) { 4853 this.remove(staleEnd.key); 4854 } 4855 4856 return value; 4857 }, 4858 4859 /** 4860 * @ngdoc method 4861 * @name $cacheFactory.Cache#get 4862 * @kind function 4863 *
4864 * @description 4865 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 4866 * 4867 * @param {string} key the key of the data to be retrieved 4868 * @returns {*} the value stored. 4869 */ 4870 get: function(key) { 4871 if (capacity < Number.MAX_VALUE) { 4872 var lruEntry = lruHash[key]; 4873 4874 if (!lruEntry) return; 4875 4876 refresh(lruEntry); 4877 } 4878 4879 return data[key]; 4880 }, 4881 4882 4883 /** 4884 * @ngdoc method 4885 * @name $cacheFactory.Cache#remove 4886 * @kind function 4887 * 4888 * @description 4889 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 4890 * 4891 * @param {string} key the key of the entry to be removed 4892 */ 4893 remove: function(key) { 4894 if (capacity < Number.MAX_VALUE) { 4895 var lruEntry = lruHash[key]; 4896 4897 if (!lruEntry) return; 4898 4899 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 4900 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 4901 link(lruEntry.n,lruEntry.p); 4902 4903 delete lruHash[key]; 4904 } 4905 4906 delete data[key]; 4907 size--; 4908 }, 4909 4910 4911 /** 4912 * @ngdoc method 4913 * @name $cacheFactory.Cache#removeAll 4914 * @kind function 4915 * 4916 * @description 4917 * Clears the cache object of any entries. 4918 */ 4919 removeAll: function() { 4920 data = {}; 4921 size = 0; 4922 lruHash = {}; 4923 freshEnd = staleEnd = null; 4924 }, 4925 4926 4927 /** 4928 * @ngdoc method 4929 * @name $cacheFactory.Cache#destroy 4930 * @kind function 4931 * 4932 * @description 4933 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 4934 * removing it from the {@link $cacheFactory $cacheFactory} set. 4935 */ 4936 destroy: function() { 4937 data = null; 4938 stats = null; 4939 lruHash = null; 4940 delete caches[cacheId]; 4941 }, 4942 4943 4944 /** 4945 * @ngdoc method 4946 * @name $cacheFactory.Cache#info 4947 * @kind function 4948 * 4949 * @description 4950 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 4951 * 4952 * @returns {object} an object with the following properties: 4953 * <ul> 4954 * <li>**id**: the id of the cache instance</li> 4955 * <li>**size**: the number of entries kept in the cache instance</li> 4956 * <li>**...**: any additional properties from the options object when creating the 4957 * cache.</li> 4958 * </ul> 4959 */ 4960 info: function() { 4961 return extend({}, stats, {size: size}); 4962 } 4963 }; 4964 4965 4966 /** 4967 * makes the `entry` the freshEnd of the LRU linked list 4968 */ 4969 function refresh(entry) { 4970 if (entry != freshEnd) { 4971 if (!staleEnd) { 4972 staleEnd = entry; 4973 } else if (staleEnd == entry) { 4974 staleEnd = entry.n;
vendor: 4,905 bytes, lines 4975-5138
4975 } 4976 4977 link(entry.n, entry.p); 4978 link(entry, freshEnd); 4979 freshEnd = entry; 4980 freshEnd.n = null; 4981 } 4982 } 4983 4984 4985 /** 4986 * bidirectionally links two entries of the LRU linked list 4987 */ 4988 function link(nextEntry, prevEntry) { 4989 if (nextEntry != prevEntry) { 4990 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 4991 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 4992 } 4993 } 4994 } 4995 4996 4997 /** 4998 * @ngdoc method 4999 * @name $cacheFactory#info 5000 * 5001 * @description 5002 * Get information about all the caches that have been created 5003 * 5004 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 5005 */ 5006 cacheFactory.info = function() { 5007 var info = {}; 5008 forEach(caches, function(cache, cacheId) { 5009 info[cacheId] = cache.info(); 5010 }); 5011 return info; 5012 }; 5013 5014 5015 /** 5016 * @ngdoc method 5017 * @name $cacheFactory#get 5018 * 5019 * @description 5020 * Get access to a cache object by the `cacheId` used when it was created. 5021 * 5022 * @param {string} cacheId Name or id of a cache to access. 5023 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 5024 */ 5025 cacheFactory.get = function(cacheId) { 5026 return caches[cacheId]; 5027 }; 5028 5029 5030 return cacheFactory; 5031 }; 5032} 5033 5034/** 5035 * @ngdoc service 5036 * @name $templateCache 5037 * 5038 * @description 5039 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 5040 * can load templates directly into the cache in a `script` tag, or by consuming the 5041 * `$templateCache` service directly. 5042 * 5043 * Adding via the `script` tag: 5044 * 5045 * ```html 5046 * <script type="text/ng-template" id="templateId.html"> 5047 * <p>This is the content of the template</p> 5048 * </script> 5049 * ``` 5050 * 5051 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 5052 * the document, but it must be below the `ng-app` definition. 5053 * 5054 * Adding via the $templateCache service: 5055 * 5056 * ```js 5057 * var myApp = angular.module('myApp', []); 5058 * myApp.run(function($templateCache) { 5059 * $templateCache.put('templateId.html', 'This is the content of the template'); 5060 * }); 5061 * ``` 5062 * 5063 * To retrieve the template later, simply use it in your HTML: 5064 * ```html 5065 * <div ng-include=" 'templateId.html' "></div> 5066 * ``` 5067 * 5068 * or get it via Javascript: 5069 * ```js 5070 * $templateCache.get('templateId.html') 5071 * ``` 5072 * 5073 * See {@link ng.$cacheFactory $cacheFactory}. 5074 * 5075 */ 5076function $TemplateCacheProvider() { 5077 this.$get = ['$cacheFactory', function($cacheFactory) { 5078 return $cacheFactory('templates'); 5079 }]; 5080} 5081 5082/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 5083 * 5084 * DOM-related variables: 5085 * 5086 * - "node" - DOM Node 5087 * - "element" - DOM Element or Node 5088 * - "$node" or "$element" - jqLite-wrapped node or element 5089 * 5090 * 5091 * Compiler related stuff: 5092 * 5093 * - "linkFn" - linking fn of a single directive 5094 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 5095 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 5096 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 5097 */ 5098 5099 5100/** 5101 * @ngdoc service 5102 * @name $compile 5103 * @kind function 5104 * 5105 * @description 5106 * Compiles an HTML string or DOM into a template and produces a template function, which 5107 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 5108 * 5109 * The compilation is a process of walking the DOM tree and matching DOM elements to 5110 * {@link ng.$compileProvider#directive directives}. 5111 * 5112 * <div class="alert alert-warning"> 5113 * **Note:** This document is an in-depth reference of all directive options. 5114 * For a gentle introduction to directives with examples of common use cases, 5115 * see the {@link guide/directive directive guide}. 5116 * </div> 5117 * 5118 * ## Comprehensive Directive API 5119 * 5120 * There are many different options for a directive. 5121 * 5122 * The difference resides in the return value of the factory function. 5123 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 5124 * or just the `postLink` function (all other properties will have the default values). 5125 * 5126 * <div class="alert alert-success"> 5127 * **Best Practice:** It's recommended to use the "directive definition object" form. 5128 * </div> 5129 * 5130 * Here's an example directive declared with a Directive Definition Object: 5131 * 5132 * ```js 5133 * var myModule = angular.module(...); 5134 * 5135 * myModule.directive('directiveName', function factory(injectables) { 5136 * var directiveDefinitionObject = { 5137 * priority: 0, 5138 * template: '<div></div>
5138', // or // function(tElement, tAttrs) { ... }, 5139 * // or 5140 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 5141 * transclude: false, 5142 * restrict: 'A', 5143 * scope: false, 5144 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 5145 * controllerAs: 'stringAlias', 5146 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 5147 * compile: function compile(tElement, tAttrs, transclude) { 5148 * return { 5149 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 5150 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 5151 * } 5152 * // or 5153 * // return function postLink( ... ) { ... } 5154 * }, 5155 * // or 5156 * // link: { 5157 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 5158 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 5159 * // } 5160 * // or 5161 * // link: function postLink( ... ) { ... } 5162 * }; 5163 * return directiveDefinitionObject; 5164 * }); 5165 * ``` 5166 * 5167 * <div class="alert alert-warning"> 5168 * **Note:** Any unspecified options will use the default value. You can see the default values below. 5169 * </div> 5170 * 5171 * Therefore the above can be simplified as: 5172 * 5173 * ```js 5174 * var myModule = angular.module(...); 5175 * 5176 * myModule.directive('directiveName', function factory(injectables) { 5177 * var directiveDefinitionObject = { 5178 * link: function postLink(scope, iElement, iAttrs) { ... } 5179 * }; 5180 * return directiveDefinitionObject; 5181 * // or 5182 * // return function postLink(scope, iElement, iAttrs) { ... } 5183 * }); 5184 * ``` 5185 * 5186 * 5187 * 5188 * ### Directive Definition Object 5189 * 5190 * The directive definition object provides instructions to the {@link ng.$compile 5191 * compiler}. The attributes are: 5192 * 5193 * #### `priority` 5194 * When there are multiple directives defined on a single DOM element, sometimes it 5195 * is necessary to specify the order in which the directives are applied. The `priority` is used 5196 * to sort the directives before their `compile` functions get called. Priority is defined as a 5197 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 5198 * are also run in priority order, but post-link functions are run in reverse order. The order 5199 * of directives with the same priority is undefined. The default priority is `0`. 5200 * 5201 * #### `terminal` 5202 * If set to true then the current `priority` will be the last set of directives 5203 * which will execute (any directives at the current priority will still execute 5204 * as the order of execution on same `priority` is undefined). 5205 * 5206 * #### `scope` 5207 * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the 5208 * same element request a new scope, only one new scope is created. The new scope rule does not 5209 * apply for the root of the template since the root of the template always gets a new scope. 5210 * 5211 * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from 5212 * normal scope in that it does not prototypically inherit from the parent scope. This is useful 5213 * when creating reusable components, which should not accidentally read or modify data in the 5214 * parent scope. 5215 * 5216 * The 'isolate' scope takes an object hash which defines a set of local scope properties 5217 * derived from the parent scope. These local properties are useful for aliasing values for 5218 * templates. Locals definition is a hash of local scope property to its source: 5219 * 5220 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is 5221 * always a string since DOM attributes are strings. If no `attr` name is specified then the 5222 * attribute name is assumed to be the same as the local name. 5223 * Given `<widget my-attr="hello {{name}}">` and widget definition 5224 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 5225 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 5226 * `localName` property on the widget scope. The `name` is read from the parent scope (not 5227 * component scope). 5228 * 5229 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 5230 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 5231 * name is specified then the attribute name is assumed to be the same as the local name. 5232 * Given `<widget my-attr="parentModel">` and widget definition of 5233 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 5234 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 5235 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 5236 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 5237 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. 5238 * 5239 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 5240 * If no `attr` name is specified then the attribute name is assumed to be the same as the 5241 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 5242 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 5243 * a function wrapper for the `count = count + value` expression. Often it's desirable to 5244 * pass data from the isolated scope via an expression and to the parent scope, this can be 5245 * done by passing a map of local variable names and values into the expression wrapper fn. 5246 * For example, if the expression is `increment(amount)` then we can specify the amount value 5247 * by calling the `localFn` as `localFn({amount: 22})`. 5248 * 5249 * 5250 * 5251 * #### `controller` 5252 * Controller constructor function. The controller is instantiated before the 5253 * pre-linking phase and it is shared with other directives (see 5254 * `require` attribute). This allows the directives to communicate with each other and augment 5255 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 5256 * 5257 * * `$scope` - Current scope associated with the element 5258 * * `$element` - Current element 5259 * * `$attrs` - Current attributes object for the element 5260 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. 5261 * The scope can be overridden by an optional first argument. 5262 * `function([scope], cloneLinkingFn)`. 5263 * 5264 * 5265 * #### `require` 5266 * Require another directive and inject its controller as the fourth argument to the linking function. The
5267 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 5268 * injected argument will be an array in corresponding order. If no such directive can be 5269 * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: 5270 * 5271 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 5272 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 5273 * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found. 5274 * * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the 5275 * `link` fn if not found. 5276 * 5277 * 5278 * #### `controllerAs` 5279 * Controller alias at the directive scope. An alias for the controller so it 5280 * can be referenced at the directive template. The directive needs to define a scope for this 5281 * configuration to be used. Useful in the case when directive is used as component. 5282 * 5283 * 5284 * #### `restrict` 5285 * String of subset of `EACM` which restricts the directive to a specific directive 5286 * declaration style. If omitted, the default (attributes only) is used. 5287 * 5288 * * `E` - Element name: `<my-directive></my-directive>` 5289 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 5290 * * `C` - Class: `<div class="my-directive: exp;"></div>` 5291 * * `M` - Comment: `<!-- directive: my-directive exp -->` 5292 * 5293 * 5294 * #### `template` 5295 * replace the current element with the contents of the HTML. The replacement process 5296 * migrates all of the attributes / classes from the old element to the new one. See the 5297 * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive 5298 * Directives Guide} for an example. 5299 * 5300 * You can specify `template` as a string representing the template or as a function which takes 5301 * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and 5302 * returns a string value representing the template. 5303 * 5304 * 5305 * #### `templateUrl` 5306 * Same as `template` but the template is loaded from the specified URL. Because 5307 * the template loading is asynchronous the compilation/linking is suspended until the template 5308 * is loaded. 5309 * 5310 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 5311 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 5312 * a string value representing the url. In either case, the template URL is passed through {@link 5313 * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 5314 * 5315 * 5316 * #### `replace` ([*DEPRECATED*!], will be removed in next major release) 5317 * specify where the template should be inserted. Defaults to `false`. 5318 * 5319 * * `true` - the template will replace the current element. 5320 * * `false` - the template will replace the contents of the current element. 5321 * 5322 * 5323 * #### `transclude` 5324 * compile the content of the element and make it available to the directive. 5325 * Typically used with {@link ng.directive:ngTransclude 5326 * ngTransclude}. The advantage of transclusion is that the linking function receives a 5327 * transclusion function which is pre-bound to the correct scope. In a typical setup the widget 5328 * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` 5329 * scope. This makes it possible for the widget to have private state, and the transclusion to 5330 * be bound to the parent (pre-`isolate`) scope. 5331 * 5332 * * `true` - transclude the content of the directive. 5333 * * `'element'` - transclude the whole element including any directives defined at lower priority. 5334 * 5335 * 5336 * #### `compile` 5337 * 5338 * ```js 5339 * function compile(tElement, tAttrs, transclude) { ... } 5340 * ``` 5341 * 5342 * The compile function deals with transforming the template DOM. Since most directives do not do 5343 * template transformation, it is not used often. The compile function takes the following arguments: 5344 * 5345 * * `tElement` - template element - The element where the directive has been declared. It is 5346 * safe to do template transformation on the element and child elements only. 5347 * 5348 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 5349 * between all directive compile functions. 5350 * 5351 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 5352 * 5353 * <div class="alert alert-warning"> 5354 * **Note:** The template instance and the link instance may be different objects if the template has 5355 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 5356 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
5357 * should be done in a linking function rather than in a compile function. 5358 * </div> 5359 5360 * <div class="alert alert-warning"> 5361 * **Note:** The compile function cannot handle directives that recursively use themselves in their 5362 * own templates or compile functions. Compiling these directives results in an infinite loop and a 5363 * stack overflow errors. 5364 * 5365 * This can be avoided by manually using $compile in the postLink function to imperatively compile 5366 * a directive's template instead of relying on automatic template compilation via `template` or 5367 * `templateUrl` declaration or manual compilation inside the compile function. 5368 * </div> 5369 * 5370 * <div class="alert alert-error"> 5371 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 5372 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 5373 * to the link function instead. 5374 * </div> 5375 5376 * A compile function can have a return value which can be either a function or an object. 5377 * 5378 * * returning a (post-link) function - is equivalent to registering the linking function via the 5379 * `link` property of the config object when the compile function is empty. 5380 * 5381 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 5382 * control when a linking function should be called during the linking phase. See info about 5383 * pre-linking and post-linking functions below. 5384 * 5385 * 5386 * #### `link` 5387 * This property is used only if the `compile` property is not defined. 5388 * 5389 * ```js 5390 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 5391 * ``` 5392 * 5393 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 5394 * executed after the template has been cloned. This is where most of the directive logic will be 5395 * put. 5396 * 5397 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 5398 * directive for registering {@link ng.$rootScope.Scope#$watch watches}. 5399 * 5400 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 5401 * manipulate the children of the element only in `postLink` function since the children have 5402 * already been linked. 5403 * 5404 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 5405 * between all directive linking functions. 5406 * 5407 * * `controller` - a controller instance - A controller instance if at least one directive on the 5408 * element defines a controller. The controller is shared among all the directives, which allows 5409 * the directives to use the controllers as a communication channel. 5410 * 5411 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 5412 * The scope can be overridden by an optional first argument. This is the same as the `$transclude` 5413 * parameter of directive controllers. 5414 * `function([scope], cloneLinkingFn)`. 5415 * 5416 * 5417 * #### Pre-linking function 5418 * 5419 * Executed before the child elements are linked. Not safe to do DOM transformation since the 5420 * compiler linking function will fail to locate the correct elements for linking. 5421 * 5422 * #### Post-linking function 5423 * 5424 * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. 5425 * 5426 * <a name="Attributes"></a> 5427 * ### Attributes 5428 * 5429 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 5430 * `link()` or `compile()` functions. It has a variety of uses. 5431 * 5432 * accessing *Normalized attribute names:* 5433 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 5434 * the attributes object allows for normalized access to 5435 * the attributes. 5436 * 5437 * * *Directive inter-communication:* All directives share the same instance of the attributes 5438 * object which allows the directives to use the attributes object as inter directive 5439 * communication. 5440 * 5441 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 5442 * allowing other directives to read the interpolated value. 5443 * 5444 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 5445 * that contain interpolation (e.g. `src="{{bar}}
5445"`). Not only is this very efficient but it's also 5446 * the only way to easily get the actual value because during the linking phase the interpolation 5447 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 5448 * 5449 * ```js 5450 * function linkingFn(scope, elm, attrs, ctrl) { 5451 * // get the attribute value 5452 * console.log(attrs.ngModel); 5453 * 5454 * // change the attribute 5455 * attrs.$set('ngModel', 'new value'); 5456 * 5457 * // observe changes to interpolated attribute 5458 * attrs.$observe('ngModel', function(value) { 5459 * console.log('ngModel has changed value to ' + value); 5460 * }); 5461 * } 5462 * ``` 5463 * 5464 * Below is an example using `$compileProvider`. 5465 * 5466 * <div class="alert alert-warning"> 5467 * **Note**: Typically directives are registered with `module.directive`. The example below is 5468 * to illustrate how `$compile` works. 5469 * </div> 5470 * 5471 <example module="compile"> 5472 <file name="index.html"> 5473 <script> 5474 angular.module('compile', [], function($compileProvider) { 5475 // configure new 'compile' directive by passing a directive 5476 // factory function. The factory function injects the '$compile' 5477 $compileProvider.directive('compile', function($compile) { 5478 // directive factory creates a link function 5479 return function(scope, element, attrs) { 5480 scope.$watch( 5481 function(scope) { 5482 // watch the 'compile' expression for changes 5483 return scope.$eval(attrs.compile); 5484 }, 5485 function(value) { 5486 // when the 'compile' expression changes 5487 // assign it into the current DOM 5488 element.html(value); 5489 5490 // compile the new DOM and link it to the current 5491 // scope. 5492 // NOTE: we only compile .childNodes so that 5493 // we don't get into infinite loop compiling ourselves 5494 $compile(element.contents())(scope); 5495 } 5496 ); 5497 }; 5498 }) 5499 }); 5500 5501 function Ctrl($scope) { 5502 $scope.name = 'Angular'; 5503 $scope.html = 'Hello {{name}}'; 5504 } 5505 </script> 5506 <div ng-controller="Ctrl"> 5507 <input ng-model="name"> <br> 5508 <textarea ng-model="html"></textarea> <br> 5509 <div compile="html"></div> 5510 </div> 5511 </file> 5512 <file name="protractor.js" type="protractor"> 5513 it('should auto compile', function() { 5514 var textarea = $('textarea'); 5515 var output = $('div[compile]'); 5516 // The initial state reads 'Hello Angular'. 5517 expect(output.getText()).toBe('Hello Angular'); 5518 textarea.clear(); 5519 textarea.sendKeys('{{name}}!'); 5520 expect(output.getText()).toBe('Angular!'); 5521 }); 5522 </file> 5523 </example> 5524 5525 * 5526 * 5527 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 5528 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. 5529 * @param {number} maxPriority only apply directives lower than given priority (Only effects the 5530 * root element(s), not their children) 5531 * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template 5532 * (a DOM element/tree) to a scope. Where: 5533 * 5534 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 5535 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 5536 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 5537 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 5538 * called as: <br> `cloneAttachFn(clonedElement, scope)` where: 5539 * 5540 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 5541 * * `scope` - is the current scope with which the linking function is working with. 5542 * 5543 * Calling the linking function returns the element of the template. It is either the original 5544 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 5545 * 5546 * After linking the view is not updated until after a call to $digest which typically is done by 5547 * Angular automatically. 5548 * 5549 * If you need access to the bound view, there are two ways to do it: 5550 * 5551 * - If you are not asking the linking function to clone the template, create the DOM element(s) 5552 * before you send them to the compiler and keep this reference around. 5553 * ```js 5554 * var element = $compile('<p>{{total}}</p>')(scope); 5555 * ``` 5556 * 5557 * - if on the other hand, you need the element to be cloned, the view reference from the original 5558 * example would not point to the clone, but rather to the original template that was cloned. In 5559 * this case, you can access the clone via the cloneAttachFn: 5560 * ```js 5561 * var templateElement = angular.element('<p>{{total}}</p>'), 5562 * scope = ....; 5563 * 5564 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 5565 * //attach the clone to DOM document at the right place 5566 * }); 5567 * 5568 * //now we have reference to the cloned DOM via `clonedElement` 5569 * ``` 5570 * 5571 * 5572 * For information on how the compiler works, see the 5573 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 5574 */ 5575 5576var $compileMinErr = minErr('$compile'); 5577 5578/** 5579 * @ngdoc provider 5580 * @name $compileProvider 5581 * @kind function 5582 * 5583 * @description 5584 */
vendor: 4,668 bytes, lines 5585-5693
5585$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 5586function $CompileProvider($provide, $$sanitizeUriProvider) { 5587 var hasDirectives = {}, 5588 Suffix = 'Directive', 5589 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/, 5590 CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/; 5591 5592 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 5593 // The assumption is that future DOM event attribute names will begin with 5594 // 'on' and be composed of only English letters. 5595 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 5596 5597 /** 5598 * @ngdoc method 5599 * @name $compileProvider#directive 5600 * @kind function 5601 * 5602 * @description 5603 * Register a new directive with the compiler. 5604 * 5605 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 5606 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 5607 * names and the values are the factories. 5608 * @param {Function|Array} directiveFactory An injectable directive factory function. See 5609 * {@link guide/directive} for more info. 5610 * @returns {ng.$compileProvider} Self for chaining. 5611 */ 5612 this.directive = function registerDirective(name, directiveFactory) { 5613 assertNotHasOwnProperty(name, 'directive'); 5614 if (isString(name)) { 5615 assertArg(directiveFactory, 'directiveFactory'); 5616 if (!hasDirectives.hasOwnProperty(name)) { 5617 hasDirectives[name] = []; 5618 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 5619 function($injector, $exceptionHandler) { 5620 var directives = []; 5621 forEach(hasDirectives[name], function(directiveFactory, index) { 5622 try { 5623 var directive = $injector.invoke(directiveFactory); 5624 if (isFunction(directive)) { 5625 directive = { compile: valueFn(directive) }; 5626 } else if (!directive.compile && directive.link) { 5627 directive.compile = valueFn(directive.link); 5628 } 5629 directive.priority = directive.priority || 0; 5630 directive.index = index; 5631 directive.name = directive.name || name; 5632 directive.require = directive.require || (directive.controller && directive.name); 5633 directive.restrict = directive.restrict || 'A'; 5634 directives.push(directive); 5635 } catch (e) { 5636 $exceptionHandler(e); 5637 } 5638 }); 5639 return directives; 5640 }]); 5641 } 5642 hasDirectives[name].push(directiveFactory); 5643 } else { 5644 forEach(name, reverseParams(registerDirective)); 5645 } 5646 return this; 5647 }; 5648 5649 5650 /** 5651 * @ngdoc method 5652 * @name $compileProvider#aHrefSanitizationWhitelist 5653 * @kind function 5654 * 5655 * @description 5656 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5657 * urls during a[href] sanitization. 5658 * 5659 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5660 * 5661 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 5662 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 5663 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 5664 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5665 * 5666 * @param {RegExp=} regexp New regexp to whitelist urls with. 5667 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5668 * chaining otherwise. 5669 */ 5670 this.aHrefSanitizationWhitelist = function(regexp) { 5671 if (isDefined(regexp)) { 5672 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 5673 return this; 5674 } else { 5675 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 5676 } 5677 }; 5678 5679 5680 /** 5681 * @ngdoc method 5682 * @name $compileProvider#imgSrcSanitizationWhitelist 5683 * @kind function 5684 * 5685 * @description 5686 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5687 * urls during img[src] sanitization. 5688 * 5689 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5690 * 5691 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 5692 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 5693 * regular expression. If a match is found, the original url is
5693written into the dom. Otherwise, 5694 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5695 * 5696 * @param {RegExp=} regexp New regexp to whitelist urls with. 5697 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5698 * chaining otherwise. 5699 */ 5700 this.imgSrcSanitizationWhitelist = function(regexp) { 5701 if (isDefined(regexp)) { 5702 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 5703 return this; 5704 } else { 5705 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 5706 } 5707 }; 5708 5709 this.$get = [ 5710 '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', 5711 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 5712 function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, 5713 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 5714 5715 var Attributes = function(element, attr) { 5716 this.$$element = element; 5717 this.$attr = attr || {}; 5718 }; 5719 5720 Attributes.prototype = { 5721 $normalize: directiveNormalize, 5722 5723 5724 /** 5725 * @ngdoc method 5726 * @name $compile.directive.Attributes#$addClass 5727 * @kind function 5728 * 5729 * @description 5730 * Adds the CSS class value specified by the classVal parameter to the element. If animations 5731 * are enabled then an animation will be triggered for the class addition. 5732 * 5733 * @param {string} classVal The className value that will be added to the element 5734 */ 5735 $addClass : function(classVal) { 5736 if(classVal && classVal.length > 0) { 5737 $animate.addClass(this.$$element, classVal); 5738 } 5739 }, 5740 5741 /** 5742 * @ngdoc method 5743 * @name $compile.directive.Attributes#$removeClass 5744 * @kind function 5745 * 5746 * @description 5747 * Removes the CSS class value specified by the classVal parameter from the element. If 5748 * animations are enabled then an animation will be triggered for the class removal. 5749 * 5750 * @param {string} classVal The className value that will be removed from the element 5751 */ 5752 $removeClass : function(classVal) { 5753 if(classVal && classVal.length > 0) { 5754 $animate.removeClass(this.$$element, classVal); 5755 } 5756 }, 5757 5758 /** 5759 * @ngdoc method 5760 * @name $compile.directive.Attributes#$updateClass 5761 * @kind function 5762 * 5763 * @description 5764 * Adds and removes the appropriate CSS class values to the element based on the difference 5765 * between the new and old CSS class values (specified as newClasses and oldClasses). 5766 * 5767 * @param {string} newClasses The current CSS className value 5768 * @param {string} oldClasses The former CSS className value 5769 */ 5770 $updateClass : function(newClasses, oldClasses) { 5771 var toAdd = tokenDifference(newClasses, oldClasses); 5772 var toRemove = tokenDifference(oldClasses, newClasses); 5773 5774 if(toAdd.length === 0) { 5775 $animate.removeClass(this.$$element, toRemove); 5776 } else if(toRemove.length === 0) { 5777 $animate.addClass(this.$$element, toAdd); 5778 } else { 5779 $animate.setClass(this.$$element, toAdd, toRemove); 5780 } 5781 }, 5782 5783 /** 5784 * Set a normalized attribute on the element in a way such that all directives 5785 * can share the attribute. This function properly handles boolean attributes. 5786 * @param {string} key Normalized key. (ie ngAttribute) 5787 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 5788 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 5789 * Defaults to true. 5790 * @param {string=} attrName Optional none normalized name. Defaults to key. 5791 */ 5792 $set: function(key, value, writeAttr, attrName) { 5793 // TODO: decide whether or not to throw an error if "class" 5794 //is set through this function since it may cause $updateClass to 5795 //become unstable. 5796 5797 var booleanKey = getBooleanAttrName(this.$$element[0], key), 5798 normalizedVal, 5799 nodeName; 5800 5801 if (booleanKey) { 5802 this.$$element.prop(key, value); 5803 attrName = booleanKey; 5804 } 5805 5806 this[key] = value; 5807 5808 // translate normalized key to actual key 5809 if (attrName) { 5810 this.$attr[key] = attrName; 5811 } else { 5812 attrName = this.$attr[key]; 5813 if (!attrName) { 5814 this.$attr[key] = attrName = snake_case(key, '-'); 5815 } 5816 } 5817 5818 nodeName = nodeName_(this.$$element); 5819 5820 // sanitize a[href] and img[src] values 5821 if ((nodeName === 'A' && key === 'href') || 5822 (nodeName === 'IMG' && key === 'src')) { 5823 this[key] = value = $$sanitizeUri(value, key === 'src'); 5824 } 5825 5826 if (writeAttr !== false) { 5827 if (value === null || value === undefined) { 5828 this.$$element.removeAttr(attrName); 5829 } else { 5830 this.$$element.attr(attrName, value); 5831 } 5832 } 5833 5834 // fire observers 5835 var $$observers = this.$$observers;
5836 $$observers && forEach($$observers[key], function(fn) { 5837 try { 5838 fn(value); 5839 } catch (e) { 5840 $exceptionHandler(e); 5841 } 5842 }); 5843 }, 5844 5845 5846 /** 5847 * @ngdoc method 5848 * @name $compile.directive.Attributes#$observe 5849 * @kind function 5850 * 5851 * @description 5852 * Observes an interpolated attribute. 5853 * 5854 * The observer function will be invoked once during the next `$digest` following 5855 * compilation. The observer is then invoked whenever the interpolated value 5856 * changes. 5857 * 5858 * @param {string} key Normalized key. (ie ngAttribute) . 5859 * @param {function(interpolatedValue)} fn Function that will be called whenever 5860 the interpolated value of the attribute changes. 5861 * See the {@link guide/directive#Attributes Directives} guide for more info. 5862 * @returns {function()} the `fn` parameter. 5863 */ 5864 $observe: function(key, fn) { 5865 var attrs = this, 5866 $$observers = (attrs.$$observers || (attrs.$$observers = {})), 5867 listeners = ($$observers[key] || ($$observers[key] = [])); 5868 5869 listeners.push(fn); 5870 $rootScope.$evalAsync(function() { 5871 if (!listeners.$$inter) { 5872 // no one registered attribute interpolation function, so lets call it manually 5873 fn(attrs[key]); 5874 } 5875 }); 5876 return fn; 5877 } 5878 }; 5879 5880 var startSymbol = $interpolate.startSymbol(), 5881 endSymbol = $interpolate.endSymbol(), 5882 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 5883 ? identity 5884 : function denormalizeTemplate(template) { 5885 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 5886 }, 5887 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 5888 5889 5890 return compile; 5891 5892 //================================ 5893 5894 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 5895 previousCompileContext) { 5896 if (!($compileNodes instanceof jqLite)) { 5897 // jquery always rewraps, whereas we need to preserve the original selector so that we can 5898 // modify it. 5899 $compileNodes = jqLite($compileNodes); 5900 } 5901 // We can not compile top level text elements since text nodes can be merged and we will 5902 // not be able to attach scope data to them, so we will wrap them in <span> 5903 forEach($compileNodes, function(node, index){ 5904 if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { 5905 $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0]; 5906 } 5907 }); 5908 var compositeLinkFn = 5909 compileNodes($compileNodes, transcludeFn, $compileNodes, 5910 maxPriority, ignoreDirective, previousCompileContext); 5911 safeAddClass($compileNodes, 'ng-scope'); 5912 return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){ 5913 assertArg(scope, 'scope'); 5914 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 5915 // and sometimes changes the structure of the DOM. 5916 var $linkNode = cloneConnectFn 5917 ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! 5918 : $compileNodes; 5919 5920 forEach(transcludeControllers, function(instance, name) { 5921 $linkNode.data('$' + name + 'Controller', instance); 5922 }); 5923 5924 // Attach scope only to non-text nodes. 5925 for(var i = 0, ii = $linkNode.length; i<ii; i++) { 5926 var node = $linkNode[i], 5927 nodeType = node.nodeType; 5928 if (nodeType === 1 /* element */ || nodeType === 9 /* document */) { 5929 $linkNode.eq(i).data('$scope', scope); 5930 } 5931 } 5932 5933 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 5934 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 5935 return $linkNode; 5936 }; 5937 } 5938 5939 function safeAddClass($element, className) {
5940 try { 5941 $element.addClass(className); 5942 } catch(e) { 5943 // ignore, since it means that we are trying to set class on 5944 // SVG element, where class name is read-only. 5945 } 5946 } 5947 5948 /** 5949 * Compile function matches each node in nodeList against the directives. Once all directives 5950 * for a particular node are collected their compile functions are executed. The compile 5951 * functions return values - the linking functions - are combined into a composite linking 5952 * function, which is the a linking function for the node. 5953 * 5954 * @param {NodeList} nodeList an array of nodes or NodeList to compile 5955 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 5956 * scope argument is auto-generated to the new child of the transcluded parent scope. 5957 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 5958 * the rootElement must be set the jqLite collection of the compile root. This is 5959 * needed so that the jqLite collection items can be replaced with widgets. 5960 * @param {number=} maxPriority Max directive priority. 5961 * @returns {Function} A composite linking function of all of the matched directives or null. 5962 */ 5963 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 5964 previousCompileContext) { 5965 var linkFns = [], 5966 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound; 5967 5968 for (var i = 0; i < nodeList.length; i++) { 5969 attrs = new Attributes(); 5970 5971 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 5972 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 5973 ignoreDirective); 5974 5975 nodeLinkFn = (directives.length) 5976 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 5977 null, [], [], previousCompileContext) 5978 : null; 5979 5980 if (nodeLinkFn && nodeLinkFn.scope) { 5981 safeAddClass(jqLite(nodeList[i]), 'ng-scope'); 5982 } 5983 5984 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 5985 !(childNodes = nodeList[i].childNodes) || 5986 !childNodes.length) 5987 ? null 5988 : compileNodes(childNodes, 5989 nodeLinkFn ? ( 5990 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 5991 && nodeLinkFn.transclude) : transcludeFn); 5992 5993 linkFns.push(nodeLinkFn, childLinkFn); 5994 linkFnFound = linkFnFound || nodeLinkFn || childLinkFn; 5995 //use the previous context only for the first element in the virtual group 5996 previousCompileContext = null; 5997 } 5998 5999 // return a linking function if we have found anything, null otherwise 6000 return linkFnFound ? compositeLinkFn : null; 6001 6002 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 6003 var nodeLinkFn, childLinkFn, node, $node, childScope, i, ii, n, childBoundTranscludeFn; 6004 6005 // copy nodeList so that linking doesn't break due to live list updates. 6006 var nodeListLength = nodeList.length, 6007 stableNodeList = new Array(nodeListLength); 6008 for (i = 0; i < nodeListLength; i++) { 6009 stableNodeList[i] = nodeList[i]; 6010 } 6011 6012 for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) { 6013 node = stableNodeList[n]; 6014 nodeLinkFn = linkFns[i++]; 6015 childLinkFn = linkFns[i++]; 6016 $node = jqLite(node); 6017 6018 if (nodeLinkFn) { 6019 if (nodeLinkFn.scope) { 6020 childScope = scope.$new(); 6021 $node.data('$scope', childScope); 6022 } else { 6023 childScope = scope; 6024 } 6025 6026 if ( nodeLinkFn.transcludeOnThisElement ) { 6027 childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
6028 6029 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 6030 childBoundTranscludeFn = parentBoundTranscludeFn; 6031 6032 } else if (!parentBoundTranscludeFn && transcludeFn) { 6033 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 6034 6035 } else { 6036 childBoundTranscludeFn = null; 6037 } 6038 6039 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); 6040 6041 } else if (childLinkFn) { 6042 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 6043 } 6044 } 6045 } 6046 } 6047 6048 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 6049 6050 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) { 6051 var scopeCreated = false; 6052 6053 if (!transcludedScope) { 6054 transcludedScope = scope.$new(); 6055 transcludedScope.$$transcluded = true; 6056 scopeCreated = true; 6057 } 6058 6059 var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn); 6060 if (scopeCreated) { 6061 clone.on('$destroy', function() { transcludedScope.$destroy(); }); 6062 } 6063 return clone; 6064 }; 6065 6066 return boundTranscludeFn; 6067 } 6068 6069 /** 6070 * Looks for directives on the given node and adds them to the directive collection which is 6071 * sorted. 6072 * 6073 * @param node Node to search. 6074 * @param directives An array to which the directives are added to. This array is sorted before 6075 * the function returns. 6076 * @param attrs The shared attrs object which is used to populate the normalized attributes. 6077 * @param {number=} maxPriority Max directive priority. 6078 */ 6079 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 6080 var nodeType = node.nodeType, 6081 attrsMap = attrs.$attr, 6082 match, 6083 className; 6084 6085 switch(nodeType) { 6086 case 1: /* Element */ 6087 // use the node name: <directive> 6088 addDirective(directives, 6089 directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); 6090 6091 // iterate over the attributes 6092 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 6093 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 6094 var attrStartName = false; 6095 var attrEndName = false; 6096 6097 attr = nAttrs[j]; 6098 if (!msie || msie >= 8 || attr.specified) { 6099 name = attr.name; 6100 value = trim(attr.value); 6101 6102 // support ngAttr attribute binding 6103 ngAttrName = directiveNormalize(name); 6104 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 6105 name = snake_case(ngAttrName.substr(6), '-'); 6106 } 6107 6108 var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); 6109 if (ngAttrName === directiveNName + 'Start') { 6110 attrStartName = name; 6111 attrEndName = name.substr(0, name.length - 5) + 'end'; 6112 name = name.substr(0, name.length - 6); 6113 } 6114 6115 nName = directiveNormalize(name.toLowerCase()); 6116 attrsMap[nName] = name; 6117 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 6118 attrs[nName] = value; 6119 if (getBooleanAttrName(node, nName)) { 6120 attrs[nName] = true; // presence means true 6121 } 6122 } 6123 addAttrInterpolateDirective(node, directives, value, nName); 6124 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 6125 attrEndName); 6126 } 6127 } 6128 6129 // use class as directive 6130 className = node.className; 6131 if (isString(className) && className !== '') { 6132 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 6133 nName = directiveNormalize(match[2]); 6134 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 6135 attrs[nName] = trim(match[3]); 6136 } 6137 className = className.substr(match.index + match[0].length); 6138 } 6139 } 6140 break; 6141 case 3: /* Text Node */ 6142 addTextInterpolateDirective(directives, node.nodeValue); 6143 break; 6144 case 8: /* Comment */ 6145 try { 6146 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 6147 if (match) { 6148 nName = directiveNormalize(match[1]); 6149 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 6150 attrs[nName] = trim(match[2]); 6151 } 6152 } 6153 } catch (e) { 6154 // turns out that under some circumstances IE9 throws errors when one attempts to read 6155 // comment's node value. 6156 // Just ignore it and continue. (Can't seem to reproduce in test case.) 6157 } 6158 break; 6159 } 6160 6161 directives.sort(byPriority); 6162 return directives; 6163 } 6164 6165 /**
6166 * Given a node with an directive-start it collects all of the siblings until it finds 6167 * directive-end. 6168 * @param node 6169 * @param attrStart 6170 * @param attrEnd 6171 * @returns {*} 6172 */ 6173 function groupScan(node, attrStart, attrEnd) { 6174 var nodes = []; 6175 var depth = 0; 6176 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 6177 var startNode = node; 6178 do { 6179 if (!node) { 6180 throw $compileMinErr('uterdir', 6181 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 6182 attrStart, attrEnd); 6183 } 6184 if (node.nodeType == 1 /** Element **/) { 6185 if (node.hasAttribute(attrStart)) depth++; 6186 if (node.hasAttribute(attrEnd)) depth--; 6187 } 6188 nodes.push(node); 6189 node = node.nextSibling; 6190 } while (depth > 0); 6191 } else { 6192 nodes.push(node); 6193 } 6194 6195 return jqLite(nodes); 6196 } 6197 6198 /** 6199 * Wrapper for linking function which converts normal linking function into a grouped 6200 * linking function. 6201 * @param linkFn 6202 * @param attrStart 6203 * @param attrEnd 6204 * @returns {Function} 6205 */ 6206 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 6207 return function(scope, element, attrs, controllers, transcludeFn) { 6208 element = groupScan(element[0], attrStart, attrEnd); 6209 return linkFn(scope, element, attrs, controllers, transcludeFn); 6210 }; 6211 } 6212 6213 /** 6214 * Once the directives have been collected, their compile functions are executed. This method 6215 * is responsible for inlining directive templates as well as terminating the application 6216 * of the directives if the terminal directive has been reached. 6217 * 6218 * @param {Array} directives Array of collected directives to execute their compile function. 6219 * this needs to be pre-sorted by priority order. 6220 * @param {Node} compileNode The raw DOM node to apply the compile functions to 6221 * @param {Object} templateAttrs The shared attribute function 6222 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 6223 * scope argument is auto-generated to the new 6224 * child of the transcluded parent scope. 6225 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 6226 * argument has the root jqLite array so that we can replace nodes 6227 * on it. 6228 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 6229 * compiling the transclusion. 6230 * @param {Array.<Function>} preLinkFns 6231 * @param {Array.<Function>} postLinkFns 6232 * @param {Object} previousCompileContext Context used for previous compilation of the current 6233 * node 6234 * @returns {Function} linkFn 6235 */ 6236 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 6237 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 6238 previousCompileContext) { 6239 previousCompileContext = previousCompileContext || {}; 6240 6241 var terminalPriority = -Number.MAX_VALUE, 6242 newScopeDirective, 6243 controllerDirectives = previousCompileContext.controllerDirectives, 6244 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 6245 templateDirective = previousCompileContext.templateDirective, 6246 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 6247 hasTranscludeDirective = false, 6248 hasTemplate = false, 6249 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 6250 $compileNode = templateAttrs.$$element = jqLite(compileNode), 6251 directive, 6252 directiveName, 6253 $template, 6254 replaceDirective = originalReplaceDirective, 6255 childTranscludeFn = transcludeFn, 6256 linkFn, 6257 directiveValue; 6258 6259 // executes all directives on the current element 6260 for(var i = 0, ii = directives.length; i < ii; i++) { 6261 directive = directives[i]; 6262 var attrStart = directive.$$start; 6263 var attrEnd = directive.$$end; 6264 6265 // collect multiblock sections 6266 if (attrStart) { 6267 $compileNode = groupScan(compileNode, attrStart, attrEnd); 6268 } 6269 $template = undefined; 6270 6271 if (terminalPriority > directive.priority) { 6272 break; // prevent further processing of directives 6273 } 6274 6275 if (directiveValue = directive.scope) { 6276 newScopeDirective = newScopeDirective || directive; 6277 6278 // skip the check for directives with async templates, we'll check the derived sync 6279 // directive when the template arrives 6280 if (!directive.templateUrl) { 6281 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 6282 $compileNode); 6283 if (isObject(directiveValue)) { 6284 newIsolateScopeDirective = directive; 6285 } 6286 } 6287 } 6288 6289 directiveName = directive.name; 6290 6291 if (!directive.templateUrl && directive.controller) { 6292 directiveValue = directive.controller; 6293 controllerDirectives = controllerDirectives || {}; 6294 assertNoDuplicate("'" + directiveName + "' controller", 6295 controllerDirectives[directiveName], directive, $compileNode); 6296 controllerDirectives[directiveName] = directive; 6297 } 6298 6299 if (directiveValue = directive.transclude) { 6300 hasTranscludeDirective = true; 6301 6302 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 6303 // This option should only be used by directives that know how to safely handle element transclusion, 6304 // where the transcluded nodes are added or replaced after linking. 6305 if (!directive.$$tlb) { 6306 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 6307 nonTlbTranscludeDirective = directive; 6308 } 6309 6310 if (directiveValue == 'element') { 6311 hasElementTranscludeDirective = true; 6312 terminalPriority = directive.priority; 6313 $template = groupScan(compileNode, attrStart, attrEnd); 6314 $compileNode = templateAttrs.$$element = 6315 jqLite(document.createComment(' ' + directiveName + ': ' + 6316 templateAttrs[directiveName] + ' ')); 6317 compileNode = $compileNode[0]; 6318 replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); 6319 6320 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 6321 replaceDirective && replaceDirective.name, { 6322 // Don't pass in: 6323 // - controllerDirectives - otherwise we'll create duplicates controllers 6324 // - newIsolateScopeDirective or templateDirective - comb
vendor: 4,711 bytes, lines 6324-6431
6324ining templates with 6325 // element transclusion doesn't make sense. 6326 // 6327 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 6328 // on the same element more than once. 6329 nonTlbTranscludeDirective: nonTlbTranscludeDirective 6330 }); 6331 } else { 6332 $template = jqLite(jqLiteClone(compileNode)).contents(); 6333 $compileNode.empty(); // clear contents 6334 childTranscludeFn = compile($template, transcludeFn); 6335 } 6336 } 6337 6338 if (directive.template) { 6339 hasTemplate = true; 6340 assertNoDuplicate('template', templateDirective, directive, $compileNode); 6341 templateDirective = directive; 6342 6343 directiveValue = (isFunction(directive.template)) 6344 ? directive.template($compileNode, templateAttrs) 6345 : directive.template; 6346 6347 directiveValue = denormalizeTemplate(directiveValue); 6348 6349 if (directive.replace) { 6350 replaceDirective = directive; 6351 if (jqLiteIsTextNode(directiveValue)) { 6352 $template = []; 6353 } else { 6354 $template = jqLite(trim(directiveValue)); 6355 } 6356 compileNode = $template[0]; 6357 6358 if ($template.length != 1 || compileNode.nodeType !== 1) { 6359 throw $compileMinErr('tplrt', 6360 "Template for directive '{0}' must have exactly one root element. {1}", 6361 directiveName, ''); 6362 } 6363 6364 replaceWith(jqCollection, $compileNode, compileNode); 6365 6366 var newTemplateAttrs = {$attr: {}}; 6367 6368 // combine directives from the original node and from the template: 6369 // - take the array of directives for this element 6370 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 6371 // - collect directives from the template and sort them by priority 6372 // - combine directives as: processed + template + unprocessed 6373 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 6374 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 6375 6376 if (newIsolateScopeDirective) { 6377 markDirectivesAsIsolate(templateDirectives); 6378 } 6379 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 6380 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 6381 6382 ii = directives.length; 6383 } else { 6384 $compileNode.html(directiveValue); 6385 } 6386 } 6387 6388 if (directive.templateUrl) { 6389 hasTemplate = true; 6390 assertNoDuplicate('template', templateDirective, directive, $compileNode); 6391 templateDirective = directive; 6392 6393 if (directive.replace) { 6394 replaceDirective = directive; 6395 } 6396 6397 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 6398 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 6399 controllerDirectives: controllerDirectives, 6400 newIsolateScopeDirective: newIsolateScopeDirective, 6401 templateDirective: templateDirective, 6402 nonTlbTranscludeDirective: nonTlbTranscludeDirective 6403 }); 6404 ii = directives.length; 6405 } else if (directive.compile) { 6406 try { 6407 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 6408 if (isFunction(linkFn)) { 6409 addLinkFns(null, linkFn, attrStart, attrEnd); 6410 } else if (linkFn) { 6411 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 6412 } 6413 } catch (e) { 6414 $exceptionHandler(e, startingTag($compileNode)); 6415 } 6416 } 6417 6418 if (directive.terminal) { 6419 nodeLinkFn.terminal = true; 6420 terminalPriority = Math.max(terminalPriority, directive.priority); 6421 } 6422 6423 } 6424 6425 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 6426 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 6427 nodeLinkFn.templateOnThisElement = hasTemplate; 6428 nodeLinkFn.transclude = childTranscludeFn; 6429 6430 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 6431
6432 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 6433 return nodeLinkFn; 6434 6435 //////////////////// 6436 6437 function addLinkFns(pre, post, attrStart, attrEnd) { 6438 if (pre) { 6439 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 6440 pre.require = directive.require; 6441 pre.directiveName = directiveName; 6442 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6443 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 6444 } 6445 preLinkFns.push(pre); 6446 } 6447 if (post) { 6448 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 6449 post.require = directive.require; 6450 post.directiveName = directiveName; 6451 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6452 post = cloneAndAnnotateFn(post, {isolateScope: true}); 6453 } 6454 postLinkFns.push(post); 6455 } 6456 } 6457 6458 6459 function getControllers(directiveName, require, $element, elementControllers) { 6460 var value, retrievalMethod = 'data', optional = false; 6461 if (isString(require)) { 6462 while((value = require.charAt(0)) == '^' || value == '?') { 6463 require = require.substr(1); 6464 if (value == '^') { 6465 retrievalMethod = 'inheritedData'; 6466 } 6467 optional = optional || value == '?'; 6468 } 6469 value = null; 6470 6471 if (elementControllers && retrievalMethod === 'data') { 6472 value = elementControllers[require]; 6473 } 6474 value = value || $element[retrievalMethod]('$' + require + 'Controller'); 6475 6476 if (!value && !optional) { 6477 throw $compileMinErr('ctreq', 6478 "Controller '{0}', required by directive '{1}', can't be found!", 6479 require, directiveName); 6480 } 6481 return value; 6482 } else if (isArray(require)) { 6483 value = []; 6484 forEach(require, function(require) { 6485 value.push(getControllers(directiveName, require, $element, elementControllers)); 6486 }); 6487 } 6488 return value; 6489 } 6490 6491 6492 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 6493 var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; 6494 6495 if (compileNode === linkNode) { 6496 attrs = templateAttrs; 6497 } else { 6498 attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); 6499 } 6500 $element = attrs.$$element; 6501 6502 if (newIsolateScopeDirective) { 6503 var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; 6504 var $linkNode = jqLite(linkNode); 6505 6506 isolateScope = scope.$new(true); 6507 6508 if (templateDirective && (templateDirective === newIsolateScopeDirective || 6509 templateDirective === newIsolateScopeDirective.$$originalDirective)) { 6510 $linkNode.data('$isolateScope', isolateScope) ; 6511 } else { 6512 $linkNode.data('$isolateScopeNoTemplate', isolateScope); 6513 } 6514 6515 6516 6517 safeAddClass($linkNode, 'ng-isolate-scope'); 6518 6519 forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { 6520 var match = definition.match(LOCAL_REGEXP) || [], 6521 attrName = match[3] || scopeName, 6522 optional = (match[2] == '?'), 6523 mode = match[1], // @, =, or & 6524 lastValue, 6525 parentGet, parentSet, compare; 6526 6527 isolateScope.$$isolateBindings[scopeName] = mode + attrName; 6528 6529 switch (mode) { 6530 6531 case '@': 6532 attrs.$observe(attrName, function(value) { 6533 isolateScope[scopeName] = value; 6534 }); 6535 attrs.$$observers[attrName].$$scope = scope; 6536 if( attrs[attrName] ) { 6537 // If the attribute has been provided then we trigger an interpolation to ensure 6538 // the value is there for use in the link fn 6539 isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); 6540 } 6541 break; 6542 6543 case '=': 6544 if (optional && !attrs[attrName]) { 6545 return; 6546 } 6547 parentGet = $parse(attrs[attrName]); 6548 if (parentGet.literal) { 6549 compare = equals; 6550 } else { 6551 compare = function(a,b) { return a === b; }; 6552 } 6553 parentSet = parentGet.assign || function() { 6554 // reset the change, or we will throw this exception on every $digest
6555 lastValue = isolateScope[scopeName] = parentGet(scope); 6556 throw $compileMinErr('nonassign', 6557 "Expression '{0}' used with directive '{1}' is non-assignable!", 6558 attrs[attrName], newIsolateScopeDirective.name); 6559 }; 6560 lastValue = isolateScope[scopeName] = parentGet(scope); 6561 isolateScope.$watch(function parentValueWatch() { 6562 var parentValue = parentGet(scope); 6563 if (!compare(parentValue, isolateScope[scopeName])) { 6564 // we are out of sync and need to copy 6565 if (!compare(parentValue, lastValue)) { 6566 // parent changed and it has precedence 6567 isolateScope[scopeName] = parentValue; 6568 } else { 6569 // if the parent can be assigned then do so 6570 parentSet(scope, parentValue = isolateScope[scopeName]); 6571 } 6572 } 6573 return lastValue = parentValue; 6574 }, null, parentGet.literal); 6575 break; 6576 6577 case '&': 6578 parentGet = $parse(attrs[attrName]); 6579 isolateScope[scopeName] = function(locals) { 6580 return parentGet(scope, locals); 6581 }; 6582 break; 6583 6584 default: 6585 throw $compileMinErr('iscp', 6586 "Invalid isolate scope definition for directive '{0}'." + 6587 " Definition: {... {1}: '{2}' ...}", 6588 newIsolateScopeDirective.name, scopeName, definition); 6589 } 6590 }); 6591 } 6592 transcludeFn = boundTranscludeFn && controllersBoundTransclude; 6593 if (controllerDirectives) { 6594 forEach(controllerDirectives, function(directive) { 6595 var locals = { 6596 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 6597 $element: $element, 6598 $attrs: attrs, 6599 $transclude: transcludeFn 6600 }, controllerInstance; 6601 6602 controller = directive.controller; 6603 if (controller == '@') { 6604 controller = attrs[directive.name]; 6605 } 6606 6607 controllerInstance = $controller(controller, locals); 6608 // For directives with element transclusion the element is a comment, 6609 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 6610 // clean up (http://bugs.jquery.com/ticket/8335). 6611 // Instead, we save the controllers for the element in a local hash and attach to .data 6612 // later, once we have the actual element. 6613 elementControllers[directive.name] = controllerInstance; 6614 if (!hasElementTranscludeDirective) { 6615 $element.data('$' + directive.name + 'Controller', controllerInstance); 6616 } 6617 6618 if (directive.controllerAs) { 6619 locals.$scope[directive.controllerAs] = controllerInstance; 6620 } 6621 }); 6622 } 6623 6624 // PRELINKING 6625 for(i = 0, ii = preLinkFns.length; i < ii; i++) { 6626 try { 6627 linkFn = preLinkFns[i]; 6628 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6629 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); 6630 } catch (e) { 6631 $exceptionHandler(e, startingTag($element)); 6632 } 6633 } 6634 6635 // RECURSION 6636 // We only pass the isolate scope, if the isolate directive has a template, 6637 // otherwise the child elements do not belong to the isolate directive. 6638 var scopeToChild = scope; 6639 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 6640 scopeToChild = isolateScope; 6641 } 6642 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
6643 6644 // POSTLINKING 6645 for(i = postLinkFns.length - 1; i >= 0; i--) { 6646 try { 6647 linkFn = postLinkFns[i]; 6648 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6649 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn); 6650 } catch (e) { 6651 $exceptionHandler(e, startingTag($element)); 6652 } 6653 } 6654 6655 // This is the function that is injected as `$transclude`. 6656 function controllersBoundTransclude(scope, cloneAttachFn) { 6657 var transcludeControllers; 6658 6659 // no scope passed 6660 if (arguments.length < 2) { 6661 cloneAttachFn = scope; 6662 scope = undefined; 6663 } 6664 6665 if (hasElementTranscludeDirective) { 6666 transcludeControllers = elementControllers; 6667 } 6668 6669 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); 6670 } 6671 } 6672 } 6673 6674 function markDirectivesAsIsolate(directives) { 6675 // mark all directives as needing isolate scope. 6676 for (var j = 0, jj = directives.length; j < jj; j++) { 6677 directives[j] = inherit(directives[j], {$$isolateScope: true}); 6678 } 6679 } 6680 6681 /** 6682 * looks up the directive and decorates it with exception handling and proper parameters. We 6683 * call this the boundDirective. 6684 * 6685 * @param {string} name name of the directive to look up. 6686 * @param {string} location The directive must be found in specific format. 6687 * String containing any of theses characters: 6688 * 6689 * * `E`: element name 6690 * * `A': attribute 6691 * * `C`: class 6692 * * `M`: comment 6693 * @returns {boolean} true if directive was added. 6694 */ 6695 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 6696 endAttrName) { 6697 if (name === ignoreDirective) return null; 6698 var match = null; 6699 if (hasDirectives.hasOwnProperty(name)) { 6700 for(var directive, directives = $injector.get(name + Suffix), 6701 i = 0, ii = directives.length; i<ii; i++) { 6702 try { 6703 directive = directives[i]; 6704 if ( (maxPriority === undefined || maxPriority > directive.priority) && 6705 directive.restrict.indexOf(location) != -1) { 6706 if (startAttrName) { 6707 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 6708 } 6709 tDirectives.push(directive); 6710 match = directive; 6711 } 6712 } catch(e) { $exceptionHandler(e); } 6713 } 6714 } 6715 return match; 6716 } 6717 6718 6719 /** 6720 * When the element is replaced with HTML template then the new attributes 6721 * on the template need to be merged with the existing attributes in the DOM. 6722 * The desired effect is to have both of the attributes present. 6723 * 6724 * @param {object} dst destination attributes (original DOM) 6725 * @param {object} src source attributes (from the directive template) 6726 */ 6727 function mergeTemplateAttributes(dst, src) { 6728 var srcAttr = src.$attr, 6729 dstAttr = dst.$attr, 6730 $element = dst.$$element; 6731 6732 // reapply the old attributes to the new element 6733 forEach(dst, function(value, key) { 6734 if (key.charAt(0) != '$') { 6735 if (src[key] && src[key] !== value) { 6736 value += (key === 'style' ? ';' : ' ') + src[key]; 6737 } 6738 dst.$set(key, value, true, srcAttr[key]); 6739 } 6740 }); 6741 6742 // copy the new attributes on the old attrs object 6743 forEach(src, function(value, key) { 6744 if (key == 'class') { 6745 safeAddClass($element, value); 6746 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 6747 } else if (key == 'style') { 6748 $element.attr('style', $element.attr('style') + ';' + value); 6749 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 6750 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 6751 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 6752 // have an attribute like "has-own-property" or "data-has-own-property", etc. 6753 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 6754 dst[key] = value; 6755 dstAttr[key] = srcAttr[key]; 6756 } 6757 }); 6758 } 6759 6760 6761 function compileTemplateUrl(directives, $compileNode, tAttrs, 6762 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 6763 var linkQueue = [], 6764 afterTemplateNodeLinkFn, 6765 afterTemplateChildLinkFn, 6766 beforeTemplateCompileNode = $compileNode[0], 6767 origAsyncDirective = directives.shift(), 6768 // The fact that we have to copy and patch the directive seems wrong! 6769 derivedSyncDirective = extend({}, origAsyncDirective, { 6770 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 6771 }), 6772 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 6773 ? origAsyncDirective.templateUrl($compileNode, tAttrs
6773) 6774 : origAsyncDirective.templateUrl; 6775 6776 $compileNode.empty(); 6777 6778 $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). 6779 success(function(content) { 6780 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 6781 6782 content = denormalizeTemplate(content); 6783 6784 if (origAsyncDirective.replace) { 6785 if (jqLiteIsTextNode(content)) { 6786 $template = []; 6787 } else { 6788 $template = jqLite(trim(content)); 6789 } 6790 compileNode = $template[0]; 6791 6792 if ($template.length != 1 || compileNode.nodeType !== 1) { 6793 throw $compileMinErr('tplrt', 6794 "Template for directive '{0}' must have exactly one root element. {1}", 6795 origAsyncDirective.name, templateUrl); 6796 } 6797 6798 tempTemplateAttrs = {$attr: {}}; 6799 replaceWith($rootElement, $compileNode, compileNode); 6800 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 6801 6802 if (isObject(origAsyncDirective.scope)) { 6803 markDirectivesAsIsolate(templateDirectives); 6804 } 6805 directives = templateDirectives.concat(directives); 6806 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 6807 } else { 6808 compileNode = beforeTemplateCompileNode; 6809 $compileNode.html(content); 6810 } 6811 6812 directives.unshift(derivedSyncDirective); 6813 6814 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 6815 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 6816 previousCompileContext); 6817 forEach($rootElement, function(node, i) { 6818 if (node == compileNode) { 6819 $rootElement[i] = $compileNode[0]; 6820 } 6821 }); 6822 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 6823 6824 while(linkQueue.length) { 6825 var scope = linkQueue.shift(), 6826 beforeTemplateLinkNode = linkQueue.shift(), 6827 linkRootElement = linkQueue.shift(), 6828 boundTranscludeFn = linkQueue.shift(), 6829 linkNode = $compileNode[0]; 6830 6831 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 6832 var oldClasses = beforeTemplateLinkNode.className; 6833 6834 if (!(previousCompileContext.hasElementTranscludeDirective && 6835 origAsyncDirective.replace)) { 6836 // it was cloned therefore we have to clone as well. 6837 linkNode = jqLiteClone(compileNode); 6838 } 6839 6840 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 6841 6842 // Copy in CSS classes from original node 6843 safeAddClass(jqLite(linkNode), oldClasses); 6844 } 6845 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 6846 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 6847 } else { 6848 childBoundTranscludeFn = boundTranscludeFn; 6849 } 6850 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 6851 childBoundTranscludeFn); 6852 } 6853 linkQueue = null; 6854 }). 6855 error(function(response, code, headers, config) { 6856 throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); 6857 }); 6858 6859 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 6860 var childBoundTranscludeFn = boundTranscludeFn; 6861 if (linkQueue) { 6862 linkQueue.push(scope); 6863 linkQueue.push(node); 6864 linkQueue.push(rootElement); 6865 linkQueue.push(childBoundTranscludeFn); 6866 } else { 6867 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 6868 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
6869 } 6870 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); 6871 } 6872 }; 6873 } 6874 6875 6876 /** 6877 * Sorting function for bound directives. 6878 */ 6879 function byPriority(a, b) { 6880 var diff = b.priority - a.priority; 6881 if (diff !== 0) return diff; 6882 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 6883 return a.index - b.index; 6884 } 6885 6886 6887 function assertNoDuplicate(what, previousDirective, directive, element) { 6888 if (previousDirective) { 6889 throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', 6890 previousDirective.name, directive.name, what, startingTag(element)); 6891 } 6892 } 6893 6894 6895 function addTextInterpolateDirective(directives, text) { 6896 var interpolateFn = $interpolate(text, true); 6897 if (interpolateFn) { 6898 directives.push({ 6899 priority: 0, 6900 compile: function textInterpolateCompileFn(templateNode) { 6901 // when transcluding a template that has bindings in the root 6902 // then we don't have a parent and should do this in the linkFn 6903 var parent = templateNode.parent(), hasCompileParent = parent.length; 6904 if (hasCompileParent) safeAddClass(templateNode.parent(), 'ng-binding'); 6905 6906 return function textInterpolateLinkFn(scope, node) { 6907 var parent = node.parent(), 6908 bindings = parent.data('$binding') || []; 6909 bindings.push(interpolateFn); 6910 parent.data('$binding', bindings); 6911 if (!hasCompileParent) safeAddClass(parent, 'ng-binding'); 6912 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 6913 node[0].nodeValue = value; 6914 }); 6915 }; 6916 } 6917 }); 6918 } 6919 } 6920 6921 6922 function getTrustedContext(node, attrNormalizedName) { 6923 if (attrNormalizedName == "srcdoc") { 6924 return $sce.HTML; 6925 } 6926 var tag = nodeName_(node); 6927 // maction[xlink:href] can source SVG. It's not limited to <maction>. 6928 if (attrNormalizedName == "xlinkHref" || 6929 (tag == "FORM" && attrNormalizedName == "action") || 6930 (tag != "IMG" && (attrNormalizedName == "src" || 6931 attrNormalizedName == "ngSrc"))) { 6932 return $sce.RESOURCE_URL; 6933 } 6934 } 6935 6936 6937 function addAttrInterpolateDirective(node, directives, value, name) { 6938 var interpolateFn = $interpolate(value, true); 6939 6940 // no interpolation found -> ignore 6941 if (!interpolateFn) return; 6942 6943 6944 if (name === "multiple" && nodeName_(node) === "SELECT") { 6945 throw $compileMinErr("selmulti", 6946 "Binding to the 'multiple' attribute is not supported. Element: {0}", 6947 startingTag(node)); 6948 } 6949 6950 directives.push({ 6951 priority: 100, 6952 compile: function() { 6953 return { 6954 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 6955 var $$observers = (attr.$$observers || (attr.$$observers = {})); 6956 6957 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 6958 throw $compileMinErr('nodomevents', 6959 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 6960 "ng- versions (such as ng-click instead of onclick) instead."); 6961 } 6962 6963 // we need to interpolate again, in case the attribute value has been updated 6964 // (e.g. by another directive's compile function) 6965 interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); 6966 6967 // if attribute was updated so that there is no interpolation going on we don't want to 6968 // register any observers 6969 if (!interpolateFn) return; 6970 6971 // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the 6972 // actual attr value 6973 attr[name] = interpolateFn(scope); 6974 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 6975 (attr.$$observers && attr.$$observers[name].$$scope || scope). 6976 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 6977 //special case for class attribute addition + removal 6978 //so that class changes can tap into the animation 6979 //hooks provided by the $animate service. Be sure to 6980 //skip animations when the first digest occurs (when 6981 //both the new and the old values are the same) since 6982 //the CSS classes are the non-interpolated values 6983 if(name === 'class' && newValue != oldValue) { 6984 attr.$updateClass(newValue, oldValue); 6985 } else { 6986 attr.$set(name, newValue); 6987 } 6988 }); 6989 } 6990 }; 6991 } 6992 }); 6993 } 6994 6995 6996 /** 6997 * This is a special jqLite.replaceWith, which can replace items which 6998 * have no parents, provided that the containing jqLite collection is provided. 6999 * 7000 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 7001 * in the root of the tree. 7002 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 7003 * the shell, but replace its DOM node reference. 7004 * @param {Node} newNode The new DOM node. 7005 */ 7006 function replaceWith($rootElement, elementsToRemove, newNode) { 7007 var firstElementToRemove = elementsToRemove[0], 7008 removeCount = elementsToRemove.length, 7009 parent = firstElementToRemove.parentNode, 7010 i, ii; 7011 7012 if ($rootElement) { 7013 for(i = 0, ii = $rootElement.length; i < ii; i++) { 7014 if ($rootElement[i] == firstElementToRemove) { 7015 $rootElement[i++] = newNode; 7016 for (var j = i, j2 = j + removeCount - 1, 7017 jj = $rootElement.length; 7018 j < jj; j++, j2++) { 7019 if (j2 < jj) { 7020 $rootElement[j] = $rootElement[j2]; 7021 } else { 7022 delete $rootElement[j]; 7023 } 7024 } 7025 $rootElement.length -= removeCount - 1; 7026 break; 7027 } 7028 } 7029 } 7030 7031 if (parent) { 7032 parent.replaceChild(newNode, firstElementToRemove); 7033 } 7034 var fragment = document.createDocumentFragment(); 7035 fragment.appendChild(firstElementToRemove); 7036 newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; 7037 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 7038 var element = elementsToRemove[k]; 7039 jqLite(element).remove(); // must do this way to clean up expando 7040 fragment.appendChild(element); 7041 delete elementsToRemove[k]; 7042 } 7043 7044 elementsToRemove[0] = newNode; 7045 elementsToRemove.length = 1; 7046 } 7047 7048 7049 function cloneAndAnnotateFn(fn, annotation) { 7050 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 7051 } 7052 }]; 7053} 7054 7055var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; 7056/** 7057 * Converts all accepted directives format into proper directive name. 7058 * All of these will become 'myDirective': 7059 * my:Directive 7060 * my-directive 7061 * x-my-directive 7062 * data-my:directive 7063 * 7064 * Also there is special case for Moz prefix starting with upper case letter. 7065 * @param name Name to normalize 7066 */ 7067function directiveNormalize(name) { 7068 return camelCase(name.replace(PREFIX_REGEXP, '')); 7069} 7070 7071/** 7072 * @ngdoc type 7073 * @name $compile.directive.Attributes 7074 * 7075 * @description 7076 * A shared object between directive compile / linking functions which contains
7076normalized DOM 7077 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 7078 * needed since all of these are treated as equivalent in Angular: 7079 * 7080 * ``` 7081 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 7082 * ``` 7083 */ 7084 7085/** 7086 * @ngdoc property 7087 * @name $compile.directive.Attributes#$attr 7088 * @returns {object} A map of DOM element attribute names to the normalized name. This is 7089 * needed to do reverse lookup from normalized name back to actual name. 7090 */ 7091 7092 7093/** 7094 * @ngdoc method 7095 * @name $compile.directive.Attributes#$set 7096 * @kind function 7097 * 7098 * @description 7099 * Set DOM element attribute value. 7100 * 7101 * 7102 * @param {string} name Normalized element attribute name of the property to modify. The name is 7103 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 7104 * property to the original name. 7105 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 7106 */ 7107 7108 7109 7110/** 7111 * Closure compiler type information 7112 */ 7113 7114function nodesetLinkingFn( 7115 /* angular.Scope */ scope, 7116 /* NodeList */ nodeList, 7117 /* Element */ rootElement, 7118 /* function(Function) */ boundTranscludeFn 7119){} 7120 7121function directiveLinkingFn( 7122 /* nodesetLinkingFn */ nodesetLinkingFn, 7123 /* angular.Scope */ scope, 7124 /* Node */ node, 7125 /* Element */ rootElement, 7126 /* function(Function) */ boundTranscludeFn 7127){} 7128 7129function tokenDifference(str1, str2) { 7130 var values = '', 7131 tokens1 = str1.split(/\s+/), 7132 tokens2 = str2.split(/\s+/); 7133 7134 outer: 7135 for(var i = 0; i < tokens1.length; i++) { 7136 var token = tokens1[i]; 7137 for(var j = 0; j < tokens2.length; j++) { 7138 if(token == tokens2[j]) continue outer; 7139 } 7140 values += (values.length > 0 ? ' ' : '') + token; 7141 } 7142 return values; 7143} 7144 7145/** 7146 * @ngdoc provider 7147 * @name $controllerProvider 7148 * @description 7149 * The {@link ng.$controller $controller service} is used by Angular to create new 7150 * controllers. 7151 * 7152 * This provider allows controller registration via the 7153 * {@link ng.$controllerProvider#register register} method. 7154 */ 7155function $ControllerProvider() { 7156 var controllers = {}, 7157 CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 7158 7159 7160 /** 7161 * @ngdoc method 7162 * @name $controllerProvider#register 7163 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 7164 * the names and the values are the constructors. 7165 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 7166 * annotations in the array notation). 7167 */ 7168 this.register = function(name, constructor) { 7169 assertNotHasOwnProperty(name, 'controller'); 7170 if (isObject(name)) { 7171 extend(controllers, name); 7172 } else { 7173 controllers[name] = constructor; 7174 } 7175 }; 7176 7177 7178 this.$get = ['$injector', '$window', function($injector, $window) { 7179 7180 /** 7181 * @ngdoc service 7182 * @name $controller 7183 * @requires $injector 7184 * 7185 * @param {Function|string} constructor If called with a function then it's considered to be the 7186 * controller constructor function. Otherwise it's considered to be a string which is used 7187 * to retrieve the controller constructor using the following steps: 7188 * 7189 * * check if a controller with given name is registered via `$controllerProvider` 7190 * * check if evaluating the string on the current scope returns a constructor 7191 * * check `window[constructor]` on the global `window` object 7192 * 7193 * @param {Object} locals Injection locals for Controller. 7194 * @return {Object} Instance of given controller. 7195 * 7196 * @description 7197 * `$controller` service is responsible for instantiating controllers. 7198 * 7199 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 7200 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 7201 */ 7202 return function(expression, locals) { 7203 var instance, match, constructor, identifier; 7204 7205 if(isString(expression)) { 7206 match = expression.match(CNTRL_REG), 7207 constructor = match[1], 7208 identifier = match[3]; 7209 expression = controllers.hasOwnProperty(constructor) 7210 ? controllers[constructor] 7211 : getter(locals.$scope, constructor, true) || getter($window, constructor, true); 7212 7213 assertArgFn(expression, constructor, true); 7214 } 7215 7216 instance = $injector.instantiate(expression, locals); 7217 7218 if (identifier) { 7219 if (!(locals && typeof locals.$scope === 'object')) { 7220 throw minErr('$controller')('noscp', 7221 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 7222 constructor || expression.name, identifier); 7223 } 7224 7225 locals.$scope[identifier] = instance; 7226 } 7227 7228 return instance; 7229 }; 7230 }]; 7231} 7232 7233/** 7234 * @ngdoc service 7235 * @name $document 7236 * @requires $window 7237 * 7238 * @description 7239 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 7240 * 7241 * @example 7242 <example> 7243 <file name="index.html"> 7244 <div ng-controller="MainCtrl"> 7245 <p>$document title: <b ng-bind="title"></b></p> 7246 <p>window.document title: <b ng-bind="windowTitle"></b></p> 7247 </div> 7248 </file> 7249 <file name="script.js"> 7250 function MainCtrl($scope, $document) { 7251 $scope.title = $document[0].title; 7252 $scope.windowTitle = angular.element(window.document)[0].title; 7253 } 7254 </file> 7255 </example> 7256 */ 7257function $DocumentProvider(){ 7258 this.$get = ['$window', function(window){ 7259 return jqLite(window.document); 7260 }]; 7261} 7262 7263/** 7264 * @ngdoc service 7265 * @name $exceptionHandler 7266 * @requires ng.$log 7267 * 7268 * @description 7269 * Any uncaught exception in angular expressions is delegated to this service. 7270 * The default implementation simply delegates to `$log.error` which logs it into 7271 * the browser console. 7272 * 7273 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 7274 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 7275 * 7276 * ## Example: 7277 * 7278 * ```js 7279 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { 7280 * return function (exception, cause) { 7281 * exception.message += ' (caused by "' + cause + '")'; 7282 * throw exception; 7283 * }; 7284 * }); 7285 * ``` 7286 * 7287 * This example will override the normal action of `$exceptionHandler`, to make angular
7288 * exceptions fail hard when they happen, instead of just logging to the console. 7289 * 7290 * @param {Error} exception Exception associated with the error. 7291 * @param {string=} cause optional information about the context in which 7292 * the error was thrown. 7293 * 7294 */ 7295function $ExceptionHandlerProvider() { 7296 this.$get = ['$log', function($log) { 7297 return function(exception, cause) { 7298 $log.error.apply($log, arguments); 7299 }; 7300 }]; 7301} 7302 7303/** 7304 * Parse headers into key value object 7305 * 7306 * @param {string} headers Raw headers as a string 7307 * @returns {Object} Parsed headers as key value object 7308 */ 7309function parseHeaders(headers) { 7310 var parsed = {}, key, val, i; 7311 7312 if (!headers) return parsed; 7313 7314 forEach(headers.split('\n'), function(line) { 7315 i = line.indexOf(':'); 7316 key = lowercase(trim(line.substr(0, i))); 7317 val = trim(line.substr(i + 1)); 7318 7319 if (key) { 7320 if (parsed[key]) { 7321 parsed[key] += ', ' + val; 7322 } else { 7323 parsed[key] = val; 7324 } 7325 } 7326 }); 7327 7328 return parsed; 7329} 7330 7331 7332/** 7333 * Returns a function that provides access to parsed headers. 7334 * 7335 * Headers are lazy parsed when first requested. 7336 * @see parseHeaders 7337 * 7338 * @param {(string|Object)} headers Headers to provide access to. 7339 * @returns {function(string=)} Returns a getter function which if called with: 7340 * 7341 * - if called with single an argument returns a single header value or null
7342 * - if called with no arguments returns an object containing all headers. 7343 */ 7344function headersGetter(headers) { 7345 var headersObj = isObject(headers) ? headers : undefined; 7346 7347 return function(name) { 7348 if (!headersObj) headersObj = parseHeaders(headers); 7349 7350 if (name) { 7351 return headersObj[lowercase(name)] || null; 7352 } 7353 7354 return headersObj; 7355 }; 7356} 7357 7358 7359/** 7360 * Chain all given functions 7361 * 7362 * This function is used for both request and response transforming 7363 * 7364 * @param {*} data Data to transform. 7365 * @param {function(string=)} headers Http headers getter fn. 7366 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 7367 * @returns {*} Transformed data. 7368 */ 7369function transformData(data, headers, fns) { 7370 if (isFunction(fns)) 7371 return fns(data, headers); 7372 7373 forEach(fns, function(fn) { 7374 data = fn(data, headers); 7375 }); 7376 7377 return data; 7378} 7379 7380 7381function isSuccess(status) { 7382 return 200 <= status && status < 300; 7383} 7384 7385 7386function $HttpProvider() { 7387 var JSON_START = /^\s*(\[|\{[^\{])/, 7388 JSON_END = /[\}\]]\s*$/, 7389 PROTECTION_PREFIX = /^\)\]\}',?\n/, 7390 CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; 7391 7392 var defaults = this.defaults = { 7393 // transform incoming response data 7394 transformResponse: [function(data) { 7395 if (isString(data)) { 7396 // strip json vulnerability protection prefix 7397 data = data.replace(PROTECTION_PREFIX, ''); 7398 if (JSON_START.test(data) && JSON_END.test(data)) 7399 data = fromJson(data); 7400 } 7401 return data; 7402 }], 7403 7404 // transform outgoing request data 7405 transformRequest: [function(d) { 7406 return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; 7407 }], 7408 7409 // default headers 7410 headers: { 7411 common: { 7412 'Accept': 'application/json, text/plain, */*' 7413 }, 7414 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 7415 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 7416 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 7417 }, 7418 7419 xsrfCookieName: 'XSRF-TOKEN', 7420 xsrfHeaderName: 'X-XSRF-TOKEN' 7421 }; 7422 7423 /** 7424 * Are ordered by request, i.e. they are applied in the same order as the 7425 * array, on request, but reverse order, on response. 7426 */ 7427 var interceptorFactories = this.interceptors = []; 7428 7429 /** 7430 * For historical reasons, response interceptors are ordered by the order in which 7431 * they are applied to the response. (This is the opposite of interceptorFactories) 7432 */ 7433 var responseInterceptorFactories = this.responseInterceptors = []; 7434 7435 this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', 7436 function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { 7437 7438 var defaultCache = $cacheFactory('$http'); 7439 7440 /** 7441 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 7442 * The reversal is needed so that we can build up the interception chain around the 7443 * server request. 7444 */ 7445 var reversedInterceptors = []; 7446 7447 forEach(interceptorFactories, function(interceptorFactory) { 7448 reversedInterceptors.unshift(isString(interceptorFactory) 7449 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 7450 }); 7451 7452 forEach(responseInterceptorFactories, function(interceptorFactory, index) { 7453 var responseFn = isString(interceptorFactory) 7454 ? $injector.get(interceptorFactory) 7455 : $injector.invoke(interceptorFactory); 7456 7457 /** 7458 * Response interceptors go before "around" interceptors (no real reason, just 7459 * had to pick one.) But they are already reversed, so we can't use unshift, hence 7460 * the splice. 7461 */ 7462 reversedInterceptors.splice(index, 0, { 7463 response: function(response) { 7464 return responseFn($q.when(response)); 7465 }, 7466 responseError: function(response) { 7467 return responseFn($q.reject(response)); 7468 } 7469 }); 7470 }); 7471 7472 7473 /** 7474 * @ngdoc service 7475 * @kind function 7476 * @name $http 7477 * @requires ng.$httpBackend 7478 * @requires $cacheFactory 7479 * @requires $rootScope 7480 * @requires $q 7481 * @requires $injector 7482 *
7483 * @description 7484 * The `$http` service is a core Angular service that facilitates communication with the remote 7485 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 7486 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 7487 * 7488 * For unit testing applications that use `$http` service, see 7489 * {@link ngMock.$httpBackend $httpBackend mock}. 7490 * 7491 * For a higher level of abstraction, please check out the {@link ngResource.$resource 7492 * $resource} service. 7493 * 7494 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 7495 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 7496 * it is important to familiarize yourself with these APIs and the guarantees they provide. 7497 * 7498 * 7499 * # General usage 7500 * The `$http` service is a function which takes a single argument â a configuration object â 7501 * that is used to generate an HTTP request and returns a {@link ng.$q promise} 7502 * with two $http specific methods: `success` and `error`. 7503 * 7504 * ```js 7505 * $http({method: 'GET', url: '/someUrl'}). 7506 * success(function(data, status, headers, config) { 7507 * // this callback will be called asynchronously 7508 * // when the response is available 7509 * }). 7510 * error(function(data, status, headers, config) { 7511 * // called asynchronously if an error occurs 7512 * // or server returns response with an error status. 7513 * }); 7514 * ``` 7515 * 7516 * Since the returned value of calling the $http function is a `promise`, you can also use 7517 * the `then` method to register callbacks, and these callbacks will receive a single argument â 7518 * an object representing the response. See the API signature and type info below for more 7519 * details. 7520 * 7521 * A response status code between 200 and 299 is considered a success status and 7522 * will result in the success callback being called. Note that if the response is a redirect, 7523 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 7524 * called for such responses. 7525 * 7526 * # Writing Unit Tests that use $http 7527 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 7528 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 7529 * request using trained responses. 7530 * 7531 * ``` 7532 * $httpBackend.expectGET(...); 7533 * $http.get(...); 7534 * $httpBackend.flush(); 7535 * ``` 7536 * 7537 * # Shortcut methods 7538 * 7539 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 7540 * request data must be passed in for POST/PUT requests. 7541 * 7542 * ```js 7543 * $http.get('/someUrl').success(successCallback); 7544 * $http.post('/someUrl', data).success(successCallback); 7545 * ``` 7546 * 7547 * Complete list of shortcut methods: 7548 * 7549 * - {@link ng.$http#get $http.get} 7550 * - {@link ng.$http#head $http.head} 7551 * - {@link ng.$http#post $http.post} 7552 * - {@link ng.$http#put $http.put} 7553 * - {@link ng.$http#delete $http.delete} 7554 * - {@link ng.$http#jsonp $http.jsonp} 7555 * 7556 * 7557 * # Setting HTTP Headers 7558 * 7559 * The $http service will automatically add certain HTTP headers to all requests. These defaults 7560 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 7561 * object, which currently contains this default configuration: 7562 * 7563 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 7564 * - `Accept: application/json, text/plain, * / *` 7565 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 7566 * - `Content-Type: application/json` 7567 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 7568 * - `Content-Type: application/json` 7569 * 7570 * To add or overwrite these defaults, simply add or remove a property from these configuration 7571 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
7572 * with the lowercased HTTP method name as the key, e.g. 7573 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. 7574 * 7575 * The defaults can also be set at runtime via the `$http.defaults` object in the same 7576 * fashion. For example: 7577 * 7578 * ``` 7579 * module.run(function($http) { 7580 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' 7581 * }); 7582 * ``` 7583 * 7584 * In addition, you can supply a `headers` property in the config object passed when 7585 * calling `$http(config)`, which overrides the defaults without changing them globally. 7586 * 7587 * 7588 * # Transforming Requests and Responses 7589 * 7590 * Both requests and responses can be transformed using transform functions. By default, Angular 7591 * applies these transformations: 7592 * 7593 * Request transformations: 7594 * 7595 * - If the `data` property of the request configuration object contains an object, serialize it 7596 * into JSON format. 7597 * 7598 * Response transformations: 7599 * 7600 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 7601 * - If JSON response is detected, deserialize it using a JSON parser. 7602 * 7603 * To globally augment or override the default transforms, modify the 7604 * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` 7605 * properties. These properties are by default an array of transform functions, which allows you 7606 * to `push` or `unshift` a new transformation function into the transformation chain. You can 7607 * also decide to completely override any default transformations by assigning your 7608 * transformation functions to these properties directly without the array wrapper. These defaults 7609 * are again available on the $http factory at run-time, which may be useful if you have run-time 7610 * services you wish to be involved in your transformations. 7611 * 7612 * Similarly, to locally override the request/response transforms, augment the 7613 * `transformRequest` and/or `transformResponse` properties of the configuration object passed 7614 * into `$http`. 7615 * 7616 * 7617 * # Caching 7618 * 7619 * To enable caching, set the request configuration `cache` property to `true` (to use default 7620 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 7621 * When the cache is enabled, `$http` stores the response from the server in the specified 7622 * cache. The next time the same request is made, the response is served from the cache without 7623 * sending a request to the server. 7624 * 7625 * Note that even if the response is served from cache, delivery of the data is asynchronous in 7626 * the same way that real requests are. 7627 * 7628 * If there are multiple GET requests for the same URL that should be cached using the same 7629 * cache, but the cache is not populated yet, only one request to the server will be made and 7630 * the remaining requests will be fulfilled using the response from the first request. 7631 * 7632 * You can change the default cache to a new object (built with 7633 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 7634 * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set 7635 * their `cache` property to `true` will now use this cache object. 7636 * 7637 * If you set the default cache to `false` then only requests that specify their own custom 7638 * cache object will be cached. 7639 * 7640 * # Interceptors 7641 * 7642 * Before you start creating interceptors, be sure to understand the 7643 * {@link ng.$q $q and deferred/promise APIs}. 7644 * 7645 * For purposes of global error handling, authentication, or any kind of synchronous or 7646 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 7647 * able to intercept requests before they are handed to the server and 7648 * responses before they are handed over to the application code that 7649 * initiated these requests. The interceptors leverage the {@link ng.$q 7650 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 7651 * 7652 * The interceptors are service factories that are registered with the `$httpProvider` by 7653 * adding them to the `$httpProvider.interceptors` array. The factory is called and 7654 * injected with dependencies (if specified) and returns the interceptor. 7655 * 7656 * There are two kinds of interceptors (and two kinds of rejection interceptors): 7657 * 7658 * * `request`: interceptors get called with a http `config` object. The function is free to 7659 * modify the `config` object or create a new one. The function needs to return the `config` 7660 * object directly, or a promise containing the `config` or a new `config` object. 7661 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 7662 * resolved with a rejection. 7663 * * `response`: interceptors get called with http `response` object. The function is free to 7664 * modify the `response` object or create a new one. The function needs to return the `response` 7665 * object directly, or as a promise containing the `response` or a new `response` object. 7666 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 7667 * resolved with a rejection. 7668 * 7669 * 7670 * ```js 7671 * // register the interceptor as a service 7672 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7673 * return { 7674 * // optional method 7675 * 'request': function(config) {
7676 * // do something on success 7677 * return config; 7678 * }, 7679 * 7680 * // optional method 7681 * 'requestError': function(rejection) { 7682 * // do something on error 7683 * if (canRecover(rejection)) { 7684 * return responseOrNewPromise 7685 * } 7686 * return $q.reject(rejection); 7687 * }, 7688 * 7689 * 7690 * 7691 * // optional method 7692 * 'response': function(response) { 7693 * // do something on success 7694 * return response; 7695 * }, 7696 * 7697 * // optional method 7698 * 'responseError': function(rejection) { 7699 * // do something on error 7700 * if (canRecover(rejection)) { 7701 * return responseOrNewPromise 7702 * } 7703 * return $q.reject(rejection); 7704 * } 7705 * }; 7706 * }); 7707 * 7708 * $httpProvider.interceptors.push('myHttpInterceptor'); 7709 * 7710 * 7711 * // alternatively, register the interceptor via an anonymous factory 7712 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 7713 * return { 7714 * 'request': function(config) { 7715 * // same as above 7716 * }, 7717 * 7718 * 'response': function(response) { 7719 * // same as above 7720 * } 7721 * }; 7722 * }); 7723 * ``` 7724 * 7725 * # Response interceptors (DEPRECATED) 7726 * 7727 * Before you start creating interceptors, be sure to understand the 7728 * {@link ng.$q $q and deferred/promise APIs}. 7729 * 7730 * For purposes of global error handling, authentication or any kind of synchronous or 7731 * asynchronous preprocessing of received responses, it is desirable to be able to intercept 7732 * responses for http requests before they are handed over to the application code that 7733 * initiated these requests. The response interceptors leverage the {@link ng.$q 7734 * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. 7735 * 7736 * The interceptors are service factories that are registered with the $httpProvider by 7737 * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and 7738 * injected with dependencies (if specified) and returns the interceptor â a function that 7739 * takes a {@link ng.$q promise} and returns the original or a new promise. 7740 * 7741 * ```js 7742 * // register the interceptor as a service 7743 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7744 * return function(promise) { 7745 * return promise.then(function(response) { 7746 * // do something on success 7747 * return response; 7748 * }, function(response) { 7749 * // do something on error 7750 * if (canRecover(response)) { 7751 * return responseOrNewPromise 7752 * } 7753 * return $q.reject(response); 7754 * }); 7755 * } 7756 * }); 7757 * 7758 * $httpProvider.responseInterceptors.push('myHttpInterceptor'); 7759 * 7760 * 7761 * // register the interceptor via an anonymous factory 7762 * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) { 7763 * return function(promise) { 7764 * // same as above 7765 * } 7766 * }); 7767 * ``` 7768 * 7769 * 7770 * # Security Considerations 7771 * 7772 * When designing web applications, consider security threats from: 7773 * 7774 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 7775 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 7776 * 7777 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 7778 * pre-configured with strategies that address these issues, but for this to work backend server 7779 * cooperation is required. 7780 * 7781 * ## JSON Vulnerability Protection 7782 * 7783 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 7784 * allows third party website to turn your JSON resource URL into 7785 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 7786 * counter this your server can prefix all JSON requests with following str
7786ing `")]}',\n"`. 7787 * Angular will automatically strip the prefix before processing it as JSON. 7788 * 7789 * For example if your server needs to return: 7790 * ```js 7791 * ['one','two'] 7792 * ``` 7793 * 7794 * which is vulnerable to attack, your server can return: 7795 * ```js 7796 * )]}', 7797 * ['one','two'] 7798 * ``` 7799 * 7800 * Angular will strip the prefix, before processing the JSON. 7801 * 7802 * 7803 * ## Cross Site Request Forgery (XSRF) Protection 7804 * 7805 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which 7806 * an unauthorized site can gain your user's private data. Angular provides a mechanism 7807 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 7808 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 7809 * JavaScript that runs on your domain could read the cookie, your server can be assured that 7810 * the XHR came from JavaScript running on your domain. The header will not be set for 7811 * cross-domain requests. 7812 * 7813 * To take advantage of this, your server needs to set a token in a JavaScript readable session 7814 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 7815 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 7816 * that only JavaScript running on your domain could have sent the request. The token must be 7817 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 7818 * making up its own tokens). We recommend that the token is a digest of your site's 7819 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 7820 * for added security. 7821 * 7822 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 7823 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 7824 * or the per-request config object. 7825 * 7826 * 7827 * @param {object} config Object describing the request to be made and how it should be 7828 * processed. The object has following properties: 7829 * 7830 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 7831 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 7832 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be turned 7833 * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be 7834 * JSONified. 7835 * - **data** â `{string|Object}` â Data to be sent as the request message data. 7836 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 7837 * HTTP headers to send to the server. If the return value of a function is null, the 7838 * header will not be sent. 7839 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 7840 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 7841 * - **transformRequest** â 7842 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 7843 * transform function or an array of such functions. The transform function takes the http 7844 * request body and headers and returns its transformed (typically serialized) version. 7845 * - **transformResponse** â 7846 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 7847 * transform function or an array of such functions. The transform function takes the http 7848 * response body and headers and returns its transformed (typically deserialized) version. 7849 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 7850 * GET request, otherwise if a cache instance built with 7851 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 7852 * caching. 7853 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 7854 * that should abort the request when resolved. 7855 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 7856 * XHR object. See [requests with credentials]https://developer.mozilla.org/en/http_access_control#section_5 7857 * for more information. 7858 * - **responseType** - `{string}` - see 7859 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). 7860 * 7861 * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the 7862 * standard `then` method and two http specific methods: `success` and `error`. The `then` 7863 * method takes two arguments a success and an error callback which will be called with a 7864 * response object. The `success` and `error` methods take a single argument - a function that 7865 * will be called when the request succeeds or fails respectively. The arguments passed into 7866 * these functions are destructured representation of the response object passed into the 7867 * `then` method. The response object has these properties: 7868 * 7869 * - **data** â `{string|Object}` â The response body transformed with the transform 7870 * functions. 7871 * - **status** â `{number}` â HTTP status code of the response. 7872 * - **headers** â `{function([headerName])}` â Header getter function. 7873 * - **config** â `{Object}` â The configuration object that was used to generate the request. 7874 * - **statusText** â `{string}` â HTTP status text of the response. 7875 * 7876 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 7877 * requests. This is primarily meant to be used for debugging purposes. 7878 * 7879 * 7880 * @example 7881<example> 7882<file name="index.html"> 7883 <div ng-controller="FetchCtrl"> 7884 <select ng-model="method"> 7885 <option>GET</option> 7886 <option>JSONP</option> 7887 </select> 7888 <input type="text" ng-model="url" size="80"/> 7889 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 7890 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 7891 <button id="samplejsonpbtn" 7892 ng-click="updateModel('JSONP', 7893 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 7894 Sample JSONP 7895 </button> 7896 <button id="invalidjsonpbtn" 7897 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 7898 Invalid JSONP 7899 </button> 7900 <pre>http status code: {{status}}</pre> 7901 <pre>http response data: {{data}}</pre> 7902 </div> 7903</file> 7904<file name="script.js"> 7905 function FetchCtrl($scope, $http, $templateCache) { 7906 $scope.method = 'GET'; 7907 $scope.url = 'http-hello.html'; 7908 7909 $scope.fetch = function() { 7910 $scope.code = null; 7911 $scope.response = null; 7912 7913 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 7914 success(function(data, status) { 7915 $scope.status = status; 7916 $scope.data = data; 7917 }). 7918 error(function(data, status) { 7919 $scope.data = data || "Request failed"; 7920 $scope.status = status; 7921 }); 7922 }; 7923 7924 $scope.updateModel = function(method, url) { 7925 $scope.method = method; 7926 $scope.url = url; 7927 }; 7928 } 7929</file> 7930<file name="http-hello.html"> 7931 Hello, $http! 7932</file> 7933<file name="protractor.js" type="protractor"> 7934 var status = element(by.binding('status')); 7935 var data = element(by.binding('data')); 7936 var fetchBtn = element(by.id('fetchbtn')); 7937 var sampleGetBtn = element(by.id('samplegetbtn')); 7938 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 7939 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 7940 7941 it('should make an xhr GET request', function() { 7942 sampleGetBtn.click(); 7943 fetchBtn.click(); 7944 expect(status.getText()).toMatch('200'); 7945 expect(data.getText()).toMatch(/Hello, \$http!/); 7946 }); 7947 7948 it('should make a JSONP request to angularjs.org', function() { 7949 sampleJsonpBtn.click(); 7950 fetchBtn.click(); 7951 expect(status.getText()).toMatch('200'); 7952 expect(data.getText()).toMatch(/Super Hero!/); 7953 }); 7954
7955 it('should make JSONP request to invalid URL and invoke the error handler', 7956 function() { 7957 invalidJsonpBtn.click(); 7958 fetchBtn.click(); 7959 expect(status.getText()).toMatch('0'); 7960 expect(data.getText()).toMatch('Request failed'); 7961 }); 7962</file> 7963</example> 7964 */ 7965 function $http(requestConfig) { 7966 var config = { 7967 method: 'get', 7968 transformRequest: defaults.transformRequest, 7969 transformResponse: defaults.transformResponse 7970 }; 7971 var headers = mergeHeaders(requestConfig); 7972 7973 extend(config, requestConfig); 7974 config.headers = headers; 7975 config.method = uppercase(config.method); 7976 7977 var serverRequest = function(config) { 7978 headers = config.headers; 7979 var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); 7980 7981 // strip content-type if data is undefined 7982 if (isUndefined(config.data)) { 7983 forEach(headers, function(value, header) { 7984 if (lowercase(header) === 'content-type') { 7985 delete headers[header]; 7986 } 7987 }); 7988 } 7989 7990 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 7991 config.withCredentials = defaults.withCredentials; 7992 } 7993 7994 // send request 7995 return sendReq(config, reqData, headers).then(transformResponse, transformResponse); 7996 }; 7997 7998 var chain = [serverRequest, undefined]; 7999 var promise = $q.when(config); 8000 8001 // apply interceptors 8002 forEach(reversedInterceptors, function(interceptor) { 8003 if (interceptor.request || interceptor.requestError) { 8004 chain.unshift(interceptor.request, interceptor.requestError); 8005 } 8006 if (interceptor.response || interceptor.responseError) { 8007 chain.push(interceptor.response, interceptor.responseError); 8008 } 8009 }); 8010 8011 while(chain.length) { 8012 var thenFn = chain.shift(); 8013 var rejectFn = chain.shift(); 8014 8015 promise = promise.then(thenFn, rejectFn); 8016 } 8017 8018 promise.success = function(fn) { 8019 promise.then(function(response) { 8020 fn(response.data, response.status, response.headers, config); 8021 }); 8022 return promise; 8023 }; 8024 8025 promise.error = function(fn) { 8026 promise.then(null, function(response) { 8027 fn(response.data, response.status, response.headers, config); 8028 }); 8029 return promise; 8030 }; 8031 8032 return promise; 8033 8034 function transformResponse(response) { 8035 // make a copy since the response must be cacheable 8036 var resp = extend({}, response, { 8037 data: transformData(response.data, response.headers, config.transformResponse) 8038 }); 8039 return (isSuccess(response.status)) 8040 ? resp 8041 : $q.reject(resp); 8042 } 8043 8044 function mergeHeaders(config) { 8045 var defHeaders = defaults.headers, 8046 reqHeaders = extend({}, config.headers), 8047 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 8048 8049 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 8050 8051 // execute if header value is function 8052 execHeaders(defHeaders); 8053 execHeaders(reqHeaders); 8054 8055 // using for-in instead of forEach to avoid unecessary iteration after header has been found 8056 defaultHeadersIteration: 8057 for (defHeaderName in defHeaders) { 8058 lowercaseDefHeaderName = lowercase(defHeaderName); 8059 8060 for (reqHeaderName in reqHeaders) { 8061 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 8062 continue defaultHeadersIteration; 8063 } 8064 } 8065 8066 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 8067 } 8068 8069 return reqHeaders; 8070 8071 function execHeaders(headers) { 8072 var headerContent; 8073 8074 forEach(headers, function(headerFn, header) { 8075 if (isFunction(headerFn)) { 8076 headerContent = headerFn(); 8077 if (headerContent != null) { 8078 headers[header] = headerContent; 8079 } else { 8080 delete headers[header]; 8081 } 8082 } 8083 }); 8084 } 8085 } 8086 } 8087 8088 $http.pendingRequests = []; 8089 8090 /** 8091 * @ngdoc method 8092 * @name $http#get 8093 *
8094 * @description 8095 * Shortcut method to perform `GET` request. 8096 * 8097 * @param {string} url Relative or absolute URL specifying the destination of the request 8098 * @param {Object=} config Optional configuration object 8099 * @returns {HttpPromise} Future object 8100 */ 8101 8102 /** 8103 * @ngdoc method 8104 * @name $http#delete 8105 * 8106 * @description 8107 * Shortcut method to perform `DELETE` request. 8108 * 8109 * @param {string} url Relative or absolute URL specifying the destination of the request 8110 * @param {Object=} config Optional configuration object 8111 * @returns {HttpPromise} Future object 8112 */ 8113 8114 /** 8115 * @ngdoc method 8116 * @name $http#head 8117 * 8118 * @description 8119 * Shortcut method to perform `HEAD` request. 8120 * 8121 * @param {string} url Relative or absolute URL specifying the destination of the request 8122 * @param {Object=} config Optional configuration object 8123 * @returns {HttpPromise} Future object 8124 */ 8125 8126 /** 8127 * @ngdoc method 8128 * @name $http#jsonp 8129 * 8130 * @description 8131 * Shortcut method to perform `JSONP` request. 8132 * 8133 * @param {string} url Relative or absolute URL specifying the destination of the request. 8134 * Should contain `JSON_CALLBACK` string. 8135 * @param {Object=} config Optional configuration object 8136 * @returns {HttpPromise} Future object 8137 */ 8138 createShortMethods('get', 'delete', 'head', 'jsonp'); 8139 8140 /** 8141 * @ngdoc method 8142 * @name $http#post 8143 * 8144 * @description 8145 * Shortcut method to perform `POST` request. 8146 * 8147 * @param {string} url Relative or absolute URL specifying the destination of the request 8148 * @param {*} data Request content 8149 * @param {Object=} config Optional configuration object 8150 * @returns {HttpPromise} Future object 8151 */ 8152 8153 /** 8154 * @ngdoc method 8155 * @name $http#put 8156 * 8157 * @description 8158 * Shortcut method to perform `PUT` request. 8159 * 8160 * @param {string} url Relative or absolute URL specifying the destination of the request 8161 * @param {*} data Request content 8162 * @param {Object=} config Optional configuration object 8163 * @returns {HttpPromise} Future object 8164 */ 8165 createShortMethodsWithData('post', 'put'); 8166 8167 /** 8168 * @ngdoc property 8169 * @name $http#defaults 8170 * 8171 * @description 8172 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 8173 * default headers, withCredentials as well as request and response transformations. 8174 * 8175 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 8176 */ 8177 $http.defaults = defaults; 8178 8179 8180 return $http; 8181 8182 8183 function createShortMethods(names) { 8184 forEach(arguments, function(name) { 8185 $http[name] = function(url, config) { 8186 return $http(extend(config || {}, { 8187 method: name, 8188 url: url 8189 })); 8190 }; 8191 }); 8192 } 8193 8194 8195 function createShortMethodsWithData(name) { 8196 forEach(arguments, function(name) { 8197 $http[name] = function(url, data, config) { 8198 return $http(extend(config || {}, { 8199 method: name, 8200 url: url, 8201 data: data 8202 })); 8203 }; 8204 }); 8205 } 8206 8207 8208 /** 8209 * Makes the request. 8210 * 8211 * !!! ACCESSES CLOSURE VARS: 8212 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 8213 */ 8214 function sendReq(config, reqData, reqHeaders) { 8215 var deferred = $q.defer(), 8216 promise = deferred.promise, 8217 cache, 8218 cachedResp, 8219 url = buildUrl(config.url, config.params); 8220 8221 $http.pendingRequests.push(config); 8222 promise.then(removePendingReq, removePendingReq); 8223 8224 8225 if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') { 8226 cache = isObject(config.cache) ? config.cache 8227 : isObject(defaults.cache) ? defaults.cache 8228 : defaultCache; 8229 } 8230 8231 if (cache) { 8232 cachedResp = cache.get(url); 8233 if (isDefined(cachedResp)) { 8234 if (cachedResp.then) { 8235 // cached request has already been sent, but there is no response yet 8236 cachedResp.then(removePendingReq, removePendingReq); 8237 return cachedResp; 8238 } else { 8239 // serving from cache 8240 if (isArray(cachedResp)) { 8241 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 8242 } else { 8243 resolvePromise(cachedResp, 200, {}, 'OK'); 8244 } 8245 } 8246 } else { 8247 // put the promise for the non-transformed response into cache as a placeholder 8248 cache.put(url, promise); 8249 } 8250 } 8251 8252 8253 // if we won't have the response in cache, set the xsrf headers and 8254 // send the request to the backend 8255 if (isUndefined(cachedResp)) { 8256 var xsrfValue = urlIsSameOrigin(config.url) 8257 ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] 8258 : undefined; 8259 if (xsrfValue) { 8260 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 8261 } 8262 8263 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 8264 config.withCredentials, config.responseType); 8265 } 8266 8267 return promise; 8268 8269 8270 /** 8271 * Callback registered to $httpBackend(): 8272 * - caches the response if desired 8273 * - resolves the raw $http promise 8274 * - calls $apply 8275 */ 8276 function done(status, response, headersString, statusText) { 8277 if (cache) { 8278 if (isSuccess(status)) { 8279 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 8280 } else { 8281 // remove promise from the cache 8282 cache.remove(url); 8283 } 8284 } 8285 8286 resolvePromise(response, status, headersString, statusText); 8287 if (!$rootScope.$$phase) $rootScope.$apply(); 8288 } 8289 8290 8291 /** 8292 * Resolves the raw $http promise. 8293 */ 8294 function resolvePromise(response, status, headers, statusText) { 8295 // normalize internal statuses to 0 8296 status = Math.max(status, 0); 8297 8298 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 8299 data: response, 8300 status: status, 8301 headers: headersGetter(headers), 8302 config: config, 8303 statusText : statusText 8304 }); 8305 } 8306 8307 8308 function removePendingReq() { 8309 var idx = indexOf($http.pendingRequests, config); 8310 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 8311 } 8312 } 8313 8314 8315 function buildUrl(url, params) { 8316 if (!params) return url; 8317 var parts = [];
8318 forEachSorted(params, function(value, key) { 8319 if (value === null || isUndefined(value)) return; 8320 if (!isArray(value)) value = [value]; 8321 8322 forEach(value, function(v) { 8323 if (isObject(v)) { 8324 v = toJson(v); 8325 } 8326 parts.push(encodeUriQuery(key) + '=' + 8327 encodeUriQuery(v)); 8328 }); 8329 }); 8330 if(parts.length > 0) { 8331 url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); 8332 } 8333 return url; 8334 } 8335 8336 8337 }]; 8338} 8339 8340function createXhr(method) { 8341 //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest 8342 //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest 8343 //if it is available 8344 if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) || 8345 !window.XMLHttpRequest)) { 8346 return new window.ActiveXObject("Microsoft.XMLHTTP"); 8347 } else if (window.XMLHttpRequest) { 8348 return new window.XMLHttpRequest(); 8349 } 8350 8351 throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); 8352} 8353 8354/** 8355 * @ngdoc service 8356 * @name $httpBackend 8357 * @requires $window 8358 * @requires $document 8359 * 8360 * @description 8361 * HTTP backend used by the {@link ng.$http service} that delegates to 8362 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 8363 * 8364 * You should never need to use this service directly, instead use the higher-level abstractions: 8365 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 8366 * 8367 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 8368 * $httpBackend} which can be trained with responses. 8369 */ 8370function $HttpBackendProvider() { 8371 this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { 8372 return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); 8373 }]; 8374} 8375 8376function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 8377 var ABORTED = -1; 8378 8379 // TODO(vojta): fix the signature 8380 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 8381 var status; 8382 $browser.$$incOutstandingRequestCount(); 8383 url = url || $browser.url(); 8384 8385 if (lowercase(method) == 'jsonp') { 8386 var callbackId = '_' + (callbacks.counter++).toString(36); 8387 callbacks[callbackId] = function(data) { 8388 callbacks[callbackId].data = data; 8389 callbacks[callbackId].called = true; 8390 }; 8391 8392 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 8393 callbackId, function(status, text) { 8394 completeRequest(callback, status, callbacks[callbackId].data, "", text); 8395 callbacks[callbackId] = noop; 8396 }); 8397 } else { 8398 8399 var xhr = createXhr(method); 8400 8401 xhr.open(method, url, true); 8402 forEach(headers, function(value, key) { 8403 if (isDefined(value)) { 8404 xhr.setRequestHeader(key, value); 8405 } 8406 }); 8407 8408 // In IE6 and 7, this might be called synchronously when xhr.send below is called and the 8409 // response is in the cache. the promise api will ensure that to the app code the api is 8410 // always async 8411 xhr.onreadystatechange = function() { 8412 // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by 8413 // xhrs that are resolved while the app is in the background (see #5426). 8414 // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before 8415 // continuing 8416 // 8417 // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and 8418 // Safari respectively. 8419 if (xhr && xhr.readyState == 4) { 8420 var responseHeaders = null, 8421 response = null, 8422 statusText = ''; 8423 8424 if(status !== ABORTED) { 8425 responseHeaders = xhr.getAllResponseHeaders(); 8426 8427 // responseText is the old-school way of retrieving response
8427(supported by IE8 & 9) 8428 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 8429 response = ('response' in xhr) ? xhr.response : xhr.responseText; 8430 } 8431 8432 // Accessing statusText on an aborted xhr object will 8433 // throw an 'c00c023f error' in IE9 and lower, don't touch it. 8434 if (!(status === ABORTED && msie < 10)) { 8435 statusText = xhr.statusText; 8436 } 8437 8438 completeRequest(callback, 8439 status || xhr.status, 8440 response, 8441 responseHeaders, 8442 statusText); 8443 } 8444 }; 8445 8446 if (withCredentials) { 8447 xhr.withCredentials = true; 8448 } 8449 8450 if (responseType) { 8451 try { 8452 xhr.responseType = responseType; 8453 } catch (e) { 8454 // WebKit added support for the json responseType value on 09/03/2013 8455 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 8456 // known to throw when setting the value "json" as the response type. Other older 8457 // browsers implementing the responseType 8458 // 8459 // The json response type can be ignored if not supported, because JSON payloads are 8460 // parsed on the client-side regardless. 8461 if (responseType !== 'json') { 8462 throw e; 8463 } 8464 } 8465 } 8466 8467 xhr.send(post || null); 8468 } 8469 8470 if (timeout > 0) { 8471 var timeoutId = $browserDefer(timeoutRequest, timeout); 8472 } else if (timeout && timeout.then) { 8473 timeout.then(timeoutRequest); 8474 } 8475 8476 8477 function timeoutRequest() { 8478 status = ABORTED; 8479 jsonpDone && jsonpDone(); 8480 xhr && xhr.abort(); 8481 } 8482 8483 function completeRequest(callback, status, response, headersString, statusText) { 8484 // cancel timeout and subsequent timeout promise resolution 8485 timeoutId && $browserDefer.cancel(timeoutId); 8486 jsonpDone = xhr = null; 8487 8488 // fix status code when it is 0 (0 status is undocumented). 8489 // Occurs when accessing file resources or on Android 4.1 stock browser 8490 // while retrieving files from application cache. 8491 if (status === 0) { 8492 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 8493 } 8494 8495 // normalize IE bug (http://bugs.jquery.com/ticket/1450) 8496 status = status === 1223 ? 204 : status; 8497 statusText = statusText || ''; 8498 8499 callback(status, response, headersString, statusText); 8500 $browser.$$completeOutstandingRequest(noop); 8501 } 8502 }; 8503 8504 function jsonpReq(url, callbackId, done) { 8505 // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: 8506 // - fetches local scripts via XHR and evals them 8507 // - adds and immediately removes script elements from the document 8508 var script = rawDocument.createElement('script'), callback = null; 8509 script.type = "text/javascript"; 8510 script.src = url; 8511 script.async = true; 8512 8513 callback = function(event) { 8514 removeEventListenerFn(script, "load", callback); 8515 removeEventListenerFn(script, "error", callback); 8516 rawDocument.body.removeChild(script); 8517 script = null; 8518 var status = -1; 8519 var text = "unknown"; 8520 8521 if (event) { 8522 if (event.type === "load" && !callbacks[callbackId].called) { 8523 event = { type: "error" }; 8524 } 8525 text = event.type; 8526 status = event.type === "error" ? 404 : 200; 8527 } 8528 8529 if (done) { 8530 done(status, text); 8531 } 8532 }; 8533 8534 addEventListenerFn(script, "load", callback); 8535 addEventListenerFn(script, "error", callback); 8536 8537 if (msie <= 8) { 8538 script.onreadystatechange = function() { 8539 if (isString(script.readyState) && /loaded|complete/.test(script.readyState)) { 8540 script.onreadystatechange = null; 8541 callback({ 8542 type: 'load' 8543 }); 8544 } 8545 }; 8546 } 8547 8548 rawDocument.body.appendChild(script); 8549 return callback; 8550 } 8551} 8552 8553var $interpolateMinErr = minErr('$interpolate'); 8554 8555/** 8556 * @ngdoc provider 8557 * @name $interpolateProvider 8558 * @kind function 8559 * 8560 * @description 8561 *
8562 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 8563 * 8564 * @example 8565<example module="customInterpolationApp"> 8566<file name="index.html"> 8567<script> 8568 var customInterpolationApp = angular.module('customInterpolationApp', []); 8569 8570 customInterpolationApp.config(function($interpolateProvider) { 8571 $interpolateProvider.startSymbol('//'); 8572 $interpolateProvider.endSymbol('//'); 8573 }); 8574 8575 8576 customInterpolationApp.controller('DemoController', function() { 8577 this.label = "This binding is brought you by // interpolation symbols."; 8578 }); 8579</script> 8580<div ng-app="App" ng-controller="DemoController as demo"> 8581 //demo.label// 8582</div> 8583</file> 8584<file name="protractor.js" type="protractor"> 8585 it('should interpolate binding with custom symbols', function() { 8586 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 8587 }); 8588</file> 8589</example> 8590 */ 8591function $InterpolateProvider() { 8592 var startSymbol = '{{'; 8593 var endSymbol = '}}'; 8594 8595 /** 8596 * @ngdoc method 8597 * @name $interpolateProvider#startSymbol 8598 * @description 8599 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 8600 * 8601 * @param {string=} value new value to set the starting symbol to. 8602 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8603 */ 8604 this.startSymbol = function(value){ 8605 if (value) { 8606 startSymbol = value; 8607 return this; 8608 } else { 8609 return startSymbol; 8610 } 8611 }; 8612 8613 /** 8614 * @ngdoc method 8615 * @name $interpolateProvider#endSymbol 8616 * @description 8617 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8618 * 8619 * @param {string=} value new value to set the ending symbol to. 8620 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8621 */ 8622 this.endSymbol = function(value){ 8623 if (value) { 8624 endSymbol = value; 8625 return this; 8626 } else { 8627 return endSymbol; 8628 } 8629 }; 8630 8631 8632 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 8633 var startSymbolLength = startSymbol.length, 8634 endSymbolLength = endSymbol.length; 8635 8636 /** 8637 * @ngdoc service 8638 * @name $interpolate 8639 * @kind function 8640 * 8641 * @requires $parse 8642 * @requires $sce 8643 * 8644 * @description 8645 * 8646 * Compiles a string with markup into an interpolation function. This service is used by the 8647 * HTML {@link ng.$compile $compile} service for data binding. See 8648 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 8649 * interpolation markup. 8650 * 8651 * 8652 * ```js 8653 * var $interpolate = ...; // injected 8654 * var exp = $interpolate('Hello {{name | uppercase}}!'); 8655 * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); 8656 * ``` 8657 * 8658 * 8659 * @param {string} text The text with markup to interpolate. 8660 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 8661 * embedded expression in order to return an interpolation function. Strings with no 8662 * embedded expression will return null for the interpolation function. 8663 * @param {string=} trustedContext when provided, the returned function passes the interpolated 8664 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 8665 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 8666 * provides Strict Contextual Escaping for details. 8667 * @returns {function(context)} an interpolation function which is used to compute the 8668 * interpolated string. The function has these parameters: 8669 * 8670 * * `context`: an object against which any expressions embedded in the strings are evaluated 8671 * against. 8672 * 8673 */ 8674 function $interpolate(text, mustHaveExpression, trustedContext) { 8675 var startIndex, 8676 endIndex, 8677 index = 0, 8678 parts = [], 8679 length = text.length, 8680 hasInterpolation = false, 8681 fn, 8682 exp, 8683 concat = []; 8684 8685 while(index < length) { 8686 if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && 8687 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { 8688 (index != startIndex) && parts.push(text.substring(index, startIndex)); 8689 parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); 8690 fn.exp = exp;
8691 index = endIndex + endSymbolLength; 8692 hasInterpolation = true; 8693 } else { 8694 // we did not find anything, so we have to add the remainder to the parts array 8695 (index != length) && parts.push(text.substring(index)); 8696 index = length; 8697 } 8698 } 8699 8700 if (!(length = parts.length)) { 8701 // we added, nothing, must have been an empty string. 8702 parts.push(''); 8703 length = 1; 8704 } 8705 8706 // Concatenating expressions makes it hard to reason about whether some combination of 8707 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 8708 // single expression be used for iframe[src], object[src], etc., we ensure that the value 8709 // that's used is assigned or constructed by some JS code somewhere that is more testable or 8710 // make it obvious that you bound the value to some user controlled value. This helps reduce 8711 // the load when auditing for XSS issues. 8712 if (trustedContext && parts.length > 1) { 8713 throw $interpolateMinErr('noconcat', 8714 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 8715 "interpolations that concatenate multiple expressions when a trusted value is " + 8716 "required. See http://docs.angularjs.org/api/ng.$sce", text); 8717 } 8718 8719 if (!mustHaveExpression || hasInterpolation) { 8720 concat.length = length; 8721 fn = function(context) { 8722 try { 8723 for(var i = 0, ii = length, part; i<ii; i++) { 8724 if (typeof (part = parts[i]) == 'function') { 8725 part = part(context); 8726 if (trustedContext) { 8727 part = $sce.getTrusted(trustedContext, part); 8728 } else { 8729 part = $sce.valueOf(part); 8730 } 8731 if (part == null) { // null || undefined 8732 part = ''; 8733 } else { 8734 switch (typeof part) { 8735 case 'string': 8736 { 8737 break; 8738 } 8739 case 'number': 8740 { 8741 part = '' + part; 8742 break; 8743 } 8744 default: 8745 { 8746 part = toJson(part); 8747 } 8748 } 8749 } 8750 } 8751 concat[i] = part; 8752 } 8753 return concat.join(''); 8754 } 8755 catch(err) { 8756 var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, 8757 err.toString()); 8758 $exceptionHandler(newErr); 8759 } 8760 }; 8761 fn.exp = text; 8762 fn.parts = parts; 8763 return fn; 8764 } 8765 } 8766 8767 8768 /** 8769 * @ngdoc method 8770 * @name $interpolate#startSymbol 8771 * @description 8772 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 8773 * 8774 * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change 8775 * the symbol. 8776 * 8777 * @returns {string} start symbol. 8778 */ 8779 $interpolate.startSymbol = function() { 8780 return startSymbol; 8781 }; 8782 8783 8784 /** 8785 * @ngdoc method 8786 * @name $interpolate#endSymbol 8787 * @description 8788 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8789 * 8790 * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change 8791 * the symbol. 8792 * 8793 * @returns {string} end symbol. 8794 */ 8795 $interpolate.endSymbol = function() { 8796 return endSymbol; 8797 }; 8798 8799 return $interpolate; 8800 }]; 8801} 8802 8803function $IntervalProvider() { 8804 this.$get = ['$rootScope', '$window', '$q', 8805 function($rootScope, $window, $q) { 8806 var intervals = {}; 8807 8808 8809 /** 8810 * @ngdoc service 8811 * @name $interval 8812 * 8813 * @description 8814 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 8815 * milliseconds. 8816 * 8817 * The return value of registering an interval function is a promise. This promise will be 8818 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 8819 * run indefinitely if `count` is not defined. The value of the notification will be the 8820 * number of iterations that have run. 8821 * To cancel an interval, call `$interval.cancel(promise)`. 8822 * 8823 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 8824 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 8825 * time. 8826 * 8827 * <div class="alert alert-warning">
8828 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 8829 * with them. In particular they are not automatically destroyed when a controller's scope or a 8830 * directive's element are destroyed. 8831 * You should take this into consideration and make sure to always cancel the interval at the 8832 * appropriate moment. See the example below for more details on how and when to do this. 8833 * </div> 8834 * 8835 * @param {function()} fn A function that should be called repeatedly. 8836 * @param {number} delay Number of milliseconds between each function call. 8837 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 8838 * indefinitely. 8839 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 8840 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 8841 * @returns {promise} A promise which will be notified on each iteration. 8842 * 8843 * @example 8844 * <example module="time"> 8845 * <file name="index.html"> 8846 * <script> 8847 * function Ctrl2($scope,$interval) { 8848 * $scope.format = 'M/d/yy h:mm:ss a'; 8849 * $scope.blood_1 = 100; 8850 * $scope.blood_2 = 120; 8851 * 8852 * var stop; 8853 * $scope.fight = function() { 8854 * // Don't start a new fight if we are already fighting 8855 * if ( angular.isDefined(stop) ) return; 8856 * 8857 * stop = $interval(function() { 8858 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 8859 * $scope.blood_1 = $scope.blood_1 - 3; 8860 * $scope.blood_2 = $scope.blood_2 - 4; 8861 * } else { 8862 * $scope.stopFight(); 8863 * } 8864 * }, 100); 8865 * }; 8866 * 8867 * $scope.stopFight = function() { 8868 * if (angular.isDefined(stop)) { 8869 * $interval.cancel(stop); 8870 * stop = undefined; 8871 * } 8872 * }; 8873 * 8874 * $scope.resetFight = function() { 8875 * $scope.blood_1 = 100; 8876 * $scope.blood_2 = 120; 8877 * } 8878 * 8879 * $scope.$on('$destroy', function() { 8880 * // Make sure that the interval is destroyed too 8881 * $scope.stopFight(); 8882 * }); 8883 * } 8884 * 8885 * angular.module('time', []) 8886 * // Register the 'myCurrentTime' directive factory method. 8887 * // We inject $interval and dateFilter service since the factory method is DI. 8888 * .directive('myCurrentTime', function($interval, dateFilter) { 8889 * // return the directive link function. (compile function not needed) 8890 * return function(scope, element, attrs) { 8891 * var format, // date format 8892 * stopTime; // so that we can cancel the time updates 8893 * 8894 * // used to update the UI 8895 * function updateTime() { 8896 * element.text(dateFilter(new Date(), format)); 8897 * } 8898 * 8899 * // watch the expression, and update the UI on change. 8900 * scope.$watch(attrs.myCurrentTime, function(value) { 8901 * format = value; 8902 * updateTime(); 8903 * }); 8904 * 8905 * stopTime = $interval(updateTime, 1000); 8906 * 8907 * // listen on DOM destroy (removal) event, and cancel the next UI update 8908 * // to prevent updating time ofter the DOM element was removed. 8909 * element.bind('$destroy', function() { 8910 * $interval.cancel(stopTime); 8911 * }); 8912 * } 8913 * }); 8914 * </script> 8915 * 8916 * <div> 8917 * <div ng-controller="Ctrl2"> 8918 * Date format: <input ng-model="format"> <hr/> 8919 * Current time is: <span my-current-time="format"></span> 8920 * <hr/> 8921 * Blood 1 : <font color='red'>{{blood_1}}</font> 8922 * Blood 2 : <font color='red'>{{blood_2}}</font> 8923 * <button type="button" data-ng-click="fight()">Fight</button> 8924 * <button type="button" data-ng-click="stopFight()">StopFight</button> 8925 * <button type="button" data-ng-click="resetFight()">resetFight</button> 8926 * </div>
8927 * </div> 8928 * 8929 * </file> 8930 * </example> 8931 */ 8932 function interval(fn, delay, count, invokeApply) { 8933 var setInterval = $window.setInterval, 8934 clearInterval = $window.clearInterval, 8935 deferred = $q.defer(), 8936 promise = deferred.promise, 8937 iteration = 0, 8938 skipApply = (isDefined(invokeApply) && !invokeApply); 8939 8940 count = isDefined(count) ? count : 0; 8941 8942 promise.then(null, null, fn); 8943 8944 promise.$$intervalId = setInterval(function tick() { 8945 deferred.notify(iteration++); 8946 8947 if (count > 0 && iteration >= count) { 8948 deferred.resolve(iteration); 8949 clearInterval(promise.$$intervalId); 8950 delete intervals[promise.$$intervalId]; 8951 } 8952 8953 if (!skipApply) $rootScope.$apply(); 8954 8955 }, delay); 8956 8957 intervals[promise.$$intervalId] = deferred; 8958 8959 return promise; 8960 } 8961 8962 8963 /** 8964 * @ngdoc method 8965 * @name $interval#cancel 8966 * 8967 * @description 8968 * Cancels a task associated with the `promise`. 8969 * 8970 * @param {promise} promise returned by the `$interval` function. 8971 * @returns {boolean} Returns `true` if the task was successfully canceled. 8972 */ 8973 interval.cancel = function(promise) { 8974 if (promise && promise.$$intervalId in intervals) { 8975 intervals[promise.$$intervalId].reject('canceled'); 8976 $window.clearInterval(promise.$$intervalId); 8977 delete intervals[promise.$$intervalId]; 8978 return true; 8979 } 8980 return false; 8981 }; 8982 8983 return interval; 8984 }]; 8985} 8986 8987/** 8988 * @ngdoc service 8989 * @name $locale 8990 * 8991 * @description 8992 * $locale service provides localization rules for various Angular components. As of right now the 8993 * only public api is: 8994 * 8995 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 8996 */ 8997function $LocaleProvider(){ 8998 this.$get = function() { 8999 return { 9000 id: 'en-us', 9001 9002 NUMBER_FORMATS: { 9003 DECIMAL_SEP: '.', 9004 GROUP_SEP: ',', 9005 PATTERNS: [ 9006 { // Decimal Pattern 9007 minInt: 1, 9008 minFrac: 0, 9009 maxFrac: 3, 9010 posPre: '', 9011 posSuf: '', 9012 negPre: '-', 9013 negSuf: '', 9014 gSize: 3, 9015 lgSize: 3 9016 },{ //Currency Pattern 9017 minInt: 1, 9018 minFrac: 2, 9019 maxFrac: 2, 9020 posPre: '\u00A4', 9021 posSuf: '', 9022 negPre: '(\u00A4', 9023 negSuf: ')', 9024 gSize: 3, 9025 lgSize: 3 9026 } 9027 ], 9028 CURRENCY_SYM: '$' 9029 }, 9030 9031 DATETIME_FORMATS: { 9032 MONTH: 9033 'January,February,March,April,May,June,July,August,September,October,November,December' 9034 .split(','), 9035 SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), 9036 DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), 9037 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), 9038 AMPMS: ['AM','PM'], 9039 medium: 'MMM d, y h:mm:ss a', 9040 short: 'M/d/yy h:mm a', 9041 fullDate: 'EEEE, MMMM d, y', 9042 longDate: 'MMMM d, y', 9043 mediumDate: 'MMM d, y', 9044 shortDate: 'M/d/yy', 9045 mediumTime: 'h:mm:ss a', 9046 shortTime: 'h:mm a' 9047 }, 9048 9049 pluralCat: function(num) { 9050 if (num === 1) { 9051 return 'one'; 9052 } 9053 return 'other'; 9054 } 9055 }; 9056 }; 9057} 9058 9059var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 9060 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 9061var $locationMinErr = minErr('$location'); 9062 9063 9064/** 9065 * Encode path using encodeUriSegment, ignoring forward slashes 9066 * 9067 * @param {string} path Path to encode 9068 * @returns {string} 9069 */ 9070function encodePath(path) { 9071 var segments = path.split('/'), 9072 i = segments.length; 9073 9074 while (i--) { 9075 segments[i] = encodeUriSegment(segments[i]); 9076 } 9077 9078 return segments.join('/'); 9079} 9080 9081function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
9082 var parsedUrl = urlResolve(absoluteUrl, appBase); 9083 9084 locationObj.$$protocol = parsedUrl.protocol; 9085 locationObj.$$host = parsedUrl.hostname; 9086 locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 9087} 9088 9089 9090function parseAppUrl(relativeUrl, locationObj, appBase) { 9091 var prefixed = (relativeUrl.charAt(0) !== '/'); 9092 if (prefixed) { 9093 relativeUrl = '/' + relativeUrl; 9094 } 9095 var match = urlResolve(relativeUrl, appBase); 9096 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 9097 match.pathname.substring(1) : match.pathname); 9098 locationObj.$$search = parseKeyValue(match.search); 9099 locationObj.$$hash = decodeURIComponent(match.hash); 9100 9101 // make sure path starts with '/'; 9102 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 9103 locationObj.$$path = '/' + locationObj.$$path; 9104 } 9105} 9106 9107 9108/** 9109 * 9110 * @param {string} begin 9111 * @param {string} whole 9112 * @returns {string} returns text from whole after begin or undefined if it does not begin with 9113 * expected string. 9114 */ 9115function beginsWith(begin, whole) { 9116 if (whole.indexOf(begin) === 0) { 9117 return whole.substr(begin.length); 9118 } 9119} 9120 9121 9122function stripHash(url) { 9123 var index = url.indexOf('#'); 9124 return index == -1 ? url : url.substr(0, index); 9125} 9126 9127 9128function stripFile(url) { 9129 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 9130} 9131 9132/* return the server only (scheme://host:port) */ 9133function serverBase(url) { 9134 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 9135} 9136 9137 9138/** 9139 * LocationHtml5Url represents an url 9140 * This object is exposed as $location service when HTML5 mode is enabled and supported 9141 * 9142 * @constructor 9143 * @param {string} appBase application base URL 9144 * @param {string} basePrefix url path prefix 9145 */ 9146function LocationHtml5Url(appBase, basePrefix) { 9147 this.$$html5 = true; 9148 basePrefix = basePrefix || ''; 9149 var appBaseNoFile = stripFile(appBase); 9150 parseAbsoluteUrl(appBase, this, appBase); 9151 9152 9153 /** 9154 * Parse given html5 (regular) url string into properties 9155 * @param {string} newAbsoluteUrl HTML5 url 9156 * @private 9157 */ 9158 this.$$parse = function(url) { 9159 var pathUrl = beginsWith(appBaseNoFile, url); 9160 if (!isString(pathUrl)) { 9161 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 9162 appBaseNoFile); 9163 } 9164 9165 parseAppUrl(pathUrl, this, appBase); 9166 9167 if (!this.$$path) { 9168 this.$$path = '/'; 9169 } 9170 9171 this.$$compose(); 9172 }; 9173 9174 /** 9175 * Compose url and update `absUrl` property 9176 * @private 9177 */ 9178 this.$$compose = function() { 9179 var search = toKeyValue(this.$$search), 9180 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9181 9182 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9183 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 9184 }; 9185 9186 this.$$rewrite = function(url) { 9187 var appUrl, prevAppUrl; 9188 9189 if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { 9190 prevAppUrl = appUrl; 9191 if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { 9192 return appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 9193 } else { 9194 return appBase + prevAppUrl; 9195 } 9196 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { 9197 return appBaseNoFile + appUrl; 9198 } else if (appBaseNoFile == url + '/') { 9199 return appBaseNoFile; 9200 } 9201 }; 9202} 9203 9204 9205/** 9206 * LocationHashbangUrl represents url 9207 * This object is exposed as $location service when developer doesn't opt into html5 mode. 9208 * It also serves as the base class for html5 mode fallback on legacy browsers. 9209 * 9210 * @constructor 9211 * @param {string} appBase application base URL 9212 * @param {string} hashPrefix hashbang prefix 9213 */ 9214function LocationHashbangUrl(appBase, hashPrefix) { 9215 var appBaseNoFile = stripFile(appBase); 9216 9217 parseAbsoluteUrl(appBase, this, appBase); 9218 9219 9220 /** 9221 * Parse given hashbang url into properties 9222 * @param {string} url Hashbang url 9223 * @private 9224 */ 9225 this.$$parse = function(url) { 9226 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 9227 var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' 9228 ? beginsWith(hashPrefix, withoutBaseUrl) 9229 : (this.$$html5) 9230 ? withoutBaseUrl 9231 : ''; 9232 9233 if (!isString(withoutHashUrl)) { 9234 throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, 9235 hashPrefix); 9236 } 9237 parseAppUrl(withoutHashUrl, this, appBase); 9238 9239 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 9240 9241 this.$$compose(); 9242 9243 /* 9244 * In Windows, on an anchor node on documents loaded from 9245 * the filesystem, the browser will return a pathname
9246 * prefixed with the drive name ('/C:/path') when a 9247 * pathname without a drive is set: 9248 * * a.setAttribute('href', '/foo') 9249 * * a.pathname === '/C:/foo' //true 9250 * 9251 * Inside of Angular, we're always using pathnames that 9252 * do not include drive names for routing. 9253 */ 9254 function removeWindowsDriveName (path, url, base) { 9255 /* 9256 Matches paths for file protocol on windows, 9257 such as /C:/foo/bar, and captures only /foo/bar. 9258 */ 9259 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 9260 9261 var firstPathSegmentMatch; 9262 9263 //Get the relative path from the input URL. 9264 if (url.indexOf(base) === 0) { 9265 url = url.replace(base, ''); 9266 } 9267 9268 // The input URL intentionally contains a first path segment that ends with a colon. 9269 if (windowsFilePathExp.exec(url)) { 9270 return path; 9271 } 9272 9273 firstPathSegmentMatch = windowsFilePathExp.exec(path); 9274 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 9275 } 9276 }; 9277 9278 /** 9279 * Compose hashbang url and update `absUrl` property 9280 * @private 9281 */ 9282 this.$$compose = function() { 9283 var search = toKeyValue(this.$$search), 9284 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9285 9286 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9287 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 9288 }; 9289 9290 this.$$rewrite = function(url) { 9291 if(stripHash(appBase) == stripHash(url)) { 9292 return url; 9293 } 9294 }; 9295} 9296 9297 9298/** 9299 * LocationHashbangUrl represents url 9300 * This object is exposed as $location service when html5 history api is enabled but the browser 9301 * does not support it. 9302 * 9303 * @constructor 9304 * @param {string} appBase application base URL 9305 * @param {string} hashPrefix hashbang prefix 9306 */ 9307function LocationHashbangInHtml5Url(appBase, hashPrefix) { 9308 this.$$html5 = true; 9309 LocationHashbangUrl.apply(this, arguments); 9310 9311 var appBaseNoFile = stripFile(appBase); 9312 9313 this.$$rewrite = function(url) { 9314 var appUrl; 9315 9316 if ( appBase == stripHash(url) ) { 9317 return url; 9318 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { 9319 return appBase + hashPrefix + appUrl; 9320 } else if ( appBaseNoFile === url + '/') { 9321 return appBaseNoFile; 9322 } 9323 }; 9324 9325 this.$$compose = function() { 9326 var search = toKeyValue(this.$$search), 9327 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9328 9329 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9330 // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#' 9331 this.$$absUrl = appBase + hashPrefix + this.$$url; 9332 }; 9333 9334} 9335 9336 9337LocationHashbangInHtml5Url.prototype = 9338 LocationHashbangUrl.prototype = 9339 LocationHtml5Url.prototype = { 9340 9341 /** 9342 * Are we in html5 mode? 9343 * @private 9344 */ 9345 $$html5: false, 9346 9347 /** 9348 * Has any change been replacing ? 9349 * @private 9350 */ 9351 $$replace: false, 9352 9353 /** 9354 * @ngdoc method 9355 * @name $location#absUrl 9356 * 9357 * @description 9358 * This method is getter only. 9359 * 9360 * Return full url representation with all segments encoded according to rules specified in 9361 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 9362 * 9363 * @return {string} full url 9364 */ 9365 absUrl: locationGetter('$$absUrl'), 9366 9367 /** 9368 * @ngdoc method 9369 * @name $location#url 9370 * 9371 * @description 9372 * This method is getter / setter. 9373 * 9374 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 9375 * 9376 * Change path, search and hash, when called with parameter and return `$location`. 9377 * 9378 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 9379 * @param {string=} replace The path that will be changed 9380 * @return {string} url 9381 */ 9382 url: function(url, replace) { 9383 if (isUndefined(url)) 9384 return this.$$url; 9385 9386 var match = PATH_MATCH.exec(url); 9387 if (match[1]) this.path(decodeURIComponent(match[1])); 9388 if (match[2] || match[1]) this.search(match[3] || ''); 9389 this.hash(match[5] || '', replace); 9390 9391 return this; 9392 }, 9393 9394 /** 9395 * @ngdoc method 9396 * @name $location#protocol 9397 * 9398 * @description 9399 * This method is getter only. 9400 * 9401 * Return protocol of current url. 9402 * 9403 * @return {string} protocol of current url 9404 */ 9405 protocol: locationGetter('$$protocol'), 9406 9407 /** 9408 * @ngdoc method 9409 * @name $location#host 9410 * 9411 * @description 9412 * This method is getter only. 9413 * 9414 * Return host of current url. 9415 * 9416 * @return {string} host of current url. 9417 */ 9418 host: locationGetter('$$host'), 9419 9420 /** 9421 * @ngdoc method 9422 * @name $location#port 9423 * 9424 * @description 9425 * This method is getter only. 9426 * 9427 * Return port of current url. 9428 * 9429 * @return {Number} port 9430 */ 9431 port: locationGetter('$$port'), 9432 9433 /** 9434 * @ngdoc method 9435 * @name $location#path 9436 * 9437 * @description 9438 * This method is getter / setter. 9439 * 9440 * Return path of current url when called without any parameter. 9441 * 9442 * Change path when called with parameter and return `$location`. 9443 * 9444 * Note: Path should always begin with forward slash (/), this method will add the forward slash 9445 * if it is missing. 9446 * 9447 * @param {string=} path New path 9448 * @return {string} path 9449 */ 9450 path: locationGetterSetter('$$path', function(path) { 9451 return path.charAt(0) == '/' ? path : '/' + path; 9452 }), 9453 9454 /** 9455 * @ngdoc method 9456 * @name $location#search 9457 * 9458 * @description 9459 * This method is getter / setter. 9460 * 9461 * Return search part (as object) of current url when called without any parameter. 9462 * 9463 * Change search part when called with parameter and return `$location`. 9464 * 9465 * 9466 * ```js
9467 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 9468 * var searchObject = $location.search(); 9469 * // => {foo: 'bar', baz: 'xoxo'} 9470 * 9471 * 9472 * // set foo to 'yipee' 9473 * $location.search('foo', 'yipee'); 9474 * // => $location 9475 * ``` 9476 * 9477 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 9478 * hash object. 9479 * 9480 * When called with a single argument the method acts as a setter, setting the `search` component 9481 * of `$location` to the specified value. 9482 * 9483 * If the argument is a hash object containing an array of values, these values will be encoded 9484 * as duplicate search parameters in the url. 9485 * 9486 * @param {(string|Array<string>)=} paramValue If `search` is a string, then `paramValue` will 9487 * override only a single search property. 9488 * 9489 * If `paramValue` is an array, it will override the property of the `search` component of 9490 * `$location` specified via the first argument. 9491 * 9492 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 9493 * 9494 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 9495 * one or more arguments returns `$location` object itself. 9496 */ 9497 search: function(search, paramValue) { 9498 switch (arguments.length) { 9499 case 0: 9500 return this.$$search; 9501 case 1: 9502 if (isString(search)) { 9503 this.$$search = parseKeyValue(search); 9504 } else if (isObject(search)) { 9505 this.$$search = search; 9506 } else { 9507 throw $locationMinErr('isrcharg', 9508 'The first argument of the `$location#search()` call must be a string or an object.'); 9509 } 9510 break; 9511 default: 9512 if (isUndefined(paramValue) || paramValue === null) { 9513 delete this.$$search[search]; 9514 } else { 9515 this.$$search[search] = paramValue; 9516 } 9517 } 9518 9519 this.$$compose(); 9520 return this; 9521 }, 9522 9523 /** 9524 * @ngdoc method 9525 * @name $location#hash 9526 * 9527 * @description 9528 * This method is getter / setter. 9529 * 9530 * Return hash fragment when called without any parameter. 9531 * 9532 * Change hash fragment when called with parameter and return `$location`. 9533 * 9534 * @param {string=} hash New hash fragment 9535 * @return {string} hash 9536 */ 9537 hash: locationGetterSetter('$$hash', identity), 9538 9539 /** 9540 * @ngdoc method 9541 * @name $location#replace 9542 * 9543 * @description 9544 * If called, all changes to $location during current `$digest` will be replacing current history 9545 * record, instead of adding new one. 9546 */ 9547 replace: function() { 9548 this.$$replace = true; 9549 return this; 9550 } 9551}; 9552 9553function locationGetter(property) { 9554 return function() { 9555 return this[property]; 9556 }; 9557} 9558 9559 9560function locationGetterSetter(property, preprocess) { 9561 return function(value) { 9562 if (isUndefined(value)) 9563 return this[property]; 9564 9565 this[property] = preprocess(value); 9566 this.$$compose(); 9567 9568 return this; 9569 }; 9570} 9571 9572 9573/** 9574 * @ngdoc service 9575 * @name $location 9576 * 9577 * @requires $rootElement 9578 * 9579 * @description 9580 * The $location service parses the URL in the browser address bar (based on the 9581 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 9582 * available to your application. Changes to the URL in the address bar are reflected into 9583 * $location service and changes to $location are reflected into the browser address bar. 9584 * 9585 * **The $location service:** 9586 * 9587 * - Exposes the current URL in the browser address bar, so you can 9588 * - Watch and observe the URL. 9589 * - Change the URL. 9590 * - Synchronizes the URL with the browser when the user 9591 * - Changes the address bar. 9592 * - Clicks the back or forward button (or clicks a History link). 9593 * - Clicks on a link. 9594 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 9595 * 9596 * For more information see {@link guide/$location Developer Guide: Using $location} 9597 */ 9598 9599/** 9600 * @ngdoc provider 9601 * @name $locationProvider 9602 * @description 9603 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 9604 */ 9605function $LocationProvider(){ 9606 var hashPrefix = '', 9607 html5Mode = false; 9608 9609 /** 9610 * @ngdoc method 9611 * @name $locationProvider#hashPrefix 9612 * @description 9613 * @param {string=} prefix Prefix for hash part (containing path and search) 9614 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9615 */ 9616 this.hashPrefix = function(prefix) { 9617 if (isDefined(prefix)) { 9618 hashPrefix = prefix; 9619 return this; 9620 } else { 9621 return hashPrefix; 9622 } 9623 }; 9624 9625 /** 9626 * @ngdoc method 9627 * @name $locationProvider#html5Mode 9628 * @description 9629 * @param {boolean=} mode Use HTML5 strategy if available. 9630 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9631 */ 9632 this.html5Mode = function(mode) { 9633 if (isDefined(mode)) { 9634 html5Mode = mode; 9635 return this; 9636 } else { 9637 return html5Mode; 9638 } 9639 }; 9640 9641 /** 9642 * @ngdoc event 9643 * @name $location#$locationChangeStart 9644 * @eventType broadcast on root scope 9645 * @description 9646 * Broadcasted before a URL will change. This change can be prevented by calling 9647 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 9648 * details about event object. Upon successful change 9649 * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. 9650 * 9651 * @param {Object} angularEvent Synthetic event object. 9652 * @param {string} newUrl New URL 9653 * @param {string=} oldUrl URL that was before it was changed. 9654 */ 9655 9656 /** 9657 * @ngdoc event 9658 * @name $location#$locationChangeSuccess 9659 * @eventType broadcast on root scope 9660 * @description 9661 * Broadcasted after a URL was changed. 9662 * 9663 * @param {Object} angularEvent Synthetic event object. 9664 * @param {string} newUrl New URL 9665 * @param {string=} oldUrl URL that was before it was changed. 9666 */ 9667 9668 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', 9669 function( $rootScope, $browser, $sniffer, $rootElement) { 9670 var $location, 9671 LocationMode, 9672 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 9673 initialUrl = $browser.url(), 9674 appBase; 9675 9676 if (html5Mode) { 9677 appBase = serverBase(initialUrl) + (baseHref || '/'); 9678 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 9679 } else { 9680 appBase = stripHash(initialUrl); 9681 LocationMode = LocationHashbangUrl; 9682 }
9683 $location = new LocationMode(appBase, '#' + hashPrefix); 9684 $location.$$parse($location.$$rewrite(initialUrl)); 9685 9686 $rootElement.on('click', function(event) { 9687 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 9688 // currently we open nice url link and redirect then 9689 9690 if (event.ctrlKey || event.metaKey || event.which == 2) return; 9691 9692 var elm = jqLite(event.target); 9693 9694 // traverse the DOM up to find first A tag 9695 while (lowercase(elm[0].nodeName) !== 'a') { 9696 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 9697 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 9698 } 9699 9700 var absHref = elm.prop('href'); 9701 9702 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 9703 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 9704 // an animation. 9705 absHref = urlResolve(absHref.animVal).href; 9706 } 9707 9708 // Make relative links work in HTML5 mode for legacy browsers (or at least IE8 & 9) 9709 // The href should be a regular url e.g. /link/somewhere or link/somewhere or ../somewhere or 9710 // somewhere#anchor or http://example.com/somewhere 9711 if (LocationMode === LocationHashbangInHtml5Url) { 9712 // get the actual href attribute - see 9713 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 9714 var href = elm.attr('href') || elm.attr('xlink:href'); 9715 9716 if (href.indexOf('://') < 0) { // Ignore absolute URLs 9717 var prefix = '#' + hashPrefix; 9718 if (href[0] == '/') { 9719 // absolute path - replace old path 9720 absHref = appBase + prefix + href; 9721 } else if (href[0] == '#') { 9722 // local anchor 9723 absHref = appBase + prefix + ($location.path() || '/') + href; 9724 } else { 9725 // relative path - join with current path 9726 var stack = $location.path().split("/"), 9727 parts = href.split("/"); 9728 for (var i=0; i<parts.length; i++) { 9729 if (parts[i] == ".") 9730 continue; 9731 else if (parts[i] == "..") 9732 stack.pop(); 9733 else if (parts[i].length) 9734 stack.push(parts[i]); 9735 } 9736 absHref = appBase + prefix + stack.join('/'); 9737 } 9738 } 9739 } 9740 9741 var rewrittenUrl = $location.$$rewrite(absHref); 9742 9743 if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) { 9744 event.preventDefault(); 9745 if (rewrittenUrl != $browser.url()) { 9746 // update location manually 9747 $location.$$parse(rewrittenUrl); 9748 $rootScope.$apply(); 9749 // hack to work around FF6 bug 684208 when scenario runner clicks on links 9750 window.angular['ff-684208-preventDefault'] = true; 9751 } 9752 } 9753 }); 9754 9755 9756 // rewrite hashbang url <> html5 url 9757 if ($location.absUrl() != initialUrl) { 9758 $browser.url($location.absUrl(), true); 9759 } 9760 9761 // update $location when $browser url changes 9762 $browser.onUrlChange(function(newUrl) { 9763 if ($location.absUrl() != newUrl) { 9764 $rootScope.$evalAsync(function() { 9765 var oldUrl = $location.absUrl(); 9766 9767 $location.$$parse(newUrl); 9768 if ($rootScope.$broadcast('$locationChangeStart', newUrl, 9769 oldUrl).defaultPrevented) { 9770 $location.$$parse(oldUrl); 9771 $browser.url(oldUrl); 9772 } else { 9773 afterLocationChange(oldUrl); 9774 } 9775 }); 9776 if (!$rootScope.$$phase) $rootScope.$digest(); 9777 } 9778 }); 9779 9780 // update browser 9781 var changeCounter = 0; 9782 $rootScope.$watch(function $locationWatch() { 9783 var oldUrl = $browser.url(); 9784 var currentReplace = $location.$$replace; 9785 9786 if (!changeCounter || oldUrl != $location.absUrl()) { 9787 changeCounter++; 9788 $rootScope.$evalAsync(function() { 9789 if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). 9790 defaultPrevented) { 9791 $location.$$parse(oldUrl); 9792 } else { 9793 $browser.url($location.absUrl(), currentReplace); 9794 afterLocationChange(oldUrl); 9795 } 9796 }); 9797 } 9798 $location.$$replace = false; 9799 9800 return changeCounter; 9801 });
9802 9803 return $location; 9804 9805 function afterLocationChange(oldUrl) { 9806 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); 9807 } 9808}]; 9809} 9810 9811/** 9812 * @ngdoc service 9813 * @name $log 9814 * @requires $window 9815 * 9816 * @description 9817 * Simple service for logging. Default implementation safely writes the message 9818 * into the browser's console (if present). 9819 * 9820 * The main purpose of this service is to simplify debugging and troubleshooting. 9821 * 9822 * The default is to log `debug` messages. You can use 9823 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 9824 * 9825 * @example 9826 <example> 9827 <file name="script.js"> 9828 function LogCtrl($scope, $log) { 9829 $scope.$log = $log; 9830 $scope.message = 'Hello World!'; 9831 } 9832 </file> 9833 <file name="index.html"> 9834 <div ng-controller="LogCtrl"> 9835 <p>Reload this page with open console, enter text and hit the log button...</p> 9836 Message: 9837 <input type="text" ng-model="message"/> 9838 <button ng-click="$log.log(message)">log</button> 9839 <button ng-click="$log.warn(message)">warn</button> 9840 <button ng-click="$log.info(message)">info</button> 9841 <button ng-click="$log.error(message)">error</button> 9842 </div> 9843 </file> 9844 </example> 9845 */ 9846 9847/** 9848 * @ngdoc provider 9849 * @name $logProvider 9850 * @description 9851 * Use the `$logProvider` to configure how the application logs messages 9852 */ 9853function $LogProvider(){ 9854 var debug = true, 9855 self = this; 9856 9857 /** 9858 * @ngdoc property 9859 * @name $logProvider#debugEnabled 9860 * @description 9861 * @param {boolean=} flag enable or disable debug level messages 9862 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9863 */ 9864 this.debugEnabled = function(flag) { 9865 if (isDefined(flag)) { 9866 debug = flag; 9867 return this; 9868 } else { 9869 return debug; 9870 } 9871 }; 9872 9873 this.$get = ['$window', function($window){ 9874 return { 9875 /** 9876 * @ngdoc method 9877 * @name $log#log 9878 * 9879 * @description 9880 * Write a log message 9881 */ 9882 log: consoleLog('log'), 9883 9884 /** 9885 * @ngdoc method 9886 * @name $log#info 9887 * 9888 * @description 9889 * Write an information message 9890 */ 9891 info: consoleLog('info'), 9892 9893 /** 9894 * @ngdoc method 9895 * @name $log#warn 9896 * 9897 * @description 9898 * Write a warning message 9899 */ 9900 warn: consoleLog('warn'), 9901 9902 /** 9903 * @ngdoc method 9904 * @name $log#error 9905 * 9906 * @description 9907 * Write an error message 9908 */ 9909 error: consoleLog('error'), 9910 9911 /** 9912 * @ngdoc method 9913 * @name $log#debug 9914 * 9915 * @description 9916 * Write a debug message 9917 */ 9918 debug: (function () { 9919 var fn = consoleLog('debug'); 9920 9921 return function() { 9922 if (debug) { 9923 fn.apply(self, arguments); 9924 } 9925 }; 9926 }()) 9927 }; 9928 9929 function formatError(arg) { 9930 if (arg instanceof Error) { 9931 if (arg.stack) { 9932 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 9933 ? 'Error: ' + arg.message + '\n' + arg.stack 9934 : arg.stack; 9935 } else if (arg.sourceURL) { 9936 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 9937 } 9938 } 9939 return arg; 9940 } 9941 9942 function consoleLog(type) { 9943 var console = $window.console || {}, 9944 logFn = console[type] || console.log || noop, 9945 hasApply = false; 9946 9947 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 9948 // The reason behind this is that console.log has type "object" in IE8... 9949 try { 9950 hasApply = !!logFn.apply; 9951 } catch (e) {} 9952 9953 if (hasApply) { 9954 return function() { 9955 var args = []; 9956 forEach(arguments, function(arg) { 9957 args.push(formatError(arg)); 9958 }); 9959 return logFn.apply(console, args); 9960 }; 9961 } 9962 9963 // we are IE which either doesn't have window.console => this is noop and we do nothing, 9964 // or we are IE where console.log doesn't have apply so we log at least first 2 args 9965 return function(arg1, arg2) { 9966 logFn(arg1, arg2 == null ? '' : arg2); 9967 }; 9968 } 9969 }]; 9970} 9971 9972var $parseMinErr = minErr('$parse'); 9973var promiseWarningCache = {}; 9974var promiseWarning; 9975 9976// Sandboxing Angular Expressions 9977// ------------------------------ 9978// Angular expressions are generally considered safe because these expressions only have direct 9979// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by 9980// obtaining a reference to native JS functions such as the Function constructor. 9981// 9982// As an example, consider the following Angular expression: 9983// 9984// {}.toString.constructor('alert("evil JS code")') 9985// 9986// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 9987// against the expression language, but not to prevent exploits that were enabled by exposing 9988// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good 9989// practice and therefore we are not even trying to protect against interaction with an object 9990// explicitly exposed in this way. 9991// 9992// In general, it is not possible to access a Window object from an angular expression unless a 9993// window or some DOM object that has a reference to window is published onto a Scope. 9994// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 9995// native objects. 9996 9997 9998function ensureSafeMemberName(name, fullExpression) { 9999 if (name === "__defineGetter__" || name === "__defineSetter__" 10000 || name === "__lookupGetter__" || name === "__lookupSetter__" 10001 || name === "__proto__") { 10002 throw $parseMinErr('isecfld', 10003 'Attempting to access a disallowed field in Angular expressions! ' 10004 +'Expression: {0}', fullExpression); 10005 } 10006 return name; 10007} 10008 10009function ensureSafeObject(obj, fullExpression) { 10010 // nifty check if obj is Function that is fast and works across iframes and other contexts 10011 if (obj) { 10012 if (obj.constructor === obj) { 10013 throw $parseMinErr('isecfn', 10014 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 10015 fullExpression); 10016 } else if (// isWindow(obj) 10017 obj.document && obj.location && obj.alert && obj.setInterval) { 10018 throw $parseMinErr('isecwindow', 10019 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 10020 fullExpression); 10021 } else if (// isElement(obj) 10022 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 10023 throw $parseMinErr('isecdom', 10024 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 10025 fullExpression); 10026 } else if (// block Object so that we can't get hold of dangerous Object.* methods 10027 obj === Object) { 10028 throw $parseMinErr('isecobj', 10029 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 10030 fullExpression); 10031 } 10032 } 10033 return obj;
10034} 10035 10036var CALL = Function.prototype.call; 10037var APPLY = Function.prototype.apply; 10038var BIND = Function.prototype.bind; 10039 10040function ensureSafeFunction(obj, fullExpression) { 10041 if (obj) { 10042 if (obj.constructor === obj) { 10043 throw $parseMinErr('isecfn', 10044 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 10045 fullExpression); 10046 } else if (obj === CALL || obj === APPLY || (BIND && obj === BIND)) { 10047 throw $parseMinErr('isecff', 10048 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 10049 fullExpression); 10050 } 10051 } 10052} 10053 10054var OPERATORS = { 10055 /* jshint bitwise : false */ 10056 'null':function(){return null;}, 10057 'true':function(){return true;}, 10058 'false':function(){return false;}, 10059 undefined:noop, 10060 '+':function(self, locals, a,b){ 10061 a=a(self, locals); b=b(self, locals); 10062 if (isDefined(a)) { 10063 if (isDefined(b)) { 10064 return a + b; 10065 } 10066 return a; 10067 } 10068 return isDefined(b)?b:undefined;}, 10069 '-':function(self, locals, a,b){ 10070 a=a(self, locals); b=b(self, locals); 10071 return (isDefined(a)?a:0)-(isDefined(b)?b:0); 10072 }, 10073 '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, 10074 '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, 10075 '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, 10076 '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, 10077 '=':noop, 10078 '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, 10079 '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, 10080 '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, 10081 '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, 10082 '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);}, 10083 '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, 10084 '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, 10085 '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, 10086 '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, 10087 '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, 10088 '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, 10089// '|':function(self, locals, a,b){return a|b;}, 10090 '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, 10091 '!':function(self, locals, a){return !a(self, locals);} 10092}; 10093/* jshint bitwise: true */ 10094var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 10095 10096 10097///////////////////////////////////////// 10098 10099 10100/** 10101 * @constructor 10102 */ 10103var Lexer = function (options) { 10104 this.options = options; 10105}; 10106 10107Lexer.prototype = { 10108 constructor: Lexer, 10109 10110 lex: function (text) { 10111 this.text = text; 10112 10113 this.index = 0; 10114 this.ch = undefined; 10115 this.lastCh = ':'; // can start regexp 10116 10117 this.tokens = []; 10118 10119 while (this.index < this.text.length) { 10120 this.ch = this.text.charAt(this.index); 10121 if (this.is('"\'')) { 10122 this.readString(this.ch); 10123 } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { 10124 this.readNumber(); 10125 } else if (this.isIdent(this.ch)) { 10126 this.readIdent(); 10127 } else if (this.is('(){}[].,;:?')) { 10128 this.tokens.push({ 10129 index: this.index, 10130 text: this.ch 10131 }); 10132 this.index++; 10133 } else if (this.isWhitespace(this.ch)) { 10134 this.index++; 10135 continue; 10136 } else { 10137 var ch2 = this.ch + this.peek(); 10138 var ch3 = ch2 + this.peek(2); 10139 var fn = OPERATORS[this.ch]; 10140 var fn2 = OPERATORS[ch2]; 10141 var fn3 = OPERATORS[ch3]; 10142 if (fn3) { 10143 this.tokens.push({index: this.index, text: ch3, fn: fn3}); 10144 this.index += 3; 10145 } else if (fn2) { 10146 this.tokens.push({index: this.index, text: ch2, fn: fn2}); 10147 this.index += 2; 10148 } else if (fn) { 10149 this.tokens.push({ 10150 index: this.index, 10151 text: this.ch, 10152 fn: fn 10153 }); 10154 this.index += 1;
10155 } else { 10156 this.throwError('Unexpected next character ', this.index, this.index + 1); 10157 } 10158 } 10159 this.lastCh = this.ch; 10160 } 10161 return this.tokens; 10162 }, 10163 10164 is: function(chars) { 10165 return chars.indexOf(this.ch) !== -1; 10166 }, 10167 10168 was: function(chars) { 10169 return chars.indexOf(this.lastCh) !== -1; 10170 }, 10171 10172 peek: function(i) { 10173 var num = i || 1; 10174 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 10175 }, 10176 10177 isNumber: function(ch) { 10178 return ('0' <= ch && ch <= '9'); 10179 }, 10180 10181 isWhitespace: function(ch) { 10182 // IE treats non-breaking space as \u00A0 10183 return (ch === ' ' || ch === '\r' || ch === '\t' || 10184 ch === '\n' || ch === '\v' || ch === '\u00A0'); 10185 }, 10186 10187 isIdent: function(ch) { 10188 return ('a' <= ch && ch <= 'z' || 10189 'A' <= ch && ch <= 'Z' || 10190 '_' === ch || ch === '$'); 10191 }, 10192 10193 isExpOperator: function(ch) { 10194 return (ch === '-' || ch === '+' || this.isNumber(ch)); 10195 }, 10196 10197 throwError: function(error, start, end) { 10198 end = end || this.index; 10199 var colStr = (isDefined(start) 10200 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 10201 : ' ' + end); 10202 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 10203 error, colStr, this.text); 10204 }, 10205 10206 readNumber: function() { 10207 var number = ''; 10208 var start = this.index; 10209 while (this.index < this.text.length) { 10210 var ch = lowercase(this.text.charAt(this.index)); 10211 if (ch == '.' || this.isNumber(ch)) { 10212 number += ch; 10213 } else { 10214 var peekCh = this.peek(); 10215 if (ch == 'e' && this.isExpOperator(peekCh)) { 10216 number += ch; 10217 } else if (this.isExpOperator(ch) && 10218 peekCh && this.isNumber(peekCh) && 10219 number.charAt(number.length - 1) == 'e') { 10220 number += ch; 10221 } else if (this.isExpOperator(ch) && 10222 (!peekCh || !this.isNumber(peekCh)) && 10223 number.charAt(number.length - 1) == 'e') { 10224 this.throwError('Invalid exponent'); 10225 } else { 10226 break; 10227 } 10228 } 10229 this.index++; 10230 } 10231 number = 1 * number; 10232 this.tokens.push({ 10233 index: start, 10234 text: number, 10235 literal: true, 10236 constant: true, 10237 fn: function() { return number; } 10238 }); 10239 }, 10240 10241 readIdent: function() { 10242 var parser = this; 10243 10244 var ident = ''; 10245 var start = this.index; 10246 10247 var lastDot, peekIndex, methodName, ch; 10248 10249 while (this.index < this.text.length) { 10250 ch = this.text.charAt(this.index); 10251 if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { 10252 if (ch === '.') lastDot = this.index; 10253 ident += ch; 10254 } else { 10255 break; 10256 } 10257 this.index++; 10258 } 10259 10260 //check if this is not a method invocation and if it is back out to last dot 10261 if (lastDot) { 10262 peekIndex = this.index; 10263 while (peekIndex < this.text.length) { 10264 ch = this.text.charAt(peekIndex); 10265 if (ch === '(') { 10266 methodName = ident.substr(lastDot - start + 1); 10267 ident = ident.substr(0, lastDot - start); 10268 this.index = peekIndex; 10269 break; 10270 } 10271 if (this.isWhitespace(ch)) { 10272 peekIndex++; 10273 } else { 10274 break; 10275 } 10276 } 10277 } 10278 10279 10280 var token = { 10281 index: start, 10282 text: ident 10283 }; 10284 10285 // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn 10286 if (OPERATORS.hasOwnProperty(ident)) { 10287 token.fn = OPERATORS[ident]; 10288 token.literal = true; 10289 token.constant = true; 10290 } else { 10291 var getter = getterFn(ident, this.options, this.text); 10292 token.fn = extend(function(self, locals) { 10293 return (getter(self, locals)); 10294 }, { 10295 assign: function(self, value) { 10296 return setter(self, ident, value, parser.text, parser.options); 10297 } 10298 }); 10299 } 10300 10301 this.tokens.push(token); 10302 10303 if (methodName) { 10304 this.tokens.push({ 10305 index:lastDot, 10306 text: '.' 10307 }); 10308 this.tokens.push({ 10309 index: lastDot + 1, 10310 text: methodName 10311 }); 10312 } 10313 }, 10314 10315 readString: function(quote) { 10316 var start = this.index; 10317 this.index++; 10318 var string = ''; 10319 var rawString = quote; 10320 var escape = false; 10321 while (this.index < this.text.length) { 10322 var ch = this.text.charAt(this.index); 10323 rawString += ch; 10324 if (escape) { 10325 if (ch === 'u') { 10326 var hex = this.text.substring(this.index + 1, this.index + 5); 10327 if (!hex.match(/[\da-f]{4}/i)) 10328 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 10329 this.index += 4;
10330 string += String.fromCharCode(parseInt(hex, 16)); 10331 } else { 10332 var rep = ESCAPE[ch]; 10333 if (rep) { 10334 string += rep; 10335 } else { 10336 string += ch; 10337 } 10338 } 10339 escape = false; 10340 } else if (ch === '\\') { 10341 escape = true; 10342 } else if (ch === quote) { 10343 this.index++; 10344 this.tokens.push({ 10345 index: start, 10346 text: rawString, 10347 string: string, 10348 literal: true, 10349 constant: true, 10350 fn: function() { return string; } 10351 }); 10352 return; 10353 } else { 10354 string += ch; 10355 } 10356 this.index++; 10357 } 10358 this.throwError('Unterminated quote', start); 10359 } 10360}; 10361 10362 10363/** 10364 * @constructor 10365 */ 10366var Parser = function (lexer, $filter, options) { 10367 this.lexer = lexer; 10368 this.$filter = $filter; 10369 this.options = options; 10370}; 10371 10372Parser.ZERO = extend(function () { 10373 return 0; 10374}, { 10375 constant: true 10376}); 10377 10378Parser.prototype = { 10379 constructor: Parser, 10380 10381 parse: function (text) { 10382 this.text = text; 10383 10384 this.tokens = this.lexer.lex(text); 10385 10386 var value = this.statements(); 10387 10388 if (this.tokens.length !== 0) { 10389 this.throwError('is an unexpected token', this.tokens[0]); 10390 } 10391 10392 value.literal = !!value.literal; 10393 value.constant = !!value.constant; 10394 10395 return value; 10396 }, 10397 10398 primary: function () { 10399 var primary; 10400 if (this.expect('(')) { 10401 primary = this.filterChain(); 10402 this.consume(')'); 10403 } else if (this.expect('[')) { 10404 primary = this.arrayDeclaration(); 10405 } else if (this.expect('{')) { 10406 primary = this.object(); 10407 } else { 10408 var token = this.expect(); 10409 primary = token.fn; 10410 if (!primary) { 10411 this.throwError('not a primary expression', token); 10412 } 10413 primary.literal = !!token.literal; 10414 primary.constant = !!token.constant; 10415 } 10416 10417 var next, context; 10418 while ((next = this.expect('(', '[', '.'))) { 10419 if (next.text === '(') { 10420 primary = this.functionCall(primary, context); 10421 context = null; 10422 } else if (next.text === '[') { 10423 context = primary; 10424 primary = this.objectIndex(primary); 10425 } else if (next.text === '.') { 10426 context = primary; 10427 primary = this.fieldAccess(primary); 10428 } else { 10429 this.throwError('IMPOSSIBLE'); 10430 } 10431 } 10432 return primary; 10433 }, 10434 10435 throwError: function(msg, token) { 10436 throw $parseMinErr('syntax', 10437 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 10438 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 10439 }, 10440 10441 peekToken: function() { 10442 if (this.tokens.length === 0) 10443 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 10444 return this.tokens[0]; 10445 }, 10446 10447 peek: function(e1, e2, e3, e4) { 10448 if (this.tokens.length > 0) { 10449 var token = this.tokens[0]; 10450 var t = token.text; 10451 if (t === e1 || t === e2 || t === e3 || t === e4 || 10452 (!e1 && !e2 && !e3 && !e4)) { 10453 return token; 10454 } 10455 } 10456 return false; 10457 }, 10458 10459 expect: function(e1, e2, e3, e4){ 10460 var token = this.peek(e1, e2, e3, e4); 10461 if (token) { 10462 this.tokens.shift(); 10463 return token; 10464 } 10465 return false; 10466 }, 10467 10468 consume: function(e1){ 10469 if (!this.expect(e1)) { 10470 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 10471 } 10472 }, 10473 10474 unaryFn: function(fn, right) { 10475 return extend(function(self, locals) { 10476 return fn(self, locals, right); 10477 }, { 10478 constant:right.constant 10479 }); 10480 }, 10481 10482 ternaryFn: function(left, middle, right){ 10483 return extend(function(self, locals){ 10484 return left(self, locals) ? middle(self, locals) : right(self, locals); 10485 }, { 10486 constant: left.constant && middle.constant && right.constant 10487 }); 10488 }, 10489 10490 binaryFn: function(left, fn, right) { 10491 return extend(function(self, locals) { 10492 return fn(self, locals, left, right); 10493 }, { 10494 constant:left.constant && right.constant 10495 });
10496 }, 10497 10498 statements: function() { 10499 var statements = []; 10500 while (true) { 10501 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 10502 statements.push(this.filterChain()); 10503 if (!this.expect(';')) { 10504 // optimize for the common case where there is only one statement. 10505 // TODO(size): maybe we should not support multiple statements? 10506 return (statements.length === 1) 10507 ? statements[0] 10508 : function(self, locals) { 10509 var value; 10510 for (var i = 0; i < statements.length; i++) { 10511 var statement = statements[i]; 10512 if (statement) { 10513 value = statement(self, locals); 10514 } 10515 } 10516 return value; 10517 }; 10518 } 10519 } 10520 }, 10521 10522 filterChain: function() { 10523 var left = this.expression(); 10524 var token; 10525 while (true) { 10526 if ((token = this.expect('|'))) { 10527 left = this.binaryFn(left, token.fn, this.filter()); 10528 } else { 10529 return left; 10530 } 10531 } 10532 }, 10533 10534 filter: function() { 10535 var token = this.expect(); 10536 var fn = this.$filter(token.text); 10537 var argsFn = []; 10538 while (true) { 10539 if ((token = this.expect(':'))) { 10540 argsFn.push(this.expression()); 10541 } else { 10542 var fnInvoke = function(self, locals, input) { 10543 var args = [input]; 10544 for (var i = 0; i < argsFn.length; i++) { 10545 args.push(argsFn[i](self, locals)); 10546 } 10547 return fn.apply(self, args); 10548 }; 10549 return function() { 10550 return fnInvoke; 10551 }; 10552 } 10553 } 10554 }, 10555 10556 expression: function() { 10557 return this.assignment(); 10558 }, 10559 10560 assignment: function() { 10561 var left = this.ternary(); 10562 var right; 10563 var token; 10564 if ((token = this.expect('='))) { 10565 if (!left.assign) { 10566 this.throwError('implies assignment but [' + 10567 this.text.substring(0, token.index) + '] can not be assigned to', token); 10568 } 10569 right = this.ternary(); 10570 return function(scope, locals) { 10571 return left.assign(scope, right(scope, locals), locals); 10572 }; 10573 } 10574 return left; 10575 }, 10576 10577 ternary: function() { 10578 var left = this.logicalOR(); 10579 var middle; 10580 var token; 10581 if ((token = this.expect('?'))) { 10582 middle = this.ternary(); 10583 if ((token = this.expect(':'))) { 10584 return this.ternaryFn(left, middle, this.ternary()); 10585 } else { 10586 this.throwError('expected :', token); 10587 } 10588 } else { 10589 return left; 10590 } 10591 }, 10592 10593 logicalOR: function() { 10594 var left = this.logicalAND(); 10595 var token; 10596 while (true) { 10597 if ((token = this.expect('||'))) { 10598 left = this.binaryFn(left, token.fn, this.logicalAND()); 10599 } else { 10600 return left; 10601 } 10602 } 10603 }, 10604 10605 logicalAND: function() { 10606 var left = this.equality(); 10607 var token; 10608 if ((token = this.expect('&&'))) { 10609 left = this.binaryFn(left, token.fn, this.logicalAND()); 10610 } 10611 return left; 10612 }, 10613 10614 equality: function() { 10615 var left = this.relational(); 10616 var token; 10617 if ((token = this.expect('==','!=','===','!=='))) { 10618 left = this.binaryFn(left, token.fn, this.equality()); 10619 } 10620 return left; 10621 }, 10622 10623 relational: function() { 10624 var left = this.additive(); 10625 var token; 10626 if ((token = this.expect('<', '>', '<=', '>='))) { 10627 left = this.binaryFn(left, token.fn, this.relational()); 10628 } 10629 return left; 10630 }, 10631 10632 additive: function() { 10633 var left = this.multiplicative(); 10634 var token; 10635 while ((token = this.expect('+','-'))) { 10636 left = this.binaryFn(left, token.fn, this.multiplicative()); 10637 } 10638 return left; 10639 }, 10640 10641 multiplicative: function() { 10642 var left = this.unary(); 10643 var token; 10644 while ((token = this.expect('*','/','%'))) { 10645 left = this.binaryFn(left, token.fn, this.unary()); 10646 } 10647 return left; 10648 }, 10649 10650 unary: function() { 10651 var token; 10652 if (this.expect('+')) { 10653 return this.primary();
10654 } else if ((token = this.expect('-'))) { 10655 return this.binaryFn(Parser.ZERO, token.fn, this.unary()); 10656 } else if ((token = this.expect('!'))) { 10657 return this.unaryFn(token.fn, this.unary()); 10658 } else { 10659 return this.primary(); 10660 } 10661 }, 10662 10663 fieldAccess: function(object) { 10664 var parser = this; 10665 var field = this.expect().text; 10666 var getter = getterFn(field, this.options, this.text); 10667 10668 return extend(function(scope, locals, self) { 10669 return getter(self || object(scope, locals)); 10670 }, { 10671 assign: function(scope, value, locals) { 10672 return setter(object(scope, locals), field, value, parser.text, parser.options); 10673 } 10674 }); 10675 }, 10676 10677 objectIndex: function(obj) { 10678 var parser = this; 10679 10680 var indexFn = this.expression(); 10681 this.consume(']'); 10682 10683 return extend(function(self, locals) { 10684 var o = obj(self, locals), 10685 i = indexFn(self, locals), 10686 v, p; 10687 10688 ensureSafeMemberName(i, parser.text); 10689 if (!o) return undefined; 10690 v = ensureSafeObject(o[i], parser.text); 10691 if (v && v.then && parser.options.unwrapPromises) { 10692 p = v; 10693 if (!('$$v' in v)) { 10694 p.$$v = undefined; 10695 p.then(function(val) { p.$$v = val; }); 10696 } 10697 v = v.$$v; 10698 } 10699 return v; 10700 }, { 10701 assign: function(self, value, locals) { 10702 var key = indexFn(self, locals); 10703 // prevent overwriting of Function.constructor which would break ensureSafeObject check 10704 var safe = ensureSafeObject(obj(self, locals), parser.text); 10705 return safe[key] = value; 10706 } 10707 }); 10708 }, 10709 10710 functionCall: function(fn, contextGetter) { 10711 var argsFn = []; 10712 if (this.peekToken().text !== ')') { 10713 do { 10714 argsFn.push(this.expression()); 10715 } while (this.expect(',')); 10716 } 10717 this.consume(')'); 10718 10719 var parser = this; 10720 10721 return function(scope, locals) { 10722 var args = []; 10723 var context = contextGetter ? contextGetter(scope, locals) : scope; 10724 10725 for (var i = 0; i < argsFn.length; i++) { 10726 args.push(argsFn[i](scope, locals)); 10727 } 10728 var fnPtr = fn(scope, locals, context) || noop; 10729 10730 ensureSafeObject(context, parser.text); 10731 ensureSafeFunction(fnPtr, parser.text); 10732 10733 // IE stupidity! (IE doesn't have apply for some native functions) 10734 var v = fnPtr.apply 10735 ? fnPtr.apply(context, args) 10736 : fnPtr(args[0], args[1], args[2], args[3], args[4]); 10737 10738 return ensureSafeObject(v, parser.text); 10739 }; 10740 }, 10741 10742 // This is used with json array declaration 10743 arrayDeclaration: function () { 10744 var elementFns = []; 10745 var allConstant = true; 10746 if (this.peekToken().text !== ']') { 10747 do { 10748 if (this.peek(']')) { 10749 // Support trailing commas per ES5.1. 10750 break; 10751 } 10752 var elementFn = this.expression(); 10753 elementFns.push(elementFn); 10754 if (!elementFn.constant) { 10755 allConstant = false; 10756 } 10757 } while (this.expect(',')); 10758 } 10759 this.consume(']'); 10760 10761 return extend(function(self, locals) { 10762 var array = []; 10763 for (var i = 0; i < elementFns.length; i++) { 10764 array.push(elementFns[i](self, locals)); 10765 } 10766 return array; 10767 }, { 10768 literal: true, 10769 constant: allConstant 10770 }); 10771 }, 10772 10773 object: function () { 10774 var keyValues = []; 10775 var allConstant = true; 10776 if (this.peekToken().text !== '}') { 10777 do { 10778 if (this.peek('}')) { 10779 // Support trailing commas per ES5.1. 10780 break; 10781 } 10782 var token = this.expect(), 10783 key = token.string || token.text; 10784 this.consume(':'); 10785 var value = this.expression(); 10786 keyValues.push({key: key, value: value}); 10787 if (!value.constant) { 10788 allConstant = false; 10789 } 10790 } while (this.expect(',')); 10791 } 10792 this.consume('}'); 10793 10794 return extend(function(self, locals) { 10795 var object = {}; 10796 for (var i = 0; i < keyValues.length; i++) { 10797 var keyValue = keyValues[i]; 10798 object[keyValue.key] = keyValue.value(self, locals); 10799 } 10800 return object; 10801 }, { 10802 literal: true, 10803 constant: allConstant 10804 });
10805 } 10806}; 10807 10808 10809////////////////////////////////////////////////// 10810// Parser helper functions 10811////////////////////////////////////////////////// 10812 10813function setter(obj, path, setValue, fullExp, options) { 10814 //needed? 10815 options = options || {}; 10816 10817 var element = path.split('.'), key; 10818 for (var i = 0; element.length > 1; i++) { 10819 key = ensureSafeMemberName(element.shift(), fullExp); 10820 var propertyObj = obj[key]; 10821 if (!propertyObj) { 10822 propertyObj = {}; 10823 obj[key] = propertyObj; 10824 } 10825 obj = propertyObj; 10826 if (obj.then && options.unwrapPromises) { 10827 promiseWarning(fullExp); 10828 if (!("$$v" in obj)) { 10829 (function(promise) { 10830 promise.then(function(val) { promise.$$v = val; }); } 10831 )(obj); 10832 } 10833 if (obj.$$v === undefined) { 10834 obj.$$v = {}; 10835 } 10836 obj = obj.$$v; 10837 } 10838 } 10839 key = ensureSafeMemberName(element.shift(), fullExp); 10840 ensureSafeObject(obj, fullExp); 10841 ensureSafeObject(obj[key], fullExp); 10842 obj[key] = setValue; 10843 return setValue; 10844} 10845 10846var getterFnCache = {}; 10847 10848/** 10849 * Implementation of the "Black Hole" variant from: 10850 * - http://jsperf.com/angularjs-parse-getter/4 10851 * - http://jsperf.com/path-evaluation-simplified/7 10852 */ 10853function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { 10854 ensureSafeMemberName(key0, fullExp); 10855 ensureSafeMemberName(key1, fullExp); 10856 ensureSafeMemberName(key2, fullExp); 10857 ensureSafeMemberName(key3, fullExp); 10858 ensureSafeMemberName(key4, fullExp); 10859 10860 return !options.unwrapPromises 10861 ? function cspSafeGetter(scope, locals) { 10862 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; 10863 10864 if (pathVal == null) return pathVal; 10865 pathVal = pathVal[key0]; 10866 10867 if (!key1) return pathVal; 10868 if (pathVal == null) return undefined; 10869 pathVal = pathVal[key1]; 10870 10871 if (!key2) return pathVal; 10872 if (pathVal == null) return undefined; 10873 pathVal = pathVal[key2]; 10874 10875 if (!key3) return pathVal; 10876 if (pathVal == null) return undefined; 10877 pathVal = pathVal[key3]; 10878 10879 if (!key4) return pathVal; 10880 if (pathVal == null) return undefined; 10881 pathVal = pathVal[key4]; 10882 10883 return pathVal; 10884 } 10885 : function cspSafePromiseEnabledGetter(scope, locals) { 10886 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, 10887 promise; 10888 10889 if (pathVal == null) return pathVal; 10890 10891 pathVal = pathVal[key0]; 10892 if (pathVal && pathVal.then) { 10893 promiseWarning(fullExp); 10894 if (!("$$v" in pathVal)) { 10895 promise = pathVal; 10896 promise.$$v = undefined; 10897 promise.then(function(val) { promise.$$v = val; }); 10898 } 10899 pathVal = pathVal.$$v; 10900 } 10901 10902 if (!key1) return pathVal; 10903 if (pathVal == null) return undefined; 10904 pathVal = pathVal[key1]; 10905 if (pathVal && pathVal.then) { 10906 promiseWarning(fullExp); 10907 if (!("$$v" in pathVal)) { 10908 promise = pathVal; 10909 promise.$$v = undefined; 10910 promise.then(function(val) { promise.$$v = val; }); 10911 } 10912 pathVal = pathVal.$$v; 10913 } 10914 10915 if (!key2) return pathVal; 10916 if (pathVal == null) return undefined; 10917 pathVal = pathVal[key2]; 10918 if (pathVal && pathVal.then) { 10919 promiseWarning(fullExp); 10920 if (!("$$v" in pathVal)) { 10921 promise = pathVal; 10922 promise.$$v = undefined; 10923 promise.then(function(val) { promise.$$v = val; }); 10924 } 10925 pathVal = pathVal.$$v; 10926 } 10927 10928 if (!key3) return pathVal; 10929 if (pathVal == null) return undefined; 10930 pathVal = pathVal[key3]; 10931 if (pathVal && pathVal.then) { 10932 promiseWarning(fullExp); 10933 if (!("$$v" in pathVal)) { 10934 promise = pathVal; 10935 promise.$$v = undefined; 10936 promise.then(function(val) { promise.$$v = val; }); 10937 } 10938 pathVal = pathVal.$$v; 10939 } 10940 10941 if (!key4) return pathVal; 10942 if (pathVal == null) return undefined; 10943 pathVal = pathVal[key4]; 10944 if (pathVal && pathVal.then) { 10945 promiseWarning(fullExp); 10946 if (!("$$v" in pathVal)) { 10947 promise = pathVal; 10948 promise.$$v = undefined; 10949 promise.then(function(val) { promise.$$v = val; }); 10950 } 10951 pathVal = pathVal.$$v; 10952 } 10953 return pathVal; 10954 }; 10955} 10956 10957function simpleGetterFn1(key0, fullExp) { 10958 ensureSafeMemberName(key0, fullExp); 10959 10960 return function simpleGetterFn1(scope, locals) { 10961 if (scope == null) return undefined; 10962 return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; 10963 }; 10964} 10965 10966function simpleGetterFn2(key0, key1, fullExp) { 10967 ensureSafeMemberName(key0, fullExp); 10968 ensureSafeMemberName(key1, fullExp); 10969 10970 return function simpleGetterFn2(scope, locals) { 10971 if (scope == null) return undefined; 10972 scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; 10973 return scope == null ? undefined : scope[key1]; 10974 }; 10975} 10976 10977function getterFn(path, options, fullExp) { 10978 // Check whether the cache has this getter already. 10979 // We can use hasOwnProperty directly on the cache because we ensure, 10980 // see below, that the cache never stores a path called 'hasOwnProperty' 10981 if (getterFnCache.hasOwnProperty(path)) { 10982 return getterFnCache[path]; 10983 } 10984 10985 var pathKeys = path.split('.'), 10986 pathKeysLength = pathKeys.length, 10987 fn; 10988 10989 // When we have only 1 or 2 tokens, use optimized special case closures. 10990 // http://jsperf.com/angularjs-parse-getter/6 10991 if (!options.unwrapPromises && pathKeysLength === 1) { 10992 fn = simpleGetterFn1(pathKeys[0], fullExp); 10993 } else if (!options.unwrapPromises && pathKeysLength === 2) { 10994 fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp); 10995 } else if (options.csp) { 10996 if (pathKeysLength < 6) { 10997 fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, 10998 options); 10999 } else { 11000 fn = function(scope, locals) { 11001 var i = 0, val; 11002 do { 11003 val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], 11004 pathKeys[i++], fullExp, options)(scope, locals); 11005 11006 locals = undefined; // clear after first iteration 11007 scope = val; 11008 } while (i < pathKeysLength); 11009 return val; 11010 }; 11011 } 11012 } else { 11013 var code = 'var p;\n';
11014 forEach(pathKeys, function(key, index) { 11015 ensureSafeMemberName(key, fullExp); 11016 code += 'if(s == null) return undefined;\n' + 11017 's='+ (index 11018 // we simply dereference 's' on any .dot notation 11019 ? 's' 11020 // but if we are first then we check locals first, and if so read it first 11021 : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + 11022 (options.unwrapPromises 11023 ? 'if (s && s.then) {\n' + 11024 ' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' + 11025 ' if (!("$$v" in s)) {\n' + 11026 ' p=s;\n' + 11027 ' p.$$v = undefined;\n' + 11028 ' p.then(function(v) {p.$$v=v;});\n' + 11029 '}\n' + 11030 ' s=s.$$v\n' + 11031 '}\n' 11032 : ''); 11033 }); 11034 code += 'return s;'; 11035 11036 /* jshint -W054 */ 11037 var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning 11038 /* jshint +W054 */ 11039 evaledFnGetter.toString = valueFn(code); 11040 fn = options.unwrapPromises ? function(scope, locals) { 11041 return evaledFnGetter(scope, locals, promiseWarning); 11042 } : evaledFnGetter; 11043 } 11044 11045 // Only cache the value if it's not going to mess up the cache object 11046 // This is more performant that using Object.prototype.hasOwnProperty.call 11047 if (path !== 'hasOwnProperty') { 11048 getterFnCache[path] = fn; 11049 } 11050 return fn; 11051} 11052 11053/////////////////////////////////// 11054 11055/** 11056 * @ngdoc service 11057 * @name $parse 11058 * @kind function 11059 * 11060 * @description 11061 * 11062 * Converts Angular {@link guide/expression expression} into a function. 11063 * 11064 * ```js 11065 * var getter = $parse('user.name'); 11066 * var setter = getter.assign; 11067 * var context = {user:{name:'angular'}}; 11068 * var locals = {user:{name:'local'}}; 11069 * 11070 * expect(getter(context)).toEqual('angular'); 11071 * setter(context, 'newValue'); 11072 * expect(context.user.name).toEqual('newValue'); 11073 * expect(getter(context, locals)).toEqual('local'); 11074 * ``` 11075 * 11076 * 11077 * @param {string} expression String expression to compile. 11078 * @returns {function(context, locals)} a function which represents the compiled expression: 11079 * 11080 * * `context` â `{object}` â an object against which any expressions embedded in the strings 11081 * are evaluated against (typically a scope object). 11082 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 11083 * `context`. 11084 * 11085 * The returned function also has the following properties: 11086 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 11087 * literal. 11088 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 11089 * constant literals. 11090 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 11091 * set to a function to change its value on the given context. 11092 * 11093 */ 11094 11095 11096/** 11097 * @ngdoc provider 11098 * @name $parseProvider 11099 * @kind function 11100 * 11101 * @description 11102 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 11103 * service. 11104 */ 11105function $ParseProvider() { 11106 var cache = {}; 11107 11108 var $parseOptions = { 11109 csp: false, 11110 unwrapPromises: false, 11111 logPromiseWarnings: true 11112 }; 11113 11114 11115 /** 11116 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 11117 * 11118 * @ngdoc method 11119 * @name $parseProvider#unwrapPromises 11120 * @description 11121 * 11122 * **This feature is deprecated, see deprecation notes below for more info** 11123 * 11124 * If set to true (default is false), $parse will unwrap promises automatically when a promise is 11125 * found at any part of the expression. In other words, if set to true, the expression will always 11126 * result in a non-promise value. 11127 * 11128 * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, 11129 * the fulfillment value is used in place of the promise while evaluating the expression. 11130 * 11131 * **Deprecation notice** 11132 * 11133 * This is a feature that didn't prove to be wildly useful or popular, primarily because of the 11134 * dichotomy between data access in templates (accessed as raw values) and controller code 11135 * (accessed as promises). 11136 * 11137 * In most code we ended up resolving promises manually in controllers anyway and thus unifying 11138 * the model access there. 11139 * 11140 * Other downsides of automatic promise unwrapping: 11141 *
11142 * - when building components it's often desirable to receive the raw promises 11143 * - adds complexity and slows down expression evaluation 11144 * - makes expression code pre-generation unattractive due to the amount of code that needs to be 11145 * generated 11146 * - makes IDE auto-completion and tool support hard 11147 * 11148 * **Warning Logs** 11149 * 11150 * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a 11151 * promise (to reduce the noise, each expression is logged only once). To disable this logging use 11152 * `$parseProvider.logPromiseWarnings(false)` api. 11153 * 11154 * 11155 * @param {boolean=} value New value. 11156 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 11157 * setter. 11158 */ 11159 this.unwrapPromises = function(value) { 11160 if (isDefined(value)) { 11161 $parseOptions.unwrapPromises = !!value; 11162 return this; 11163 } else { 11164 return $parseOptions.unwrapPromises; 11165 } 11166 }; 11167 11168 11169 /** 11170 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 11171 * 11172 * @ngdoc method 11173 * @name $parseProvider#logPromiseWarnings 11174 * @description 11175 * 11176 * Controls whether Angular should log a warning on any encounter of a promise in an expression. 11177 * 11178 * The default is set to `true`. 11179 * 11180 * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. 11181 * 11182 * @param {boolean=} value New value. 11183 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 11184 * setter. 11185 */ 11186 this.logPromiseWarnings = function(value) { 11187 if (isDefined(value)) { 11188 $parseOptions.logPromiseWarnings = value; 11189 return this; 11190 } else { 11191 return $parseOptions.logPromiseWarnings; 11192 } 11193 }; 11194 11195 11196 this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { 11197 $parseOptions.csp = $sniffer.csp; 11198 11199 promiseWarning = function promiseWarningFn(fullExp) { 11200 if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; 11201 promiseWarningCache[fullExp] = true; 11202 $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + 11203 'Automatic unwrapping of promises in Angular expressions is deprecated.'); 11204 }; 11205 11206 return function(exp) { 11207 var parsedExpression; 11208 11209 switch (typeof exp) { 11210 case 'string': 11211 11212 if (cache.hasOwnProperty(exp)) { 11213 return cache[exp]; 11214 } 11215 11216 var lexer = new Lexer($parseOptions); 11217 var parser = new Parser(lexer, $filter, $parseOptions); 11218 parsedExpression = parser.parse(exp); 11219 11220 if (exp !== 'hasOwnProperty') { 11221 // Only cache the value if it's not going to mess up the cache object 11222 // This is more performant that using Object.prototype.hasOwnProperty.call 11223 cache[exp] = parsedExpression; 11224 } 11225 11226 return parsedExpression; 11227 11228 case 'function': 11229 return exp; 11230 11231 default: 11232 return noop; 11233 } 11234 }; 11235 }]; 11236} 11237 11238/** 11239 * @ngdoc service 11240 * @name $q 11241 * @requires $rootScope 11242 * 11243 * @description 11244 * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). 11245 * 11246 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 11247 * interface for interacting with an object that represents the result of an action that is 11248 * performed asynchronously, and may or may not be finished at any given point in time. 11249 * 11250 * From the perspective of dealing with error handling, deferred and promise APIs are to 11251 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 11252 * 11253 * ```js 11254 * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` 11255 * // are available in the current lexical scope (they could have been injected or passed in). 11256 * 11257 * function asyncGreet(name) { 11258 * var deferred = $q.defer(); 11259 * 11260 * setTimeout(function() { 11261 * deferred.notify('About to greet ' + name + '.'); 11262 * 11263 * if (okToGreet(name)) { 11264 * deferred.resolve('Hello, ' + name + '!'); 11265 * } else { 11266 * deferred.reject('Greeting ' + name + ' is not allowed.'); 11267 * } 11268 * }, 1000); 11269 * 11270 * return deferred.promise; 11271 * } 11272 * 11273 * var promise = asyncGreet('Robin Hood'); 11274 * promise.then(function(greeting) { 11275 * alert('Success: ' + greeting); 11276 * }, function(reason) { 11277 * alert('Failed: ' + reason); 11278 * }, function(update) { 11279 * alert('Got notification: ' + update); 11280 * }); 11281 * ``` 11282 * 11283 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 11284 * comes in the way of guarantees that promise and deferred APIs make, see 11285 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 11286 *
11287 * Additionally the promise api allows for composition that is very hard to do with the 11288 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 11289 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 11290 * section on serial or parallel joining of promises. 11291 * 11292 * 11293 * # The Deferred API 11294 * 11295 * A new instance of deferred is constructed by calling `$q.defer()`. 11296 * 11297 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 11298 * that can be used for signaling the successful or unsuccessful completion, as well as the status 11299 * of the task. 11300 * 11301 * **Methods** 11302 * 11303 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 11304 * constructed via `$q.reject`, the promise will be rejected instead. 11305 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 11306 * resolving it with a rejection constructed via `$q.reject`. 11307 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 11308 * multiple times before the promise is either resolved or rejected. 11309 * 11310 * **Properties** 11311 * 11312 * - promise â `{Promise}` â promise object associated with this deferred. 11313 * 11314 * 11315 * # The Promise API 11316 * 11317 * A new promise instance is created when a deferred instance is created and can be retrieved by 11318 * calling `deferred.promise`. 11319 * 11320 * The purpose of the promise object is to allow for interested parties to get access to the result 11321 * of the deferred task when it completes. 11322 * 11323 * **Methods** 11324 * 11325 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 11326 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 11327 * as soon as the result is available. The callbacks are called with a single argument: the result 11328 * or rejection reason. Additionally, the notify callback may be called zero or more times to 11329 * provide a progress indication, before the promise is resolved or rejected. 11330 * 11331 * This method *returns a new promise* which is resolved or rejected via the return value of the 11332 * `successCallback`, `errorCallback`. It also notifies via the return value of the 11333 * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback 11334 * method. 11335 * 11336 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 11337 * 11338 * - `finally(callback)` â allows you to observe either the fulfillment or rejection of a promise, 11339 * but to do so without modifying the final value. This is useful to release resources or do some 11340 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 11341 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 11342 * more information. 11343 * 11344 * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as 11345 * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to 11346 * make your code IE8 and Android 2.x compatible. 11347 * 11348 * # Chaining promises 11349 * 11350 * Because calling the `then` method of a promise returns a new derived promise, it is easily 11351 * possible to create a chain of promises: 11352 * 11353 * ```js 11354 * promiseB = promiseA.then(function(result) { 11355 * return result + 1; 11356 * }); 11357 * 11358 * // promiseB will be resolved immediately after promiseA is resolved and its value 11359 * // will be the result of promiseA incremented by 1 11360 * ``` 11361 * 11362 * It is possible to create chains of any length and since a promise can be resolved with another 11363 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 11364 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 11365 * $http's response interceptors. 11366 * 11367 * 11368 * # Differences between Kris Kowal's Q and $q 11369 * 11370 * There are two main differences: 11371 *
11372 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 11373 * mechanism in angular, which means faster propagation of resolution or rejection into your 11374 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 11375 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains 11376 * all the important functionality needed for common async tasks. 11377 * 11378 * # Testing 11379 * 11380 * ```js 11381 * it('should simulate promise', inject(function($q, $rootScope) { 11382 * var deferred = $q.defer(); 11383 * var promise = deferred.promise; 11384 * var resolvedValue; 11385 * 11386 * promise.then(function(value) { resolvedValue = value; }); 11387 * expect(resolvedValue).toBeUndefined(); 11388 * 11389 * // Simulate resolving of promise 11390 * deferred.resolve(123); 11391 * // Note that the 'then' function does not get called synchronously. 11392 * // This is because we want the promise API to always be async, whether or not 11393 * // it got called synchronously or asynchronously. 11394 * expect(resolvedValue).toBeUndefined(); 11395 * 11396 * // Propagate promise resolution to 'then' functions using $apply(). 11397 * $rootScope.$apply(); 11398 * expect(resolvedValue).toEqual(123); 11399 * })); 11400 * ``` 11401 */ 11402function $QProvider() { 11403 11404 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 11405 return qFactory(function(callback) { 11406 $rootScope.$evalAsync(callback); 11407 }, $exceptionHandler); 11408 }]; 11409} 11410 11411 11412/** 11413 * Constructs a promise manager. 11414 * 11415 * @param {function(Function)} nextTick Function for executing functions in the next turn. 11416 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 11417 * debugging purposes. 11418 * @returns {object} Promise manager. 11419 */ 11420function qFactory(nextTick, exceptionHandler) { 11421 11422 /** 11423 * @ngdoc method 11424 * @name $q#defer 11425 * @kind function 11426 * 11427 * @description 11428 * Creates a `Deferred` object which represents a task which will finish in the future. 11429 * 11430 * @returns {Deferred} Returns a new instance of deferred. 11431 */ 11432 var defer = function() { 11433 var pending = [], 11434 value, deferred; 11435 11436 deferred = { 11437 11438 resolve: function(val) { 11439 if (pending) { 11440 var callbacks = pending; 11441 pending = undefined; 11442 value = ref(val); 11443 11444 if (callbacks.length) { 11445 nextTick(function() { 11446 var callback; 11447 for (var i = 0, ii = callbacks.length; i < ii; i++) { 11448 callback = callbacks[i]; 11449 value.then(callback[0], callback[1], callback[2]); 11450 } 11451 }); 11452 } 11453 } 11454 }, 11455 11456 11457 reject: function(reason) { 11458 deferred.resolve(createInternalRejectedPromise(reason)); 11459 }, 11460 11461 11462 notify: function(progress) { 11463 if (pending) { 11464 var callbacks = pending; 11465 11466 if (pending.length) { 11467 nextTick(function() { 11468 var callback; 11469 for (var i = 0, ii = callbacks.length; i < ii; i++) { 11470 callback = callbacks[i]; 11471 callback[2](progress); 11472 } 11473 }); 11474 } 11475 } 11476 }, 11477 11478 11479 promise: { 11480 then: function(callback, errback, progressback) { 11481 var result = defer(); 11482 11483 var wrappedCallback = function(value) { 11484 try { 11485 result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); 11486 } catch(e) { 11487 result.reject(e); 11488 exceptionHandler(e); 11489 } 11490 }; 11491 11492 var wrappedErrback = function(reason) { 11493 try { 11494 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11495 } catch(e) { 11496 result.reject(e); 11497 exceptionHandler(e); 11498 } 11499 }; 11500 11501 var wrappedProgressback = function(progress) { 11502 try { 11503 result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); 11504 } catch(e) { 11505 exceptionHandler(e); 11506 } 11507 }; 11508 11509 if (pending) { 11510 pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); 11511 } else { 11512 value.then(wrappedCallback, wrappedErrback, wrappedProgressback); 11513 } 11514 11515 return result.promise; 11516 }, 11517 11518 "catch": function(callback) { 11519 return this.then(null, callback); 11520 }, 11521 11522 "finally": function(callback) { 11523 11524 function makePromise(value, resolved) { 11525 var result = defer(); 11526 if (resolved) { 11527 result.resolve(value); 11528 } else { 11529 result.reject(value); 11530 } 11531 return result.promise; 11532 } 11533 11534 function handleCallback(value, isResolved) { 11535 var callbackOutput = null; 11536 try { 11537 callbackOutput = (callback ||defaultCallback)(); 11538 } catch(e) { 11539 return makePromise(e, false); 11540 } 11541 if (callbackOutput && isFunction(callbackOutput.then)) { 11542 return callbackOutput.then(function() { 11543 return makePromise(value, isResolved); 11544 }, function(error) { 11545 return makePromise(error, false); 11546 }); 11547 } else { 11548 return makePromise(value, isResolved); 11549 } 11550 } 11551 11552 return this.then(function(value) { 11553 return handleCallback(value, true); 11554 }, function(error) { 11555 return handleCallback(error, false); 11556 }); 11557 } 11558 } 11559 }; 11560 11561 return deferred; 11562 }; 11563 11564 11565 var ref = function(value) { 11566 if (value && isFunction(value.then)) return value; 11567 return { 11568 then: function(callback) { 11569 var result = defer(); 11570 nextTick(function() { 11571 result.resolve(callback(value)); 11572 }); 11573 return result.promise; 11574 } 11575 }; 11576 }; 11577 11578 11579 /** 11580 * @ngdoc method 11581 * @name $q#reject 11582 * @kind function 11583 * 11584 * @description 11585 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 11586 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 11587 * a promise chain, you don't need to worry about it. 11588 * 11589 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 11590 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 11591 * a promise error callback and you want to forward the error to the promise derived from the 11592 * current promise, you have to "rethrow" the error by returning a rejection constructed via 11593 * `reject`. 11594 * 11595 * ```js 11596 * promiseB = promiseA.then(function(result) { 11597 * // success: do something and resolve promiseB 11598 * // with the old or a new result 11599 * return result; 11600 * }, function(reason) { 11601 * // error: handle the error if possible and
11602 * // resolve promiseB with newPromiseOrValue, 11603 * // otherwise forward the rejection to promiseB 11604 * if (canHandle(reason)) { 11605 * // handle the error and recover 11606 * return newPromiseOrValue; 11607 * } 11608 * return $q.reject(reason); 11609 * }); 11610 * ``` 11611 * 11612 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 11613 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 11614 */ 11615 var reject = function(reason) { 11616 var result = defer(); 11617 result.reject(reason); 11618 return result.promise; 11619 }; 11620 11621 var createInternalRejectedPromise = function(reason) { 11622 return { 11623 then: function(callback, errback) { 11624 var result = defer(); 11625 nextTick(function() { 11626 try { 11627 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11628 } catch(e) { 11629 result.reject(e); 11630 exceptionHandler(e); 11631 } 11632 }); 11633 return result.promise; 11634 } 11635 }; 11636 }; 11637 11638 11639 /** 11640 * @ngdoc method 11641 * @name $q#when 11642 * @kind function 11643 * 11644 * @description 11645 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 11646 * This is useful when you are dealing with an object that might or might not be a promise, or if 11647 * the promise comes from a source that can't be trusted. 11648 * 11649 * @param {*} value Value or a promise 11650 * @returns {Promise} Returns a promise of the passed value or promise 11651 */ 11652 var when = function(value, callback, errback, progressback) { 11653 var result = defer(), 11654 done; 11655 11656 var wrappedCallback = function(value) { 11657 try { 11658 return (isFunction(callback) ? callback : defaultCallback)(value); 11659 } catch (e) { 11660 exceptionHandler(e); 11661 return reject(e); 11662 } 11663 }; 11664 11665 var wrappedErrback = function(reason) { 11666 try { 11667 return (isFunction(errback) ? errback : defaultErrback)(reason); 11668 } catch (e) { 11669 exceptionHandler(e); 11670 return reject(e); 11671 } 11672 }; 11673 11674 var wrappedProgressback = function(progress) { 11675 try { 11676 return (isFunction(progressback) ? progressback : defaultCallback)(progress); 11677 } catch (e) { 11678 exceptionHandler(e); 11679 } 11680 }; 11681 11682 nextTick(function() { 11683 ref(value).then(function(value) { 11684 if (done) return; 11685 done = true; 11686 result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); 11687 }, function(reason) { 11688 if (done) return; 11689 done = true; 11690 result.resolve(wrappedErrback(reason)); 11691 }, function(progress) { 11692 if (done) return; 11693 result.notify(wrappedProgressback(progress)); 11694 }); 11695 }); 11696 11697 return result.promise; 11698 }; 11699 11700 11701 function defaultCallback(value) { 11702 return value; 11703 } 11704 11705 11706 function defaultErrback(reason) { 11707 return reject(reason); 11708 } 11709 11710 11711 /** 11712 * @ngdoc method 11713 * @name $q#all 11714 * @kind function 11715 * 11716 * @description 11717 * Combines multiple promises into a single promise that is resolved when all of the input 11718 * promises are resolved. 11719 * 11720 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 11721 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 11722 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 11723 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 11724 * with the same rejection value. 11725 */ 11726 function all(promises) { 11727 var deferred = defer(), 11728 counter = 0, 11729 results = isArray(promises) ? [] : {}; 11730 11731 forEach(promises, function(promise, key) { 11732 counter++; 11733 ref(promise).then(function(value) { 11734 if (results.hasOwnProperty(key)) return; 11735 results[key] = value; 11736 if (!(--counter)) deferred.resolve(results); 11737 }, function(reason) { 11738 if (results.hasOwnProperty(key)) return; 11739 deferred.reject(reason); 11740 }); 11741 });
vendor: 4,332 bytes, lines 11742-11875
11742 11743 if (counter === 0) { 11744 deferred.resolve(results); 11745 } 11746 11747 return deferred.promise; 11748 } 11749 11750 return { 11751 defer: defer, 11752 reject: reject, 11753 when: when, 11754 all: all 11755 }; 11756} 11757 11758function $$RAFProvider(){ //rAF 11759 this.$get = ['$window', '$timeout', function($window, $timeout) { 11760 var requestAnimationFrame = $window.requestAnimationFrame || 11761 $window.webkitRequestAnimationFrame || 11762 $window.mozRequestAnimationFrame; 11763 11764 var cancelAnimationFrame = $window.cancelAnimationFrame || 11765 $window.webkitCancelAnimationFrame || 11766 $window.mozCancelAnimationFrame || 11767 $window.webkitCancelRequestAnimationFrame; 11768 11769 var rafSupported = !!requestAnimationFrame; 11770 var raf = rafSupported 11771 ? function(fn) { 11772 var id = requestAnimationFrame(fn); 11773 return function() { 11774 cancelAnimationFrame(id); 11775 }; 11776 } 11777 : function(fn) { 11778 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 11779 return function() { 11780 $timeout.cancel(timer); 11781 }; 11782 }; 11783 11784 raf.supported = rafSupported; 11785 11786 return raf; 11787 }]; 11788} 11789 11790/** 11791 * DESIGN NOTES 11792 * 11793 * The design decisions behind the scope are heavily favored for speed and memory consumption. 11794 * 11795 * The typical use of scope is to watch the expressions, which most of the time return the same 11796 * value as last time so we optimize the operation. 11797 * 11798 * Closures construction is expensive in terms of speed as well as memory: 11799 * - No closures, instead use prototypical inheritance for API 11800 * - Internal state needs to be stored on scope directly, which means that private state is 11801 * exposed as $$____ properties 11802 * 11803 * Loop operations are optimized by using while(count--) { ... } 11804 * - this means that in order to keep the same order of execution as addition we have to add 11805 * items to the array at the beginning (unshift) instead of at the end (push) 11806 * 11807 * Child scopes are created and removed often 11808 * - Using an array would be slow since inserts in middle are expensive so we use linked list 11809 * 11810 * There are few watches then a lot of observers. This is why you don't want the observer to be 11811 * implemented in the same way as watch. Watch requires return of initialization function which 11812 * are expensive to construct. 11813 */ 11814 11815 11816/** 11817 * @ngdoc provider 11818 * @name $rootScopeProvider 11819 * @description 11820 * 11821 * Provider for the $rootScope service. 11822 */ 11823 11824/** 11825 * @ngdoc method 11826 * @name $rootScopeProvider#digestTtl 11827 * @description 11828 * 11829 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 11830 * assuming that the model is unstable. 11831 * 11832 * The current default is 10 iterations. 11833 * 11834 * In complex applications it's possible that the dependencies between `$watch`s will result in 11835 * several digest iterations. However if an application needs more than the default 10 digest 11836 * iterations for its model to stabilize then you should investigate what is causing the model to 11837 * continuously change during the digest. 11838 * 11839 * Increasing the TTL could have performance implications, so you should not change it without 11840 * proper justification. 11841 * 11842 * @param {number} limit The number of digest iterations. 11843 */ 11844 11845 11846/** 11847 * @ngdoc service 11848 * @name $rootScope 11849 * @description 11850 * 11851 * Every application has a single root {@link ng.$rootScope.Scope scope}. 11852 * All other scopes are descendant scopes of the root scope. Scopes provide separation 11853 * between the model and the view, via a mechanism for watching the model for changes. 11854 * They also provide an event emission/broadcast and subscription facility. See the 11855 * {@link guide/scope developer guide on scopes}. 11856 */ 11857function $RootScopeProvider(){ 11858 var TTL = 10; 11859 var $rootScopeMinErr = minErr('$rootScope'); 11860 var lastDirtyWatch = null; 11861 11862 this.digestTtl = function(value) { 11863 if (arguments.length) { 11864 TTL = value; 11865 } 11866 return TTL; 11867 }; 11868 11869 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 11870 function( $injector, $exceptionHandler, $parse, $browser) { 11871 11872 /** 11873 * @ngdoc type 11874 * @name $rootScope.Scope 11875 *
11876 * @description 11877 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 11878 * {@link auto.$injector $injector}. Child scopes are created using the 11879 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 11880 * compiled HTML template is executed.) 11881 * 11882 * Here is a simple scope snippet to show how you can interact with the scope. 11883 * ```html 11884 * <file src="./test/ng/rootScopeSpec.js" tag="docs1" /> 11885 * ``` 11886 * 11887 * # Inheritance 11888 * A scope can inherit from a parent scope, as in this example: 11889 * ```js 11890 var parent = $rootScope; 11891 var child = parent.$new(); 11892 11893 parent.salutation = "Hello"; 11894 child.name = "World"; 11895 expect(child.salutation).toEqual('Hello'); 11896 11897 child.salutation = "Welcome"; 11898 expect(child.salutation).toEqual('Welcome'); 11899 expect(parent.salutation).toEqual('Hello'); 11900 * ``` 11901 * 11902 * 11903 * @param {Object.<string, function()>=} providers Map of service factory which need to be 11904 * provided for the current scope. Defaults to {@link ng}. 11905 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 11906 * append/override services provided by `providers`. This is handy 11907 * when unit-testing and having the need to override a default 11908 * service. 11909 * @returns {Object} Newly created scope. 11910 * 11911 */ 11912 function Scope() { 11913 this.$id = nextUid(); 11914 this.$$phase = this.$parent = this.$$watchers = 11915 this.$$nextSibling = this.$$prevSibling = 11916 this.$$childHead = this.$$childTail = null; 11917 this['this'] = this.$root = this; 11918 this.$$destroyed = false; 11919 this.$$asyncQueue = []; 11920 this.$$postDigestQueue = []; 11921 this.$$listeners = {}; 11922 this.$$listenerCount = {}; 11923 this.$$isolateBindings = {}; 11924 } 11925 11926 /** 11927 * @ngdoc property 11928 * @name $rootScope.Scope#$id 11929 * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for 11930 * debugging. 11931 */ 11932 11933 11934 Scope.prototype = { 11935 constructor: Scope, 11936 /** 11937 * @ngdoc method 11938 * @name $rootScope.Scope#$new 11939 * @kind function 11940 * 11941 * @description 11942 * Creates a new child {@link ng.$rootScope.Scope scope}. 11943 * 11944 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and 11945 * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the 11946 * scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 11947 * 11948 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 11949 * desired for the scope and its child scopes to be permanently detached from the parent and 11950 * thus stop participating in model change detection and listener notification by invoking. 11951 * 11952 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 11953 * parent scope. The scope is isolated, as it can not see parent scope properties. 11954 * When creating widgets, it is useful for the widget to not accidentally read parent 11955 * state. 11956 * 11957 * @returns {Object} The newly created child scope. 11958 * 11959 */ 11960 $new: function(isolate) { 11961 var ChildScope, 11962 child; 11963 11964 if (isolate) { 11965 child = new Scope(); 11966 child.$root = this.$root; 11967 // ensure that there is just one async queue per $rootScope and its children 11968 child.$$asyncQueue = this.$$asyncQueue; 11969 child.$$postDigestQueue = this.$$postDigestQueue; 11970 } else { 11971 // Only create a child scope class if somebody asks for one, 11972 // but cache it to allow the VM to optimize lookups. 11973 if (!this.$$childScopeClass) { 11974 this.$$childScopeClass = function() {
11975 this.$$watchers = this.$$nextSibling = 11976 this.$$childHead = this.$$childTail = null; 11977 this.$$listeners = {}; 11978 this.$$listenerCount = {}; 11979 this.$id = nextUid(); 11980 this.$$childScopeClass = null; 11981 }; 11982 this.$$childScopeClass.prototype = this; 11983 } 11984 child = new this.$$childScopeClass(); 11985 } 11986 child['this'] = child; 11987 child.$parent = this; 11988 child.$$prevSibling = this.$$childTail; 11989 if (this.$$childHead) { 11990 this.$$childTail.$$nextSibling = child; 11991 this.$$childTail = child; 11992 } else { 11993 this.$$childHead = this.$$childTail = child; 11994 } 11995 return child; 11996 }, 11997 11998 /** 11999 * @ngdoc method 12000 * @name $rootScope.Scope#$watch 12001 * @kind function 12002 * 12003 * @description 12004 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 12005 * 12006 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 12007 * $digest()} and should return the value that will be watched. (Since 12008 * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the 12009 * `watchExpression` can execute multiple times per 12010 * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) 12011 * - The `listener` is called only when the value from the current `watchExpression` and the 12012 * previous call to `watchExpression` are not equal (with the exception of the initial run, 12013 * see below). Inequality is determined according to reference inequality, 12014 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 12015 * via the `!==` Javascript operator, unless `objectEquality == true` 12016 * (see next point) 12017 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 12018 * according to the {@link angular.equals} function. To save the value of the object for 12019 * later comparison, the {@link angular.copy} function is used. This therefore means that 12020 * watching complex objects will have adverse memory and performance implications. 12021 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 12022 * This is achieved by rerunning the watchers until no changes are detected. The rerun 12023 * iteration limit is 10 to prevent an infinite loop deadlock. 12024 * 12025 * 12026 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 12027 * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` 12028 * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a 12029 * change is detected, be prepared for multiple calls to your listener.) 12030 * 12031 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 12032 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 12033 * watcher. In rare cases, this is undesirable because the listener is called when the result 12034 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 12035 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 12036 * listener was called due to initialization. 12037 * 12038 * The example below contains an illustration of using a function as your $watch listener 12039 * 12040 * 12041 * # Example 12042 * ```js 12043 // let's assume that scope was dependency injected as the $rootScope 12044 var scope = $rootScope; 12045 scope.name = 'misko'; 12046 scope.counter = 0; 12047 12048 expect(scope.counter).toEqual(0); 12049 scope.$watch('name', function(newValue, oldValue) { 12050 scope.counter = scope.counter + 1; 12051 }); 12052 expect(scope.counter).toEqual(0); 12053 12054 scope.$digest(); 12055 // the listener is always called during the first $digest loop after it was registered 12056 expect(scope.counter).toEqual(1); 12057 12058 scope.$digest(); 12059 // but now it will not be called unless the value changes 12060 expect(scope.counter).toEqual(1); 12061 12062 scope.name = 'adam'; 12063 scope.$digest(); 12064 expect(scope.counter).toEqual(2); 12065 12066 12067 12068 // Using a listener function 12069 var food; 12070 scope.foodCounter = 0; 12071 expect(scope.foodCounter).toEqual(0); 12072 scope.$watch( 12073 // This is the listener function 12074 function() { return food; }, 12075 // This is the change handler 12076 function(newValue, oldValue) { 12077 if ( newValue !== oldValue ) { 12078 // Only increment the counter if the value changed 12079 scope.foodCounter = scope.foodCounter + 1; 12080 } 12081 } 12082 ); 12083 // No digest has been run so the counter will be zero 12084 expect(scope.foodCounter).toEqual(0); 12085 12086 // Run the digest but since food has not changed count will still be zero 12087 scope.$digest(); 12088 expect(scope.foodCounter).toEqual(0); 12089 12090 // Update food and run digest. Now the counter will increment 12091 food = 'cheeseburger'; 12092 scope.$digest(); 12093 expect(scope.foodCounter).toEqual(1); 12094 12095 * ``` 12096 * 12097 * 12098 * 12099 * @param {(function()|string)} watchExpression Expression that is evaluated on each 12100 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 12101 * a call to the `listener`. 12102 * 12103 * - `string`: Evaluated as {@link guide/expression expression} 12104 * - `function(scope)`: called with current `scope` as a parameter. 12105 * @param {(function()|string)=} listener Callback called whenever the return value of 12106 * the `watchExpression` changes. 12107 * 12108 * - `string`: Evaluated as {@link guide/expression expression} 12109 * - `function(newValue, oldValue, scope)`: called with current and previous values as 12110 * parameters. 12111 * 12112 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of 12113 * comparing for reference equality. 12114 * @returns {function()} Returns a deregistration function for this listener. 12115 */ 12116 $watch: function(watchExp, listener, objectEquality) { 12117 var scope = this, 12118 get = compileToFn(watchExp, 'watch'), 12119 array = scope.$$watchers, 12120 watcher = { 12121 fn: listener, 12122 last: initWatchVal, 12123 get: get, 12124 exp: watchExp, 12125 eq: !!objectEquality 12126 }; 12127 12128 lastDirtyWatch = null; 12129 12130 // in the case user pass string, we need to compile it, do we really need this ? 12131 if (!isFunction(listener)) { 12132 var listenFn = compileToFn(listener || noop, 'listener'); 12133 watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; 12134 } 12135 12136 if (typeof watchExp == 'string' && get.constant) { 12137 var originalFn = watcher.fn; 12138 watcher.fn = function(newVal, oldVal, scope) { 12139 originalFn.call(this, newVal, oldVal, scope); 12140 arrayRemove(array, watcher); 12141 }; 12142 } 12143 12144 if (!array) { 12145 array = scope.$$watchers = []; 12146 } 12147 // we use unshift since we use a while loop in $digest for speed. 12148 // the while loop reads in reverse order. 12149 array.unshift(watcher); 12150 12151 return function deregisterWatch() { 12152 arrayRemove(array, watcher); 12153 lastDirtyWatch = null; 12154 }; 12155 }, 12156 12157 12158 /** 12159 * @ngdoc method 12160 * @name $rootScope.Scope#$watchCollection 12161 * @kind function 12162 *
12163 * @description 12164 * Shallow watches the properties of an object and fires whenever any of the properties change 12165 * (for arrays, this implies watching the array items; for object maps, this implies watching 12166 * the properties). If a change is detected, the `listener` callback is fired. 12167 * 12168 * - The `obj` collection is observed via standard $watch operation and is examined on every 12169 * call to $digest() to see if any items have been added, removed, or moved. 12170 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 12171 * adding, removing, and moving items belonging to an object or array. 12172 * 12173 * 12174 * # Example 12175 * ```js 12176 $scope.names = ['igor', 'matias', 'misko', 'james']; 12177 $scope.dataCount = 4; 12178 12179 $scope.$watchCollection('names', function(newNames, oldNames) { 12180 $scope.dataCount = newNames.length; 12181 }); 12182 12183 expect($scope.dataCount).toEqual(4); 12184 $scope.$digest(); 12185 12186 //still at 4 ... no changes 12187 expect($scope.dataCount).toEqual(4); 12188 12189 $scope.names.pop(); 12190 $scope.$digest(); 12191 12192 //now there's been a change 12193 expect($scope.dataCount).toEqual(3); 12194 * ``` 12195 * 12196 * 12197 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 12198 * expression value should evaluate to an object or an array which is observed on each 12199 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 12200 * collection will trigger a call to the `listener`. 12201 * 12202 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 12203 * when a change is detected. 12204 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 12205 * - The `oldCollection` object is a copy of the former collection data. 12206 * Due to performance considerations, the`oldCollection` value is computed only if the 12207 * `listener` function declares two or more arguments. 12208 * - The `scope` argument refers to the current scope. 12209 * 12210 * @returns {function()} Returns a de-registration function for this listener. When the 12211 * de-registration function is executed, the internal watch operation is terminated. 12212 */ 12213 $watchCollection: function(obj, listener) { 12214 var self = this; 12215 // the current value, updated on each dirty-check run 12216 var newValue; 12217 // a shallow copy of the newValue from the last dirty-check run, 12218 // updated to match newValue during dirty-check run 12219 var oldValue; 12220 // a shallow copy of the newValue from when the last change happened 12221 var veryOldValue; 12222 // only track veryOldValue if the listener is asking for it 12223 var trackVeryOldValue = (listener.length > 1); 12224 var changeDetected = 0; 12225 var objGetter = $parse(obj); 12226 var internalArray = []; 12227 var internalObject = {}; 12228 var initRun = true; 12229 var oldLength = 0; 12230 12231 function $watchCollectionWatch() { 12232 newValue = objGetter(self); 12233 var newLength, key; 12234 12235 if (!isObject(newValue)) { // if primitive 12236 if (oldValue !== newValue) { 12237 oldValue = newValue; 12238 changeDetected++; 12239 } 12240 } else if (isArrayLike(newValue)) { 12241 if (oldValue !== internalArray) { 12242 // we are transitioning from something which was not an array into array. 12243 oldValue = internalArray; 12244 oldLength = oldValue.length = 0; 12245 changeDetected++; 12246 } 12247 12248 newLength = newValue.length; 12249 12250 if (oldLength !== newLength) { 12251 // if lengths do not match we need to trigger change notification 12252 changeDetected++; 12253 oldValue.length = oldLength = newLength; 12254 } 12255 // copy the items to oldValue and look for changes. 12256 for (var i = 0; i < newLength; i++) { 12257 var bothNaN = (oldValue[i] !== oldValue[i]) && 12258 (newValue[i] !== newValue[i]);
12259 if (!bothNaN && (oldValue[i] !== newValue[i])) { 12260 changeDetected++; 12261 oldValue[i] = newValue[i]; 12262 } 12263 } 12264 } else { 12265 if (oldValue !== internalObject) { 12266 // we are transitioning from something which was not an object into object. 12267 oldValue = internalObject = {}; 12268 oldLength = 0; 12269 changeDetected++; 12270 } 12271 // copy the items to oldValue and look for changes. 12272 newLength = 0; 12273 for (key in newValue) { 12274 if (newValue.hasOwnProperty(key)) { 12275 newLength++; 12276 if (oldValue.hasOwnProperty(key)) { 12277 if (oldValue[key] !== newValue[key]) { 12278 changeDetected++; 12279 oldValue[key] = newValue[key]; 12280 } 12281 } else { 12282 oldLength++; 12283 oldValue[key] = newValue[key]; 12284 changeDetected++; 12285 } 12286 } 12287 } 12288 if (oldLength > newLength) { 12289 // we used to have more keys, need to find them and destroy them. 12290 changeDetected++; 12291 for(key in oldValue) { 12292 if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { 12293 oldLength--; 12294 delete oldValue[key]; 12295 } 12296 } 12297 } 12298 } 12299 return changeDetected; 12300 } 12301 12302 function $watchCollectionAction() { 12303 if (initRun) { 12304 initRun = false; 12305 listener(newValue, newValue, self); 12306 } else { 12307 listener(newValue, veryOldValue, self); 12308 } 12309 12310 // make a copy for the next time a collection is changed 12311 if (trackVeryOldValue) { 12312 if (!isObject(newValue)) { 12313 //primitive 12314 veryOldValue = newValue; 12315 } else if (isArrayLike(newValue)) { 12316 veryOldValue = new Array(newValue.length); 12317 for (var i = 0; i < newValue.length; i++) { 12318 veryOldValue[i] = newValue[i]; 12319 } 12320 } else { // if object 12321 veryOldValue = {}; 12322 for (var key in newValue) { 12323 if (hasOwnProperty.call(newValue, key)) { 12324 veryOldValue[key] = newValue[key]; 12325 } 12326 } 12327 } 12328 } 12329 } 12330 12331 return this.$watch($watchCollectionWatch, $watchCollectionAction); 12332 }, 12333 12334 /** 12335 * @ngdoc method 12336 * @name $rootScope.Scope#$digest 12337 * @kind function 12338 * 12339 * @description 12340 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 12341 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 12342 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 12343 * until no more listeners are firing. This means that it is possible to get into an infinite 12344 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 12345 * iterations exceeds 10. 12346 * 12347 * Usually, you don't call `$digest()` directly in 12348 * {@link ng.directive:ngController controllers} or in 12349 * {@link ng.$compileProvider#directive directives}. 12350 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 12351 * a {@link ng.$compileProvider#directive directives}), which will force a `$digest()`. 12352 * 12353 * If you want to be notified whenever `$digest()` is called, 12354 * you can register a `watchExpression` function with 12355 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 12356 * 12357 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 12358 * 12359 * # Example 12360 * ```js 12361 var scope = ...; 12362 scope.name = 'misko'; 12363 scope.counter = 0; 12364 12365 expect(scope.counter).toEqual(0); 12366 scope.$watch('name', function(newValue, oldValue) { 12367 scope.counter = scope.counter + 1; 12368 }); 12369 expect(scope.counter).toEqual(0); 12370 12371 scope.$digest(); 12372 // the listener is always called during the first $digest loop after it was registered 12373 expect(scope.counter).toEqual(1); 12374 12375 scope.$digest(); 12376 // but now it will not be called unless the value changes 12377 expect(scope.counter).toEqual(1); 12378 12379 scope.name = 'adam'; 12380 scope.$digest(); 12381 expect(scope.counter).toEqual(2); 12382 * ``` 12383 * 12384 */ 12385 $digest: function() { 12386 var watch, value, last, 12387 watchers, 12388 asyncQueue = this.$$asyncQueue, 12389 postDigestQueue = this.$$postDigestQueue, 12390 length, 12391 dirty, ttl = TTL, 12392 next, current, target = this, 12393 watchLog = [], 12394 logIdx, logMsg, asyncTask; 12395 12396 beginPhase('$digest'); 12397 12398 lastDirtyWatch = null; 12399
12400 do { // "while dirty" loop 12401 dirty = false; 12402 current = target; 12403 12404 while(asyncQueue.length) { 12405 try { 12406 asyncTask = asyncQueue.shift(); 12407 asyncTask.scope.$eval(asyncTask.expression); 12408 } catch (e) { 12409 clearPhase(); 12410 $exceptionHandler(e); 12411 } 12412 lastDirtyWatch = null; 12413 } 12414 12415 traverseScopesLoop: 12416 do { // "traverse the scopes" loop 12417 if ((watchers = current.$$watchers)) { 12418 // process our watches 12419 length = watchers.length; 12420 while (length--) { 12421 try { 12422 watch = watchers[length]; 12423 // Most common watches are on primitives, in which case we can short 12424 // circuit it with === operator, only when === fails do we use .equals 12425 if (watch) { 12426 if ((value = watch.get(current)) !== (last = watch.last) && 12427 !(watch.eq 12428 ? equals(value, last) 12429 : (typeof value === 'number' && typeof last === 'number' 12430 && isNaN(value) && isNaN(last)))) { 12431 dirty = true; 12432 lastDirtyWatch = watch; 12433 watch.last = watch.eq ? copy(value, null) : value; 12434 watch.fn(value, ((last === initWatchVal) ? value : last), current); 12435 if (ttl < 5) { 12436 logIdx = 4 - ttl; 12437 if (!watchLog[logIdx]) watchLog[logIdx] = []; 12438 logMsg = (isFunction(watch.exp)) 12439 ? 'fn: ' + (watch.exp.name || watch.exp.toString()) 12440 : watch.exp; 12441 logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); 12442 watchLog[logIdx].push(logMsg); 12443 } 12444 } else if (watch === lastDirtyWatch) { 12445 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 12446 // have already been tested. 12447 dirty = false; 12448 break traverseScopesLoop; 12449 } 12450 } 12451 } catch (e) { 12452 clearPhase(); 12453 $exceptionHandler(e); 12454 } 12455 } 12456 } 12457 12458 // Insanity Warning: scope depth-first traversal 12459 // yes, this code is a bit crazy, but it works and we have tests to prove it! 12460 // this piece should be kept in sync with the traversal in $broadcast 12461 if (!(next = (current.$$childHead || 12462 (current !== target && current.$$nextSibling)))) { 12463 while(current !== target && !(next = current.$$nextSibling)) { 12464 current = current.$parent; 12465 } 12466 } 12467 } while ((current = next)); 12468 12469 // `break traverseScopesLoop;` takes us to here 12470 12471 if((dirty || asyncQueue.length) && !(ttl--)) { 12472 clearPhase(); 12473 throw $rootScopeMinErr('infdig', 12474 '{0} $digest() iterations reached. Aborting!\n' + 12475 'Watchers fired in the last 5 iterations: {1}', 12476 TTL, toJson(watchLog)); 12477 } 12478 12479 } while (dirty || asyncQueue.length); 12480 12481 clearPhase(); 12482 12483 while(postDigestQueue.length) { 12484 try { 12485 postDigestQueue.shift()(); 12486 } catch (e) { 12487 $exceptionHandler(e); 12488 } 12489 } 12490 }, 12491 12492 12493 /** 12494 * @ngdoc event 12495 * @name $rootScope.Scope#$destroy 12496 * @eventType broadcast on scope being destroyed 12497 * 12498 * @description 12499 * Broadcasted when a scope and its children are being destroyed. 12500 * 12501 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 12502 * clean up DOM bindings before an element is removed from the DOM. 12503 */ 12504 12505 /** 12506 * @ngdoc method 12507 * @name $rootScope.Scope#$destroy 12508 * @kind function 12509 *
12510 * @description 12511 * Removes the current scope (and all of its children) from the parent scope. Removal implies 12512 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 12513 * propagate to the current scope and its children. Removal also implies that the current 12514 * scope is eligible for garbage collection. 12515 * 12516 * The `$destroy()` is usually used by directives such as 12517 * {@link ng.directive:ngRepeat ngRepeat} for managing the 12518 * unrolling of the loop. 12519 * 12520 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 12521 * Application code can register a `$destroy` event handler that will give it a chance to 12522 * perform any necessary cleanup. 12523 * 12524 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 12525 * clean up DOM bindings before an element is removed from the DOM. 12526 */ 12527 $destroy: function() { 12528 // we can't destroy the root scope or a scope that has been already destroyed 12529 if (this.$$destroyed) return; 12530 var parent = this.$parent; 12531 12532 this.$broadcast('$destroy'); 12533 this.$$destroyed = true; 12534 if (this === $rootScope) return; 12535 12536 forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); 12537 12538 // sever all the references to parent scopes (after this cleanup, the current scope should 12539 // not be retained by any of our references and should be eligible for garbage collection) 12540 if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 12541 if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 12542 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 12543 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 12544 12545 12546 // All of the code below is bogus code that works around V8's memory leak via optimized code 12547 // and inline caches. 12548 // 12549 // see: 12550 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 12551 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 12552 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 12553 12554 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 12555 this.$$childTail = this.$root = null; 12556 12557 // don't reset these to null in case some async task tries to register a listener/watch/task 12558 this.$$listeners = {}; 12559 this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = []; 12560 12561 // prevent NPEs since these methods have references to properties we nulled out 12562 this.$destroy = this.$digest = this.$apply = noop; 12563 this.$on = this.$watch = function() { return noop; }; 12564 }, 12565 12566 /** 12567 * @ngdoc method 12568 * @name $rootScope.Scope#$eval 12569 * @kind function 12570 * 12571 * @description 12572 * Executes the `expression` on the current scope and returns the result. Any exceptions in 12573 * the expression are propagated (uncaught). This is useful when evaluating Angular 12574 * expressions. 12575 * 12576 * # Example 12577 * ```js 12578 var scope = ng.$rootScope.Scope(); 12579 scope.a = 1; 12580 scope.b = 2; 12581 12582 expect(scope.$eval('a+b')).toEqual(3); 12583 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 12584 * ``` 12585 * 12586 * @param {(string|function())=} expression An angular expression to be executed. 12587 * 12588 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12589 * - `function(scope)`: execute the function with the current `scope` parameter. 12590 * 12591 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 12592 * @returns {*} The result of evaluating the expression. 12593 */ 12594 $eval: function(expr, locals) { 12595 return $parse(expr)(this, locals); 12596 }, 12597 12598 /** 12599 * @ngdoc method 12600 * @name $rootScope.Scope#$evalAsync 12601 * @kind function 12602 *
12603 * @description 12604 * Executes the expression on the current scope at a later point in time. 12605 * 12606 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 12607 * that: 12608 * 12609 * - it will execute after the function that scheduled the evaluation (preferably before DOM 12610 * rendering). 12611 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 12612 * `expression` execution. 12613 * 12614 * Any exceptions from the execution of the expression are forwarded to the 12615 * {@link ng.$exceptionHandler $exceptionHandler} service. 12616 * 12617 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 12618 * will be scheduled. However, it is encouraged to always call code that changes the model 12619 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 12620 * 12621 * @param {(string|function())=} expression An angular expression to be executed. 12622 * 12623 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12624 * - `function(scope)`: execute the function with the current `scope` parameter. 12625 * 12626 */ 12627 $evalAsync: function(expr) { 12628 // if we are outside of an $digest loop and this is the first time we are scheduling async 12629 // task also schedule async auto-flush 12630 if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { 12631 $browser.defer(function() { 12632 if ($rootScope.$$asyncQueue.length) { 12633 $rootScope.$digest(); 12634 } 12635 }); 12636 } 12637 12638 this.$$asyncQueue.push({scope: this, expression: expr}); 12639 }, 12640 12641 $$postDigest : function(fn) { 12642 this.$$postDigestQueue.push(fn); 12643 }, 12644 12645 /** 12646 * @ngdoc method 12647 * @name $rootScope.Scope#$apply 12648 * @kind function 12649 * 12650 * @description 12651 * `$apply()` is used to execute an expression in angular from outside of the angular 12652 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 12653 * Because we are calling into the angular framework we need to perform proper scope life 12654 * cycle of {@link ng.$exceptionHandler exception handling}, 12655 * {@link ng.$rootScope.Scope#$digest executing watches}. 12656 * 12657 * ## Life cycle 12658 * 12659 * # Pseudo-Code of `$apply()` 12660 * ```js 12661 function $apply(expr) { 12662 try { 12663 return $eval(expr); 12664 } catch (e) { 12665 $exceptionHandler(e); 12666 } finally { 12667 $root.$digest(); 12668 } 12669 } 12670 * ``` 12671 * 12672 * 12673 * Scope's `$apply()` method transitions through the following stages: 12674 * 12675 * 1. The {@link guide/expression expression} is executed using the 12676 * {@link ng.$rootScope.Scope#$eval $eval()} method. 12677 * 2. Any exceptions from the execution of the expression are forwarded to the 12678 * {@link ng.$exceptionHandler $exceptionHandler} service. 12679 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 12680 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 12681 * 12682 * 12683 * @param {(string|function())=} exp An angular expression to be executed. 12684 * 12685 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12686 * - `function(scope)`: execute the function with current `scope` parameter. 12687 * 12688 * @returns {*} The result of evaluating the expression. 12689 */ 12690 $apply: function(expr) { 12691 try { 12692 beginPhase('$apply'); 12693 return this.$eval(expr); 12694 } catch (e) { 12695 $exceptionHandler(e); 12696 } finally { 12697 clearPhase(); 12698 try { 12699 $rootScope.$digest(); 12700 } catch (e) { 12701 $exceptionHandler(e); 12702 throw e; 12703 } 12704 } 12705 }, 12706 12707 /** 12708 * @ngdoc method 12709 * @name $rootScope.Scope#$on 12710 * @kind function 12711 *
12712 * @description 12713 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 12714 * discussion of event life cycle. 12715 * 12716 * The event listener function format is: `function(event, args...)`. The `event` object 12717 * passed into the listener has the following attributes: 12718 * 12719 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 12720 * `$broadcast`-ed. 12721 * - `currentScope` - `{Scope}`: the current scope which is handling the event. 12722 * - `name` - `{string}`: name of the event. 12723 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 12724 * further event propagation (available only for events that were `$emit`-ed). 12725 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 12726 * to true. 12727 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 12728 * 12729 * @param {string} name Event name to listen on. 12730 * @param {function(event, ...args)} listener Function to call when the event is emitted. 12731 * @returns {function()} Returns a deregistration function for this listener. 12732 */ 12733 $on: function(name, listener) { 12734 var namedListeners = this.$$listeners[name]; 12735 if (!namedListeners) { 12736 this.$$listeners[name] = namedListeners = []; 12737 } 12738 namedListeners.push(listener); 12739 12740 var current = this; 12741 do { 12742 if (!current.$$listenerCount[name]) { 12743 current.$$listenerCount[name] = 0; 12744 } 12745 current.$$listenerCount[name]++; 12746 } while ((current = current.$parent)); 12747 12748 var self = this; 12749 return function() { 12750 namedListeners[indexOf(namedListeners, listener)] = null; 12751 decrementListenerCount(self, 1, name); 12752 }; 12753 }, 12754 12755 12756 /** 12757 * @ngdoc method 12758 * @name $rootScope.Scope#$emit 12759 * @kind function 12760 * 12761 * @description 12762 * Dispatches an event `name` upwards through the scope hierarchy notifying the 12763 * registered {@link ng.$rootScope.Scope#$on} listeners. 12764 * 12765 * The event life cycle starts at the scope on which `$emit` was called. All 12766 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 12767 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 12768 * registered listeners along the way. The event will stop propagating if one of the listeners 12769 * cancels it. 12770 * 12771 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 12772 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 12773 * 12774 * @param {string} name Event name to emit. 12775 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 12776 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 12777 */ 12778 $emit: function(name, args) { 12779 var empty = [], 12780 namedListeners, 12781 scope = this, 12782 stopPropagation = false, 12783 event = { 12784 name: name, 12785 targetScope: scope, 12786 stopPropagation: function() {stopPropagation = true;}, 12787 preventDefault: function() { 12788 event.defaultPrevented = true; 12789 }, 12790 defaultPrevented: false 12791 }, 12792 listenerArgs = concat([event], arguments, 1), 12793 i, length; 12794 12795 do { 12796 namedListeners = scope.$$listeners[name] || empty; 12797 event.currentScope = scope; 12798 for (i=0, length=namedListeners.length; i<length; i++) { 12799 12800 // if listeners were deregistered, defragment the array 12801 if (!namedListeners[i]) { 12802 namedListeners.splice(i, 1); 12803 i--; 12804 length--; 12805 continue; 12806 } 12807 try { 12808 //allow all listeners attached to the current scope to run 12809 namedListeners[i].apply(null, listenerArgs); 12810 } catch (e) { 12811 $exceptionHandler(e); 12812 } 12813 } 12814 //if any listener on the current scope stops propagation, prevent bubbling 12815 if (stopPropagation) return event; 12816 //traverse upwards 12817 scope = scope.$parent; 12818 } while (scope); 12819 12820 return event; 12821 }, 12822 12823 12824 /** 12825 * @ngdoc method 12826 * @name $rootScope.Scope#$broadcast 12827 * @kind function 12828 *
12829 * @description 12830 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 12831 * registered {@link ng.$rootScope.Scope#$on} listeners. 12832 * 12833 * The event life cycle starts at the scope on which `$broadcast` was called. All 12834 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 12835 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 12836 * scope and calls all registered listeners along the way. The event cannot be canceled. 12837 * 12838 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 12839 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 12840 * 12841 * @param {string} name Event name to broadcast. 12842 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 12843 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 12844 */ 12845 $broadcast: function(name, args) { 12846 var target = this, 12847 current = target, 12848 next = target, 12849 event = { 12850 name: name, 12851 targetScope: target, 12852 preventDefault: function() { 12853 event.defaultPrevented = true; 12854 }, 12855 defaultPrevented: false 12856 }, 12857 listenerArgs = concat([event], arguments, 1), 12858 listeners, i, length; 12859 12860 //down while you can, then up and next sibling or up and next sibling until back at root 12861 while ((current = next)) { 12862 event.currentScope = current; 12863 listeners = current.$$listeners[name] || []; 12864 for (i=0, length = listeners.length; i<length; i++) { 12865 // if listeners were deregistered, defragment the array 12866 if (!listeners[i]) { 12867 listeners.splice(i, 1); 12868 i--; 12869 length--; 12870 continue; 12871 } 12872 12873 try { 12874 listeners[i].apply(null, listenerArgs); 12875 } catch(e) { 12876 $exceptionHandler(e); 12877 } 12878 } 12879 12880 // Insanity Warning: scope depth-first traversal 12881 // yes, this code is a bit crazy, but it works and we have tests to prove it! 12882 // this piece should be kept in sync with the traversal in $digest 12883 // (though it differs due to having the extra check for $$listenerCount) 12884 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 12885 (current !== target && current.$$nextSibling)))) { 12886 while(current !== target && !(next = current.$$nextSibling)) { 12887 current = current.$parent; 12888 } 12889 } 12890 } 12891 12892 return event; 12893 } 12894 }; 12895 12896 var $rootScope = new Scope(); 12897 12898 return $rootScope; 12899 12900 12901 function beginPhase(phase) { 12902 if ($rootScope.$$phase) { 12903 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 12904 } 12905 12906 $rootScope.$$phase = phase; 12907 } 12908 12909 function clearPhase() { 12910 $rootScope.$$phase = null; 12911 } 12912 12913 function compileToFn(exp, name) { 12914 var fn = $parse(exp); 12915 assertArgFn(fn, name); 12916 return fn; 12917 } 12918 12919 function decrementListenerCount(current, count, name) { 12920 do { 12921 current.$$listenerCount[name] -= count; 12922 12923 if (current.$$listenerCount[name] === 0) { 12924 delete current.$$listenerCount[name]; 12925 } 12926 } while ((current = current.$parent)); 12927 } 12928 12929 /** 12930 * function used as an initial value for watchers. 12931 * because it's unique we can easily tell it apart from other values 12932 */ 12933 function initWatchVal() {} 12934 }]; 12935} 12936 12937/** 12938 * @description 12939 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 12940 */ 12941function $$SanitizeUriProvider() { 12942 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 12943 imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//; 12944 12945 /** 12946 * @description 12947 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 12948 * urls during a[href] sanitization. 12949 * 12950 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 12951 * 12952 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 12953 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 12954 * regular expression. If a match is found, the original url is
12954written into the dom. Otherwise, 12955 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 12956 * 12957 * @param {RegExp=} regexp New regexp to whitelist urls with. 12958 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 12959 * chaining otherwise. 12960 */ 12961 this.aHrefSanitizationWhitelist = function(regexp) { 12962 if (isDefined(regexp)) { 12963 aHrefSanitizationWhitelist = regexp; 12964 return this; 12965 } 12966 return aHrefSanitizationWhitelist; 12967 }; 12968 12969 12970 /** 12971 * @description 12972 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 12973 * urls during img[src] sanitization. 12974 * 12975 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 12976 * 12977 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 12978 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 12979 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 12980 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 12981 * 12982 * @param {RegExp=} regexp New regexp to whitelist urls with. 12983 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 12984 * chaining otherwise. 12985 */ 12986 this.imgSrcSanitizationWhitelist = function(regexp) { 12987 if (isDefined(regexp)) { 12988 imgSrcSanitizationWhitelist = regexp; 12989 return this; 12990 } 12991 return imgSrcSanitizationWhitelist; 12992 }; 12993 12994 this.$get = function() { 12995 return function sanitizeUri(uri, isImage) { 12996 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 12997 var normalizedVal; 12998 // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case. 12999 if (!msie || msie >= 8 ) { 13000 normalizedVal = urlResolve(uri).href; 13001 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 13002 return 'unsafe:'+normalizedVal; 13003 } 13004 } 13005 return uri; 13006 }; 13007 }; 13008} 13009 13010var $sceMinErr = minErr('$sce'); 13011 13012var SCE_CONTEXTS = { 13013 HTML: 'html', 13014 CSS: 'css', 13015 URL: 'url', 13016 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 13017 // url. (e.g. ng-include, script src, templateUrl) 13018 RESOURCE_URL: 'resourceUrl', 13019 JS: 'js' 13020}; 13021 13022// Helper functions follow. 13023 13024// Copied from: 13025// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 13026// Prereq: s is a string. 13027function escapeForRegexp(s) { 13028 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 13029 replace(/\x08/g, '\\x08'); 13030} 13031 13032 13033function adjustMatcher(matcher) { 13034 if (matcher === 'self') { 13035 return matcher; 13036 } else if (isString(matcher)) { 13037 // Strings match exactly except for 2 wildcards - '*' and '**'. 13038 // '*' matches any character except those from the set ':/.?&'. 13039 // '**' matches any character (like .* in a RegExp). 13040 // More than 2 *'s raises an error as it's ill defined. 13041 if (matcher.indexOf('***') > -1) { 13042 throw $sceMinErr('iwcard', 13043 'Illegal sequence *** in string matcher. String: {0}', matcher); 13044 } 13045 matcher = escapeForRegexp(matcher). 13046 replace('\\*\\*', '.*'). 13047 replace('\\*', '[^:/.?&;]*'); 13048 return new RegExp('^' + matcher + '$'); 13049 } else if (isRegExp(matcher)) { 13050 // The only other type of matcher allowed is a Regexp. 13051 // Match entire URL / disallow partial matches. 13052 // Flags are reset (i.e. no global, ignoreCase or multiline) 13053 return new RegExp('^' + matcher.source + '$'); 13054 } else { 13055 throw $sceMinErr('imatcher', 13056 'Matchers may only be "self", string patterns or RegExp objects'); 13057 } 13058} 13059 13060 13061function adjustMatchers(matchers) { 13062 var adjustedMatchers = []; 13063 if (isDefined(matchers)) { 13064 forEach(matchers, function(matcher) { 13065 adjustedMatchers.push(adjustMatcher(matcher)); 13066 }); 13067 } 13068 return adjustedMatchers; 13069} 13070 13071 13072/** 13073 * @ngdoc service 13074 * @name $sceDelegate 13075 * @kind function 13076 * 13077 * @description 13078 * 13079 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 13080 * Contextual Escaping (SCE)} services to AngularJS. 13081 * 13082 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 13083 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 13084 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 13085 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 13086 * work because `$sce` delegates to `$sceDelegate` for these operations. 13087 * 13088 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 13089 * 13090 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
13091 * can override it completely to change the behavior of `$sce`, the common case would 13092 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 13093 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 13094 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 13095 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 13096 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 13097 */ 13098 13099/** 13100 * @ngdoc provider 13101 * @name $sceDelegateProvider 13102 * @description 13103 * 13104 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 13105 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 13106 * that the URLs used for sourcing Angular templates are safe. Refer {@link 13107 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 13108 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 13109 * 13110 * For the general details about this service in Angular, read the main page for {@link ng.$sce 13111 * Strict Contextual Escaping (SCE)}. 13112 * 13113 * **Example**: Consider the following case. <a name="example"></a> 13114 * 13115 * - your app is hosted at url `http://myapp.example.com/` 13116 * - but some of your templates are hosted on other domains you control such as 13117 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 13118 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 13119 * 13120 * Here is what a secure configuration for this scenario might look like: 13121 * 13122 * <pre class="prettyprint"> 13123 * angular.module('myApp', []).config(function($sceDelegateProvider) { 13124 * $sceDelegateProvider.resourceUrlWhitelist([ 13125 * // Allow same origin resource loads. 13126 * 'self', 13127 * // Allow loading from our assets domain. Notice the difference between * and **. 13128 * 'http://srv*.assets.example.com/**']); 13129 * 13130 * // The blacklist overrides the whitelist so the open redirect here is blocked. 13131 * $sceDelegateProvider.resourceUrlBlacklist([ 13132 * 'http://myapp.example.com/clickThru**']); 13133 * }); 13134 * </pre> 13135 */ 13136 13137function $SceDelegateProvider() { 13138 this.SCE_CONTEXTS = SCE_CONTEXTS; 13139 13140 // Resource URLs can also be trusted by policy. 13141 var resourceUrlWhitelist = ['self'], 13142 resourceUrlBlacklist = []; 13143 13144 /** 13145 * @ngdoc method 13146 * @name $sceDelegateProvider#resourceUrlWhitelist 13147 * @kind function 13148 * 13149 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 13150 * provided. This must be an array or null. A snapshot of this array is used so further 13151 * changes to the array are ignored. 13152 * 13153 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 13154 * allowed in this array. 13155 * 13156 * Note: **an empty whitelist array will block all URLs**! 13157 * 13158 * @return {Array} the currently set whitelist array. 13159 * 13160 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 13161 * same origin resource requests. 13162 * 13163 * @description 13164 * Sets/Gets the whitelist of trusted resource URLs. 13165 */ 13166 this.resourceUrlWhitelist = function (value) { 13167 if (arguments.length) { 13168 resourceUrlWhitelist = adjustMatchers(value); 13169 } 13170 return resourceUrlWhitelist; 13171 }; 13172 13173 /** 13174 * @ngdoc method 13175 * @name $sceDelegateProvider#resourceUrlBlacklist 13176 * @kind function 13177 * 13178 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 13179 * provided. This must be an array or null. A snapshot of this array is used so further 13180 * changes to the array are ignored. 13181 * 13182 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 13183 * allowed in this array. 13184 * 13185 * The typical usage for the blacklist is to **block 13186 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 13187 * these would otherwise be trusted but actually return content from the redirected domain. 13188 *
vendor: 4,741 bytes, lines 13189-13316
13189 * Finally, **the blacklist overrides the whitelist** and has the final say. 13190 * 13191 * @return {Array} the currently set blacklist array. 13192 * 13193 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 13194 * is no blacklist.) 13195 * 13196 * @description 13197 * Sets/Gets the blacklist of trusted resource URLs. 13198 */ 13199 13200 this.resourceUrlBlacklist = function (value) { 13201 if (arguments.length) { 13202 resourceUrlBlacklist = adjustMatchers(value); 13203 } 13204 return resourceUrlBlacklist; 13205 }; 13206 13207 this.$get = ['$injector', function($injector) { 13208 13209 var htmlSanitizer = function htmlSanitizer(html) { 13210 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 13211 }; 13212 13213 if ($injector.has('$sanitize')) { 13214 htmlSanitizer = $injector.get('$sanitize'); 13215 } 13216 13217 13218 function matchUrl(matcher, parsedUrl) { 13219 if (matcher === 'self') { 13220 return urlIsSameOrigin(parsedUrl); 13221 } else { 13222 // definitely a regex. See adjustMatchers() 13223 return !!matcher.exec(parsedUrl.href); 13224 } 13225 } 13226 13227 function isResourceUrlAllowedByPolicy(url) { 13228 var parsedUrl = urlResolve(url.toString()); 13229 var i, n, allowed = false; 13230 // Ensure that at least one item from the whitelist allows this url. 13231 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 13232 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 13233 allowed = true; 13234 break; 13235 } 13236 } 13237 if (allowed) { 13238 // Ensure that no item from the blacklist blocked this url. 13239 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 13240 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 13241 allowed = false; 13242 break; 13243 } 13244 } 13245 } 13246 return allowed; 13247 } 13248 13249 function generateHolderType(Base) { 13250 var holderType = function TrustedValueHolderType(trustedValue) { 13251 this.$$unwrapTrustedValue = function() { 13252 return trustedValue; 13253 }; 13254 }; 13255 if (Base) { 13256 holderType.prototype = new Base(); 13257 } 13258 holderType.prototype.valueOf = function sceValueOf() { 13259 return this.$$unwrapTrustedValue(); 13260 }; 13261 holderType.prototype.toString = function sceToString() { 13262 return this.$$unwrapTrustedValue().toString(); 13263 }; 13264 return holderType; 13265 } 13266 13267 var trustedValueHolderBase = generateHolderType(), 13268 byType = {}; 13269 13270 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 13271 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 13272 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 13273 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 13274 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 13275 13276 /** 13277 * @ngdoc method 13278 * @name $sceDelegate#trustAs 13279 * 13280 * @description 13281 * Returns an object that is trusted by angular for use in specified strict 13282 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 13283 * attribute interpolation, any dom event binding attribute interpolation 13284 * such as for onclick, etc.) that uses the provided value. 13285 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 13286 * 13287 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 13288 * resourceUrl, html, js and css. 13289 * @param {*} value The value that that should be considered trusted/safe. 13290 * @returns {*} A value that can be used to stand in for the provided `value` in places 13291 * where Angular expects a $sce.trustAs() return value. 13292 */ 13293 function trustAs(type, trustedValue) { 13294 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 13295 if (!Constructor) { 13296 throw $sceMinErr('icontext', 13297 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 13298 type, trustedValue); 13299 } 13300 if (trustedValue === null || trustedValue === undefined || trustedValue === '') { 13301 return trustedValue; 13302 } 13303 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 13304 // mutable objects, we ensure here that the value passed in is actually a string. 13305 if (typeof trustedValue !== 'string') { 13306 throw $sceMinErr('itype', 13307 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 13308 type); 13309 } 13310 return new Constructor(trustedValue); 13311 } 13312 13313 /** 13314 * @ngdoc method 13315 * @name $sceDelegate#valueOf 13316 *
13317 * @description 13318 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 13319 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 13320 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 13321 * 13322 * If the passed parameter is not a value that had been returned by {@link 13323 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 13324 * 13325 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 13326 * call or anything else. 13327 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 13328 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 13329 * `value` unchanged. 13330 */ 13331 function valueOf(maybeTrusted) { 13332 if (maybeTrusted instanceof trustedValueHolderBase) { 13333 return maybeTrusted.$$unwrapTrustedValue(); 13334 } else { 13335 return maybeTrusted; 13336 } 13337 } 13338 13339 /** 13340 * @ngdoc method 13341 * @name $sceDelegate#getTrusted 13342 * 13343 * @description 13344 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 13345 * returns the originally supplied value if the queried context type is a supertype of the 13346 * created type. If this condition isn't satisfied, throws an exception. 13347 * 13348 * @param {string} type The kind of context in which this value is to be used. 13349 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 13350 * `$sceDelegate.trustAs`} call. 13351 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 13352 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 13353 */ 13354 function getTrusted(type, maybeTrusted) { 13355 if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { 13356 return maybeTrusted; 13357 } 13358 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 13359 if (constructor && maybeTrusted instanceof constructor) { 13360 return maybeTrusted.$$unwrapTrustedValue(); 13361 } 13362 // If we get here, then we may only take one of two actions. 13363 // 1. sanitize the value for the requested type, or 13364 // 2. throw an exception. 13365 if (type === SCE_CONTEXTS.RESOURCE_URL) { 13366 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 13367 return maybeTrusted; 13368 } else { 13369 throw $sceMinErr('insecurl', 13370 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 13371 maybeTrusted.toString()); 13372 } 13373 } else if (type === SCE_CONTEXTS.HTML) { 13374 return htmlSanitizer(maybeTrusted); 13375 } 13376 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 13377 } 13378 13379 return { trustAs: trustAs, 13380 getTrusted: getTrusted, 13381 valueOf: valueOf }; 13382 }]; 13383} 13384 13385 13386/** 13387 * @ngdoc provider 13388 * @name $sceProvider 13389 * @description 13390 * 13391 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 13392 * - enable/disable Strict Contextual Escaping (SCE) in a module 13393 * - override the default implementation with a custom delegate 13394 * 13395 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 13396 */ 13397 13398/* jshint maxlen: false*/ 13399 13400/** 13401 * @ngdoc service 13402 * @name $sce 13403 * @kind function 13404 * 13405 * @description 13406 * 13407 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 13408 * 13409 * # Strict Contextual Escaping 13410 * 13411 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 13412 * contexts to result in a value that is marked as safe to use for that context. One example of 13413 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 13414 * to these contexts as privileged or SCE contexts. 13415 * 13416 * As of version 1.2, Angular ships with SCE enabled by default. 13417 * 13418 * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows 13419 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 13420 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
13421 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 13422 * to the top of your HTML document. 13423 * 13424 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 13425 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 13426 * 13427 * Here's an example of a binding in a privileged context: 13428 * 13429 * <pre class="prettyprint"> 13430 * <input ng-model="userHtml"> 13431 * <div ng-bind-html="userHtml"> 13432 * </pre> 13433 * 13434 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 13435 * disabled, this application allows the user to render arbitrary HTML into the DIV. 13436 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 13437 * bindings. (HTML is just one example of a context where rendering user controlled input creates 13438 * security vulnerabilities.) 13439 * 13440 * For the case of HTML, you might use a library, either on the client side, or on the server side, 13441 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 13442 * 13443 * How would you ensure that every place that used these types of bindings was bound to a value that 13444 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 13445 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 13446 * properties/fields and forgot to update the binding to the sanitized value? 13447 * 13448 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 13449 * determine that something explicitly says it's safe to use a value for binding in that
13450 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 13451 * for those values that you can easily tell are safe - because they were received from your server, 13452 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 13453 * allowing only the files in a specific directory to do this. Ensuring that the internal API 13454 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 13455 * 13456 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 13457 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 13458 * obtain values that will be accepted by SCE / privileged contexts. 13459 * 13460 * 13461 * ## How does it work? 13462 * 13463 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 13464 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 13465 * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 13466 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 13467 * 13468 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 13469 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 13470 * simplified): 13471 * 13472 * <pre class="prettyprint"> 13473 * var ngBindHtmlDirective = ['$sce', function($sce) { 13474 * return function(scope, element, attr) { 13475 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 13476 * element.html(value || ''); 13477 * }); 13478 * }; 13479 * }]; 13480 * </pre> 13481 * 13482 * ## Impact on loading templates 13483 * 13484 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 13485 * `templateUrl`'s specified by {@link guide/directive directives}. 13486 * 13487 * By default, Angular only loads templates from the same domain and protocol as the application 13488 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 13489 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 13490 * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 13491 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 13492 * 13493 * *Please note*: 13494 * The browser's 13495 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 13496 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 13497 * policy apply in addition to this and may further restrict whether the template is successfully 13498 * loaded. This means that without the right CORS policy, loading templates from a different domain 13499 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 13500 * browsers. 13501 * 13502 * ## This feels like too much overhead for the developer? 13503 * 13504 * It's important to remember that SCE only applies to interpolation expressions. 13505 * 13506 * If your expressions are constant literals, they're automatically trusted and you don't need to 13507 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 13508 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 13509 * 13510 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 13511 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 13512 * 13513 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 13514 * templates in `ng-include` from your application's domain without having to even know about SCE. 13515 * It blocks loading templates from other domains or loading templates over http from an https 13516 * served document. You can change these by setting your own custom {@link 13517 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 13518 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 13519 * 13520 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 13521 * application that's secure and can be audited to verify that with much more ease than bolting 13522 * security onto an application later. 13523 * 13524 * <a name="contexts"></a> 13525 * ## What trusted context types are supported? 13526 * 13527 * | Context | Notes | 13528 * |---------------------|----------------| 13529 * | `$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. | 13530 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 13531 * | `$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. | 13532 * | `$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. | 13533 * | `$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. | 13534 * 13535 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 13536 * 13537 * Each element in these arrays must be one of the following: 13538 * 13539 * - **'self'** 13540 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 13541 * domain** as the application document using the **same protocol**. 13542 * - **String** (except the special value `'self'`) 13543 * - The string is matched against the full *normalized / absolute URL* of the resource 13544 * being tested (substring matches are not good enough.) 13545 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 13546 * match themselves. 13547 * - `*`: matches zero or more occurrences of any character other than one of the following 6 13548 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use
13549 * in a whitelist. 13550 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 13551 * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. 13552 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 13553 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 13554 * http://foo.example.com/templates/**). 13555 * - **RegExp** (*see caveat below*) 13556 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 13557 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 13558 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 13559 * have good test coverage.). For instance, the use of `.` in the regex is correct only in a 13560 * small number of cases. A `.` character in the regex used when matching the scheme or a 13561 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 13562 * is highly recommended to use the string patterns and only fall back to regular expressions 13563 * if they as a last resort. 13564 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 13565 * matched against the **entire** *normalized / absolute URL* of the resource being tested 13566 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 13567 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 13568 * - If you are generating your JavaScript from some other templating engine (not 13569 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 13570 * remember to escape your regular expression (and be aware that you might need more than 13571 * one level of escaping depending on your templating engine and the way you interpolated 13572 * the value.) Do make use of your platform's escaping mechanism as it might be good 13573 * enough before coding your own. e.g. Ruby has 13574 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 13575 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 13576 * Javascript lacks a similar built in function for escaping. Take a look at Google 13577 * Closure library's [goog.string.regExpEscape(s)]( 13578 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 13579 * 13580 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 13581 * 13582 * ## Show me an example using SCE. 13583 * 13584 * @example 13585<example module="mySceApp" deps="angular-sanitize.js"> 13586<file name="index.html"> 13587 <div ng-controller="myAppController as myCtrl"> 13588 <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 13589 <b>User comments</b><br> 13590 By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 13591 $sanitize is available. If $sanitize isn't available, this results in an error instead of an 13592 exploit. 13593 <div class="well"> 13594 <div ng-repeat="userComment in myCtrl.userComments"> 13595 <b>{{userComment.name}}</b>: 13596 <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 13597 <br> 13598 </div> 13599 </div> 13600 </div> 13601</file> 13602 13603<file name="script.js"> 13604 var mySceApp = angular.module('mySceApp', ['ngSanitize']); 13605 13606 mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { 13607 var self = this; 13608 $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 13609 self.userComments = userComments; 13610 }); 13611 self.explicitlyTrustedHtml = $sce.trustAsHtml( 13612 '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 13613 'sanitization."">Hover over this text.</span>'); 13614 }); 13615</file> 13616 13617<file name="test_data.json"> 13618[ 13619 { "name": "Alice", 13620 "htmlComment": 13621 "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 13622 }, 13623 { "name": "Bob",
13624 "htmlComment": "<i>Yes!</i> Am I the only other one?" 13625 } 13626] 13627</file> 13628 13629<file name="protractor.js" type="protractor"> 13630 describe('SCE doc demo', function() { 13631 it('should sanitize untrusted values', function() { 13632 expect(element(by.css('.htmlComment')).getInnerHtml()) 13633 .toBe('<span>Is <i>anyone</i> reading this?</span>'); 13634 }); 13635 13636 it('should NOT sanitize explicitly trusted values', function() { 13637 expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 13638 '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 13639 'sanitization."">Hover over this text.</span>'); 13640 }); 13641 }); 13642</file> 13643</example> 13644 * 13645 * 13646 * 13647 * ## Can I disable SCE completely? 13648 * 13649 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 13650 * for little coding overhead. It will be much harder to take an SCE disabled application and 13651 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 13652 * for cases where you have a lot of existing code that was written before SCE was introduced and 13653 * you're migrating them a module at a time. 13654 * 13655 * That said, here's how you can completely disable SCE: 13656 * 13657 * <pre class="prettyprint"> 13658 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 13659 * // Completely disable SCE. For demonstration purposes only! 13660 * // Do not use in new projects. 13661 * $sceProvider.enabled(false); 13662 * }); 13663 * </pre> 13664 * 13665 */ 13666/* jshint maxlen: 100 */ 13667 13668function $SceProvider() { 13669 var enabled = true; 13670 13671 /** 13672 * @ngdoc method 13673 * @name $sceProvider#enabled 13674 * @kind function 13675 * 13676 * @param {boolean=} value If provided, then enables/disables SCE. 13677 * @return {boolean} true if SCE is enabled, false otherwise. 13678 * 13679 * @description 13680 * Enables/disables SCE and returns the current value. 13681 */ 13682 this.enabled = function (value) { 13683 if (arguments.length) { 13684 enabled = !!value; 13685 } 13686 return enabled; 13687 }; 13688 13689 13690 /* Design notes on the default implementation for SCE. 13691 * 13692 * The API contract for the SCE delegate 13693 * ------------------------------------- 13694 * The SCE delegate object must provide the following 3 methods: 13695 * 13696 * - trustAs(contextEnum, value) 13697 * This method is used to tell the SCE service that the provided value is OK to use in the 13698 * contexts specified by contextEnum. It must return an object that will be accepted by 13699 * getTrusted() for a compatible contextEnum and return this value. 13700 * 13701 * - valueOf(value) 13702 * For values that were not produced by trustAs(), return them as is. For values that were 13703 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 13704 * trustAs is wrapping the given values into some type, this operation unwraps it when given 13705 * such a value. 13706 * 13707 * - getTrusted(contextEnum, value) 13708 * This function should return the a value that is safe to use in the context specified by 13709 * contextEnum or throw and exception otherwise. 13710 * 13711 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 13712 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 13713 * instance, an implementation could maintain a registry of all trusted objects by context. In 13714 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 13715 * return the same object passed in if it was found in the registry under a compatible context or 13716 * throw an exception otherwise. An implementation might only wrap values some of the time based 13717 * on some criteria. getTrusted() might return a value and not throw an exception for special 13718 * constants or objects even if not wrapped. All such implementations fulfill this contract. 13719 * 13720 * 13721 * A note on the inheritance model for SCE contexts 13722 * ------------------------------------------------ 13723 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 13724 * is purely an implementation details. 13725 * 13726 * The contract is simply this: 13727 * 13728 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 13729 * will also succeed. 13730 * 13731 * Inheritance happens to capture this in a natural way. In some future, we
13732 * may not use inheritance anymore. That is OK because no code outside of 13733 * sce.js and sceSpecs.js would need to be aware of this detail. 13734 */ 13735 13736 this.$get = ['$parse', '$sniffer', '$sceDelegate', function( 13737 $parse, $sniffer, $sceDelegate) { 13738 // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows 13739 // the "expression(javascript expression)" syntax which is insecure. 13740 if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { 13741 throw $sceMinErr('iequirks', 13742 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + 13743 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 13744 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 13745 } 13746 13747 var sce = shallowCopy(SCE_CONTEXTS); 13748 13749 /** 13750 * @ngdoc method 13751 * @name $sce#isEnabled 13752 * @kind function 13753 * 13754 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 13755 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 13756 * 13757 * @description 13758 * Returns a boolean indicating if SCE is enabled. 13759 */ 13760 sce.isEnabled = function () { 13761 return enabled; 13762 }; 13763 sce.trustAs = $sceDelegate.trustAs; 13764 sce.getTrusted = $sceDelegate.getTrusted; 13765 sce.valueOf = $sceDelegate.valueOf; 13766 13767 if (!enabled) { 13768 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 13769 sce.valueOf = identity; 13770 } 13771 13772 /** 13773 * @ngdoc method 13774 * @name $sce#parseAs 13775 * 13776 * @description 13777 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 13778 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 13779 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 13780 * *result*)} 13781 * 13782 * @param {string} type The kind of SCE context in which this result will be used. 13783 * @param {string} expression String expression to compile. 13784 * @returns {function(context, locals)} a function which represents the compiled expression: 13785 * 13786 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13787 * are evaluated against (typically a scope object). 13788 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13789 * `context`. 13790 */ 13791 sce.parseAs = function sceParseAs(type, expr) { 13792 var parsed = $parse(expr); 13793 if (parsed.literal && parsed.constant) { 13794 return parsed; 13795 } else { 13796 return function sceParseAsTrusted(self, locals) { 13797 return sce.getTrusted(type, parsed(self, locals)); 13798 }; 13799 } 13800 }; 13801 13802 /** 13803 * @ngdoc method 13804 * @name $sce#trustAs 13805 * 13806 * @description 13807 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 13808 * returns an object that is trusted by angular for use in specified strict contextual 13809 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 13810 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 13811 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 13812 * escaping. 13813 * 13814 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 13815 * resource_url, html, js and css. 13816 * @param {*} value The value that that should be considered trusted/safe. 13817 * @returns {*} A value that can be used to stand in for the provided `value` in places 13818 * where Angular expects a $sce.trustAs() return value. 13819 */ 13820 13821 /** 13822 * @ngdoc method 13823 * @name $sce#trustAsHtml 13824 * 13825 * @description 13826 * Shorthand method. `$sce.trustAsHtml(value)` â 13827 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 13828 * 13829 * @param {*} value The value to trustAs. 13830 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 13831 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 13832 * only accept expressions that are either literal constants or are the 13833 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 13834 */ 13835 13836 /** 13837 * @ngdoc method 13838 * @name $sce#trustAsUrl 13839 *
13840 * @description 13841 * Shorthand method. `$sce.trustAsUrl(value)` â 13842 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 13843 * 13844 * @param {*} value The value to trustAs. 13845 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 13846 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 13847 * only accept expressions that are either literal constants or are the 13848 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 13849 */ 13850 13851 /** 13852 * @ngdoc method 13853 * @name $sce#trustAsResourceUrl 13854 * 13855 * @description 13856 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 13857 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 13858 * 13859 * @param {*} value The value to trustAs. 13860 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 13861 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 13862 * only accept expressions that are either literal constants or are the return 13863 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 13864 */ 13865 13866 /** 13867 * @ngdoc method 13868 * @name $sce#trustAsJs 13869 * 13870 * @description 13871 * Shorthand method. `$sce.trustAsJs(value)` â 13872 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 13873 * 13874 * @param {*} value The value to trustAs. 13875 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 13876 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 13877 * only accept expressions that are either literal constants or are the 13878 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 13879 */ 13880 13881 /** 13882 * @ngdoc method 13883 * @name $sce#getTrusted 13884 * 13885 * @description 13886 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 13887 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 13888 * originally supplied value if the queried context type is a supertype of the created type. 13889 * If this condition isn't satisfied, throws an exception. 13890 * 13891 * @param {string} type The kind of context in which this value is to be used. 13892 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 13893 * call. 13894 * @returns {*} The value the was originally provided to 13895 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 13896 * Otherwise, throws an exception. 13897 */ 13898 13899 /** 13900 * @ngdoc method 13901 * @name $sce#getTrustedHtml 13902 * 13903 * @description 13904 * Shorthand method. `$sce.getTrustedHtml(value)` â 13905 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 13906 * 13907 * @param {*} value The value to pass to `$sce.getTrusted`. 13908 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 13909 */ 13910 13911 /** 13912 * @ngdoc method 13913 * @name $sce#getTrustedCss 13914 * 13915 * @description 13916 * Shorthand method. `$sce.getTrustedCss(value)` â 13917 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 13918 * 13919 * @param {*} value The value to pass to `$sce.getTrusted`. 13920 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 13921 */ 13922 13923 /** 13924 * @ngdoc method 13925 * @name $sce#getTrustedUrl 13926 * 13927 * @description 13928 * Shorthand method. `$sce.getTrustedUrl(value)` â 13929 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 13930 * 13931 * @param {*} value The value to pass to `$sce.getTrusted`. 13932 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 13933 */ 13934 13935 /** 13936 * @ngdoc method 13937 * @name $sce#getTrustedResourceUrl 13938 * 13939 * @description 13940 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 13941 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 13942 * 13943 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 13944 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 13945 */ 13946 13947 /** 13948 * @ngdoc method 13949 * @name $sce#getTrustedJs 13950 *
13951 * @description 13952 * Shorthand method. `$sce.getTrustedJs(value)` â 13953 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 13954 * 13955 * @param {*} value The value to pass to `$sce.getTrusted`. 13956 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 13957 */ 13958 13959 /** 13960 * @ngdoc method 13961 * @name $sce#parseAsHtml 13962 * 13963 * @description 13964 * Shorthand method. `$sce.parseAsHtml(expression string)` â 13965 * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} 13966 * 13967 * @param {string} expression String expression to compile. 13968 * @returns {function(context, locals)} a function which represents the compiled expression: 13969 * 13970 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13971 * are evaluated against (typically a scope object). 13972 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13973 * `context`. 13974 */ 13975 13976 /** 13977 * @ngdoc method 13978 * @name $sce#parseAsCss 13979 * 13980 * @description 13981 * Shorthand method. `$sce.parseAsCss(value)` â 13982 * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} 13983 * 13984 * @param {string} expression String expression to compile. 13985 * @returns {function(context, locals)} a function which represents the compiled expression: 13986 * 13987 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13988 * are evaluated against (typically a scope object). 13989 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13990 * `context`. 13991 */ 13992 13993 /** 13994 * @ngdoc method 13995 * @name $sce#parseAsUrl 13996 * 13997 * @description 13998 * Shorthand method. `$sce.parseAsUrl(value)` â 13999 * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} 14000 * 14001 * @param {string} expression String expression to compile. 14002 * @returns {function(context, locals)} a function which represents the compiled expression: 14003 * 14004 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14005 * are evaluated against (typically a scope object). 14006 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14007 * `context`. 14008 */ 14009 14010 /** 14011 * @ngdoc method 14012 * @name $sce#parseAsResourceUrl 14013 * 14014 * @description 14015 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 14016 * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} 14017 * 14018 * @param {string} expression String expression to compile. 14019 * @returns {function(context, locals)} a function which represents the compiled expression: 14020 * 14021 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14022 * are evaluated against (typically a scope object). 14023 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14024 * `context`. 14025 */ 14026 14027 /** 14028 * @ngdoc method 14029 * @name $sce#parseAsJs 14030 * 14031 * @description 14032 * Shorthand method. `$sce.parseAsJs(value)` â 14033 * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} 14034 * 14035 * @param {string} expression String expression to compile. 14036 * @returns {function(context, locals)} a function which represents the compiled expression: 14037 * 14038 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14039 * are evaluated against (typically a scope object). 14040 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14041 * `context`. 14042 */ 14043 14044 // Shorthand delegations. 14045 var parse = sce.parseAs, 14046 getTrusted = sce.getTrusted, 14047 trustAs = sce.trustAs; 14048 14049 forEach(SCE_CONTEXTS, function (enumValue, name) { 14050 var lName = lowercase(name); 14051 sce[camelCase("parse_as_" + lName)] = function (expr) { 14052 return parse(enumValue, expr); 14053 }; 14054 sce[camelCase("get_trusted_" + lName)] = function (value) { 14055 return getTrusted(enumValue, value); 14056 }; 14057 sce[camelCase("trust_as_" + lName)] = function (value) { 14058 return trustAs(enumValue, value); 14059 }; 14060 });
14061 14062 return sce; 14063 }]; 14064} 14065 14066/** 14067 * !!! This is an undocumented "private" service !!! 14068 * 14069 * @name $sniffer 14070 * @requires $window 14071 * @requires $document 14072 * 14073 * @property {boolean} history Does the browser support html5 history api ? 14074 * @property {boolean} hashchange Does the browser support hashchange event ? 14075 * @property {boolean} transitions Does the browser support CSS transition events ? 14076 * @property {boolean} animations Does the browser support CSS animation events ? 14077 * 14078 * @description 14079 * This is very simple implementation of testing browser's features. 14080 */ 14081function $SnifferProvider() { 14082 this.$get = ['$window', '$document', function($window, $document) { 14083 var eventSupport = {}, 14084 android = 14085 int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 14086 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 14087 document = $document[0] || {}, 14088 documentMode = document.documentMode, 14089 vendorPrefix, 14090 vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, 14091 bodyStyle = document.body && document.body.style, 14092 transitions = false, 14093 animations = false, 14094 match; 14095 14096 if (bodyStyle) { 14097 for(var prop in bodyStyle) { 14098 if(match = vendorRegex.exec(prop)) { 14099 vendorPrefix = match[0]; 14100 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 14101 break; 14102 } 14103 } 14104 14105 if(!vendorPrefix) { 14106 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 14107 } 14108 14109 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 14110 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 14111 14112 if (android && (!transitions||!animations)) { 14113 transitions = isString(document.body.style.webkitTransition); 14114 animations = isString(document.body.style.webkitAnimation); 14115 } 14116 } 14117 14118 14119 return { 14120 // Android has history.pushState, but it does not update location correctly 14121 // so let's not use the history API at all. 14122 // http://code.google.com/p/android/issues/detail?id=17471 14123 // https://github.com/angular/angular.js/issues/904 14124 14125 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 14126 // so let's not use the history API also 14127 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 14128 // jshint -W018 14129 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 14130 // jshint +W018 14131 hashchange: 'onhashchange' in $window && 14132 // IE8 compatible mode lies 14133 (!documentMode || documentMode > 7), 14134 hasEvent: function(event) { 14135 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 14136 // it. In particular the event is not fired when backspace or delete key are pressed or 14137 // when cut operation is performed. 14138 if (event == 'input' && msie == 9) return false; 14139 14140 if (isUndefined(eventSupport[event])) { 14141 var divElm = document.createElement('div'); 14142 eventSupport[event] = 'on' + event in divElm; 14143 } 14144 14145 return eventSupport[event]; 14146 }, 14147 csp: csp(), 14148 vendorPrefix: vendorPrefix, 14149 transitions : transitions, 14150 animations : animations, 14151 android: android, 14152 msie : msie, 14153 msieDocumentMode: documentMode 14154 }; 14155 }]; 14156} 14157 14158function $TimeoutProvider() { 14159 this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', 14160 function($rootScope, $browser, $q, $exceptionHandler) { 14161 var deferreds = {}; 14162 14163 14164 /** 14165 * @ngdoc service 14166 * @name $timeout 14167 * 14168 * @description 14169 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 14170 * block and delegates any exceptions to 14171 * {@link ng.$exceptionHandler $exceptionHandler} service. 14172 * 14173 * The return value of registering a timeout function is a promise, which will be resolved when 14174 * the timeout is reached and the timeout function is executed. 14175 * 14176 * To cancel a timeout request, call `$timeout.cancel(promise)`. 14177 * 14178 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 14179 * synchronously flush the queue of deferred functions. 14180 * 14181 * @param {function()} fn A function, whose execution should be delayed. 14182 * @param {number=} [delay=0] Delay in milliseconds. 14183 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 14184 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 14185 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 14186 * promise will be resolved with is the return value of the `fn` function. 14187 * 14188 */ 14189 function timeout(fn, delay, invokeApply) { 14190 var deferred = $q.defer(), 14191 promise = deferred.promise, 14192 skipApply = (isDefined(invokeApply) && !invokeApply), 14193 timeoutId; 14194 14195 timeoutId = $browser.defer(function() { 14196 try { 14197 deferred.resolve(fn()); 14198 } catch(e) { 14199 deferred.reject(e); 14200 $exceptionHandler(e); 14201 } 14202 finally { 14203 delete deferreds[promise.$$timeoutId]; 14204 } 14205 14206 if (!skipApply) $rootScope.$apply(); 14207 }, delay); 14208 14209 promise.$$timeoutId = timeoutId; 14210 deferreds[timeoutId] = deferred; 14211 14212 return promise; 14213 } 14214 14215 14216 /** 14217 * @ngdoc method 14218 * @name $timeout#cancel 14219 *
14220 * @description 14221 * Cancels a task associated with the `promise`. As a result of this, the promise will be 14222 * resolved with a rejection. 14223 * 14224 * @param {Promise=} promise Promise returned by the `$timeout` function. 14225 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 14226 * canceled. 14227 */ 14228 timeout.cancel = function(promise) { 14229 if (promise && promise.$$timeoutId in deferreds) { 14230 deferreds[promise.$$timeoutId].reject('canceled'); 14231 delete deferreds[promise.$$timeoutId]; 14232 return $browser.defer.cancel(promise.$$timeoutId); 14233 } 14234 return false; 14235 }; 14236 14237 return timeout; 14238 }]; 14239} 14240 14241// NOTE: The usage of window and document instead of $window and $document here is 14242// deliberate. This service depends on the specific behavior of anchor nodes created by the 14243// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 14244// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 14245// doesn't know about mocked locations and resolves URLs to the real document - which is 14246// exactly the behavior needed here. There is little value is mocking these out for this 14247// service. 14248var urlParsingNode = document.createElement("a"); 14249var originUrl = urlResolve(window.location.href, true); 14250 14251 14252/** 14253 * 14254 * Implementation Notes for non-IE browsers 14255 * ---------------------------------------- 14256 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 14257 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 14258 * URL will be resolved into an absolute URL in the context of the application document. 14259 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 14260 * properties are all populated to reflect the normalized URL. This approach has wide 14261 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 14262 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 14263 * 14264 * Implementation Notes for IE 14265 * --------------------------- 14266 * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other 14267 * browsers. However, the parsed components will not be set if the URL assigned did not specify 14268 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 14269 * work around that by performing the parsing in a 2nd step by taking a previously normalized 14270 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 14271 * properties such as protocol, hostname, port, etc. 14272 * 14273 * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one 14274 * uses the inner HTML approach to assign the URL as part of an HTML snippet - 14275 * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. 14276 * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. 14277 * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that 14278 * method and IE < 8 is unsupported. 14279 * 14280 * References: 14281 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 14282 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 14283 * http://url.spec.whatwg.org/#urlutils 14284 * https://github.com/angular/angular.js/pull/2902 14285 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 14286 * 14287 * @kind function 14288 * @param {string} url The URL to be parsed. 14289 * @description Normalizes and parses a URL. 14290 * @returns {object} Returns the normalized URL as a dictionary. 14291 * 14292 * | member name | Description | 14293 * |---------------|----------------| 14294 * | href | A normalized version of the provided URL if it was not an absolute URL | 14295 * | protocol | The protocol including the trailing colon | 14296 * | host | The host and port (if the port is non-default) of the normalizedUrl | 14297 * | search | The search params, minus the question mark | 14298 * | hash | The hash string, minus the hash symbol 14299 * | hostname | The hostname 14300 * | port | The port, without ":" 14301 * | pathname | The pathname, beginning with "/" 14302 * 14303 */ 14304function urlResolve(url, base) { 14305 var href = url; 14306 14307 if (msie) { 14308 // Normalize before parse. Refer Implementation Notes on why this is 14309 // done in two steps on IE. 14310 urlParsingNode.setAttribute("href", href); 14311 href = urlParsingNode.href; 14312 } 14313 14314 urlParsingNode.setAttribute('href', href); 14315 14316 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 14317 return { 14318 href: urlParsingNode.href, 14319 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 14320 host: urlParsingNode.host, 14321 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 14322 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 14323 hostname: urlParsingNode.hostname, 14324 port: urlParsingNode.port, 14325 pathname: (urlParsingNode.pathname.charAt(0) === '/') 14326 ? urlParsingNode.pathname 14327 : '/' + urlParsingNode.pathname 14328 }; 14329} 14330 14331/** 14332 * Parse a request URL and determine whether this is a same-origin request as the application document. 14333 * 14334 * @param {string|object} requestUrl The url of the request as a string that will be resolved 14335 * or a parsed URL object. 14336 * @returns {boolean} Whether the request is for the same origin as the application document. 14337 */ 14338function urlIsSameOrigin(requestUrl) { 14339 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 14340 return (parsed.protocol === originUrl.protocol && 14341 parsed.host === originUrl.host); 14342} 14343 14344/** 14345 * @ngdoc service 14346 * @name $window 14347 * 14348 * @description 14349 * A reference to the browser's `window` object. While `window` 14350 * is globally available in JavaScript, it causes testability problems, because 14351 * it is a global variable. In angular we always refer to it through the 14352 * `$window` service, so it may be overridden, removed or mocked for testing. 14353 * 14354 * Expressions, like the one defined for the `ngClick` directive in the example 14355 * below, are evaluated with respect to the current scope. Therefore, there is 14356 * no risk of inadvertently coding in a dependency on a global value in such an 14357 * expression. 14358 * 14359 * @example 14360 <example> 14361 <file name="index.html"> 14362 <script> 14363 function Ctrl($scope, $window) { 14364 $scope.greeting = 'Hello, World!'; 14365 $scope.doGreeting = function(greeting) { 14366 $window.alert(greeting); 14367 }; 14368 } 14369 </script> 14370 <div ng-controller="Ctrl"> 14371 <input type="text" ng-model="greeting" /> 14372 <button ng-click="doGreeting(greeting)">ALERT</button> 14373 </div> 14374 </file> 14375 <file name="protractor.js" type="protractor"> 14376 it('should display the greeting in the input box', function() {
14377 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 14378 // If we click the button it will block the test runner 14379 // element(':button').click(); 14380 }); 14381 </file> 14382 </example> 14383 */ 14384function $WindowProvider(){ 14385 this.$get = valueFn(window); 14386} 14387 14388/** 14389 * @ngdoc provider 14390 * @name $filterProvider 14391 * @description 14392 * 14393 * Filters are just functions which transform input to an output. However filters need to be 14394 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 14395 * annotated with dependencies and is responsible for creating a filter function. 14396 * 14397 * ```js 14398 * // Filter registration 14399 * function MyModule($provide, $filterProvider) { 14400 * // create a service to demonstrate injection (not always needed) 14401 * $provide.value('greet', function(name){ 14402 * return 'Hello ' + name + '!'; 14403 * }); 14404 * 14405 * // register a filter factory which uses the 14406 * // greet service to demonstrate DI. 14407 * $filterProvider.register('greet', function(greet){ 14408 * // return the filter function which uses the greet service 14409 * // to generate salutation 14410 * return function(text) { 14411 * // filters need to be forgiving so check input validity 14412 * return text && greet(text) || text; 14413 * }; 14414 * }); 14415 * } 14416 * ``` 14417 * 14418 * The filter function is registered with the `$injector` under the filter name suffix with 14419 * `Filter`. 14420 * 14421 * ```js 14422 * it('should be the same instance', inject( 14423 * function($filterProvider) { 14424 * $filterProvider.register('reverse', function(){ 14425 * return ...; 14426 * }); 14427 * }, 14428 * function($filter, reverseFilter) { 14429 * expect($filter('reverse')).toBe(reverseFilter); 14430 * }); 14431 * ``` 14432 * 14433 * 14434 * For more information about how angular filters work, and how to create your own filters, see 14435 * {@link guide/filter Filters} in the Angular Developer Guide. 14436 */ 14437/** 14438 * @ngdoc method 14439 * @name $filterProvider#register 14440 * @description 14441 * Register filter factory function. 14442 * 14443 * @param {String} name Name of the filter. 14444 * @param {Function} fn The filter factory function which is injectable. 14445 */ 14446 14447 14448/** 14449 * @ngdoc service 14450 * @name $filter 14451 * @kind function 14452 * @description 14453 * Filters are used for formatting data displayed to the user. 14454 * 14455 * The general syntax in templates is as follows: 14456 * 14457 * {{ expression [| filter_name[:parameter_value] ... ] }} 14458 * 14459 * @param {String} name Name of the filter function to retrieve 14460 * @return {Function} the filter function 14461 * @example 14462 <example name="$filter" module="filterExample"> 14463 <file name="index.html"> 14464 <div ng-controller="MainCtrl"> 14465 <h3>{{ originalText }}</h3> 14466 <h3>{{ filteredText }}</h3> 14467 </div> 14468 </file> 14469 14470 <file name="script.js"> 14471 angular.module('filterExample', []) 14472 .controller('MainCtrl', function($scope, $filter) { 14473 $scope.originalText = 'hello'; 14474 $scope.filteredText = $filter('uppercase')($scope.originalText); 14475 }); 14476 </file> 14477 </example> 14478 */ 14479$FilterProvider.$inject = ['$provide']; 14480function $FilterProvider($provide) { 14481 var suffix = 'Filter'; 14482 14483 /** 14484 * @ngdoc method 14485 * @name $controllerProvider#register 14486 * @param {string|Object} name Name of the filter function, or an object map of filters where 14487 * the keys are the filter names and the values are the filter factories. 14488 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 14489 * of the registered filter instances. 14490 */ 14491 function register(name, factory) { 14492 if(isObject(name)) { 14493 var filters = {}; 14494 forEach(name, function(filter, key) { 14495 filters[key] = register(key, filter); 14496 }); 14497 return filters; 14498 } else { 14499 return $provide.factory(name + suffix, factory); 14500 } 14501 } 14502 this.register = register; 14503 14504 this.$get = ['$injector', function($injector) { 14505 return function(name) { 14506 return $injector.get(name + suffix); 14507 }; 14508 }]; 14509 14510 //////////////////////////////////////// 14511 14512 /* global 14513 currencyFilter: false, 14514 dateFilter: false, 14515 filterFilter: false, 14516 jsonFilter: false, 14517 limitToFilter: false, 14518 lowercaseFilter: false, 14519 numberFilter: false, 14520 orderByFilter: false, 14521 uppercaseFilter: false, 14522 */ 14523 14524 register('currency', currencyFilter); 14525 register('date', dateFilter); 14526 register('filter', filterFilter); 14527 register('json', jsonFilter); 14528 register('limitTo', limitToFilter); 14529 register('lowercase', lowercaseFilter); 14530 register('number', numberFilter); 14531 register('orderBy', orderByFilter); 14532 register('uppercase', uppercaseFilter); 14533} 14534 14535/** 14536 * @ngdoc filter 14537 * @name filter 14538 * @kind function 14539 * 14540 * @description 14541 * Selects a subset of items from `array` and returns it as a new array. 14542 * 14543 * @param {Array} array The source array. 14544 * @param {string|Object|function()} expression The predicate to be used for selecting items from 14545 * `array`. 14546 * 14547 * Can be one of: 14548 * 14549 * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against
14550 * the contents of the `array`. All strings or objects with string properties in `array` that contain this string 14551 * will be returned. The predicate can be negated by prefixing the string with `!`. 14552 * 14553 * - `Object`: A pattern object can be used to filter specific properties on objects contained 14554 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 14555 * which have property `name` containing "M" and property `phone` containing "1". A special 14556 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 14557 * property of the object. That's equivalent to the simple substring match with a `string` 14558 * as described above. 14559 * 14560 * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is 14561 * called for each element of `array`. The final result is an array of those elements that 14562 * the predicate returned true for. 14563 * 14564 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 14565 * determining if the expected value (from the filter expression) and actual value (from 14566 * the object in the array) should be considered a match. 14567 * 14568 * Can be one of: 14569 * 14570 * - `function(actual, expected)`: 14571 * The function will be given the object value and the predicate value to compare and 14572 * should return true if the item should be included in filtered result. 14573 * 14574 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. 14575 * this is essentially strict comparison of expected and actual. 14576 * 14577 * - `false|undefined`: A short hand for a function which will look for a substring match in case 14578 * insensitive way. 14579 * 14580 * @example 14581 <example> 14582 <file name="index.html"> 14583 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 14584 {name:'Mary', phone:'800-BIG-MARY'}, 14585 {name:'Mike', phone:'555-4321'}, 14586 {name:'Adam', phone:'555-5678'}, 14587 {name:'Julie', phone:'555-8765'}, 14588 {name:'Juliette', phone:'555-5678'}]"></div> 14589 14590 Search: <input ng-model="searchText"> 14591 <table id="searchTextResults"> 14592 <tr><th>Name</th><th>Phone</th></tr> 14593 <tr ng-repeat="friend in friends | filter:searchText"> 14594 <td>{{friend.name}}</td> 14595 <td>{{friend.phone}}</td> 14596 </tr> 14597 </table> 14598 <hr> 14599 Any: <input ng-model="search.$"> <br> 14600 Name only <input ng-model="search.name"><br> 14601 Phone only <input ng-model="search.phone"><br> 14602 Equality <input type="checkbox" ng-model="strict"><br> 14603 <table id="searchObjResults"> 14604 <tr><th>Name</th><th>Phone</th></tr> 14605 <tr ng-repeat="friendObj in friends | filter:search:strict"> 14606 <td>{{friendObj.name}}</td> 14607 <td>{{friendObj.phone}}</td> 14608 </tr> 14609 </table> 14610 </file> 14611 <file name="protractor.js" type="protractor"> 14612 var expectFriendNames = function(expectedNames, key) { 14613 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 14614 arr.forEach(function(wd, i) { 14615 expect(wd.getText()).toMatch(expectedNames[i]); 14616 }); 14617 }); 14618 }; 14619 14620 it('should search across all fields when filtering with a string', function() { 14621 var searchText = element(by.model('searchText')); 14622 searchText.clear(); 14623 searchText.sendKeys('m'); 14624 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 14625 14626 searchText.clear(); 14627 searchText.sendKeys('76'); 14628 expectFriendNames(['John', 'Julie'], 'friend'); 14629 }); 14630 14631 it('should search in specific fields when filtering with a predicate object', function() { 14632 var searchAny = element(by.model('search.$')); 14633 searchAny.clear(); 14634 searchAny.sendKeys('i'); 14635 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 14636 }); 14637 it('should use a equal comparison when comparator is true', function() { 14638 var searchName = element(by.model('search.name')); 14639 var strict = element(by.model('strict')); 14640 searchName.clear(); 14641 searchName.sendKeys('Julie');
14642 strict.click(); 14643 expectFriendNames(['Julie'], 'friendObj'); 14644 }); 14645 </file> 14646 </example> 14647 */ 14648function filterFilter() { 14649 return function(array, expression, comparator) { 14650 if (!isArray(array)) return array; 14651 14652 var comparatorType = typeof(comparator), 14653 predicates = []; 14654 14655 predicates.check = function(value) { 14656 for (var j = 0; j < predicates.length; j++) { 14657 if(!predicates[j](value)) { 14658 return false; 14659 } 14660 } 14661 return true; 14662 }; 14663 14664 if (comparatorType !== 'function') { 14665 if (comparatorType === 'boolean' && comparator) { 14666 comparator = function(obj, text) { 14667 return angular.equals(obj, text); 14668 }; 14669 } else { 14670 comparator = function(obj, text) { 14671 if (obj && text && typeof obj === 'object' && typeof text === 'object') { 14672 for (var objKey in obj) { 14673 if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) && 14674 comparator(obj[objKey], text[objKey])) { 14675 return true; 14676 } 14677 } 14678 return false; 14679 } 14680 text = (''+text).toLowerCase(); 14681 return (''+obj).toLowerCase().indexOf(text) > -1; 14682 }; 14683 } 14684 } 14685 14686 var search = function(obj, text){ 14687 if (typeof text == 'string' && text.charAt(0) === '!') { 14688 return !search(obj, text.substr(1)); 14689 } 14690 switch (typeof obj) { 14691 case "boolean": 14692 case "number": 14693 case "string": 14694 return comparator(obj, text); 14695 case "object": 14696 switch (typeof text) { 14697 case "object": 14698 return comparator(obj, text); 14699 default: 14700 for ( var objKey in obj) { 14701 if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { 14702 return true; 14703 } 14704 } 14705 break; 14706 } 14707 return false; 14708 case "array": 14709 for ( var i = 0; i < obj.length; i++) { 14710 if (search(obj[i], text)) { 14711 return true; 14712 } 14713 } 14714 return false; 14715 default: 14716 return false; 14717 } 14718 }; 14719 switch (typeof expression) { 14720 case "boolean": 14721 case "number": 14722 case "string": 14723 // Set up expression object and fall through 14724 expression = {$:expression}; 14725 // jshint -W086 14726 case "object": 14727 // jshint +W086 14728 for (var key in expression) { 14729 (function(path) { 14730 if (typeof expression[path] === 'undefined') return; 14731 predicates.push(function(value) { 14732 return search(path == '$' ? value : (value && value[path]), expression[path]); 14733 }); 14734 })(key); 14735 } 14736 break; 14737 case 'function': 14738 predicates.push(expression); 14739 break; 14740 default: 14741 return array; 14742 } 14743 var filtered = []; 14744 for ( var j = 0; j < array.length; j++) { 14745 var value = array[j]; 14746 if (predicates.check(value)) { 14747 filtered.push(value); 14748 } 14749 } 14750 return filtered; 14751 }; 14752} 14753 14754/** 14755 * @ngdoc filter 14756 * @name currency 14757 * @kind function 14758 * 14759 * @description 14760 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 14761 * symbol for current locale is used. 14762 * 14763 * @param {number} amount Input to filter. 14764 * @param {string=} symbol Currency symbol or identifier to be displayed. 14765 * @returns {string} Formatted number. 14766 * 14767 * 14768 * @example 14769 <example> 14770 <file name="index.html"> 14771 <script> 14772 function Ctrl($scope) { 14773 $scope.amount = 1234.56; 14774 } 14775 </script> 14776 <div ng-controller="Ctrl"> 14777 <input type="number" ng-model="amount"> <br> 14778 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 14779 custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span> 14780 </div> 14781 </file> 14782 <file name="protractor.js" type="protractor"> 14783 it('should init with 1234.56', function() { 14784 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 14785 expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56'); 14786 }); 14787 it('should update', function() { 14788 if (browser.params.browser == 'safari') { 14789 // Safari does not understand the minus key. See 14790 // https://github.com/angular/protractor/issues/481 14791 return; 14792 }
14793 element(by.model('amount')).clear(); 14794 element(by.model('amount')).sendKeys('-1234'); 14795 expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); 14796 expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)'); 14797 }); 14798 </file> 14799 </example> 14800 */ 14801currencyFilter.$inject = ['$locale']; 14802function currencyFilter($locale) { 14803 var formats = $locale.NUMBER_FORMATS; 14804 return function(amount, currencySymbol){ 14805 if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; 14806 return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). 14807 replace(/\u00A4/g, currencySymbol); 14808 }; 14809} 14810 14811/** 14812 * @ngdoc filter 14813 * @name number 14814 * @kind function 14815 * 14816 * @description 14817 * Formats a number as text. 14818 * 14819 * If the input is not a number an empty string is returned. 14820 * 14821 * @param {number|string} number Number to format. 14822 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 14823 * If this is not provided then the fraction size is computed from the current locale's number 14824 * formatting pattern. In the case of the default locale, it will be 3. 14825 * @returns {string} Number rounded to decimalPlaces and places a â,â after each third digit. 14826 * 14827 * @example 14828 <example> 14829 <file name="index.html"> 14830 <script> 14831 function Ctrl($scope) { 14832 $scope.val = 1234.56789; 14833 } 14834 </script> 14835 <div ng-controller="Ctrl"> 14836 Enter number: <input ng-model='val'><br> 14837 Default formatting: <span id='number-default'>{{val | number}}</span><br> 14838 No fractions: <span>{{val | number:0}}</span><br> 14839 Negative number: <span>{{-val | number:4}}</span> 14840 </div> 14841 </file> 14842 <file name="protractor.js" type="protractor"> 14843 it('should format numbers', function() { 14844 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 14845 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 14846 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 14847 }); 14848 14849 it('should update', function() { 14850 element(by.model('val')).clear(); 14851 element(by.model('val')).sendKeys('3374.333'); 14852 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 14853 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 14854 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 14855 }); 14856 </file> 14857 </example> 14858 */ 14859 14860 14861numberFilter.$inject = ['$locale']; 14862function numberFilter($locale) { 14863 var formats = $locale.NUMBER_FORMATS; 14864 return function(number, fractionSize) { 14865 return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 14866 fractionSize); 14867 }; 14868} 14869 14870var DECIMAL_SEP = '.'; 14871function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 14872 if (number == null || !isFinite(number) || isObject(number)) return ''; 14873 14874 var isNegative = number < 0; 14875 number = Math.abs(number); 14876 var numStr = number + '', 14877 formatedText = '', 14878 parts = []; 14879 14880 var hasExponent = false; 14881 if (numStr.indexOf('e') !== -1) { 14882 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 14883 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 14884 numStr = '0'; 14885 number = 0; 14886 } else { 14887 formatedText = numStr; 14888 hasExponent = true; 14889 } 14890 } 14891 14892 if (!hasExponent) { 14893 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 14894 14895 // determine fractionSize if it is not specified 14896 if (isUndefined(fractionSize)) { 14897 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 14898 } 14899 14900 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics 14901 // inspired by: 14902 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round 14903 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); 14904 14905 var fraction = ('' + number).split(DECIMAL_SEP); 14906 var whole = fraction[0]; 14907 fraction = fraction[1] || ''; 14908 14909 var i, pos = 0, 14910 lgroup = pattern.lgSize, 14911 group = pattern.gSize; 14912 14913 if (whole.length >= (lgroup + group)) { 14914 pos = whole.length - lgroup; 14915 for (i = 0; i < pos; i++) { 14916 if ((pos - i)%group === 0 && i !== 0) { 14917 formatedText += groupSep; 14918 } 14919 formatedText += whole.charAt(i); 14920 } 14921 } 14922 14923 for (i = pos; i < whole.length; i++) { 14924 if ((whole.length - i)%lgroup === 0 && i !== 0) { 14925 formatedText += groupSep; 14926 } 14927 formatedText += whole.charAt(i); 14928 } 14929 14930 // format fraction part. 14931 while(fraction.length < fractionSize) { 14932 fraction += '0'; 14933 } 14934 14935 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 14936 } else { 14937 14938 if (fractionSize > 0 && number > -1 && number < 1) { 14939 formatedText = number.toFixed(fractionSize); 14940 } 14941 } 14942 14943 parts.push(isNegative ? pattern.negPre : pattern.posPre); 14944 parts.push(formatedText); 14945 parts.push(isNegative ? pattern.negSuf : pattern.posSuf); 14946 return parts.join(''); 14947} 14948 14949function padNumber(num, digits, trim) { 14950 var neg = ''; 14951 if (num < 0) { 14952 neg = '-'; 14953 num = -num; 14954 } 14955 num = '' + num; 14956 while(num.length < digits) num = '0' + num; 14957 if (trim) 14958 num = num.substr(num.length - digits); 14959 return neg + num; 14960} 14961 14962 14963function dateGetter(name, size, offset, trim) { 14964 offset = offset || 0; 14965 return function(date) { 14966 var value = date['get' + name](); 14967 if (offset > 0 || value > -offset) 14968 value += offset; 14969 if (value === 0 && offset == -12 ) value = 12; 14970 return padNumber(value, size, trim); 14971 }; 14972} 14973 14974function dateStrGetter(name, shortForm) { 14975 return function(date, formats) { 14976 var value = date['get' + name](); 14977 var get = uppercase(shortForm ? ('SHORT' + name) : name); 14978 14979 return formats[get][value]; 14980 }; 14981} 14982 14983function timeZoneGetter(date) { 14984 var zone = -1 * date.getTimezoneOffset(); 14985 var paddedZone = (zone >= 0) ? "+" : ""; 14986 14987 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 14988 padNumber(Math.abs(zone % 60), 2); 14989 14990 return paddedZone; 14991} 14992 14993function ampmGetter(date, formats) { 14994 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 14995} 14996 14997var DATE_FORMATS = { 14998 yyyy: dateGetter('FullYear', 4), 14999 yy: dateGetter('FullYear', 2, 0, true), 15000 y: dateGetter('FullYear', 1), 15001 MMMM: dateStrGetter('Month'), 15002 MMM: dateStrGetter('Month', true), 15003 MM: dateGetter('Month', 2, 1), 15004 M: dateGetter('Month', 1, 1), 15005 dd: dateGetter('Date', 2), 15006 d: dateGetter('Date', 1), 15007 HH: dateGetter('Hours', 2), 15008 H: dateGetter('Hours', 1), 15009 hh: dateGetter('Hours', 2, -12), 15010 h: dateGetter('Hours', 1, -12), 15011 mm: dateGetter('Minutes', 2), 15012 m: dateGetter('Minutes', 1), 15013 ss: dateGetter('Seconds', 2), 15014 s: dateGetter('Seconds', 1), 15015 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 15016 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 15017 sss: dateGetter('Milliseconds', 3), 15018 EEEE: dateStrGetter('Day'), 15019 EEE: dateStrGetter('Day', true), 15020 a: ampmGetter, 15021 Z: timeZoneGetter 15022}; 15023 15024var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, 15025 NUMBER_STRING = /^\-?\d+$/; 15026 15027/** 15028 * @ngdoc filter 15029 * @name date 15030 * @kind function 15031 * 15032 * @description 15033 * Formats `date` to a string based on the requested `format`. 15034 * 15035 * `format` string can be composed of the following elements: 15036 * 15037 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 15038 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 15039 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 15040 * * `'MMMM'`: Month in year (January-December) 15041 * * `'MMM'`: Month in year (Jan-Dec) 15042 * * `'MM'`: Month in year, padded (01-12) 15043 * * `'M'`: Month in year (1-12) 15044 * * `'dd'`: Day in month, padded (01-31) 15045 * * `'d'`: Day in month (1-31) 15046 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 15047 * * `'EEE'`: Day in Week, (Sun-Sat) 15048 * * `'HH'`: Hour in day, padded (00-23) 15049 * * `'H'`: Hour in day (0-23) 15050 * * `'hh'`: Hour in am/pm, padded (01-12) 15051 * * `'h'`: Hour in am/pm, (1-12) 15052 * * `'mm'`: Minute in hour, padded (00-59) 15053 * * `'m'`: Minute in hour (0-59) 15054 * * `'ss'`: Second in minute, padded (00-59) 15055 * * `'s'`: Second in minute (0-59) 15056 * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) 15057 * * `'a'`: am/pm marker 15058 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 15059 * 15060 * `format` string can also be one of the following predefined 15061 * {@link guide/i18n localizable formats}: 15062 * 15063 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 15064 * (e.g. Sep 3, 2010 12:05:08 pm) 15065 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) 15066 * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale 15067 * (e.g. Friday, September 3, 2010) 15068 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 15069 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 15070 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 15071 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) 15072 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) 15073 * 15074 * `format` string can contain literal values. These need to be quoted with single quotes (e.g. 15075 * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence 15076 * (e.g. `"h 'o''clock'"`). 15077 * 15078 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 15079 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its 15080 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 15081 * specified in the string input, the time is considered to be in the local timezone. 15082 * @param {string=} format Formatting rules (see Description). If not specified, 15083 * `mediumDate` is used. 15084 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 15085 * 15086 * @example 15087 <example> 15088 <file name="index.html">
15089 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 15090 <span>{{1288323623006 | date:'medium'}}</span><br> 15091 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 15092 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 15093 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 15094 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 15095 </file> 15096 <file name="protractor.js" type="protractor"> 15097 it('should format date', function() { 15098 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 15099 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 15100 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 15101 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 15102 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 15103 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 15104 }); 15105 </file> 15106 </example> 15107 */ 15108dateFilter.$inject = ['$locale']; 15109function dateFilter($locale) { 15110 15111 15112 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 15113 // 1 2 3 4 5 6 7 8 9 10 11 15114 function jsonStringToDate(string) { 15115 var match; 15116 if (match = string.match(R_ISO8601_STR)) { 15117 var date = new Date(0), 15118 tzHour = 0, 15119 tzMin = 0, 15120 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 15121 timeSetter = match[8] ? date.setUTCHours : date.setHours; 15122 15123 if (match[9]) { 15124 tzHour = int(match[9] + match[10]); 15125 tzMin = int(match[9] + match[11]); 15126 } 15127 dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); 15128 var h = int(match[4]||0) - tzHour; 15129 var m = int(match[5]||0) - tzMin; 15130 var s = int(match[6]||0); 15131 var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); 15132 timeSetter.call(date, h, m, s, ms); 15133 return date; 15134 } 15135 return string; 15136 } 15137 15138 15139 return function(date, format) { 15140 var text = '', 15141 parts = [], 15142 fn, match; 15143 15144 format = format || 'mediumDate'; 15145 format = $locale.DATETIME_FORMATS[format] || format; 15146 if (isString(date)) { 15147 if (NUMBER_STRING.test(date)) { 15148 date = int(date); 15149 } else { 15150 date = jsonStringToDate(date); 15151 } 15152 } 15153 15154 if (isNumber(date)) { 15155 date = new Date(date); 15156 } 15157 15158 if (!isDate(date)) { 15159 return date; 15160 } 15161 15162 while(format) { 15163 match = DATE_FORMATS_SPLIT.exec(format); 15164 if (match) { 15165 parts = concat(parts, match, 1); 15166 format = parts.pop(); 15167 } else { 15168 parts.push(format); 15169 format = null; 15170 } 15171 } 15172 15173 forEach(parts, function(value){ 15174 fn = DATE_FORMATS[value]; 15175 text += fn ? fn(date, $locale.DATETIME_FORMATS) 15176 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 15177 }); 15178 15179 return text; 15180 }; 15181} 15182 15183 15184/** 15185 * @ngdoc filter 15186 * @name json 15187 * @kind function 15188 * 15189 * @description 15190 * Allows you to convert a JavaScript object into JSON string. 15191 * 15192 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 15193 * the binding is automatically converted to JSON. 15194 * 15195 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 15196 * @returns {string} JSON string. 15197 * 15198 * 15199 * @example 15200 <example> 15201 <file name="index.html"> 15202 <pre>{{ {'name':'value'} | json }}</pre> 15203 </file> 15204 <file name="protractor.js" type="protractor"> 15205 it('should jsonify filtered objects', function() { 15206 expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/); 15207 }); 15208 </file> 15209 </example> 15210 * 15211 */ 15212function jsonFilter() { 15213 return function(object) { 15214 return toJson(object, true); 15215 }; 15216} 15217 15218 15219/** 15220 * @ngdoc filter 15221 * @name lowercase 15222 * @kind function 15223 * @description 15224 * Converts string to lowercase. 15225 * @see angular.lowercase 15226 */ 15227var lowercaseFilter = valueFn(lowercase); 15228 15229 15230/** 15231 * @ngdoc filter 15232 * @name uppercase 15233 * @kind function 15234 * @description 15235 * Converts string to uppercase. 15236 * @see angular.uppercase 15237 */ 15238var uppercaseFilter = valueFn(uppercase); 15239 15240/** 15241 * @ngdoc filter 15242 * @name limitTo 15243 * @kind function 15244 * 15245 * @description 15246 * Creates a new array or string containing only a specified number of elements. The elements 15247 * are taken from either the beginning or the end of the source array or string, as specified by 15248 * the value and sign (positive or negative) of `limit`. 15249 * 15250 * @param {Array|string} input Source array or string to be limited. 15251 * @param {string|number} limit The length of the returned array or string. If the `limit` number 15252 * is positive, `limit` number of items from the beginning of the source array/string are copied. 15253 * If the number is negative, `limit` number of items from the end of the source array/string 15254 * are copied. The `limit` will be trimmed if it exceeds `array.length` 15255 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 15256 * had less than `limit` elements. 15257 * 15258 * @example 15259 <example> 15260 <file name="index.html"> 15261 <script> 15262 function Ctrl($scope) { 15263 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 15264 $scope.letters = "abcdefghi"; 15265 $scope.numLimit = 3; 15266 $scope.letterLimit = 3; 15267 } 15268 </script> 15269 <div ng-controller="Ctrl"> 15270 Limit {{numbers}} to: <input type="integer" ng-model="numLimit"> 15271 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 15272 Limit {{letters}} to: <input type="integer" ng-model="letterLimit"> 15273 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 15274 </div> 15275 </file> 15276 <file name="protractor.js" type="protractor"> 15277 var numLimitInput = element(by.model('numLimit')); 15278 var letterLimitInput = element(by.model('letterLimit')); 15279 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 15280 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 15281 15282 it('should limit the number array to first three items', function() { 15283 expect(numLimitInput.getAttribute('value')).toBe('3'); 15284 expect(letterLimitInput.getAttribute('value')).toBe('3'); 15285 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 15286 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 15287 }); 15288 15289 it('should update the output when -3 is entered', function() { 15290 numLimitInput.clear(); 15291 numLimitInput.sendKeys('-3'); 15292 letterLimitInput.clear(); 15293 letterLimitInput.sendKeys('-3'); 15294 expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 15295 expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 15296 }); 15297 15298 it('should not exceed the maximum size of input array', function() { 15299 numLimitInput.clear(); 15300 numLimitInput.sendKeys('100'); 15301 letterLimitInput.clear(); 15302 letterLimitInput.sendKeys('100'); 15303 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 15304 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 15305 }); 15306 </file> 15307 </example> 15308 */ 15309function limitToFilter(){ 15310 return function(input, limit) { 15311 if (!isArray(input) && !isString(input)) return input; 15312 15313 if (Math.abs(Number(limit)) === Infinity) { 15314 limit = Number(limit); 15315 } else { 15316 limit = int(limit); 15317 } 15318 15319 if (isString(input)) { 15320 //NaN check on limit 15321 if (limit) { 15322 return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); 15323 } else { 15324 return ""; 15325 } 15326 } 15327 15328 var out = [], 15329 i, n; 15330 15331 // if abs(limit) exceeds maximum length, trim it 15332 if (limit > input.length) 15333 limit = input.length; 15334 else if (limit < -input.length) 15335 limit = -input.length; 15336 15337 if (limit > 0) { 15338 i = 0; 15339 n = limit; 15340 } else { 15341 i = input.length + limit; 15342 n = input.length; 15343 } 15344 15345 for (; i<n; i++) { 15346 out.push(input[i]); 15347 } 15348 15349 return out; 15350 }; 15351} 15352 15353/** 15354 * @ngdoc filter 15355 * @name orderBy 15356 * @kind function 15357 * 15358 * @description 15359 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 15360 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 15361 * correctly, make sure they are actually being saved as numbers and not strings. 15362 * 15363 * @param {Array} array The array to sort. 15364 * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be 15365 * used by the comparator to determine the order of elements. 15366 * 15367 * Can be one of: 15368 * 15369 * - `function`: Getter function. The result of this function will be sorted using the 15370 * `<`, `=`, `>` operator. 15371 * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' 15372 * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control 15373 * ascending or descending sort order (for example, +name or -name). 15374 * - `Array`: An array of function or string predicates. The first predicate in the array 15375 * is used for sorting, but when two items are equivalent, the next predicate is used. 15376 * 15377 * @param {boolean=} reverse Reverse the order of the array. 15378 * @returns {Array} Sorted copy of the source array. 15379 * 15380 * @example 15381 <example> 15382 <file name="index.html"> 15383 <script> 15384 function Ctrl($scope) { 15385 $scope.friends = 15386 [{name:'John', phone:'555-1212', age:10}, 15387 {name:'Mary', phone:'555-9876', age:19}, 15388 {name:'Mike', phone:'555-4321', age:21}, 15389 {name:'Adam', phone:'555-5678', age:35}, 15390 {name:'Julie', phone:'555-8765', age:29}] 15391 $scope.predicate = '-age'; 15392 } 15393 </script> 15394 <div ng-controller="Ctrl"> 15395 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 15396 <hr/> 15397 [ <a href="" ng-click="predicate=''">unsorted</a> ] 15398 <table class="friend"> 15399 <tr> 15400 <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a> 15401 (<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th> 15402 <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> 15403 <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th> 15404 </tr> 15405 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 15406 <td>{{friend.name}}</td> 15407 <td>{{friend.phone}}</td> 15408 <td>{{friend.age}}</td> 15409 </tr> 15410 </table> 15411 </div> 15412 </file> 15413 </example> 15414 * 15415 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
15416 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 15417 * desired parameters. 15418 * 15419 * Example: 15420 * 15421 * @example 15422 <example> 15423 <file name="index.html"> 15424 <div ng-controller="Ctrl"> 15425 <table class="friend"> 15426 <tr> 15427 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a> 15428 (<a href="" ng-click="order('-name',false)">^</a>)</th> 15429 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th> 15430 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th> 15431 </tr> 15432 <tr ng-repeat="friend in friends"> 15433 <td>{{friend.name}}</td> 15434 <td>{{friend.phone}}</td> 15435 <td>{{friend.age}}</td> 15436 </tr> 15437 </table> 15438 </div> 15439 </file> 15440 15441 <file name="script.js"> 15442 function Ctrl($scope, $filter) { 15443 var orderBy = $filter('orderBy'); 15444 $scope.friends = [ 15445 { name: 'John', phone: '555-1212', age: 10 }, 15446 { name: 'Mary', phone: '555-9876', age: 19 }, 15447 { name: 'Mike', phone: '555-4321', age: 21 }, 15448 { name: 'Adam', phone: '555-5678', age: 35 }, 15449 { name: 'Julie', phone: '555-8765', age: 29 } 15450 ]; 15451 15452 $scope.order = function(predicate, reverse) { 15453 $scope.friends = orderBy($scope.friends, predicate, reverse); 15454 }; 15455 $scope.order('-age',false); 15456 } 15457 </file> 15458</example> 15459 */ 15460orderByFilter.$inject = ['$parse']; 15461function orderByFilter($parse){ 15462 return function(array, sortPredicate, reverseOrder) { 15463 if (!isArray(array)) return array; 15464 if (!sortPredicate) return array; 15465 sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; 15466 sortPredicate = map(sortPredicate, function(predicate){ 15467 var descending = false, get = predicate || identity; 15468 if (isString(predicate)) { 15469 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 15470 descending = predicate.charAt(0) == '-'; 15471 predicate = predicate.substring(1); 15472 } 15473 get = $parse(predicate); 15474 if (get.constant) { 15475 var key = get(); 15476 return reverseComparator(function(a,b) { 15477 return compare(a[key], b[key]); 15478 }, descending); 15479 } 15480 } 15481 return reverseComparator(function(a,b){ 15482 return compare(get(a),get(b)); 15483 }, descending); 15484 }); 15485 var arrayCopy = []; 15486 for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } 15487 return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); 15488 15489 function comparator(o1, o2){ 15490 for ( var i = 0; i < sortPredicate.length; i++) { 15491 var comp = sortPredicate[i](o1, o2); 15492 if (comp !== 0) return comp; 15493 } 15494 return 0; 15495 } 15496 function reverseComparator(comp, descending) { 15497 return toBoolean(descending) 15498 ? function(a,b){return comp(b,a);} 15499 : comp; 15500 } 15501 function compare(v1, v2){ 15502 var t1 = typeof v1; 15503 var t2 = typeof v2; 15504 if (t1 == t2) { 15505 if (t1 == "string") { 15506 v1 = v1.toLowerCase(); 15507 v2 = v2.toLowerCase(); 15508 } 15509 if (v1 === v2) return 0; 15510 return v1 < v2 ? -1 : 1; 15511 } else { 15512 return t1 < t2 ? -1 : 1; 15513 } 15514 } 15515 }; 15516} 15517 15518function ngDirective(directive) { 15519 if (isFunction(directive)) { 15520 directive = { 15521 link: directive 15522 }; 15523 } 15524 directive.restrict = directive.restrict || 'AC'; 15525 return valueFn(directive); 15526} 15527 15528/** 15529 * @ngdoc directive 15530 * @name a 15531 * @restrict E 15532 * 15533 * @description 15534 * Modifies the default behavior of the html A tag so that the default action is prevented when 15535 * the href attribute is empty. 15536 * 15537 * This change permits the easy creation of action links with the `ngClick` directive 15538 * without changing the location or causing page reloads, e.g.: 15539 * `<a href="" ng-click="list.addItem()">Add Item</a>` 15540 */ 15541var htmlAnchorDirective = valueFn({ 15542 restrict: 'E',
15543 compile: function(element, attr) { 15544 15545 if (msie <= 8) { 15546 15547 // turn <a href ng-click="..">link</a> into a stylable link in IE 15548 // but only if it doesn't have name attribute, in which case it's an anchor 15549 if (!attr.href && !attr.name) { 15550 attr.$set('href', ''); 15551 } 15552 15553 // add a comment node to anchors to workaround IE bug that causes element content to be reset 15554 // to new attribute content if attribute is updated with value containing @ and element also 15555 // contains value with @ 15556 // see issue #1949 15557 element.append(document.createComment('IE fix')); 15558 } 15559 15560 if (!attr.href && !attr.xlinkHref && !attr.name) { 15561 return function(scope, element) { 15562 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 15563 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 15564 'xlink:href' : 'href'; 15565 element.on('click', function(event){ 15566 // if we have no href url, then don't navigate anywhere. 15567 if (!element.attr(href)) { 15568 event.preventDefault(); 15569 } 15570 }); 15571 }; 15572 } 15573 } 15574}); 15575 15576/** 15577 * @ngdoc directive 15578 * @name ngHref 15579 * @restrict A 15580 * @priority 99 15581 * 15582 * @description 15583 * Using Angular markup like `{{hash}}` in an href attribute will 15584 * make the link go to the wrong URL if the user clicks it before 15585 * Angular has a chance to replace the `{{hash}}` markup with its 15586 * value. Until Angular replaces the markup the link will be broken 15587 * and will most likely return a 404 error. 15588 * 15589 * The `ngHref` directive solves this problem. 15590 * 15591 * The wrong way to write it: 15592 * ```html 15593 * <a href="http://www.gravatar.com/avatar/{{hash}}"/> 15594 * ``` 15595 * 15596 * The correct way to write it: 15597 * ```html 15598 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/> 15599 * ``` 15600 * 15601 * @element A 15602 * @param {template} ngHref any string which can contain `{{}}` markup. 15603 * 15604 * @example 15605 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 15606 * in links and their different behaviors: 15607 <example> 15608 <file name="index.html"> 15609 <input ng-model="value" /><br /> 15610 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 15611 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 15612 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 15613 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 15614 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 15615 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 15616 </file> 15617 <file name="protractor.js" type="protractor"> 15618 it('should execute ng-click but not reload when href without value', function() { 15619 element(by.id('link-1')).click(); 15620 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 15621 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 15622 }); 15623 15624 it('should execute ng-click but not reload when href empty string', function() { 15625 element(by.id('link-2')).click(); 15626 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 15627 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 15628 }); 15629 15630 it('should execute ng-click and change url when ng-href specified', function() { 15631 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 15632 15633 element(by.id('link-3')).click(); 15634 15635 // At this point, we navigate away from an Angular page, so we need 15636 // to use browser.driver to get the base webdriver. 15637 15638 browser.wait(function() { 15639 return browser.driver.getCurrentUrl().then(function(url) { 15640 return url.match(/\/123$/); 15641 }); 15642 }, 1000, 'page should navigate to /123'); 15643 }); 15644 15645 xit('should execute ng-click but not reload when href empty string and name specified', function() {
15646 element(by.id('link-4')).click(); 15647 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 15648 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 15649 }); 15650 15651 it('should execute ng-click but not reload when no href but name specified', function() { 15652 element(by.id('link-5')).click(); 15653 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 15654 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 15655 }); 15656 15657 it('should only change url when only ng-href', function() { 15658 element(by.model('value')).clear(); 15659 element(by.model('value')).sendKeys('6'); 15660 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 15661 15662 element(by.id('link-6')).click(); 15663 15664 // At this point, we navigate away from an Angular page, so we need 15665 // to use browser.driver to get the base webdriver. 15666 browser.wait(function() { 15667 return browser.driver.getCurrentUrl().then(function(url) { 15668 return url.match(/\/6$/); 15669 }); 15670 }, 1000, 'page should navigate to /6'); 15671 }); 15672 </file> 15673 </example> 15674 */ 15675 15676/** 15677 * @ngdoc directive 15678 * @name ngSrc 15679 * @restrict A 15680 * @priority 99 15681 * 15682 * @description 15683 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 15684 * work right: The browser will fetch from the URL with the literal 15685 * text `{{hash}}` until Angular replaces the expression inside 15686 * `{{hash}}`. The `ngSrc` directive solves this problem. 15687 * 15688 * The buggy way to write it: 15689 * ```html 15690 * <img src="http://www.gravatar.com/avatar/{{hash}}"/> 15691 * ``` 15692 * 15693 * The correct way to write it: 15694 * ```html 15695 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/> 15696 * ``` 15697 * 15698 * @element IMG 15699 * @param {template} ngSrc any string which can contain `{{}}` markup. 15700 */ 15701 15702/** 15703 * @ngdoc directive 15704 * @name ngSrcset 15705 * @restrict A 15706 * @priority 99 15707 * 15708 * @description 15709 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 15710 * work right: The browser will fetch from the URL with the literal 15711 * text `{{hash}}` until Angular replaces the expression inside 15712 * `{{hash}}`. The `ngSrcset` directive solves this problem. 15713 * 15714 * The buggy way to write it: 15715 * ```html 15716 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 15717 * ``` 15718 * 15719 * The correct way to write it: 15720 * ```html 15721 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 15722 * ``` 15723 * 15724 * @element IMG 15725 * @param {template} ngSrcset any string which can contain `{{}}` markup. 15726 */ 15727 15728/** 15729 * @ngdoc directive 15730 * @name ngDisabled 15731 * @restrict A 15732 * @priority 100 15733 * 15734 * @description 15735 * 15736 * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: 15737 * ```html 15738 * <div ng-init="scope = { isDisabled: false }"> 15739 * <button disabled="{{scope.isDisabled}}">Disabled</button> 15740 * </div> 15741 * ``` 15742 * 15743 * The HTML specification does not require browsers to preserve the values of boolean attributes 15744 * such as disabled. (Their presence means true and their absence means false.) 15745 * If we put an Angular interpolation expression into such an attribute then the 15746 * binding information would be lost when the browser removes the attribute. 15747 * The `ngDisabled` directive solves this problem for the `disabled` attribute. 15748 * This complementary directive is not removed by the browser and so provides 15749 * a permanent reliable place to store the binding information. 15750 * 15751 * @example 15752 <example> 15753 <file name="index.html"> 15754 Click me to toggle: <input type="checkbox" ng-model="checked"><br/> 15755 <button ng-model="button" ng-disabled="checked">Button</button> 15756 </file> 15757 <file name="protractor.js" type="protractor"> 15758 it('should toggle button', function() { 15759 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); 15760 element(by.model('checked')).click(); 15761 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 15762 }); 15763 </file> 15764 </example> 15765 * 15766 * @element INPUT 15767 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 15768 * then special attribute "disabled" will be set on the element 15769 */ 15770 15771 15772/** 15773 * @ngdoc directive 15774 * @name ngChecked 15775 * @restrict A 15776 * @priority 100 15777 * 15778 * @description 15779 * The HTML specification does not require browsers to preserve the values of boolean attributes 15780 * such as checked. (Their presence means true and their absence means false.) 15781 * If we put an Angular interpolation expression into such an attribute then the 15782 * binding information would be lost when the browser removes the attribute. 15783 * The `ngChecked` directive solves this problem for the `checked` attribute. 15784 * This complementary directive is not removed by the browser and so provides 15785 * a permanent reliable place to store the binding information. 15786 * @example 15787 <example> 15788 <file name="index.html"> 15789 Check me to check both: <input type="checkbox" ng-model="master"><br/> 15790 <input id="checkSlave" type="checkbox" ng-checked="master"> 15791 </file> 15792 <file name="protractor.js" type="protractor"> 15793 it('should check both checkBoxes', function() { 15794 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
15795 element(by.model('master')).click(); 15796 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 15797 }); 15798 </file> 15799 </example> 15800 * 15801 * @element INPUT 15802 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 15803 * then special attribute "checked" will be set on the element 15804 */ 15805 15806 15807/** 15808 * @ngdoc directive 15809 * @name ngReadonly 15810 * @restrict A 15811 * @priority 100 15812 * 15813 * @description 15814 * The HTML specification does not require browsers to preserve the values of boolean attributes 15815 * such as readonly. (Their presence means true and their absence means false.) 15816 * If we put an Angular interpolation expression into such an attribute then the 15817 * binding information would be lost when the browser removes the attribute. 15818 * The `ngReadonly` directive solves this problem for the `readonly` attribute. 15819 * This complementary directive is not removed by the browser and so provides 15820 * a permanent reliable place to store the binding information. 15821 * @example 15822 <example> 15823 <file name="index.html"> 15824 Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/> 15825 <input type="text" ng-readonly="checked" value="I'm Angular"/> 15826 </file> 15827 <file name="protractor.js" type="protractor"> 15828 it('should toggle readonly attr', function() { 15829 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 15830 element(by.model('checked')).click(); 15831 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 15832 }); 15833 </file> 15834 </example> 15835 * 15836 * @element INPUT 15837 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 15838 * then special attribute "readonly" will be set on the element 15839 */ 15840 15841 15842/** 15843 * @ngdoc directive 15844 * @name ngSelected 15845 * @restrict A 15846 * @priority 100 15847 * 15848 * @description 15849 * The HTML specification does not require browsers to preserve the values of boolean attributes 15850 * such as selected. (Their presence means true and their absence means false.) 15851 * If we put an Angular interpolation expression into such an attribute then the 15852 * binding information would be lost when the browser removes the attribute. 15853 * The `ngSelected` directive solves this problem for the `selected` attribute. 15854 * This complementary directive is not removed by the browser and so provides 15855 * a permanent reliable place to store the binding information. 15856 * 15857 * @example 15858 <example> 15859 <file name="index.html"> 15860 Check me to select: <input type="checkbox" ng-model="selected"><br/> 15861 <select> 15862 <option>Hello!</option> 15863 <option id="greet" ng-selected="selected">Greetings!</option> 15864 </select> 15865 </file> 15866 <file name="protractor.js" type="protractor"> 15867 it('should select Greetings!', function() { 15868 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); 15869 element(by.model('selected')).click(); 15870 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 15871 }); 15872 </file> 15873 </example> 15874 * 15875 * @element OPTION 15876 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 15877 * then special attribute "selected" will be set on the element 15878 */ 15879 15880/** 15881 * @ngdoc directive 15882 * @name ngOpen 15883 * @restrict A 15884 * @priority 100 15885 * 15886 * @description 15887 * The HTML specification does not require browsers to preserve the values of boolean attributes 15888 * such as open. (Their presence means true and their absence means false.) 15889 * If we put an Angular interpolation expression into such an attribute then the 15890 * binding information would be lost when the browser removes the attribute. 15891 * The `ngOpen` directive solves this problem for the `open` attribute. 15892 * This complementary directive is not removed by the browser and so provides 15893 * a permanent reliable place to store the binding information. 15894 * @example 15895 <example> 15896 <file name="index.html"> 15897 Check me check multiple: <input type="checkbox" ng-model="open"><br/> 15898 <details id="details" ng-open="open"> 15899 <summary>Show/Hide me</summary> 15900 </details> 15901 </file> 15902 <file name="protractor.js" type="protractor"> 15903 it('should toggle open', function() { 15904 expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
15905 element(by.model('open')).click(); 15906 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 15907 }); 15908 </file> 15909 </example> 15910 * 15911 * @element DETAILS 15912 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 15913 * then special attribute "open" will be set on the element 15914 */ 15915 15916var ngAttributeAliasDirectives = {}; 15917 15918 15919// boolean attrs are evaluated 15920forEach(BOOLEAN_ATTR, function(propName, attrName) { 15921 // binding to multiple is not supported 15922 if (propName == "multiple") return; 15923 15924 var normalized = directiveNormalize('ng-' + attrName); 15925 ngAttributeAliasDirectives[normalized] = function() { 15926 return { 15927 priority: 100, 15928 link: function(scope, element, attr) { 15929 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 15930 attr.$set(attrName, !!value); 15931 }); 15932 } 15933 }; 15934 }; 15935}); 15936 15937 15938// ng-src, ng-srcset, ng-href are interpolated 15939forEach(['src', 'srcset', 'href'], function(attrName) { 15940 var normalized = directiveNormalize('ng-' + attrName); 15941 ngAttributeAliasDirectives[normalized] = function() { 15942 return { 15943 priority: 99, // it needs to run after the attributes are interpolated 15944 link: function(scope, element, attr) { 15945 var propName = attrName, 15946 name = attrName; 15947 15948 if (attrName === 'href' && 15949 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 15950 name = 'xlinkHref'; 15951 attr.$attr[name] = 'xlink:href'; 15952 propName = null; 15953 } 15954 15955 attr.$observe(normalized, function(value) { 15956 if (!value) 15957 return; 15958 15959 attr.$set(name, value); 15960 15961 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 15962 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 15963 // to set the property as well to achieve the desired effect. 15964 // we use attr[attrName] value since $set can sanitize the url. 15965 if (msie && propName) element.prop(propName, attr[name]); 15966 }); 15967 } 15968 }; 15969 }; 15970}); 15971 15972/* global -nullFormCtrl */ 15973var nullFormCtrl = { 15974 $addControl: noop, 15975 $removeControl: noop, 15976 $setValidity: noop, 15977 $setDirty: noop, 15978 $setPristine: noop 15979}; 15980 15981/** 15982 * @ngdoc type 15983 * @name form.FormController 15984 * 15985 * @property {boolean} $pristine True if user has not interacted with the form yet. 15986 * @property {boolean} $dirty True if user has already interacted with the form. 15987 * @property {boolean} $valid True if all of the containing forms and controls are valid. 15988 * @property {boolean} $invalid True if at least one containing control or form is invalid. 15989 * 15990 * @property {Object} $error Is an object hash, containing references to all invalid controls or 15991 * forms, where: 15992 * 15993 * - keys are validation tokens (error names), 15994 * - values are arrays of controls or forms that are invalid for given error name. 15995 * 15996 * 15997 * Built-in validation tokens: 15998 * 15999 * - `email` 16000 * - `max` 16001 * - `maxlength` 16002 * - `min` 16003 * - `minlength` 16004 * - `number` 16005 * - `pattern` 16006 * - `required` 16007 * - `url` 16008 * 16009 * @description 16010 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 16011 * such as being valid/invalid or dirty/pristine. 16012 * 16013 * Each {@link ng.directive:form form} directive creates an instance 16014 * of `FormController`. 16015 * 16016 */ 16017//asks for $scope to fool the BC controller module 16018FormController.$inject = ['$element', '$attrs', '$scope', '$animate']; 16019function FormController(element, attrs, $scope, $animate) { 16020 var form = this, 16021 parentForm = element.parent().controller('form') || nullFormCtrl, 16022 invalidCount = 0, // used to easily determine if we are valid 16023 errors = form.$error = {}, 16024 controls = []; 16025 16026 // init state 16027 form.$name = attrs.name || attrs.ngForm; 16028 form.$dirty = false; 16029 form.$pristine = true; 16030 form.$valid = true; 16031 form.$invalid = false; 16032 16033 parentForm.$addControl(form); 16034 16035 // Setup initial state of the control 16036 element.addClass(PRISTINE_CLASS);
16037 toggleValidCss(true); 16038 16039 // convenience method for easy toggling of classes 16040 function toggleValidCss(isValid, validationErrorKey) { 16041 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 16042 $animate.removeClass(element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); 16043 $animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 16044 } 16045 16046 /** 16047 * @ngdoc method 16048 * @name form.FormController#$addControl 16049 * 16050 * @description 16051 * Register a control with the form. 16052 * 16053 * Input elements using ngModelController do this automatically when they are linked. 16054 */ 16055 form.$addControl = function(control) { 16056 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 16057 // and not added to the scope. Now we throw an error. 16058 assertNotHasOwnProperty(control.$name, 'input'); 16059 controls.push(control); 16060 16061 if (control.$name) { 16062 form[control.$name] = control; 16063 } 16064 }; 16065 16066 /** 16067 * @ngdoc method 16068 * @name form.FormController#$removeControl 16069 * 16070 * @description 16071 * Deregister a control from the form. 16072 * 16073 * Input elements using ngModelController do this automatically when they are destroyed. 16074 */ 16075 form.$removeControl = function(control) { 16076 if (control.$name && form[control.$name] === control) { 16077 delete form[control.$name]; 16078 } 16079 forEach(errors, function(queue, validationToken) { 16080 form.$setValidity(validationToken, true, control); 16081 }); 16082 16083 arrayRemove(controls, control); 16084 }; 16085 16086 /** 16087 * @ngdoc method 16088 * @name form.FormController#$setValidity 16089 * 16090 * @description 16091 * Sets the validity of a form control. 16092 * 16093 * This method will also propagate to parent forms. 16094 */ 16095 form.$setValidity = function(validationToken, isValid, control) { 16096 var queue = errors[validationToken]; 16097 16098 if (isValid) { 16099 if (queue) { 16100 arrayRemove(queue, control); 16101 if (!queue.length) { 16102 invalidCount--; 16103 if (!invalidCount) { 16104 toggleValidCss(isValid); 16105 form.$valid = true; 16106 form.$invalid = false; 16107 } 16108 errors[validationToken] = false; 16109 toggleValidCss(true, validationToken); 16110 parentForm.$setValidity(validationToken, true, form); 16111 } 16112 } 16113 16114 } else { 16115 if (!invalidCount) { 16116 toggleValidCss(isValid); 16117 } 16118 if (queue) { 16119 if (includes(queue, control)) return; 16120 } else { 16121 errors[validationToken] = queue = []; 16122 invalidCount++; 16123 toggleValidCss(false, validationToken); 16124 parentForm.$setValidity(validationToken, false, form); 16125 } 16126 queue.push(control); 16127 16128 form.$valid = false; 16129 form.$invalid = true; 16130 } 16131 }; 16132 16133 /** 16134 * @ngdoc method 16135 * @name form.FormController#$setDirty 16136 * 16137 * @description 16138 * Sets the form to a dirty state. 16139 * 16140 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 16141 * state (ng-dirty class). This method will also propagate to parent forms. 16142 */ 16143 form.$setDirty = function() { 16144 $animate.removeClass(element, PRISTINE_CLASS); 16145 $animate.addClass(element, DIRTY_CLASS); 16146 form.$dirty = true; 16147 form.$pristine = false; 16148 parentForm.$setDirty(); 16149 }; 16150 16151 /** 16152 * @ngdoc method 16153 * @name form.FormController#$setPristine 16154 * 16155 * @description 16156 * Sets the form to its pristine state. 16157 * 16158 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 16159 * state (ng-pristine class). This method will also propagate to all the controls contained 16160 * in this form. 16161 * 16162 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 16163 * saving or resetting it. 16164 */ 16165 form.$setPristine = function () { 16166 $animate.removeClass(element, DIRTY_CLASS); 16167 $animate.addClass(element, PRISTINE_CLASS); 16168 form.$dirty = false; 16169 form.$pristine = true; 16170 forEach(controls, function(control) { 16171 control.$setPristine(); 16172 });
16173 }; 16174} 16175 16176 16177/** 16178 * @ngdoc directive 16179 * @name ngForm 16180 * @restrict EAC 16181 * 16182 * @description 16183 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 16184 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 16185 * sub-group of controls needs to be determined. 16186 * 16187 * Note: the purpose of `ngForm` is to group controls, 16188 * but not to be a replacement for the `<form>` tag with all of its capabilities 16189 * (e.g. posting to the server, ...). 16190 * 16191 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 16192 * related scope, under this name. 16193 * 16194 */ 16195 16196 /** 16197 * @ngdoc directive 16198 * @name form 16199 * @restrict E 16200 * 16201 * @description 16202 * Directive that instantiates 16203 * {@link form.FormController FormController}. 16204 * 16205 * If the `name` attribute is specified, the form controller is published onto the current scope under 16206 * this name. 16207 * 16208 * # Alias: {@link ng.directive:ngForm `ngForm`} 16209 * 16210 * In Angular forms can be nested. This means that the outer form is valid when all of the child 16211 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 16212 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to 16213 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when 16214 * using Angular validation directives in forms that are dynamically generated using the 16215 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` 16216 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an 16217 * `ngForm` directive and nest these in an outer `form` element. 16218 * 16219 * 16220 * # CSS classes 16221 * - `ng-valid` is set if the form is valid. 16222 * - `ng-invalid` is set if the form is invalid. 16223 * - `ng-pristine` is set if the form is pristine. 16224 * - `ng-dirty` is set if the form is dirty. 16225 * 16226 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 16227 * 16228 * 16229 * # Submitting a form and preventing the default action 16230 * 16231 * Since the role of forms in client-side Angular applications is different than in classical 16232 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 16233 * page reload that sends the data to the server. Instead some javascript logic should be triggered 16234 * to handle the form submission in an application-specific way. 16235 * 16236 * For this reason, Angular prevents the default action (form submission to the server) unless the 16237 * `<form>` element has an `action` attribute specified. 16238 * 16239 * You can use one of the following two ways to specify what javascript method should be called when 16240 * a form is submitted: 16241 * 16242 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 16243 * - {@link ng.directive:ngClick ngClick} directive on the first 16244 * button or input field of type submit (input[type=submit]) 16245 * 16246 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 16247 * or {@link ng.directive:ngClick ngClick} directives. 16248 * This is because of the following form submission rules in the HTML specification: 16249 * 16250 * - If a form has only one input field then hitting enter in this field triggers form submit 16251 * (`ngSubmit`) 16252 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 16253 * doesn't trigger submit 16254 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 16255 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 16256 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 16257 * 16258 * @param {string=} name Name of the form. If specified, the form controller will be published into 16259 * related scope, under this name. 16260 * 16261 * ## Animation Hooks 16262 * 16263 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 16264 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 16265 * other validations that are performed within the form. Animations in ngForm are similar to how 16266 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 16267 * as JS animations. 16268 * 16269 * The following example shows a simple way to utilize CSS transitions to style a form element
16270 * that has been rendered as invalid after it has been validated: 16271 * 16272 * <pre> 16273 * //be sure to include ngAnimate as a module to hook into more 16274 * //advanced animations 16275 * .my-form { 16276 * transition:0.5s linear all; 16277 * background: white; 16278 * } 16279 * .my-form.ng-invalid { 16280 * background: red; 16281 * color:white; 16282 * } 16283 * </pre> 16284 * 16285 * @example 16286 <example deps="angular-animate.js" animations="true" fixBase="true"> 16287 <file name="index.html"> 16288 <script> 16289 function Ctrl($scope) { 16290 $scope.userType = 'guest'; 16291 } 16292 </script> 16293 <style> 16294 .my-form { 16295 -webkit-transition:all linear 0.5s; 16296 transition:all linear 0.5s; 16297 background: transparent; 16298 } 16299 .my-form.ng-invalid { 16300 background: red; 16301 } 16302 </style> 16303 <form name="myForm" ng-controller="Ctrl" class="my-form"> 16304 userType: <input name="input" ng-model="userType" required> 16305 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 16306 <tt>userType = {{userType}}</tt><br> 16307 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br> 16308 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br> 16309 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 16310 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 16311 </form> 16312 </file> 16313 <file name="protractor.js" type="protractor"> 16314 it('should initialize to model', function() { 16315 var userType = element(by.binding('userType')); 16316 var valid = element(by.binding('myForm.input.$valid')); 16317 16318 expect(userType.getText()).toContain('guest'); 16319 expect(valid.getText()).toContain('true'); 16320 }); 16321 16322 it('should be invalid if empty', function() { 16323 var userType = element(by.binding('userType')); 16324 var valid = element(by.binding('myForm.input.$valid')); 16325 var userInput = element(by.model('userType')); 16326 16327 userInput.clear(); 16328 userInput.sendKeys(''); 16329 16330 expect(userType.getText()).toEqual('userType ='); 16331 expect(valid.getText()).toContain('false'); 16332 }); 16333 </file> 16334 </example> 16335 * 16336 */ 16337var formDirectiveFactory = function(isNgForm) { 16338 return ['$timeout', function($timeout) { 16339 var formDirective = { 16340 name: 'form', 16341 restrict: isNgForm ? 'EAC' : 'E', 16342 controller: FormController, 16343 compile: function() { 16344 return { 16345 pre: function(scope, formElement, attr, controller) { 16346 if (!attr.action) { 16347 // we can't use jq events because if a form is destroyed during submission the default 16348 // action is not prevented. see #1238 16349 // 16350 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 16351 // page reload if the form was destroyed by submission of the form via a click handler 16352 // on a button in the form. Looks like an IE9 specific bug. 16353 var preventDefaultListener = function(event) { 16354 event.preventDefault 16355 ? event.preventDefault() 16356 : event.returnValue = false; // IE 16357 }; 16358 16359 addEventListenerFn(formElement[0], 'submit', preventDefaultListener); 16360 16361 // unregister the preventDefault listener so that we don't not leak memory but in a 16362 // way that will achieve the prevention of the default action. 16363 formElement.on('$destroy', function() { 16364 $timeout(function() { 16365 removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); 16366 }, 0, false); 16367 }); 16368 } 16369 16370 var parentFormCtrl = formElement.parent().controller('form'), 16371 alias = attr.name || attr.ngForm; 16372 16373 if (alias) { 16374 setter(scope, alias, controller, alias); 16375 } 16376 if (parentFormCtrl) { 16377 formElement.on('$destroy', function() { 16378 parentFormCtrl.$removeControl(controller); 16379 if (alias) { 16380 setter(scope, alias, undefined, alias); 16381 } 16382 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 16383 }); 16384 } 16385 } 16386 }; 16387 } 16388 }; 16389 16390 return formDirective; 16391 }]; 16392}; 16393 16394var formDirective = formDirectiveFactory(); 16395var ngFormDirective = formDirectiveFactory(true); 16396 16397/* global 16398 16399 -VALID_CLASS, 16400 -INVALID_CLASS, 16401 -PRISTINE_CLASS, 16402 -DIRTY_CLASS 16403*/ 16404 16405var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 16406var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i;
16407var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; 16408 16409var inputType = { 16410 16411 /** 16412 * @ngdoc input 16413 * @name input[text] 16414 * 16415 * @description 16416 * Standard HTML text input with angular data binding. 16417 * 16418 * @param {string} ngModel Assignable angular expression to data-bind to. 16419 * @param {string=} name Property name of the form under which the control is published. 16420 * @param {string=} required Adds `required` validation error key if the value is not entered. 16421 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16422 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16423 * `required` when you want to data-bind to the `required` attribute. 16424 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16425 * minlength. 16426 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16427 * maxlength. 16428 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16429 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16430 * patterns defined as scope expressions. 16431 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16432 * interaction with the input element. 16433 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 16434 * 16435 * @example 16436 <example name="text-input-directive"> 16437 <file name="index.html"> 16438 <script> 16439 function Ctrl($scope) { 16440 $scope.text = 'guest'; 16441 $scope.word = /^\s*\w*\s*$/; 16442 } 16443 </script> 16444 <form name="myForm" ng-controller="Ctrl"> 16445 Single word: <input type="text" name="input" ng-model="text" 16446 ng-pattern="word" required ng-trim="false"> 16447 <span class="error" ng-show="myForm.input.$error.required"> 16448 Required!</span> 16449 <span class="error" ng-show="myForm.input.$error.pattern"> 16450 Single word only!</span> 16451 16452 <tt>text = {{text}}</tt><br/> 16453 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16454 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16455 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16456 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16457 </form> 16458 </file> 16459 <file name="protractor.js" type="protractor"> 16460 var text = element(by.binding('text')); 16461 var valid = element(by.binding('myForm.input.$valid')); 16462 var input = element(by.model('text')); 16463 16464 it('should initialize to model', function() { 16465 expect(text.getText()).toContain('guest'); 16466 expect(valid.getText()).toContain('true'); 16467 }); 16468 16469 it('should be invalid if empty', function() { 16470 input.clear(); 16471 input.sendKeys(''); 16472 16473 expect(text.getText()).toEqual('text ='); 16474 expect(valid.getText()).toContain('false'); 16475 }); 16476 16477 it('should be invalid if multi word', function() { 16478 input.clear(); 16479 input.sendKeys('hello world'); 16480 16481 expect(valid.getText()).toContain('false'); 16482 }); 16483 </file> 16484 </example> 16485 */ 16486 'text': textInputType, 16487 16488 16489 /** 16490 * @ngdoc input 16491 * @name input[number] 16492 * 16493 * @description 16494 * Text input with number validation and transformation. Sets the `number` validation 16495 * error if not a valid number. 16496 * 16497 * @param {string} ngModel Assignable angular expression to data-bind to. 16498 * @param {string=} name Property name of the form under which the control is published. 16499 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 16500 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 16501 * @param {string=} required Sets `required` validation error key if the value is not entered. 16502 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16503 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16504 * `required` when you want to data-bind to the `required` attribute. 16505 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16506 * minlength. 16507 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16508 * maxlength. 16509 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16510 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16511 * patterns defined as scope expressions. 16512 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16513 * interaction with the input element. 16514 * 16515 * @example 16516 <example name="number-input-directive"> 16517 <file name="index.html"> 16518 <script> 16519 function Ctrl($scope) { 16520 $scope.value = 12; 16521 } 16522 </script> 16523 <form name="myForm" ng-controller="Ctrl"> 16524 Number: <input type="number" name="input" ng-model="value" 16525 min="0" max="99" required>
16526 <span class="error" ng-show="myForm.input.$error.required"> 16527 Required!</span> 16528 <span class="error" ng-show="myForm.input.$error.number"> 16529 Not valid number!</span> 16530 <tt>value = {{value}}</tt><br/> 16531 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16532 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16533 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16534 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16535 </form> 16536 </file> 16537 <file name="protractor.js" type="protractor"> 16538 var value = element(by.binding('value')); 16539 var valid = element(by.binding('myForm.input.$valid')); 16540 var input = element(by.model('value')); 16541 16542 it('should initialize to model', function() { 16543 expect(value.getText()).toContain('12'); 16544 expect(valid.getText()).toContain('true'); 16545 }); 16546 16547 it('should be invalid if empty', function() { 16548 input.clear(); 16549 input.sendKeys(''); 16550 expect(value.getText()).toEqual('value ='); 16551 expect(valid.getText()).toContain('false'); 16552 }); 16553 16554 it('should be invalid if over max', function() { 16555 input.clear(); 16556 input.sendKeys('123'); 16557 expect(value.getText()).toEqual('value ='); 16558 expect(valid.getText()).toContain('false'); 16559 }); 16560 </file> 16561 </example> 16562 */ 16563 'number': numberInputType, 16564 16565 16566 /** 16567 * @ngdoc input 16568 * @name input[url] 16569 * 16570 * @description 16571 * Text input with URL validation. Sets the `url` validation error key if the content is not a 16572 * valid URL. 16573 * 16574 * @param {string} ngModel Assignable angular expression to data-bind to. 16575 * @param {string=} name Property name of the form under which the control is published. 16576 * @param {string=} required Sets `required` validation error key if the value is not entered. 16577 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16578 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16579 * `required` when you want to data-bind to the `required` attribute. 16580 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16581 * minlength. 16582 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16583 * maxlength. 16584 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16585 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16586 * patterns defined as scope expressions. 16587 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16588 * interaction with the input element. 16589 * 16590 * @example 16591 <example name="url-input-directive"> 16592 <file name="index.html"> 16593 <script> 16594 function Ctrl($scope) { 16595 $scope.text = 'http://google.com'; 16596 } 16597 </script> 16598 <form name="myForm" ng-controller="Ctrl"> 16599 URL: <input type="url" name="input" ng-model="text" required> 16600 <span class="error" ng-show="myForm.input.$error.required"> 16601 Required!</span> 16602 <span class="error" ng-show="myForm.input.$error.url"> 16603 Not valid url!</span> 16604 <tt>text = {{text}}</tt><br/> 16605 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16606 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16607 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16608 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16609 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 16610 </form> 16611 </file> 16612 <file name="protractor.js" type="protractor"> 16613 var text = element(by.binding('text')); 16614 var valid = element(by.binding('myForm.input.$valid')); 16615 var input = element(by.model('text'));
16616 16617 it('should initialize to model', function() { 16618 expect(text.getText()).toContain('http://google.com'); 16619 expect(valid.getText()).toContain('true'); 16620 }); 16621 16622 it('should be invalid if empty', function() { 16623 input.clear(); 16624 input.sendKeys(''); 16625 16626 expect(text.getText()).toEqual('text ='); 16627 expect(valid.getText()).toContain('false'); 16628 }); 16629 16630 it('should be invalid if not url', function() { 16631 input.clear(); 16632 input.sendKeys('box'); 16633 16634 expect(valid.getText()).toContain('false'); 16635 }); 16636 </file> 16637 </example> 16638 */ 16639 'url': urlInputType, 16640 16641 16642 /** 16643 * @ngdoc input 16644 * @name input[email] 16645 * 16646 * @description 16647 * Text input with email validation. Sets the `email` validation error key if not a valid email 16648 * address. 16649 * 16650 * @param {string} ngModel Assignable angular expression to data-bind to. 16651 * @param {string=} name Property name of the form under which the control is published. 16652 * @param {string=} required Sets `required` validation error key if the value is not entered. 16653 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16654 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16655 * `required` when you want to data-bind to the `required` attribute. 16656 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16657 * minlength. 16658 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16659 * maxlength. 16660 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16661 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16662 * patterns defined as scope expressions. 16663 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16664 * interaction with the input element. 16665 * 16666 * @example 16667 <example name="email-input-directive"> 16668 <file name="index.html"> 16669 <script> 16670 function Ctrl($scope) { 16671 $scope.text = '[email protected]'; 16672 } 16673 </script> 16674 <form name="myForm" ng-controller="Ctrl"> 16675 Email: <input type="email" name="input" ng-model="text" required> 16676 <span class="error" ng-show="myForm.input.$error.required"> 16677 Required!</span> 16678 <span class="error" ng-show="myForm.input.$error.email"> 16679 Not valid email!</span> 16680 <tt>text = {{text}}</tt><br/> 16681 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16682 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16683 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16684 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16685 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 16686 </form> 16687 </file> 16688 <file name="protractor.js" type="protractor"> 16689 var text = element(by.binding('text')); 16690 var valid = element(by.binding('myForm.input.$valid')); 16691 var input = element(by.model('text')); 16692 16693 it('should initialize to model', function() { 16694 expect(text.getText()).toContain('[email protected]'); 16695 expect(valid.getText()).toContain('true'); 16696 }); 16697 16698 it('should be invalid if empty', function() { 16699 input.clear(); 16700 input.sendKeys(''); 16701 expect(text.getText()).toEqual('text ='); 16702 expect(valid.getText()).toContain('false'); 16703 }); 16704 16705 it('should be invalid if not email', function() { 16706 input.clear(); 16707 input.sendKeys('xxx'); 16708 16709 expect(valid.getText()).toContain('false'); 16710 }); 16711 </file> 16712 </example> 16713 */ 16714 'email': emailInputType, 16715 16716 16717 /** 16718 * @ngdoc input 16719 * @name input[radio] 16720 * 16721 * @description 16722 * HTML radio button. 16723 * 16724 * @param {string} ngModel Assignable angular expression to data-bind to. 16725 * @param {string} value The value to which the expression should be set when selected. 16726 * @param {string=} name Property name of the form under which the control is published. 16727 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16728 * interaction with the input element. 16729 * @param {string} ngValue Angular expression which sets the value to which the expression should 16730 * be set when selected. 16731 * 16732 * @example 16733 <example name="radio-input-directive"> 16734 <file name="index.html"> 16735 <script> 16736 function Ctrl($scope) { 16737 $scope.color = 'blue'; 16738 $scope.specialValue = { 16739 "id": "12345", 16740 "value": "green" 16741 }; 16742 } 16743 </script> 16744 <form name="myForm" ng-controller="Ctrl"> 16745 <input type="radio" ng-model="color" value="red"> Red <br/> 16746 <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/> 16747 <input type="radio" ng-model="color" value="blue"> Blue <br/> 16748 <tt>color = {{color | json}}</tt><br/> 16749 </form> 16750 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 16751 </file> 16752 <file name="protractor.js" type="protractor"> 16753 it('should change state', function() { 16754 var color = element(by.binding('color')); 16755 16756 expect(color.getText()).toContain('blue'); 16757 16758 element.all(by.model('color')).get(0).click(); 16759 16760 expect(color.getText()).toContain('red'); 16761 }); 16762 </file> 16763 </example> 16764 */ 16765 'radio': radioInputType, 16766 16767 16768 /** 16769 * @ngdoc input 16770 * @name input[checkbox] 16771 * 16772 * @description 16773 * HTML checkbox. 16774 * 16775 * @param {string} ngModel Assignable angular expression to data-bind to. 16776 * @param {string=} name Property name of the form under which the control is published. 16777 * @param {string=} ngTrueValue The value to which the expression should be set when selected. 16778 * @param {string=} ngFalseValue The value to which the expression should be set when not selected. 16779 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16780 * interaction with the input element. 16781 * 16782 * @example 16783 <example name="checkbox-input-directive"> 16784 <file name="index.html"> 16785 <script> 16786 function Ctrl($scope) { 16787 $scope.value1 = true; 16788 $scope.value2 = 'YES' 16789 } 16790 </script> 16791 <form name="myForm" ng-controller="Ctrl"> 16792 Value1: <input type="checkbox" ng-model="value1"> <br/> 16793 Value2: <input type="checkbox" ng-model="value2" 16794 ng-true-value="YES" ng-false-value="NO"> <br/> 16795 <tt>value1 = {{value1}}</tt><br/> 16796 <tt>value2 = {{value2}}</tt><br/> 16797 </form> 16798 </file> 16799 <file name="protractor.js" type="protractor"> 16800 it('should change state', function() { 16801 var value1 = element(by.binding('value1')); 16802 var value2 = element(by.binding('value2')); 16803 16804 expect(value1.getText()).toContain('true'); 16805 expect(value2.getText()).toContain('YES'); 16806
16807 element(by.model('value1')).click(); 16808 element(by.model('value2')).click(); 16809 16810 expect(value1.getText()).toContain('false'); 16811 expect(value2.getText()).toContain('NO'); 16812 }); 16813 </file> 16814 </example> 16815 */ 16816 'checkbox': checkboxInputType, 16817 16818 'hidden': noop, 16819 'button': noop, 16820 'submit': noop, 16821 'reset': noop, 16822 'file': noop 16823}; 16824 16825// A helper function to call $setValidity and return the value / undefined, 16826// a pattern that is repeated a lot in the input validation logic. 16827function validate(ctrl, validatorName, validity, value){ 16828 ctrl.$setValidity(validatorName, validity); 16829 return validity ? value : undefined; 16830} 16831 16832function testFlags(validity, flags) { 16833 var i, flag; 16834 if (flags) { 16835 for (i=0; i<flags.length; ++i) { 16836 flag = flags[i]; 16837 if (validity[flag]) { 16838 return true; 16839 } 16840 } 16841 } 16842 return false; 16843} 16844 16845// Pass validity so that behaviour can be mocked easier. 16846function addNativeHtml5Validators(ctrl, validatorName, badFlags, ignoreFlags, validity) { 16847 if (isObject(validity)) { 16848 ctrl.$$hasNativeValidators = true; 16849 var validator = function(value) { 16850 // Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can 16851 // perform the required validation) 16852 if (!ctrl.$error[validatorName] && 16853 !testFlags(validity, ignoreFlags) && 16854 testFlags(validity, badFlags)) { 16855 ctrl.$setValidity(validatorName, false); 16856 return; 16857 } 16858 return value; 16859 }; 16860 ctrl.$parsers.push(validator); 16861 } 16862} 16863 16864function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16865 var validity = element.prop(VALIDITY_STATE_PROPERTY); 16866 var placeholder = element[0].placeholder, noevent = {}; 16867 ctrl.$$validityState = validity; 16868 16869 // In composition mode, users are still inputing intermediate text buffer, 16870 // hold the listener until composition is done. 16871 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 16872 if (!$sniffer.android) { 16873 var composing = false; 16874 16875 element.on('compositionstart', function(data) { 16876 composing = true; 16877 }); 16878 16879 element.on('compositionend', function() { 16880 composing = false; 16881 listener(); 16882 }); 16883 } 16884 16885 var listener = function(ev) { 16886 if (composing) return; 16887 var value = element.val(); 16888 16889 // IE (11 and under) seem to emit an 'input' event if the placeholder value changes. 16890 // We don't want to dirty the value when this happens, so we abort here. Unfortunately, 16891 // IE also sends input events for other non-input-related things, (such as focusing on a 16892 // form control), so this change is not entirely enough to solve this. 16893 if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) { 16894 placeholder = element[0].placeholder; 16895 return; 16896 } 16897 16898 // By default we will trim the value 16899 // If the attribute ng-trim exists we will avoid trimming 16900 // e.g. <input ng-model="foo" ng-trim="false"> 16901 if (toBoolean(attr.ngTrim || 'T')) { 16902 value = trim(value); 16903 } 16904 16905 // If a control is suffering from bad input, browsers discard its value, so it may be 16906 // necessary to revalidate even if the control's value is the same empty value twice in 16907 // a row. 16908 var revalidate = validity && ctrl.$$hasNativeValidators; 16909 if (ctrl.$viewValue !== value || (value === '' && revalidate)) { 16910 if (scope.$$phase) { 16911 ctrl.$setViewValue(value); 16912 } else { 16913 scope.$apply(function() { 16914 ctrl.$setViewValue(value); 16915 }); 16916 } 16917 } 16918 }; 16919 16920 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 16921 // input event on backspace, delete or cut 16922 if ($sniffer.hasEvent('input')) { 16923 element.on('input', listener); 16924 } else { 16925 var timeout; 16926 16927 var deferListener = function() { 16928 if (!timeout) { 16929 timeout = $browser.defer(function() { 16930 listener(); 16931 timeout = null; 16932 }); 16933 } 16934 }; 16935 16936 element.on('keydown', function(event) { 16937 var key = event.keyCode; 16938 16939 // ignore 16940 // command modifiers arrows 16941 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 16942 16943 deferListener(); 16944 });
16945 16946 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 16947 if ($sniffer.hasEvent('paste')) { 16948 element.on('paste cut', deferListener); 16949 } 16950 } 16951 16952 // if user paste into input using mouse on older browser 16953 // or form autocomplete on newer browser, we need "change" event to catch it 16954 element.on('change', listener); 16955 16956 ctrl.$render = function() { 16957 element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); 16958 }; 16959 16960 // pattern validator 16961 var pattern = attr.ngPattern, 16962 patternValidator, 16963 match; 16964 16965 if (pattern) { 16966 var validateRegex = function(regexp, value) { 16967 return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value); 16968 }; 16969 match = pattern.match(/^\/(.*)\/([gim]*)$/); 16970 if (match) { 16971 pattern = new RegExp(match[1], match[2]); 16972 patternValidator = function(value) { 16973 return validateRegex(pattern, value); 16974 }; 16975 } else { 16976 patternValidator = function(value) { 16977 var patternObj = scope.$eval(pattern); 16978 16979 if (!patternObj || !patternObj.test) { 16980 throw minErr('ngPattern')('noregexp', 16981 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, 16982 patternObj, startingTag(element)); 16983 } 16984 return validateRegex(patternObj, value); 16985 }; 16986 } 16987 16988 ctrl.$formatters.push(patternValidator); 16989 ctrl.$parsers.push(patternValidator); 16990 } 16991 16992 // min length validator 16993 if (attr.ngMinlength) { 16994 var minlength = int(attr.ngMinlength); 16995 var minLengthValidator = function(value) { 16996 return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value); 16997 }; 16998 16999 ctrl.$parsers.push(minLengthValidator); 17000 ctrl.$formatters.push(minLengthValidator); 17001 } 17002 17003 // max length validator 17004 if (attr.ngMaxlength) { 17005 var maxlength = int(attr.ngMaxlength); 17006 var maxLengthValidator = function(value) { 17007 return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value); 17008 }; 17009 17010 ctrl.$parsers.push(maxLengthValidator); 17011 ctrl.$formatters.push(maxLengthValidator); 17012 } 17013} 17014 17015var numberBadFlags = ['badInput']; 17016 17017function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 17018 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 17019 17020 ctrl.$parsers.push(function(value) { 17021 var empty = ctrl.$isEmpty(value); 17022 if (empty || NUMBER_REGEXP.test(value)) { 17023 ctrl.$setValidity('number', true); 17024 return value === '' ? null : (empty ? value : parseFloat(value)); 17025 } else { 17026 ctrl.$setValidity('number', false); 17027 return undefined; 17028 } 17029 }); 17030 17031 addNativeHtml5Validators(ctrl, 'number', numberBadFlags, null, ctrl.$$validityState); 17032 17033 ctrl.$formatters.push(function(value) { 17034 return ctrl.$isEmpty(value) ? '' : '' + value; 17035 }); 17036 17037 if (attr.min) { 17038 var minValidator = function(value) { 17039 var min = parseFloat(attr.min); 17040 return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); 17041 }; 17042 17043 ctrl.$parsers.push(minValidator); 17044 ctrl.$formatters.push(minValidator); 17045 } 17046 17047 if (attr.max) { 17048 var maxValidator = function(value) { 17049 var max = parseFloat(attr.max); 17050 return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); 17051 }; 17052 17053 ctrl.$parsers.push(maxValidator); 17054 ctrl.$formatters.push(maxValidator); 17055 } 17056 17057 ctrl.$formatters.push(function(value) { 17058 return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); 17059 }); 17060} 17061 17062function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 17063 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 17064 17065 var urlValidator = function(value) { 17066 return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); 17067 }; 17068 17069 ctrl.$formatters.push(urlValidator); 17070 ctrl.$parsers.push(urlValidator); 17071} 17072 17073function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 17074 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 17075 17076 var emailValidator = function(value) { 17077 return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); 17078 }; 17079 17080 ctrl.$formatters.push(emailValidator);
17081 ctrl.$parsers.push(emailValidator); 17082} 17083 17084function radioInputType(scope, element, attr, ctrl) { 17085 // make the name unique, if not defined 17086 if (isUndefined(attr.name)) { 17087 element.attr('name', nextUid()); 17088 } 17089 17090 element.on('click', function() { 17091 if (element[0].checked) { 17092 scope.$apply(function() { 17093 ctrl.$setViewValue(attr.value); 17094 }); 17095 } 17096 }); 17097 17098 ctrl.$render = function() { 17099 var value = attr.value; 17100 element[0].checked = (value == ctrl.$viewValue); 17101 }; 17102 17103 attr.$observe('value', ctrl.$render); 17104} 17105 17106function checkboxInputType(scope, element, attr, ctrl) { 17107 var trueValue = attr.ngTrueValue, 17108 falseValue = attr.ngFalseValue; 17109 17110 if (!isString(trueValue)) trueValue = true; 17111 if (!isString(falseValue)) falseValue = false; 17112 17113 element.on('click', function() { 17114 scope.$apply(function() { 17115 ctrl.$setViewValue(element[0].checked); 17116 }); 17117 }); 17118 17119 ctrl.$render = function() { 17120 element[0].checked = ctrl.$viewValue; 17121 }; 17122 17123 // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. 17124 ctrl.$isEmpty = function(value) { 17125 return value !== trueValue; 17126 }; 17127 17128 ctrl.$formatters.push(function(value) { 17129 return value === trueValue; 17130 }); 17131 17132 ctrl.$parsers.push(function(value) { 17133 return value ? trueValue : falseValue; 17134 }); 17135} 17136 17137 17138/** 17139 * @ngdoc directive 17140 * @name textarea 17141 * @restrict E 17142 * 17143 * @description 17144 * HTML textarea element control with angular data-binding. The data-binding and validation 17145 * properties of this element are exactly the same as those of the 17146 * {@link ng.directive:input input element}. 17147 * 17148 * @param {string} ngModel Assignable angular expression to data-bind to. 17149 * @param {string=} name Property name of the form under which the control is published. 17150 * @param {string=} required Sets `required` validation error key if the value is not entered. 17151 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 17152 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 17153 * `required` when you want to data-bind to the `required` attribute. 17154 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 17155 * minlength. 17156 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 17157 * maxlength. 17158 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 17159 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 17160 * patterns defined as scope expressions. 17161 * @param {string=} ngChange Angular expression to be executed when input changes due to user 17162 * interaction with the input element. 17163 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 17164 */ 17165 17166 17167/** 17168 * @ngdoc directive 17169 * @name input 17170 * @restrict E 17171 * 17172 * @description 17173 * HTML input element control with angular data-binding. Input control follows HTML5 input types 17174 * and polyfills the HTML5 validation behavior for older browsers. 17175 * 17176 * @param {string} ngModel Assignable angular expression to data-bind to. 17177 * @param {string=} name Property name of the form under which the control is published. 17178 * @param {string=} required Sets `required` validation error key if the value is not entered. 17179 * @param {boolean=} ngRequired Sets `required` attribute if set to true 17180 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 17181 * minlength. 17182 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 17183 * maxlength. 17184 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 17185 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 17186 * patterns defined as scope expressions. 17187 * @param {string=} ngChange Angular expression to be executed when input changes due to user 17188 * interaction with the input element. 17189 * 17190 * @example 17191 <example name="input-directive"> 17192 <file name="index.html"> 17193 <script> 17194 function Ctrl($scope) { 17195 $scope.user = {name: 'guest', last: 'visitor'}; 17196 } 17197 </script> 17198 <div ng-controller="Ctrl"> 17199 <form name="myForm"> 17200 User name: <input type="text" name="userName" ng-model="user.n
17200ame" required> 17201 <span class="error" ng-show="myForm.userName.$error.required"> 17202 Required!</span><br> 17203 Last name: <input type="text" name="lastName" ng-model="user.last" 17204 ng-minlength="3" ng-maxlength="10"> 17205 <span class="error" ng-show="myForm.lastName.$error.minlength"> 17206 Too short!</span> 17207 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 17208 Too long!</span><br> 17209 </form> 17210 <hr> 17211 <tt>user = {{user}}</tt><br/> 17212 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br> 17213 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br> 17214 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br> 17215 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br> 17216 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 17217 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 17218 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br> 17219 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br> 17220 </div> 17221 </file> 17222 <file name="protractor.js" type="protractor"> 17223 var user = element(by.binding('{{user}}')); 17224 var userNameValid = element(by.binding('myForm.userName.$valid')); 17225 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 17226 var lastNameError = element(by.binding('myForm.lastName.$error')); 17227 var formValid = element(by.binding('myForm.$valid')); 17228 var userNameInput = element(by.model('user.name')); 17229 var userLastInput = element(by.model('user.last')); 17230 17231 it('should initialize to model', function() {
17232 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 17233 expect(userNameValid.getText()).toContain('true'); 17234 expect(formValid.getText()).toContain('true'); 17235 }); 17236 17237 it('should be invalid if empty when required', function() { 17238 userNameInput.clear(); 17239 userNameInput.sendKeys(''); 17240 17241 expect(user.getText()).toContain('{"last":"visitor"}'); 17242 expect(userNameValid.getText()).toContain('false'); 17243 expect(formValid.getText()).toContain('false'); 17244 }); 17245 17246 it('should be valid if empty when min length is set', function() { 17247 userLastInput.clear(); 17248 userLastInput.sendKeys(''); 17249 17250 expect(user.getText()).toContain('{"name":"guest","last":""}'); 17251 expect(lastNameValid.getText()).toContain('true'); 17252 expect(formValid.getText()).toContain('true'); 17253 }); 17254 17255 it('should be invalid if less than required min length', function() { 17256 userLastInput.clear(); 17257 userLastInput.sendKeys('xx'); 17258
17259 expect(user.getText()).toContain('{"name":"guest"}'); 17260 expect(lastNameValid.getText()).toContain('false'); 17261 expect(lastNameError.getText()).toContain('minlength'); 17262 expect(formValid.getText()).toContain('false'); 17263 }); 17264 17265 it('should be invalid if longer than max length', function() { 17266 userLastInput.clear(); 17267 userLastInput.sendKeys('some ridiculously long name'); 17268 17269 expect(user.getText()).toContain('{"name":"guest"}'); 17270 expect(lastNameValid.getText()).toContain('false'); 17271 expect(lastNameError.getText()).toContain('maxlength'); 17272 expect(formValid.getText()).toContain('false'); 17273 }); 17274 </file> 17275 </example> 17276 */ 17277var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { 17278 return { 17279 restrict: 'E', 17280 require: '?ngModel', 17281 link: function(scope, element, attr, ctrl) { 17282 if (ctrl) { 17283 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, 17284 $browser); 17285 } 17286 } 17287 }; 17288}]; 17289 17290var VALID_CLASS = 'ng-valid', 17291 INVALID_CLASS = 'ng-invalid', 17292 PRISTINE_CLASS = 'ng-pristine', 17293 DIRTY_CLASS = 'ng-dirty'; 17294 17295/** 17296 * @ngdoc type 17297 * @name ngModel.NgModelController 17298 * 17299 * @property {string} $viewValue Actual string value in the view. 17300 * @property {*} $modelValue The value in the model, that the control is bound to. 17301 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 17302 the control reads value from the DOM. Each function is called, in turn, passing the value 17303 through to the next. The last return value is used to populate the model. 17304 Used to sanitize / convert the value as well as validation. For validation, 17305 the parsers should update the validity state using 17306 {@link ngModel.NgModelController#$setValidity $setValidity()}, 17307 and return `undefined` for invalid values. 17308 17309 * 17310 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 17311 the model value changes. Each function is called, in turn, passing the value through to the 17312 next. Used to format / convert values for display in the control and validation. 17313 * ```js 17314 * function formatter(value) { 17315 * if (value) { 17316 * return value.toUpperCase(); 17317 * } 17318 * } 17319 * ngModel.$formatters.push(formatter); 17320 * ``` 17321 * 17322 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 17323 * view value has changed. It is called with no arguments, and its return value is ignored. 17324 * This can be used in place of additional $watches against the model value. 17325 * 17326 * @property {Object} $error An object hash with all errors as keys. 17327 * 17328 * @property {boolean} $pristine True if user has not interacted with the control yet. 17329 * @property {boolean} $dirty True if user has already interacted with the control. 17330 * @property {boolean} $valid True if there is no error. 17331 * @property {boolean} $invalid True if at least one error on the control. 17332 * 17333 * @description 17334 * 17335 * `NgModelController` provides API for the `ng-model` directive. The controller contains 17336 * services for data-binding, validation, CSS updates, and value formatting and parsing. It 17337 * purposefully does not contain any logic which deals with DOM rendering or listening to 17338 * DOM events. Such DOM related logic should be provided by other directives which make use of 17339 * `NgModelController` for data-binding. 17340 * 17341 * ## Custom Control Example 17342 * This example shows how to use `NgModelController` with a custom control to achieve 17343 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 17344 * collaborate together to achieve the desired result. 17345 * 17346 * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element 17347 * contents be edited in place by the user. This will not work on older browsers. 17348 * 17349 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 17350 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 17351 * However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks 17352 * that content using the `$sce` service. 17353 * 17354 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 17355 <file name="style.css"> 17356 [contenteditable] { 17357 border: 1px solid black; 17358 background-color: white; 17359 min-height: 20px; 17360 } 17361 17362 .ng-invalid { 17363 border: 1px solid red; 17364 } 17365 17366 </file> 17367 <file name="script.js">
17368 angular.module('customControl', ['ngSanitize']). 17369 directive('contenteditable', ['$sce', function($sce) { 17370 return { 17371 restrict: 'A', // only activate on element attribute 17372 require: '?ngModel', // get a hold of NgModelController 17373 link: function(scope, element, attrs, ngModel) { 17374 if(!ngModel) return; // do nothing if no ng-model 17375 17376 // Specify how UI should be updated 17377 ngModel.$render = function() { 17378 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 17379 }; 17380 17381 // Listen for change events to enable binding 17382 element.on('blur keyup change', function() { 17383 scope.$apply(read); 17384 }); 17385 read(); // initialize 17386 17387 // Write data to the model 17388 function read() { 17389 var html = element.html(); 17390 // When we clear the content editable the browser leaves a <br> behind 17391 // If strip-br attribute is provided then we strip this out 17392 if( attrs.stripBr && html == '<br>' ) { 17393 html = ''; 17394 } 17395 ngModel.$setViewValue(html); 17396 } 17397 } 17398 }; 17399 }]); 17400 </file> 17401 <file name="index.html"> 17402 <form name="myForm"> 17403 <div contenteditable 17404 name="myWidget" ng-model="userContent" 17405 strip-br="true" 17406 required>Change me!</div> 17407 <span ng-show="myForm.myWidget.$error.required">Required!</span> 17408 <hr> 17409 <textarea ng-model="userContent"></textarea> 17410 </form> 17411 </file> 17412 <file name="protractor.js" type="protractor"> 17413 it('should data-bind and become invalid', function() { 17414 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 17415 // SafariDriver can't handle contenteditable 17416 // and Firefox driver can't clear contenteditables very well 17417 return; 17418 } 17419 var contentEditable = element(by.css('[contenteditable]')); 17420 var content = 'Change me!'; 17421 17422 expect(contentEditable.getText()).toEqual(content); 17423 17424 contentEditable.clear(); 17425 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 17426 expect(contentEditable.getText()).toEqual(''); 17427 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 17428 }); 17429 </file> 17430 * </example> 17431 * 17432 * 17433 */ 17434var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', 17435 function($scope, $exceptionHandler, $attr, $element, $parse, $animate) { 17436 this.$viewValue = Number.NaN; 17437 this.$modelValue = Number.NaN; 17438 this.$parsers = []; 17439 this.$formatters = []; 17440 this.$viewChangeListeners = []; 17441 this.$pristine = true; 17442 this.$dirty = false; 17443 this.$valid = true; 17444 this.$invalid = false; 17445 this.$name = $attr.name; 17446 17447 var ngModelGet = $parse($attr.ngModel), 17448 ngModelSet = ngModelGet.assign; 17449 17450 if (!ngModelSet) { 17451 throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 17452 $attr.ngModel, startingTag($element)); 17453 } 17454 17455 /** 17456 * @ngdoc method 17457 * @name ngModel.NgModelController#$render 17458 * 17459 * @description 17460 * Called when the view needs to be updated. It is expected that the user of the ng-model 17461 * directive will implement this method. 17462 */ 17463 this.$render = noop; 17464 17465 /** 17466 * @ngdoc method 17467 * @name ngModel.NgModelController#$isEmpty 17468 * 17469 * @description 17470 * This is called when we need to determine if the value of the input is empty. 17471 * 17472 * For instance, the required directive does this to work out if the input has data or not. 17473 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 17474 * 17475 * You can override this for input directives whose concept of being empty is different to the 17476 * default. The `checkboxInputType` directive does this because in its case a value of `false` 17477 * implies empty. 17478 * 17479 * @param {*} value Reference to check. 17480 * @returns {boolean} True if `value` is empty. 17481 */ 17482 this.$isEmpty = function(value) { 17483 return isUndefined(value) || value === '' || value === null || value !== value; 17484 }; 17485 17486 var parentForm = $element.inheritedData('$formController') || nullFormCtrl, 17487 invalidCount = 0, // used to easily determine if we are valid 17488 $error = this.$error = {};
17488 // keep invalid keys here 17489 17490 17491 // Setup initial state of the control 17492 $element.addClass(PRISTINE_CLASS); 17493 toggleValidCss(true); 17494 17495 // convenience method for easy toggling of classes 17496 function toggleValidCss(isValid, validationErrorKey) { 17497 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 17498 $animate.removeClass($element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); 17499 $animate.addClass($element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 17500 } 17501 17502 /** 17503 * @ngdoc method 17504 * @name ngModel.NgModelController#$setValidity 17505 * 17506 * @description 17507 * Change the validity state, and notifies the form when the control changes validity. (i.e. it 17508 * does not notify form if given validator is already marked as invalid). 17509 * 17510 * This method should be called by validators - i.e. the parser or formatter functions. 17511 * 17512 * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign 17513 * to `$error[validationErrorKey]=!isValid` so that it is available for data-binding. 17514 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 17515 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 17516 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 17517 * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). 17518 */ 17519 this.$setValidity = function(validationErrorKey, isValid) { 17520 // Purposeful use of ! here to cast isValid to boolean in case it is undefined 17521 // jshint -W018 17522 if ($error[validationErrorKey] === !isValid) return; 17523 // jshint +W018 17524 17525 if (isValid) { 17526 if ($error[validationErrorKey]) invalidCount--; 17527 if (!invalidCount) { 17528 toggleValidCss(true); 17529 this.$valid = true; 17530 this.$invalid = false; 17531 } 17532 } else { 17533 toggleValidCss(false); 17534 this.$invalid = true; 17535 this.$valid = false; 17536 invalidCount++; 17537 } 17538 17539 $error[validationErrorKey] = !isValid; 17540 toggleValidCss(isValid, validationErrorKey); 17541 17542 parentForm.$setValidity(validationErrorKey, isValid, this); 17543 }; 17544 17545 /** 17546 * @ngdoc method 17547 * @name ngModel.NgModelController#$setPristine 17548 * 17549 * @description 17550 * Sets the control to its pristine state. 17551 * 17552 * This method can be called to remove the 'ng-dirty' class and set the control to its pristine 17553 * state (ng-pristine class). 17554 */ 17555 this.$setPristine = function () { 17556 this.$dirty = false; 17557 this.$pristine = true; 17558 $animate.removeClass($element, DIRTY_CLASS); 17559 $animate.addClass($element, PRISTINE_CLASS); 17560 }; 17561 17562 /** 17563 * @ngdoc method 17564 * @name ngModel.NgModelController#$setViewValue 17565 * 17566 * @description 17567 * Update the view value. 17568 * 17569 * This method should be called when the view value changes, typically from within a DOM event handler. 17570 * For example {@link ng.directive:input input} and 17571 * {@link ng.directive:select select} directives call it. 17572 * 17573 * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, 17574 * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to 17575 * `$modelValue` and the **expression** specified in the `ng-model` attribute. 17576 * 17577 * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. 17578 * 17579 * Note that calling this function does not trigger a `$digest`. 17580 * 17581 * @param {string} value Value from the view. 17582 */ 17583 this.$setViewValue = function(value) { 17584 this.$viewValue = value; 17585 17586 // change to dirty 17587 if (this.$pristine) { 17588 this.$dirty = true; 17589 this.$pristine = false; 17590 $animate.removeClass($element, PRISTINE_CLASS); 17591 $animate.addClass($element, DIRTY_CLASS); 17592 parentForm.$setDirty(); 17593 } 17594 17595 forEach(this.$parsers, function(fn) { 17596 value = fn(value); 17597 }); 17598 17599 if (this.$modelValue !== value) { 17600 this.$modelValue = value; 17601 ngModelSet($scope, value);
17602 forEach(this.$viewChangeListeners, function(listener) { 17603 try { 17604 listener(); 17605 } catch(e) { 17606 $exceptionHandler(e); 17607 } 17608 }); 17609 } 17610 }; 17611 17612 // model -> value 17613 var ctrl = this; 17614 17615 $scope.$watch(function ngModelWatch() { 17616 var value = ngModelGet($scope); 17617 17618 // if scope model value and ngModel value are out of sync 17619 if (ctrl.$modelValue !== value) { 17620 17621 var formatters = ctrl.$formatters, 17622 idx = formatters.length; 17623 17624 ctrl.$modelValue = value; 17625 while(idx--) { 17626 value = formatters[idx](value); 17627 } 17628 17629 if (ctrl.$viewValue !== value) { 17630 ctrl.$viewValue = value; 17631 ctrl.$render(); 17632 } 17633 } 17634 17635 return value; 17636 }); 17637}]; 17638 17639 17640/** 17641 * @ngdoc directive 17642 * @name ngModel 17643 * 17644 * @element input 17645 * 17646 * @description 17647 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 17648 * property on the scope using {@link ngModel.NgModelController NgModelController}, 17649 * which is created and exposed by this directive. 17650 * 17651 * `ngModel` is responsible for: 17652 * 17653 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 17654 * require. 17655 * - Providing validation behavior (i.e. required, number, email, url). 17656 * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). 17657 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`) including animations. 17658 * - Registering the control with its parent {@link ng.directive:form form}. 17659 * 17660 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 17661 * current scope. If the property doesn't already exist on this scope, it will be created 17662 * implicitly and added to the scope. 17663 * 17664 * For best practices on using `ngModel`, see: 17665 * 17666 * - [https://github.com/angular/angular.js/wiki/Understanding-Scopes] 17667 * 17668 * For basic examples, how to use `ngModel`, see: 17669 * 17670 * - {@link ng.directive:input input} 17671 * - {@link input[text] text} 17672 * - {@link input[checkbox] checkbox} 17673 * - {@link input[radio] radio} 17674 * - {@link input[number] number} 17675 * - {@link input[email] email} 17676 * - {@link input[url] url} 17677 * - {@link ng.directive:select select} 17678 * - {@link ng.directive:textarea textarea} 17679 * 17680 * # CSS classes 17681 * The following CSS classes are added and removed on the associated input/select/textarea element 17682 * depending on the validity of the model. 17683 * 17684 * - `ng-valid` is set if the model is valid. 17685 * - `ng-invalid` is set if the model is invalid. 17686 * - `ng-pristine` is set if the model is pristine. 17687 * - `ng-dirty` is set if the model is dirty. 17688 * 17689 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 17690 * 17691 * ## Animation Hooks 17692 * 17693 * Animations within models are triggered when any of the associated CSS classes are added and removed 17694 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, 17695 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 17696 * The animations that are triggered within ngModel are similar to how they work in ngClass and 17697 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 17698 * 17699 * The following example shows a simple way to utilize CSS transitions to style an input element 17700 * that has been rendered as invalid after it has been validated: 17701 * 17702 * <pre> 17703 * //be sure to include ngAnimate as a module to hook into more 17704 * //advanced animations 17705 * .my-input { 17706 * transition:0.5s linear all; 17707 * background: white; 17708 * } 17709 * .my-input.ng-invalid { 17710 * background: red; 17711 * color:white; 17712 * } 17713 * </pre> 17714 * 17715 * @example 17716 * <example deps="angular-animate.js" animations="true" fixBase="true"> 17717 <file name="index.html"> 17718 <script> 17719 function Ctrl($scope) { 17720 $scope.val = '1'; 17721 } 17722 </script> 17723 <style> 17724 .my-input { 17725 -webkit-transition:all linear 0.5s; 17726 transition:all linear 0.5s; 17727 background: transparent; 17728 } 17729 .my-input.ng-invalid { 17730 color:white; 17731 background: red; 17732 } 17733 </style> 17734 Update input to see transitions when valid/invalid. 17735 Integer is a valid value. 17736 <form name="testForm" ng-controller="Ctrl"> 17737 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" /> 17738 </form> 17739 </file> 17740 * </example> 17741 */ 17742var ngModelDirective = function() { 17743 return { 17744 require: ['ngModel', '^?form'], 17745 controller: NgModelController, 17746 link: function(scope, element, attr, ctrls) { 17747 // notify others, especially parent forms 17748 17749 var modelCtrl = ctrls[0], 17750 formCtrl = ctrls[1] || nullFormCtrl; 17751 17752 formCtrl.$addControl(modelCtrl); 17753 17754 scope.$on('$destroy', function() { 17755 formCtrl.$removeControl(modelCtrl); 17756 }); 17757 } 17758 }; 17759};
17760 17761 17762/** 17763 * @ngdoc directive 17764 * @name ngChange 17765 * 17766 * @description 17767 * Evaluate the given expression when the user changes the input. 17768 * The expression is evaluated immediately, unlike the JavaScript onchange event 17769 * which only triggers at the end of a change (usually, when the user leaves the 17770 * form element or presses the return key). 17771 * The expression is not evaluated when the value change is coming from the model. 17772 * 17773 * Note, this directive requires `ngModel` to be present. 17774 * 17775 * @element input 17776 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 17777 * in input value. 17778 * 17779 * @example 17780 * <example name="ngChange-directive"> 17781 * <file name="index.html"> 17782 * <script> 17783 * function Controller($scope) { 17784 * $scope.counter = 0; 17785 * $scope.change = function() { 17786 * $scope.counter++; 17787 * }; 17788 * } 17789 * </script> 17790 * <div ng-controller="Controller"> 17791 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 17792 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 17793 * <label for="ng-change-example2">Confirmed</label><br /> 17794 * <tt>debug = {{confirmed}}</tt><br/> 17795 * <tt>counter = {{counter}}</tt><br/> 17796 * </div> 17797 * </file> 17798 * <file name="protractor.js" type="protractor"> 17799 * var counter = element(by.binding('counter')); 17800 * var debug = element(by.binding('confirmed')); 17801 * 17802 * it('should evaluate the expression if changing from view', function() { 17803 * expect(counter.getText()).toContain('0'); 17804 * 17805 * element(by.id('ng-change-example1')).click(); 17806 * 17807 * expect(counter.getText()).toContain('1'); 17808 * expect(debug.getText()).toContain('true'); 17809 * }); 17810 * 17811 * it('should not evaluate the expression if changing from model', function() { 17812 * element(by.id('ng-change-example2')).click(); 17813 17814 * expect(counter.getText()).toContain('0'); 17815 * expect(debug.getText()).toContain('true'); 17816 * }); 17817 * </file> 17818 * </example> 17819 */ 17820var ngChangeDirective = valueFn({ 17821 require: 'ngModel', 17822 link: function(scope, element, attr, ctrl) { 17823 ctrl.$viewChangeListeners.push(function() { 17824 scope.$eval(attr.ngChange); 17825 }); 17826 } 17827}); 17828 17829 17830var requiredDirective = function() { 17831 return { 17832 require: '?ngModel', 17833 link: function(scope, elm, attr, ctrl) { 17834 if (!ctrl) return; 17835 attr.required = true; // force truthy in case we are on non input element 17836 17837 var validator = function(value) { 17838 if (attr.required && ctrl.$isEmpty(value)) { 17839 ctrl.$setValidity('required', false); 17840 return; 17841 } else { 17842 ctrl.$setValidity('required', true); 17843 return value; 17844 } 17845 }; 17846 17847 ctrl.$formatters.push(validator); 17848 ctrl.$parsers.unshift(validator); 17849 17850 attr.$observe('required', function() { 17851 validator(ctrl.$viewValue); 17852 }); 17853 } 17854 }; 17855}; 17856 17857 17858/** 17859 * @ngdoc directive 17860 * @name ngList 17861 * 17862 * @description 17863 * Text input that converts between a delimited string and an array of strings. The delimiter 17864 * can be a fixed string (by default a comma) or a regular expression. 17865 * 17866 * @element input 17867 * @param {string=} ngList optional delimiter that should be used to split the value. If 17868 * specified in form `/something/` then the value will be converted into a regular expression. 17869 * 17870 * @example 17871 <example name="ngList-directive"> 17872 <file name="index.html"> 17873 <script> 17874 function Ctrl($scope) { 17875 $scope.names = ['igor', 'misko', 'vojta']; 17876 } 17877 </script> 17878 <form name="myForm" ng-controller="Ctrl"> 17879 List: <input name="namesInput" ng-model="names" ng-list required> 17880 <span class="error" ng-show="myForm.namesInput.$error.required"> 17881 Required!</span> 17882 <br> 17883 <tt>names = {{names}}</tt><br/> 17884 <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 17885 <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 17886 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 17887 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 17888 </form> 17889 </file> 17890 <file name="protractor.js" type="protractor"> 17891 var listInput = element(by.model('names')); 17892 var names = element(by.binding('{{names}}'));
17893 var valid = element(by.binding('myForm.namesInput.$valid')); 17894 var error = element(by.css('span.error')); 17895 17896 it('should initialize to model', function() { 17897 expect(names.getText()).toContain('["igor","misko","vojta"]'); 17898 expect(valid.getText()).toContain('true'); 17899 expect(error.getCssValue('display')).toBe('none'); 17900 }); 17901 17902 it('should be invalid if empty', function() { 17903 listInput.clear(); 17904 listInput.sendKeys(''); 17905 17906 expect(names.getText()).toContain(''); 17907 expect(valid.getText()).toContain('false'); 17908 expect(error.getCssValue('display')).not.toBe('none'); }); 17909 </file> 17910 </example> 17911 */ 17912var ngListDirective = function() { 17913 return { 17914 require: 'ngModel', 17915 link: function(scope, element, attr, ctrl) { 17916 var match = /\/(.*)\//.exec(attr.ngList), 17917 separator = match && new RegExp(match[1]) || attr.ngList || ','; 17918 17919 var parse = function(viewValue) { 17920 // If the viewValue is invalid (say required but empty) it will be `undefined` 17921 if (isUndefined(viewValue)) return; 17922 17923 var list = []; 17924 17925 if (viewValue) { 17926 forEach(viewValue.split(separator), function(value) { 17927 if (value) list.push(trim(value)); 17928 }); 17929 } 17930 17931 return list; 17932 }; 17933 17934 ctrl.$parsers.push(parse); 17935 ctrl.$formatters.push(function(value) { 17936 if (isArray(value)) { 17937 return value.join(', '); 17938 } 17939 17940 return undefined; 17941 }); 17942 17943 // Override the standard $isEmpty because an empty array means the input is empty. 17944 ctrl.$isEmpty = function(value) { 17945 return !value || !value.length; 17946 }; 17947 } 17948 }; 17949}; 17950 17951 17952var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 17953/** 17954 * @ngdoc directive 17955 * @name ngValue 17956 * 17957 * @description 17958 * Binds the given expression to the value of `input[select]` or `input[radio]`, so 17959 * that when the element is selected, the `ngModel` of that element is set to the 17960 * bound value. 17961 * 17962 * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as 17963 * shown below. 17964 * 17965 * @element input 17966 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 17967 * of the `input` element 17968 * 17969 * @example 17970 <example name="ngValue-directive"> 17971 <file name="index.html"> 17972 <script> 17973 function Ctrl($scope) { 17974 $scope.names = ['pizza', 'unicorns', 'robots']; 17975 $scope.my = { favorite: 'unicorns' }; 17976 } 17977 </script> 17978 <form ng-controller="Ctrl"> 17979 <h2>Which is your favorite?</h2> 17980 <label ng-repeat="name in names" for="{{name}}"> 17981 {{name}} 17982 <input type="radio" 17983 ng-model="my.favorite" 17984 ng-value="name" 17985 id="{{name}}" 17986 name="favorite"> 17987 </label> 17988 <div>You chose {{my.favorite}}</div> 17989 </form> 17990 </file> 17991 <file name="protractor.js" type="protractor"> 17992 var favorite = element(by.binding('my.favorite')); 17993 17994 it('should initialize to model', function() { 17995 expect(favorite.getText()).toContain('unicorns'); 17996 }); 17997 it('should bind the values to the inputs', function() { 17998 element.all(by.model('my.favorite')).get(0).click(); 17999 expect(favorite.getText()).toContain('pizza'); 18000 }); 18001 </file> 18002 </example> 18003 */ 18004var ngValueDirective = function() { 18005 return { 18006 priority: 100, 18007 compile: function(tpl, tplAttr) { 18008 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 18009 return function ngValueConstantLink(scope, elm, attr) { 18010 attr.$set('value', scope.$eval(attr.ngValue)); 18011 }; 18012 } else { 18013 return function ngValueLink(scope, elm, attr) { 18014 scope.$watch(attr.ngValue, function valueWatchAction(value) { 18015 attr.$set('value', value); 18016 }); 18017 }; 18018 } 18019 } 18020 }; 18021};
18022 18023/** 18024 * @ngdoc directive 18025 * @name ngBind 18026 * @restrict AC 18027 * 18028 * @description 18029 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 18030 * with the value of a given expression, and to update the text content when the value of that 18031 * expression changes. 18032 * 18033 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 18034 * `{{ expression }}` which is similar but less verbose. 18035 * 18036 * It is preferable to use `ngBind` instead of `{{ expression }}` when a template is momentarily 18037 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 18038 * element attribute, it makes the bindings invisible to the user while the page is loading. 18039 * 18040 * An alternative solution to this problem would be using the 18041 * {@link ng.directive:ngCloak ngCloak} directive. 18042 * 18043 * 18044 * @element ANY 18045 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 18046 * 18047 * @example 18048 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 18049 <example> 18050 <file name="index.html"> 18051 <script> 18052 function Ctrl($scope) { 18053 $scope.name = 'Whirled'; 18054 } 18055 </script> 18056 <div ng-controller="Ctrl"> 18057 Enter name: <input type="text" ng-model="name"><br> 18058 Hello <span ng-bind="name"></span>! 18059 </div> 18060 </file> 18061 <file name="protractor.js" type="protractor"> 18062 it('should check ng-bind', function() { 18063 var nameInput = element(by.model('name')); 18064 18065 expect(element(by.binding('name')).getText()).toBe('Whirled'); 18066 nameInput.clear(); 18067 nameInput.sendKeys('world'); 18068 expect(element(by.binding('name')).getText()).toBe('world'); 18069 }); 18070 </file> 18071 </example> 18072 */ 18073var ngBindDirective = ngDirective({ 18074 compile: function(templateElement) { 18075 templateElement.addClass('ng-binding'); 18076 return function (scope, element, attr) { 18077 element.data('$binding', attr.ngBind); 18078 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 18079 // We are purposefully using == here rather than === because we want to 18080 // catch when value is "null or undefined" 18081 // jshint -W041 18082 element.text(value == undefined ? '' : value); 18083 }); 18084 }; 18085 } 18086}); 18087 18088 18089/** 18090 * @ngdoc directive 18091 * @name ngBindTemplate 18092 * 18093 * @description 18094 * The `ngBindTemplate` directive specifies that the element 18095 * text content should be replaced with the interpolation of the template 18096 * in the `ngBindTemplate` attribute. 18097 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 18098 * expressions. This directive is needed since some HTML elements 18099 * (such as TITLE and OPTION) cannot contain SPAN elements. 18100 * 18101 * @element ANY 18102 * @param {string} ngBindTemplate template of form 18103 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 18104 * 18105 * @example 18106 * Try it here: enter text in text box and watch the greeting change. 18107 <example> 18108 <file name="index.html"> 18109 <script> 18110 function Ctrl($scope) { 18111 $scope.salutation = 'Hello'; 18112 $scope.name = 'World'; 18113 } 18114 </script> 18115 <div ng-controller="Ctrl"> 18116 Salutation: <input type="text" ng-model="salutation"><br> 18117 Name: <input type="text" ng-model="name"><br> 18118 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 18119 </div> 18120 </file> 18121 <file name="protractor.js" type="protractor"> 18122 it('should check ng-bind', function() { 18123 var salutationElem = element(by.binding('salutation')); 18124 var salutationInput = element(by.model('salutation')); 18125 var nameInput = element(by.model('name')); 18126 18127 expect(salutationElem.getText()).toBe('Hello World!'); 18128 18129 salutationInput.clear(); 18130 salutationInput.sendKeys('Greetings'); 18131 nameInput.clear(); 18132 nameInput.sendKeys('user'); 18133 18134 expect(salutationElem.getText()).toBe('Greetings user!'); 18135 }); 18136 </file> 18137 </example> 18138 */ 18139var ngBindTemplateDirective = ['$interpolate', function($interpolate) { 18140 return function(scope, element, attr) { 18141 // TODO: move this to scenario runner 18142 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 18143 element.addClass('ng-binding').data('$binding', interpolateFn); 18144 attr.$observe('ngBindTemplate', function(value) { 18145 element.text(value); 18146 });
18147 }; 18148}]; 18149 18150 18151/** 18152 * @ngdoc directive 18153 * @name ngBindHtml 18154 * 18155 * @description 18156 * Creates a binding that will innerHTML the result of evaluating the `expression` into the current 18157 * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link 18158 * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` 18159 * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in 18160 * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to 18161 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 18162 * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. 18163 * 18164 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 18165 * will have an exception (instead of an exploit.) 18166 * 18167 * @element ANY 18168 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 18169 * 18170 * @example 18171 Try it here: enter text in text box and watch the greeting change. 18172 18173 <example module="ngBindHtmlExample" deps="angular-sanitize.js"> 18174 <file name="index.html"> 18175 <div ng-controller="ngBindHtmlCtrl"> 18176 <p ng-bind-html="myHTML"></p> 18177 </div> 18178 </file> 18179 18180 <file name="script.js"> 18181 angular.module('ngBindHtmlExample', ['ngSanitize']) 18182 18183 .controller('ngBindHtmlCtrl', ['$scope', function ngBindHtmlCtrl($scope) { 18184 $scope.myHTML = 18185 'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>'; 18186 }]); 18187 </file> 18188 18189 <file name="protractor.js" type="protractor"> 18190 it('should check ng-bind-html', function() { 18191 expect(element(by.binding('myHTML')).getText()).toBe( 18192 'I am an HTMLstring with links! and other stuff'); 18193 }); 18194 </file> 18195 </example> 18196 */ 18197var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { 18198 return function(scope, element, attr) { 18199 element.addClass('ng-binding').data('$binding', attr.ngBindHtml); 18200 18201 var parsed = $parse(attr.ngBindHtml); 18202 function getStringValue() { return (parsed(scope) || '').toString(); } 18203 18204 scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { 18205 element.html($sce.getTrustedHtml(parsed(scope)) || ''); 18206 }); 18207 }; 18208}]; 18209 18210function classDirective(name, selector) { 18211 name = 'ngClass' + name; 18212 return ['$animate', function($animate) { 18213 return { 18214 restrict: 'AC', 18215 link: function(scope, element, attr) { 18216 var oldVal; 18217 18218 scope.$watch(attr[name], ngClassWatchAction, true); 18219 18220 attr.$observe('class', function(value) { 18221 ngClassWatchAction(scope.$eval(attr[name])); 18222 }); 18223 18224 18225 if (name !== 'ngClass') { 18226 scope.$watch('$index', function($index, old$index) { 18227 // jshint bitwise: false 18228 var mod = $index & 1; 18229 if (mod !== (old$index & 1)) { 18230 var classes = arrayClasses(scope.$eval(attr[name])); 18231 mod === selector ? 18232 addClasses(classes) : 18233 removeClasses(classes); 18234 } 18235 }); 18236 } 18237 18238 function addClasses(classes) { 18239 var newClasses = digestClassCounts(classes, 1); 18240 attr.$addClass(newClasses); 18241 } 18242 18243 function removeClasses(classes) { 18244 var newClasses = digestClassCounts(classes, -1); 18245 attr.$removeClass(newClasses); 18246 } 18247 18248 function digestClassCounts (classes, count) { 18249 var classCounts = element.data('$classCounts') || {}; 18250 var classesToUpdate = []; 18251 forEach(classes, function (className) { 18252 if (count > 0 || classCounts[className]) { 18253 classCounts[className] = (classCounts[className] || 0) + count; 18254 if (classCounts[className] === +(count > 0)) { 18255 classesToUpdate.push(className); 18256 } 18257 } 18258 }); 18259 element.data('$classCounts', classCounts); 18260 return classesToUpdate.join(' '); 18261 } 18262
18263 function updateClasses (oldClasses, newClasses) { 18264 var toAdd = arrayDifference(newClasses, oldClasses); 18265 var toRemove = arrayDifference(oldClasses, newClasses); 18266 toRemove = digestClassCounts(toRemove, -1); 18267 toAdd = digestClassCounts(toAdd, 1); 18268 18269 if (toAdd.length === 0) { 18270 $animate.removeClass(element, toRemove); 18271 } else if (toRemove.length === 0) { 18272 $animate.addClass(element, toAdd); 18273 } else { 18274 $animate.setClass(element, toAdd, toRemove); 18275 } 18276 } 18277 18278 function ngClassWatchAction(newVal) { 18279 if (selector === true || scope.$index % 2 === selector) { 18280 var newClasses = arrayClasses(newVal || []); 18281 if (!oldVal) { 18282 addClasses(newClasses); 18283 } else if (!equals(newVal,oldVal)) { 18284 var oldClasses = arrayClasses(oldVal); 18285 updateClasses(oldClasses, newClasses); 18286 } 18287 } 18288 oldVal = shallowCopy(newVal); 18289 } 18290 } 18291 }; 18292 18293 function arrayDifference(tokens1, tokens2) { 18294 var values = []; 18295 18296 outer: 18297 for(var i = 0; i < tokens1.length; i++) { 18298 var token = tokens1[i]; 18299 for(var j = 0; j < tokens2.length; j++) { 18300 if(token == tokens2[j]) continue outer; 18301 } 18302 values.push(token); 18303 } 18304 return values; 18305 } 18306 18307 function arrayClasses (classVal) { 18308 if (isArray(classVal)) { 18309 return classVal; 18310 } else if (isString(classVal)) { 18311 return classVal.split(' '); 18312 } else if (isObject(classVal)) { 18313 var classes = [], i = 0; 18314 forEach(classVal, function(v, k) { 18315 if (v) { 18316 classes = classes.concat(k.split(' ')); 18317 } 18318 }); 18319 return classes; 18320 } 18321 return classVal; 18322 } 18323 }]; 18324} 18325 18326/** 18327 * @ngdoc directive 18328 * @name ngClass 18329 * @restrict AC 18330 * 18331 * @description 18332 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 18333 * an expression that represents all classes to be added. 18334 * 18335 * The directive operates in three different ways, depending on which of three types the expression 18336 * evaluates to: 18337 * 18338 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 18339 * names. 18340 * 18341 * 2. If the expression evaluates to an array, each element of the array should be a string that is 18342 * one or more space-delimited class names. 18343 * 18344 * 3. If the expression evaluates to an object, then for each key-value pair of the 18345 * object with a truthy value the corresponding key is used as a class name. 18346 * 18347 * The directive won't add duplicate classes if a particular class was already set. 18348 * 18349 * When the expression changes, the previously added classes are removed and only then the 18350 * new classes are added. 18351 * 18352 * @animations 18353 * add - happens just before the class is applied to the element 18354 * remove - happens just before the class is removed from the element 18355 * 18356 * @element ANY 18357 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 18358 * of the evaluation can be a string representing space delimited class 18359 * names, an array, or a map of class names to boolean values. In the case of a map, the 18360 * names of the properties whose values are truthy will be added as css classes to the 18361 * element. 18362 * 18363 * @example Example that demonstrates basic bindings via ngClass directive. 18364 <example> 18365 <file name="index.html"> 18366 <p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p> 18367 <input type="checkbox" ng-model="deleted"> deleted (apply "strike" class)<br> 18368 <input type="checkbox" ng-model="important"> important (apply "bold" class)<br> 18369 <input type="checkbox" ng-model="error"> error (apply "red" class) 18370 <hr> 18371 <p ng-class="style">Using String Syntax</p> 18372 <input type="text" ng-model="style" placeholder="Type: bold strike red"> 18373 <hr> 18374 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 18375 <input ng-model="style1" placeholder="Type: bold, strike or red"><br> 18376 <input ng-model="style2" placeholder="Type: bold, strike or red"><br> 18377 <input ng-model="style3" placeholder="Type: bold, strike or red"><br> 18378 </file> 18379 <file name="style.css"> 18380 .strike { 18381 text-decoration: line-through; 18382 } 18383 .bold { 18384 font-weight: bold; 18385 } 18386 .red { 18387 color: red; 18388 } 18389 </file> 18390 <file name="protractor.js" type="protractor"> 18391 var ps = element.all(by.css('p')); 18392 18393 it('should let you toggle the class', function() { 18394 18395 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 18396 expect(ps.first().getAttribute('class')).not.toMatch(/red/); 18397
18398 element(by.model('important')).click(); 18399 expect(ps.first().getAttribute('class')).toMatch(/bold/); 18400 18401 element(by.model('error')).click(); 18402 expect(ps.first().getAttribute('class')).toMatch(/red/); 18403 }); 18404 18405 it('should let you toggle string example', function() { 18406 expect(ps.get(1).getAttribute('class')).toBe(''); 18407 element(by.model('style')).clear(); 18408 element(by.model('style')).sendKeys('red'); 18409 expect(ps.get(1).getAttribute('class')).toBe('red'); 18410 }); 18411 18412 it('array example should have 3 classes', function() { 18413 expect(ps.last().getAttribute('class')).toBe(''); 18414 element(by.model('style1')).sendKeys('bold'); 18415 element(by.model('style2')).sendKeys('strike'); 18416 element(by.model('style3')).sendKeys('red'); 18417 expect(ps.last().getAttribute('class')).toBe('bold strike red'); 18418 }); 18419 </file> 18420 </example> 18421 18422 ## Animations 18423 18424 The example below demonstrates how to perform animations using ngClass. 18425 18426 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 18427 <file name="index.html"> 18428 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 18429 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 18430 <br> 18431 <span class="base-class" ng-class="myVar">Sample Text</span> 18432 </file> 18433 <file name="style.css"> 18434 .base-class { 18435 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18436 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18437 } 18438 18439 .base-class.my-class { 18440 color: red; 18441 font-size:3em; 18442 } 18443 </file> 18444 <file name="protractor.js" type="protractor"> 18445 it('should check ng-class', function() { 18446 expect(element(by.css('.base-class')).getAttribute('class')).not. 18447 toMatch(/my-class/); 18448 18449 element(by.id('setbtn')).click(); 18450 18451 expect(element(by.css('.base-class')).getAttribute('class')). 18452 toMatch(/my-class/); 18453 18454 element(by.id('clearbtn')).click(); 18455 18456 expect(element(by.css('.base-class')).getAttribute('class')).not. 18457 toMatch(/my-class/); 18458 }); 18459 </file> 18460 </example> 18461 18462 18463 ## ngClass and pre-existing CSS3 Transitions/Animations 18464 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 18465 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 18466 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 18467 to view the step by step details of {@link ngAnimate.$animate#addclass $animate.addClass} and 18468 {@link ngAnimate.$animate#removeclass $animate.removeClass}. 18469 */ 18470var ngClassDirective = classDirective('', true); 18471 18472/** 18473 * @ngdoc directive 18474 * @name ngClassOdd 18475 * @restrict AC 18476 * 18477 * @description 18478 * The `ngClassOdd` and `ngClassEven` directives work exactly as 18479 * {@link ng.directive:ngClass ngClass}, except they work in 18480 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 18481 * 18482 * This directive can be applied only within the scope of an 18483 * {@link ng.directive:ngRepeat ngRepeat}. 18484 * 18485 * @element ANY 18486 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 18487 * of the evaluation can be a string representing space delimited class names or an array. 18488 * 18489 * @example 18490 <example> 18491 <file name="index.html"> 18492 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 18493 <li ng-repeat="name in names"> 18494 <span ng-class-odd="'odd'" ng-class-even="'even'"> 18495 {{name}} 18496 </span> 18497 </li> 18498 </ol> 18499 </file> 18500 <file name="style.css"> 18501 .odd { 18502 color: red; 18503 } 18504 .even { 18505 color: blue; 18506 } 18507 </file> 18508 <file name="protractor.js" type="protractor"> 18509 it('should check ng-class-odd and ng-class-even', function() { 18510 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 18511 toMatch(/odd/); 18512 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 18513 toMatch(/even/); 18514 }); 18515 </file> 18516 </example> 18517 */ 18518var ngClassOddDirective = classDirective('Odd', 0); 18519 18520/** 18521 * @ngdoc directive 18522 * @name ngClassEven 18523 * @restrict AC 18524 * 18525 * @description 18526 * The `ngClassOdd` and `ngClassEven` directives work exactly as 18527 * {@link ng.directive:ngClass ngClass}, except they work in 18528 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 18529 * 18530 * This directive can be applied only within the scope of an 18531 * {@link ng.directive:ngRepeat ngRepeat}. 18532 * 18533 * @element ANY 18534 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 18535 * result of the evaluation can be a string representing space delimited class names or an array. 18536 * 18537 * @example 18538 <example> 18539 <file name="index.html"> 18540 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 18541 <li ng-repeat="name in names">
18542 <span ng-class-odd="'odd'" ng-class-even="'even'"> 18543 {{name}} 18544 </span> 18545 </li> 18546 </ol> 18547 </file> 18548 <file name="style.css"> 18549 .odd { 18550 color: red; 18551 } 18552 .even { 18553 color: blue; 18554 } 18555 </file> 18556 <file name="protractor.js" type="protractor"> 18557 it('should check ng-class-odd and ng-class-even', function() { 18558 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 18559 toMatch(/odd/); 18560 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 18561 toMatch(/even/); 18562 }); 18563 </file> 18564 </example> 18565 */ 18566var ngClassEvenDirective = classDirective('Even', 1); 18567 18568/** 18569 * @ngdoc directive 18570 * @name ngCloak 18571 * @restrict AC 18572 * 18573 * @description 18574 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 18575 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 18576 * directive to avoid the undesirable flicker effect caused by the html template display. 18577 * 18578 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 18579 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 18580 * of the browser view. 18581 * 18582 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 18583 * `angular.min.js`. 18584 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 18585 * 18586 * ```css 18587 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 18588 * display: none !important; 18589 * } 18590 * ``` 18591 * 18592 * When this css rule is loaded by the browser, all html elements (including their children) that 18593 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 18594 * during the compilation of the template it deletes the `ngCloak` element attribute, making 18595 * the compiled element visible. 18596 * 18597 * For the best result, the `angular.js` script must be loaded in the head section of the html 18598 * document; alternatively, the css rule above must be included in the external stylesheet of the 18599 * application. 18600 * 18601 * Legacy browsers, like IE7, do not provide attribute selector support (a
18601dded in CSS 2.1) so they 18602 * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css 18603 * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. 18604 * 18605 * @element ANY 18606 * 18607 * @example 18608 <example> 18609 <file name="index.html"> 18610 <div id="template1" ng-cloak>{{ 'hello' }}</div> 18611 <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div> 18612 </file> 18613 <file name="protractor.js" type="protractor"> 18614 it('should remove the template directive and css class', function() { 18615 expect($('#template1').getAttribute('ng-cloak')). 18616 toBeNull(); 18617 expect($('#template2').getAttribute('ng-cloak')). 18618 toBeNull(); 18619 }); 18620 </file> 18621 </example> 18622 * 18623 */ 18624var ngCloakDirective = ngDirective({ 18625 compile: function(element, attr) { 18626 attr.$set('ngCloak', undefined); 18627 element.removeClass('ng-cloak'); 18628 } 18629}); 18630 18631/** 18632 * @ngdoc directive 18633 * @name ngController 18634 * 18635 * @description 18636 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 18637 * supports the principles behind the Model-View-Controller design pattern. 18638 * 18639 * MVC components in angular: 18640 * 18641 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 18642 * are accessed through bindings. 18643 * * View â The template (HTML with data bindings) that is rendered into the View. 18644 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 18645 * logic behind the application to decorate the scope with functions and values 18646 * 18647 * Note that you can also attach controllers to the DOM by declaring it in a route definition 18648 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 18649 * again using `ng-controller` in the template itself. This will cause the controller to be attached 18650 * and executed twice. 18651 * 18652 * @element ANY 18653 * @scope 18654 * @param {expression} ngController Name of a globally accessible constructor function or an 18655 * {@link guide/expression expression} that on the current scope evaluates to a 18656 * constructor function. The controller instance can be published into a scope property 18657 * by specifying `as propertyName`. 18658 * 18659 * @example 18660 * Here is a simple form for editing user contact information. Adding, removing, clearing, and 18661 * greeting are methods declared on the controller (see source tab). These methods can 18662 * easily be called from the angular markup. Any changes to the data are automatically reflected 18663 * in the View without the need for a manual update. 18664 * 18665 * Two different declaration styles are included below: 18666 * 18667 * * one binds methods and properties directly onto the controller using `this`: 18668 * `ng-controller="SettingsController1 as settings"` 18669 * * one injects `$scope` into the controller: 18670 * `ng-controller="SettingsController2"` 18671 * 18672 * The second option is more common in the Angular community, and is generally used in boilerplates 18673 * and in this guide. However, there are advantages to binding properties directly to the controller 18674 * and avoiding scope. 18675 * 18676 * * Using `controller as` makes it obvious which controller you are accessing in the template when 18677 * multiple controllers apply to an element. 18678 * * If you are writing your controllers as classes you have easier access to the properties and 18679 * methods, which will appear on the scope, from inside the controller code. 18680 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 18681 * inheritance masking primitives. 18682 * 18683 * This example demonstrates the `controller as` syntax. 18684 * 18685 * <example name="ngControllerAs"> 18686 * <file name="index.html"> 18687 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 18688 * Name: <input type="text" ng-model="settings.name"/> 18689 * [ <a href="" ng-click="settings.greet()">greet</a> ]<br/> 18690 * Contact: 18691 * <ul> 18692 * <li ng-repeat="contact in settings.contacts"> 18693 * <select ng-model="contact.type"> 18694 * <option>phone</option> 18695 * <option>email</option> 18696 * </select> 18697 * <input type="text" ng-model="contact.value"/> 18698 * [ <a href="" ng-click="settings.clearContact(contact)">clear</a> 18699 * | <a href="" ng-click="settings.removeContact(contact)">X</a> ] 18700 * </li> 18701 * <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li> 18702 * </ul> 18703 * </div> 18704 * </file> 18705 * <file name="app.js"> 18706 * function SettingsController1() { 18707 * this.name = "John Smith"; 18708 * this.contacts = [ 18709 * {type: 'phone', value: '408 555 1212'}, 18710 * {type: 'email', value: '[email protected]'} ]; 18711 * } 18712 * 18713 * SettingsController1.prototype.greet = function() { 18714 * alert(this.name); 18715 * }; 18716 * 18717 * SettingsController1.prototype.addContact = function() { 18718 * this.contacts.push({type: 'email', value: '[email protected]'}); 18719 * }; 18720 * 18721 * SettingsController1.prototype.removeContact = function(contactToRemove) { 18722 * var index = this.contacts.indexOf(contactToRemove); 18723 * this.contacts.splice(index, 1); 18724 * }; 18725 * 18726 * SettingsController1.prototype.clearContact = function(contact) { 18727 * contact.type = 'phone'; 18728 * contact.value = ''; 18729 * }; 18730 * </file> 18731 * <file name="protractor.js" type="protractor"> 18732 * it('should check controller as', function() { 18733 * var container = element(by.id('ctrl-as-exmpl')); 18734 * expect(container.findElement(by.model('settings.name')) 18735 * .getAttribute('value')).toBe('John Smith'); 18736 * 18737 * var firstRepeat = 18738 * container.findElement(by.repeater('contact in settings.contacts').row(0)); 18739 * var secondRepeat = 18740 * container.findElement(by.repeater('contact in settings.contacts').row(1)); 18741 * 18742 * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18743 * .toBe('408 555 1212'); 18744 * 18745 * expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18746 * .toBe('[email protected]'); 18747 * 18748 * firstRepeat.findElement(by.linkText('clear')).click(); 18749 * 18750 * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18751 * .toBe(''); 18752 * 18753 * container.findElement(by.linkText('add')).click(); 18754 * 18755 * expect(container.findElement(by.repeater('contact in settings.contacts').row(2)) 18756 * .findElement(by.model('contact.value')) 18757 * .getAttribute('value')) 18758 * .toBe('[email protected]'); 18759 * }); 18760 * </file> 18761 * </example> 18762 * 18763 * This example demonstrates the "attach to `$scope`" style of controller. 18764 * 18765 * <example name="ngController"> 18766 * <file name="index.html"> 18767 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 18768 * Name: <input type="text" ng-model="name"/> 18769 * [ <a href="" ng-click="greet()">greet</a> ]<br/> 18770 * Contact: 18771 * <ul> 18772 * <li ng-repeat="contact in contacts"> 18773 * <select ng-model="contact.type"> 18774 * <option>phone</option> 18775 * <option>email</option> 18776 * </select> 18777 * <input type="text" ng-model="contact.value"/> 18778 * [ <a href="" ng-click="clearContact(contact)">clear</a> 18779 * | <a href="" ng-click="removeContact(contact)">X</a> ] 18780 * </li> 18781 * <li>[ <a href="" ng-click="addContact()">add</a> ]</li> 18782 * </ul> 18783 * </div> 18784 * </file> 18785 * <file name="app.js"> 18786 * function SettingsController2($scope) { 18787 * $scope.name = "John Smith"; 18788 * $scope.contacts = [ 18789 * {type:'phone', value:'408 555 1212'}, 18790 * {type:'email', value:'[email protected]'} ]; 18791 * 18792 * $scope.greet = function() { 18793 * alert($scope.name); 18794 * }; 18795 * 18796 * $scope.addContact = function() { 18797 * $scope.contacts.push({type:'email', value:'[email protected]'}); 18798 * }; 18799 * 18800 * $scope.removeContact = function(contactToRemove) { 18801 * var index = $scope.contacts.indexOf(contactToRemove); 18802 * $scope.contacts.splice(index, 1); 18803 * }; 18804 * 18805 * $scope.clearContact = function(contact) { 18806 * contact.type = 'phone'; 18807 * contact.value = ''; 18808 * }; 18809 * } 18810 * </file> 18811 * <file name="protractor.js" type="protractor"> 18812 * it('should check controller', function() { 18813 * var container = element(by.id('ctrl-exmpl')); 18814 * 18815 * expect(container.findElement(by.model('name')) 18816 * .getAttribute('value')).toBe('John Smith'); 18817 * 18818 * var firstRepeat = 18819 * container.findElement(by.repeater('contact in contacts').row(0)); 18820 * var secondRepeat = 18821 * container.findElement(by.repeater('contact in contacts').row(1)); 18822 * 18823 * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18824 * .toBe('408 555 1212'); 18825 * expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18826 * .toBe('[email protected]'); 18827 * 18828 * firstRepeat.findElement(by.linkText('clear')).click(); 18829 * 18830 * expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18831 * .toBe(''); 18832 * 18833 * container.findElement(by.linkText('add')).click(); 18834 * 18835 * expect(container.findElement(by.repeater('contact in contacts').row(2)) 18836 * .findElement(by.model('contact.value')) 18837 * .getAttribute('value')) 18838 * .toBe('[email protected]'); 18839 * });
18840 * </file> 18841 *</example> 18842 18843 */ 18844var ngControllerDirective = [function() { 18845 return { 18846 scope: true, 18847 controller: '@', 18848 priority: 500 18849 }; 18850}]; 18851 18852/** 18853 * @ngdoc directive 18854 * @name ngCsp 18855 * 18856 * @element html 18857 * @description 18858 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. 18859 * 18860 * This is necessary when developing things like Google Chrome Extensions. 18861 * 18862 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). 18863 * For us to be compatible, we just need to implement the "getterFn" in $parse without violating 18864 * any of these restrictions. 18865 * 18866 * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` 18867 * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will 18868 * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will 18869 * be raised. 18870 * 18871 * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically 18872 * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). 18873 * To make those directives work in CSP mode, include the `angular-csp.css` manually. 18874 * 18875 * In order to use this feature put the `ngCsp` directive on the root element of the application. 18876 * 18877 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 18878 * 18879 * @example 18880 * This example shows how to apply the `ngCsp` directive to the `html` tag. 18881 ```html 18882 <!doctype html> 18883 <html ng-app ng-csp> 18884 ... 18885 ... 18886 </html> 18887 ``` 18888 */ 18889 18890// ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap 18891// the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute 18892// anywhere in the current doc 18893 18894/** 18895 * @ngdoc directive 18896 * @name ngClick 18897 * 18898 * @description 18899 * The ngClick directive allows you to specify custom behavior when 18900 * an element is clicked. 18901 * 18902 * @element ANY 18903 * @priority 0 18904 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 18905 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 18906 * 18907 * @example 18908 <example> 18909 <file name="index.html"> 18910 <button ng-click="count = count + 1" ng-init="count=0"> 18911 Increment 18912 </button> 18913 count: {{count}} 18914 </file> 18915 <file name="protractor.js" type="protractor"> 18916 it('should check ng-click', function() { 18917 expect(element(by.binding('count')).getText()).toMatch('0'); 18918 element(by.css('button')).click(); 18919 expect(element(by.binding('count')).getText()).toMatch('1'); 18920 }); 18921 </file> 18922 </example> 18923 */ 18924/* 18925 * A directive that allows creation of custom onclick handlers that are defined as angular 18926 * expressions and are compiled and executed within the current scope. 18927 * 18928 * Events that are handled via these handler are always configured not to propagate further. 18929 */ 18930var ngEventDirectives = {}; 18931forEach( 18932 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 18933 function(name) { 18934 var directiveName = directiveNormalize('ng-' + name); 18935 ngEventDirectives[directiveName] = ['$parse', function($parse) { 18936 return { 18937 compile: function($element, attr) { 18938 var fn = $parse(attr[directiveName]); 18939 return function ngEventHandler(scope, element) { 18940 element.on(lowercase(name), function(event) { 18941 scope.$apply(function() { 18942 fn(scope, {$event:event}); 18943 }); 18944 }); 18945 }; 18946 } 18947 }; 18948 }]; 18949 } 18950); 18951 18952/** 18953 * @ngdoc directive 18954 * @name ngDblclick 18955 * 18956 * @description 18957 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 18958 * 18959 * @element ANY 18960 * @priority 0 18961 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 18962 * a dblclick. (The Event object is available as `$event`) 18963 * 18964 * @example 18965 <example> 18966 <file name="index.html"> 18967 <button ng-dblclick="count = count + 1" ng-init="count=0"> 18968 Increment (on double click) 18969 </button> 18970 count: {{count}} 18971 </file> 18972 </example> 18973 */ 18974 18975 18976/** 18977 * @ngdoc directive 18978 * @name ngMousedown 18979 * 18980 * @description 18981 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 18982 * 18983 * @element ANY 18984 * @priority 0 18985 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 18986 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 18987 * 18988 * @example 18989 <example> 18990 <file name="index.html"> 18991 <button ng-mousedown="count = count + 1" ng-init="count=0"> 18992 Increment (on mouse down) 18993 </button> 18994 count: {{count}} 18995 </file> 18996 </example> 18997 */ 18998 18999 19000/** 19001 * @ngdoc directive 19002 * @name ngMouseup 19003 * 19004 * @description 19005 * Specify custom behavior on mouseup event. 19006 * 19007 * @element ANY 19008 * @priority 0 19009 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 19010 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 19011 * 19012 * @example 19013 <example> 19014 <file name="index.html"> 19015 <button ng-mouseup="count = count + 1" ng-init="count=0"> 19016 Increment (on mouse up) 19017 </button> 19018 count: {{count}} 19019 </file> 19020 </example> 19021 */ 19022 19023/** 19024 * @ngdoc directive 19025 * @name ngMouseover 19026 * 19027 * @description 19028 * Specify custom behavior on mouseover event. 19029 * 19030 * @element ANY 19031 * @priority 0 19032 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 19033 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 19034 * 19035 * @example 19036 <example> 19037 <file name="index.html"> 19038 <button ng-mouseover="count = count + 1" ng-init="count=0"> 19039 Increment (when mouse is over) 19040 </button> 19041 count: {{count}} 19042 </file> 19043 </example> 19044 */ 19045 19046 19047/** 19048 * @ngdoc directive 19049 * @name ngMouseenter 19050 * 19051 * @description 19052 * Specify custom behavior on mouseenter event. 19053 * 19054 * @element ANY 19055 * @priority 0 19056 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 19057 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 19058 * 19059 * @example 19060 <example> 19061 <file name="index.html"> 19062 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 19063 Increment (when mouse enters) 19064 </button> 19065 count: {{count}} 19066 </file> 19067 </example> 19068 */ 19069 19070 19071/** 19072 * @ngdoc directive 19073 * @name ngMouseleave 19074 * 19075 * @description 19076 * Specify custom behavior on mouseleave event. 19077 * 19078 * @element ANY 19079 * @priority 0 19080 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 19081 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 19082 * 19083 * @example 19084 <example> 19085 <file name="index.html"> 19086 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 19087 Increment (when mouse leaves) 19088 </button> 19089 count: {{count}} 19090 </file> 19091 </example> 19092 */ 19093 19094 19095/** 19096 * @ngdoc directive 19097 * @name ngMousemove 19098 * 19099 * @description 19100 * Specify custom behavior on mousemove event. 19101 * 19102 * @element ANY 19103 * @priority 0 19104 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 19105 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 19106 * 19107 * @example 19108 <example> 19109 <file name="index.html"> 19110 <button ng-mousemove="count = count + 1" ng-init="count=0"> 19111 Increment (when mouse moves) 19112 </button> 19113 count: {{count}} 19114 </file> 19115 </example> 19116 */ 19117 19118 19119/** 19120 * @ngdoc directive 19121 * @name ngKeydown 19122 * 19123 * @description 19124 * Specify custom behavior on keydown event. 19125 * 19126 * @element ANY 19127 * @priority 0 19128 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 19129 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 19130 * 19131 * @example 19132 <example> 19133 <file name="index.html"> 19134 <input ng-keydown="count = count + 1" ng-init="count=0"> 19135 key down count: {{count}} 19136 </file> 19137 </example> 19138 */ 19139 19140 19141/** 19142 * @ngdoc directive 19143 * @name ngKeyup 19144 * 19145 * @description 19146 * Specify custom behavior on keyup event. 19147 * 19148 * @element ANY 19149 * @priority 0 19150 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 19151 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 19152 * 19153 * @example 19154 <example> 19155 <file name="index.html"> 19156 <p>Typing in the input box below updates the key count</p> 19157 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 19158 19159 <p>Typing in the input box below updates the keycode</p> 19160 <input ng-keyup="event=$event"> 19161 <p>event keyCode: {{ event.keyCode }}</p> 19162 <p>event altKey: {{ event.altKey }}</p> 19163 </file> 19164 </example> 19165 */ 19166 19167 19168/** 19169 * @ngdoc directive 19170 * @name ngKeypress 19171 * 19172 * @description 19173 * Specify custom behavior on keypress event. 19174 * 19175 * @element ANY 19176 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 19177 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 19178 * and can be interrogated for keyCode, altKey, etc.) 19179 * 19180 * @example 19181 <example> 19182 <file name="index.html"> 19183 <input ng-keypress="count = count + 1" ng-init="count=0"> 19184 key press count: {{count}} 19185 </file> 19186 </example> 19187 */ 19188 19189 19190/** 19191 * @ngdoc directive 19192 * @name ngSubmit 19193 * 19194 * @description 19195 * Enables binding angular expressions to onsubmit events. 19196 * 19197 * Additionally it prevents the default action (which for form means
19197sending the request to the 19198 * server and reloading the current page), but only if the form does not contain `action`, 19199 * `data-action`, or `x-action` attributes. 19200 * 19201 * @element form 19202 * @priority 0 19203 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 19204 * ({@link guide/expression#-event- Event object is available as `$event`}) 19205 * 19206 * @example 19207 <example> 19208 <file name="index.html"> 19209 <script> 19210 function Ctrl($scope) { 19211 $scope.list = []; 19212 $scope.text = 'hello'; 19213 $scope.submit = function() { 19214 if ($scope.text) { 19215 $scope.list.push(this.text); 19216 $scope.text = ''; 19217 } 19218 }; 19219 } 19220 </script> 19221 <form ng-submit="submit()" ng-controller="Ctrl"> 19222 Enter text and hit enter: 19223 <input type="text" ng-model="text" name="text" /> 19224 <input type="submit" id="submit" value="Submit" /> 19225 <pre>list={{list}}</pre> 19226 </form> 19227 </file> 19228 <file name="protractor.js" type="protractor"> 19229 it('should check ng-submit', function() { 19230 expect(element(by.binding('list')).getText()).toBe('list=[]'); 19231 element(by.css('#submit')).click(); 19232 expect(element(by.binding('list')).getText()).toContain('hello'); 19233 expect(element(by.input('text')).getAttribute('value')).toBe(''); 19234 }); 19235 it('should ignore empty strings', function() { 19236 expect(element(by.binding('list')).getText()).toBe('list=[]'); 19237 element(by.css('#submit')).click(); 19238 element(by.css('#submit')).click(); 19239 expect(element(by.binding('list')).getText()).toContain('hello'); 19240 }); 19241 </file> 19242 </example> 19243 */ 19244 19245/** 19246 * @ngdoc directive 19247 * @name ngFocus 19248 * 19249 * @description 19250 * Specify custom behavior on focus event. 19251 * 19252 * @element window, input, select, textarea, a 19253 * @priority 0 19254 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 19255 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 19256 * 19257 * @example 19258 * See {@link ng.directive:ngClick ngClick} 19259 */ 19260 19261/** 19262 * @ngdoc directive 19263 * @name ngBlur 19264 * 19265 * @description 19266 * Specify custom behavior on blur event. 19267 * 19268 * @element window, input, select, textarea, a 19269 * @priority 0 19270 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 19271 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 19272 * 19273 * @example 19274 * See {@link ng.directive:ngClick ngClick} 19275 */ 19276 19277/** 19278 * @ngdoc directive 19279 * @name ngCopy 19280 * 19281 * @description 19282 * Specify custom behavior on copy event. 19283 * 19284 * @element window, input, select, textarea, a 19285 * @priority 0 19286 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 19287 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 19288 * 19289 * @example 19290 <example> 19291 <file name="index.html"> 19292 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 19293 copied: {{copied}} 19294 </file> 19295 </example> 19296 */ 19297 19298/** 19299 * @ngdoc directive 19300 * @name ngCut 19301 * 19302 * @description 19303 * Specify custom behavior on cut event. 19304 * 19305 * @element window, input, select, textarea, a 19306 * @priority 0 19307 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 19308 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 19309 * 19310 * @example 19311 <example> 19312 <file name="index.html"> 19313 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 19314 cut: {{cut}} 19315 </file> 19316 </example> 19317 */ 19318 19319/** 19320 * @ngdoc directive 19321 * @name ngPaste 19322 * 19323 * @description 19324 * Specify custom behavior on paste event. 19325 * 19326 * @element window, input, select, textarea, a 19327 * @priority 0 19328 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 19329 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 19330 * 19331 * @example 19332 <example> 19333 <file name="index.html"> 19334 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 19335 pasted: {{paste}} 19336 </file> 19337 </example> 19338 */ 19339 19340/** 19341 * @ngdoc directive 19342 * @name ngIf 19343 * @restrict A 19344 * 19345 * @description 19346 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 19347 * {expression}. If the expression assigned to `ngIf` evaluates to a false 19348 * value then the element is removed from the DOM, otherwise a clone of the 19349 * element is reinserted into the DOM. 19350 * 19351 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 19352 * element in the DOM rather than changing its visibility via the `display` css property. A common 19353 * case when this difference is significant is when using css selectors that rely on an element's 19354 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
19354s. 19355 * 19356 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 19357 * is created when the element is restored. The scope created within `ngIf` inherits from 19358 * its parent scope using 19359 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance). 19360 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 19361 * a javascript primitive defined in the parent scope. In this case any modifications made to the 19362 * variable within the child scope will override (hide) the value in the parent scope. 19363 * 19364 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 19365 * is if an element's class attribute is directly modified after it's compiled, using something like 19366 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 19367 * the added class will be lost because the original compiled state is used to regenerate the element. 19368 * 19369 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 19370 * and `leave` effects. 19371 * 19372 * @animations 19373 * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container 19374 * leave - happens just before the ngIf contents are removed from the DOM 19375 * 19376 * @element ANY 19377 * @scope 19378 * @priority 600 19379 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 19380 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 19381 * element is added to the DOM tree. 19382 * 19383 * @example 19384 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 19385 <file name="index.html"> 19386 Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/> 19387 Show when checked: 19388 <span ng-if="checked" class="animate-if"> 19389 I'm removed when the checkbox is unchecked. 19390 </span> 19391 </file> 19392 <file name="animations.css"> 19393 .animate-if { 19394 background:white; 19395 border:1px solid black; 19396 padding:10px; 19397 } 19398 19399 .animate-if.ng-enter, .animate-if.ng-leave { 19400 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19401 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19402 } 19403 19404 .animate-if.ng-enter, 19405 .animate-if.ng-leave.ng-leave-active { 19406 opacity:0; 19407 } 19408 19409 .animate-if.ng-leave, 19410 .animate-if.ng-enter.ng-enter-active { 19411 opacity:1; 19412 } 19413 </file> 19414 </example> 19415 */ 19416var ngIfDirective = ['$animate', function($animate) { 19417 return { 19418 transclude: 'element', 19419 priority: 600, 19420 terminal: true, 19421 restrict: 'A', 19422 $$tlb: true, 19423 link: function ($scope, $element, $attr, ctrl, $transclude) { 19424 var block, childScope, previousElements; 19425 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 19426 19427 if (toBoolean(value)) { 19428 if (!childScope) { 19429 childScope = $scope.$new(); 19430 $transclude(childScope, function (clone) { 19431 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 19432 // Note: We only need the first/last node of the cloned nodes. 19433 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 19434 // by a directive with templateUrl when its template arrives. 19435 block = { 19436 clone: clone 19437 }; 19438 $animate.enter(clone, $element.parent(), $element); 19439 }); 19440 } 19441 } else { 19442 if(previousElements) { 19443 previousElements.remove(); 19444 previousElements = null; 19445 } 19446 if(childScope) { 19447 childScope.$destroy(); 19448 childScope = null; 19449 } 19450 if(block) { 19451 previousElements = getBlockElements(block.clone); 19452 $animate.leave(previousElements, function() { 19453 previousElements = null; 19454 }); 19455 block = null; 19456 } 19457 } 19458 }); 19459 } 19460 }; 19461}]; 19462 19463/** 19464 * @ngdoc directive 19465 * @name ngInclude 19466 * @restrict ECA 19467 * 19468 * @description 19469 * Fetches, compiles and includes an external HTML fragment. 19470 * 19471 * By default, the template URL is restricted to the same domain and protocol as the 19472 * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 19473 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 19474 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 19475 * [wrap them](ng.$sce#trustAsResourceUrl) as trusted values. Refer to Angular's {@link 19476 * ng.$sce Strict Contextual Escaping}. 19477 * 19478 * In addition, the browser's 19479 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 19480 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 19481 * policy may further restrict whether the template is successfully loaded. 19482 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 19483 * access on some browsers. 19484 * 19485 * @animations 19486 * enter - animation is used to bring new content into the browser. 19487 * leave - animation is used to animate existing content away. 19488 * 19489 * The enter and leave animation occur concurrently. 19490 * 19491 * @scope 19492 * @priority 400 19493 * 19494 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 19495 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 19496 * @param {string=} onload Expression to evaluate when a new partial is loaded. 19497 * 19498 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 19499 * $anchorScroll} to scroll the viewport after the content is loaded. 19500 * 19501 * - If the attribute is not set, disable scrolling. 19502 * - If the attribute is set without value, enable scrolling. 19503 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 19504 * 19505 * @example 19506 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 19507 <file name="index.html"> 19508 <div ng-controller="Ctrl"> 19509 <select ng-model="template" ng-options="t.name for t in templates"> 19510 <option value="">(blank)</option> 19511 </select> 19512 url of the template: <tt>{{template.url}}</tt> 19513 <hr/> 19514 <div class="slide-animate-container"> 19515 <div class="slide-animate" ng-include="template.url"></div> 19516 </div> 19517 </div> 19518 </file> 19519 <file name="script.js"> 19520 function Ctrl($scope) { 19521 $scope.templates = 19522 [ { name: 'template1.html', url: 'template1.html'}, 19523 { name: 'template2.html', url: 'template2.html'} ]; 19524 $scope.template = $scope.templates[0]; 19525 } 19526 </file> 19527 <file name="template1.html"> 19528 Content of template1.html 19529 </file> 19530 <file name="template2.html"> 19531 Content of template2.html 19532 </file> 19533 <file name="animations.css"> 19534 .slide-animate-container { 19535 position:relative; 19536 background:white; 19537 border:1px solid black; 19538 height:40px; 19539 overflow:hidden; 19540 } 19541 19542 .slide-animate { 19543 padding:10px; 19544 } 19545 19546 .slide-animate.ng-enter, .slide-animate.ng-leave { 19547 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19548 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19549 19550 position:absolute; 19551 top:0; 19552 left:0; 19553 right:0; 19554 bottom:0; 19555 display:block; 19556 padding:10px; 19557 } 19558 19559 .slide-animate.ng-enter { 19560 top:-50px; 19561 } 19562 .slide-animate.ng-enter.ng-enter-active { 19563 top:0; 19564 } 19565 19566 .slide-animate.ng-leave { 19567 top:0; 19568 } 19569 .slide-animate.ng-leave.ng-leave-active { 19570 top:50px; 19571 } 19572 </file> 19573 <file name="protractor.js" type="protractor"> 19574 var templateSelect = element(by.model('template'));
19575 var includeElem = element(by.css('[ng-include]')); 19576 19577 it('should load template1.html', function() { 19578 expect(includeElem.getText()).toMatch(/Content of template1.html/); 19579 }); 19580 19581 it('should load template2.html', function() { 19582 if (browser.params.browser == 'firefox') { 19583 // Firefox can't handle using selects 19584 // See https://github.com/angular/protractor/issues/480 19585 return; 19586 } 19587 templateSelect.click(); 19588 templateSelect.element.all(by.css('option')).get(2).click(); 19589 expect(includeElem.getText()).toMatch(/Content of template2.html/); 19590 }); 19591 19592 it('should change to blank', function() { 19593 if (browser.params.browser == 'firefox') { 19594 // Firefox can't handle using selects 19595 return; 19596 } 19597 templateSelect.click(); 19598 templateSelect.element.all(by.css('option')).get(0).click(); 19599 expect(includeElem.isPresent()).toBe(false); 19600 }); 19601 </file> 19602 </example> 19603 */ 19604 19605 19606/** 19607 * @ngdoc event 19608 * @name ngInclude#$includeContentRequested 19609 * @eventType emit on the scope ngInclude was declared in 19610 * @description 19611 * Emitted every time the ngInclude content is requested. 19612 */ 19613 19614 19615/** 19616 * @ngdoc event 19617 * @name ngInclude#$includeContentLoaded 19618 * @eventType emit on the current ngInclude scope 19619 * @description 19620 * Emitted every time the ngInclude content is reloaded. 19621 */ 19622var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce', 19623 function($http, $templateCache, $anchorScroll, $animate, $sce) { 19624 return { 19625 restrict: 'ECA', 19626 priority: 400, 19627 terminal: true, 19628 transclude: 'element', 19629 controller: angular.noop, 19630 compile: function(element, attr) { 19631 var srcExp = attr.ngInclude || attr.src, 19632 onloadExp = attr.onload || '', 19633 autoScrollExp = attr.autoscroll; 19634 19635 return function(scope, $element, $attr, ctrl, $transclude) { 19636 var changeCounter = 0, 19637 currentScope, 19638 previousElement, 19639 currentElement; 19640 19641 var cleanupLastIncludeContent = function() { 19642 if(previousElement) { 19643 previousElement.remove(); 19644 previousElement = null; 19645 } 19646 if(currentScope) { 19647 currentScope.$destroy(); 19648 currentScope = null; 19649 } 19650 if(currentElement) { 19651 $animate.leave(currentElement, function() { 19652 previousElement = null; 19653 }); 19654 previousElement = currentElement; 19655 currentElement = null; 19656 } 19657 }; 19658 19659 scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { 19660 var afterAnimation = function() { 19661 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 19662 $anchorScroll(); 19663 } 19664 }; 19665 var thisChangeId = ++changeCounter; 19666 19667 if (src) { 19668 $http.get(src, {cache: $templateCache}).success(function(response) { 19669 if (thisChangeId !== changeCounter) return; 19670 var newScope = scope.$new(); 19671 ctrl.template = response; 19672 19673 // Note: This will also link all children of ng-include that were contained in the original 19674 // html. If that content contains controllers, ... they could pollute/change the scope. 19675 // However, using ng-include on an element with additional content does not make sense... 19676 // Note: We can't remove them in the cloneAttchFn of $transclude as that 19677 // function is called before linking the content, which would apply child 19678 // directives to non existing elements. 19679 var clone = $transclude(newScope, function(clone) { 19680 cleanupLastIncludeContent(); 19681 $animate.enter(clone, null, $element, afterAnimation); 19682 }); 19683 19684 currentScope = newScope; 19685 currentElement = clone; 19686 19687 currentScope.$emit('$includeContentLoaded'); 19688 scope.$eval(onloadExp); 19689 }).error(function() { 19690 if (thisChangeId === changeCounter) cleanupLastIncludeContent(); 19691 }); 19692 scope.$emit('$includeContentRequested'); 19693 } else { 19694 cleanupLastIncludeContent(); 19695 ctrl.template = null; 19696 } 19697 }); 19698 }; 19699 } 19700 }; 19701}]; 19702 19703// This directive is called during the $transclude call of the first `ngInclude` directive. 19704// It will replace and compile the content of the element with the loaded template. 19705// We need this directive so that the element content is already filled when 19706// the link function of another directive on the same element as ngInclude 19707// is called. 19708var ngIncludeFillContentDirective = ['$compile', 19709 function($compile) { 19710 return { 19711 restrict: 'ECA', 19712 priority: -400, 19713 require: 'ngInclude', 19714 link: function(scope, $element, $attr, ctrl) { 19715 $element.html(ctrl.template); 19716 $compile($element.contents())(scope); 19717 } 19718 }; 19719 }]; 19720 19721/** 19722 * @ngdoc directive 19723 * @name ngInit 19724 * @restrict AC 19725 * 19726 * @description 19727 * The `ngInit` directive allows you to evaluate an expression in the 19728 * current scope. 19729 * 19730 * <div class="alert alert-error"> 19731 * The only appropriate use of `ngInit` is for aliasing special properties of 19732 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you 19733 * should use {@link guide/controller controllers} rather than `ngInit` 19734 * to initialize values on a scope. 19735 * </div> 19736 * <div class="alert alert-warning"> 19737 * **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
19738 * sure you have parenthesis for correct precedence: 19739 * <pre class="prettyprint"> 19740 * <div ng-init="test1 = (data | orderBy:'name')"></div> 19741 * </pre> 19742 * </div> 19743 * 19744 * @priority 450 19745 * 19746 * @element ANY 19747 * @param {expression} ngInit {@link guide/expression Expression} to eval. 19748 * 19749 * @example 19750 <example> 19751 <file name="index.html"> 19752 <script> 19753 function Ctrl($scope) { 19754 $scope.list = [['a', 'b'], ['c', 'd']]; 19755 } 19756 </script> 19757 <div ng-controller="Ctrl"> 19758 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 19759 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 19760 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 19761 </div> 19762 </div> 19763 </div> 19764 </file> 19765 <file name="protractor.js" type="protractor"> 19766 it('should alias index positions', function() { 19767 var elements = element.all(by.css('.example-init')); 19768 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 19769 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 19770 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 19771 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 19772 }); 19773 </file> 19774 </example> 19775 */ 19776var ngInitDirective = ngDirective({ 19777 priority: 450, 19778 compile: function() { 19779 return { 19780 pre: function(scope, element, attrs) { 19781 scope.$eval(attrs.ngInit); 19782 } 19783 }; 19784 } 19785}); 19786 19787/** 19788 * @ngdoc directive 19789 * @name ngNonBindable 19790 * @restrict AC 19791 * @priority 1000 19792 * 19793 * @description 19794 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 19795 * DOM element. This is useful if the element contains what appears to be Angular directives and 19796 * bindings but which should be ignored by Angular. This could be the case if you have a site that 19797 * displays snippets of code, for instance. 19798 * 19799 * @element ANY 19800 * 19801 * @example 19802 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 19803 * but the one wrapped in `ngNonBindable` is left alone. 19804 * 19805 * @example 19806 <example> 19807 <file name="index.html"> 19808 <div>Normal: {{1 + 2}}</div> 19809 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 19810 </file> 19811 <file name="protractor.js" type="protractor"> 19812 it('should check ng-non-bindable', function() { 19813 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 19814 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 19815 }); 19816 </file> 19817 </example> 19818 */ 19819var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 19820 19821/** 19822 * @ngdoc directive 19823 * @name ngPluralize 19824 * @restrict EA 19825 * 19826 * @description 19827 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 19828 * These rules are bundled with angular.js, but can be overridden 19829 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 19830 * by specifying the mappings between 19831 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 19832 * and the strings to be displayed. 19833 * 19834 * # Plural categories and explicit number rules 19835 * There are two 19836 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 19837 * in Angular's default en-US locale: "one" and "other". 19838 * 19839 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 19840 * any number that is not 1), an explicit number rule can only match one number. For example, the 19841 * explicit number rule for "3" matches the number 3. There are examples of plural categories 19842 * and explicit number rules throughout the rest of this documentation. 19843 * 19844 * # Configuring ngPluralize 19845 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 19846 * You can also provide an optional attribute, `offset`. 19847 * 19848 * The value of the `count` attribute can be either a string or an {@link guide/expression 19849 * Angular expression}; these are evaluated on the current scope for its bound value. 19850 * 19851 * The `when` attribute specifies the mappings between plural categories and the actual 19852 * string to be displayed. The value of the attribute should be a JSON object. 19853 * 19854 * The following example shows how to configure ngPluralize: 19855 * 19856 * ```html 19857 * <ng-pluralize count="personCount"
19858 when="{'0': 'Nobody is viewing.', 19859 * 'one': '1 person is viewing.', 19860 * 'other': '{} people are viewing.'}"> 19861 * </ng-pluralize> 19862 *``` 19863 * 19864 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 19865 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 19866 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 19867 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 19868 * show "a dozen people are viewing". 19869 * 19870 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 19871 * into pluralized strings. In the previous example, Angular will replace `{}` with 19872 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 19873 * for <span ng-non-bindable>{{numberExpression}}</span>. 19874 * 19875 * # Configuring ngPluralize with offset 19876 * The `offset` attribute allows further customization of pluralized text, which can result in 19877 * a better user experience. For example, instead of the message "4 people are viewing this document", 19878 * you might display "John, Kate and 2 others are viewing this document". 19879 * The offset attribute allows you to offset a number by any desired value. 19880 * Let's take a look at an example: 19881 * 19882 * ```html 19883 * <ng-pluralize count="personCount" offset=2 19884 * when="{'0': 'Nobody is viewing.', 19885 * '1': '{{person1}} is viewing.', 19886 * '2': '{{person1}} and {{person2}} are viewing.', 19887 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 19888 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 19889 * </ng-pluralize> 19890 * ``` 19891 * 19892 * Notice that we are still using two plural categories(one, other), but we added 19893 * three explicit number rules 0, 1 and 2. 19894 * When one person, perhaps John, views the document, "John is viewing" will be shown. 19895 * When three people view the document, no explicit number rule is found, so 19896 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 19897 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 19898 * is shown. 19899 * 19900 * Note that when you specify offsets, you must provide explicit number rules for 19901 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 19902 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 19903 * plural categories "one" and "other". 19904 * 19905 * @param {string|expression} count The variable to be bound to. 19906 * @param {string} when The mapping between plural category to its corresponding strings. 19907 * @param {number=} offset Offset to deduct from the total number. 19908 * 19909 * @example 19910 <example> 19911 <file name="index.html"> 19912 <script> 19913 function Ctrl($scope) { 19914 $scope.person1 = 'Igor'; 19915 $scope.person2 = 'Misko'; 19916 $scope.personCount = 1; 19917 } 19918 </script> 19919 <div ng-controller="Ctrl"> 19920 Person 1:<input type="text" ng-model="person1" value="Igor" /><br/> 19921 Person 2:<input type="text" ng-model="person2" value="Misko" /><br/> 19922 Number of People:<input type="text" ng-model="personCount" value="1" /><br/> 19923 19924 <!--- Example with simple pluralization rules for en locale ---> 19925 Without Offset: 19926 <ng-pluralize count="personCount" 19927 when="{'0': 'Nobody is viewing.', 19928 'one': '1 person is viewing.', 19929 'other': '{} people are viewing.'}"> 19930 </ng-pluralize><br> 19931 19932 <!--- Example with offset ---> 19933 With Offset(2): 19934 <ng-pluralize count="personCount" offset=2 19935 when="{'0': 'Nobody is viewing.', 19936 '1': '{{person1}} is viewing.', 19937 '2': '{{person1}} and {{person2}} are viewing.', 19938 'one': '{{person1}}, {{person2}} and one other person are viewing.',
19939 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 19940 </ng-pluralize> 19941 </div> 19942 </file> 19943 <file name="protractor.js" type="protractor"> 19944 it('should show correct pluralized string', function() { 19945 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 19946 var withOffset = element.all(by.css('ng-pluralize')).get(1); 19947 var countInput = element(by.model('personCount')); 19948 19949 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 19950 expect(withOffset.getText()).toEqual('Igor is viewing.'); 19951 19952 countInput.clear(); 19953 countInput.sendKeys('0'); 19954 19955 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 19956 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 19957 19958 countInput.clear(); 19959 countInput.sendKeys('2'); 19960 19961 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 19962 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 19963 19964 countInput.clear(); 19965 countInput.sendKeys('3'); 19966 19967 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 19968 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 19969 19970 countInput.clear(); 19971 countInput.sendKeys('4'); 19972 19973 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 19974 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 19975 }); 19976 it('should show data-bound names', function() { 19977 var withOffset = element.all(by.css('ng-pluralize')).get(1); 19978 var personCount = element(by.model('personCount')); 19979 var person1 = element(by.model('person1')); 19980 var person2 = element(by.model('person2')); 19981 personCount.clear(); 19982 personCount.sendKeys('4'); 19983 person1.clear(); 19984 person1.sendKeys('Di'); 19985 person2.clear(); 19986 person2.sendKeys('Vojta'); 19987 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 19988 }); 19989 </file> 19990 </example> 19991 */ 19992var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { 19993 var BRACE = /{}/g; 19994 return { 19995 restrict: 'EA', 19996 link: function(scope, element, attr) { 19997 var numberExp = attr.count, 19998 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 19999 offset = attr.offset || 0, 20000 whens = scope.$eval(whenExp) || {}, 20001 whensExpFns = {}, 20002 startSymbol = $interpolate.startSymbol(), 20003 endSymbol = $interpolate.endSymbol(), 20004 isWhen = /^when(Minus)?(.+)$/; 20005 20006 forEach(attr, function(expression, attributeName) { 20007 if (isWhen.test(attributeName)) { 20008 whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = 20009 element.attr(attr.$attr[attributeName]); 20010 } 20011 }); 20012 forEach(whens, function(expression, key) { 20013 whensExpFns[key] = 20014 $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + 20015 offset + endSymbol)); 20016 }); 20017 20018 scope.$watch(function ngPluralizeWatch() { 20019 var value = parseFloat(scope.$eval(numberExp)); 20020 20021 if (!isNaN(value)) { 20022 //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, 20023 //check it against pluralization rules in $locale service 20024 if (!(value in whens)) value = $locale.pluralCat(value - offset); 20025 return whensExpFns[value](scope, element, true); 20026 } else { 20027 return ''; 20028 } 20029 }, function ngPluralizeWatchAction(newVal) { 20030 element.text(newVal); 20031 }); 20032 } 20033 }; 20034}]; 20035 20036/** 20037 * @ngdoc directive 20038 * @name ngRepeat 20039 * 20040 * @description 20041 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 20042 * instance gets its own scope, where the given loop variable is set to the current collection item, 20043 * and `$index` is set to the item index or key. 20044 * 20045 * Special properties are exposed on the local scope of each template instance, including: 20046 * 20047 * | Variable | Type | Details | 20048 * |-----------|-----------------|-----------------------------------------------------------------------------| 20049 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 20050 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 20051 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 20052 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 20053 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 20054 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 20055 *
20056 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 20057 * This may be useful when, for instance, nesting ngRepeats. 20058 * 20059 * # Special repeat start and end points 20060 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 20061 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 20062 * 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) 20063 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 20064 * 20065 * The example below makes use of this feature: 20066 * ```html 20067 * <header ng-repeat-start="item in items"> 20068 * Header {{ item }} 20069 * </header> 20070 * <div class="body"> 20071 * Body {{ item }} 20072 * </div> 20073 * <footer ng-repeat-end> 20074 * Footer {{ item }} 20075 * </footer> 20076 * ``` 20077 * 20078 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 20079 * ```html 20080 * <header> 20081 * Header A 20082 * </header> 20083 * <div class="body"> 20084 * Body A 20085 * </div> 20086 * <footer> 20087 * Footer A 20088 * </footer> 20089 * <header> 20090 * Header B 20091 * </header> 20092 * <div class="body"> 20093 * Body B 20094 * </div> 20095 * <footer> 20096 * Footer B 20097 * </footer> 20098 * ``` 20099 * 20100 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 20101 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 20102 * 20103 * @animations 20104 * **.enter** - when a new item is added to the list or when an item is revealed after a filter 20105 * 20106 * **.leave** - when an item is removed from the list or when an item is filtered out 20107 * 20108 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 20109 * 20110 * @element ANY 20111 * @scope 20112 * @priority 1000 20113 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 20114 * formats are currently supported: 20115 * 20116 * * `variable in expression` â where variable is the user defined loop variable and `expression` 20117 * is a scope expression giving the collection to enumerate. 20118 * 20119 * For example: `album in artist.albums`. 20120 * 20121 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 20122 * and `expression` is the scope expression giving the collection to enumerate. 20123 * 20124 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 20125 * 20126 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking function 20127 * which can be used to associate the objects in the collection with the DOM elements. If no tracking function 20128 * is specified the ng-repeat associates elements by identity in the collection. It is an error to have 20129 * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are 20130 * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, 20131 * before specifying a tracking expression. 20132 * 20133 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 20134 * will be associated by item identity in the array. 20135 * 20136 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 20137 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 20138 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 20139 * element in the same way in the DOM. 20140 * 20141 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 20142 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 20143 * property is same. 20144 * 20145 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 20146 * to items in conjunction with a tracking expression. 20147 * 20148 * @example 20149 * This example initializes the scope to a list of names and 20150 * then uses `ngRepeat` to display every person: 20151 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20152 <file name="index.html"> 20153 <div ng-init="friends = [ 20154 {name:'John', age:25, gender:'boy'}, 20155 {name:'Jessie', age:30, gender:'girl'}, 20156 {name:'Johanna', age:28, gender:'girl'}, 20157 {name:'Joy', age:15, gender:'girl'}, 20158 {name:'Mary', age:28, gender:'girl'}, 20159 {name:'Peter', age:95, gender:'boy'}, 20160 {name:'Sebastian', age:50, gender:'boy'}, 20161 {name:'Erika', age:27, gender:'girl'}, 20162 {name:'Patrick', age:40, gender:'boy'}, 20163 {name:'Samantha', age:60, gender:'girl'} 20164 ]"> 20165 I have {{friends.length}} friends. They are: 20166 <input type="search" ng-model="q" placeholder="filter friends..." /> 20167 <ul class="example-animate-container"> 20168 <li class="animate-repeat" ng-repeat="friend in friends | filter:q"> 20169 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 20170 </li> 20171 </ul> 20172 </div> 20173 </file> 20174 <file name="animations.css"> 20175 .example-animate-container { 20176 background:white; 20177 border:1px solid black; 20178 list-style:none; 20179 margin:0; 20180 padding:0 10px; 20181 } 20182 20183 .animate-repeat { 20184 line-height:40px; 20185 list-style:none; 20186 box-sizing:border-box; 20187 } 20188 20189 .animate-repeat.ng-move, 20190 .animate-repeat.ng-enter, 20191 .animate-repeat.ng-leave { 20192 -webkit-transition:all linear 0.5s; 20193 transition:all linear 0.5s; 20194 } 20195 20196 .animate-repeat.ng-leave.ng-leave-active, 20197 .animate-repeat.ng-move, 20198 .animate-repeat.ng-enter { 20199 opacity:0; 20200 max-height:0; 20201 } 20202 20203 .animate-repeat.ng-leave,
20204 .animate-repeat.ng-move.ng-move-active, 20205 .animate-repeat.ng-enter.ng-enter-active { 20206 opacity:1; 20207 max-height:40px; 20208 } 20209 </file> 20210 <file name="protractor.js" type="protractor"> 20211 var friends = element.all(by.repeater('friend in friends')); 20212 20213 it('should render initial data set', function() { 20214 expect(friends.count()).toBe(10); 20215 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 20216 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 20217 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 20218 expect(element(by.binding('friends.length')).getText()) 20219 .toMatch("I have 10 friends. They are:"); 20220 }); 20221 20222 it('should update repeater when filter predicate changes', function() { 20223 expect(friends.count()).toBe(10); 20224 20225 element(by.model('q')).sendKeys('ma'); 20226 20227 expect(friends.count()).toBe(2); 20228 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 20229 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 20230 }); 20231 </file> 20232 </example> 20233 */ 20234var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 20235 var NG_REMOVED = '$$NG_REMOVED'; 20236 var ngRepeatMinErr = minErr('ngRepeat'); 20237 return { 20238 transclude: 'element', 20239 priority: 1000, 20240 terminal: true, 20241 $$tlb: true, 20242 link: function($scope, $element, $attr, ctrl, $transclude){ 20243 var expression = $attr.ngRepeat; 20244 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), 20245 trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, 20246 lhs, rhs, valueIdentifier, keyIdentifier, 20247 hashFnLocals = {$id: hashKey}; 20248 20249 if (!match) { 20250 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 20251 expression); 20252 } 20253 20254 lhs = match[1]; 20255 rhs = match[2]; 20256 trackByExp = match[3]; 20257 20258 if (trackByExp) { 20259 trackByExpGetter = $parse(trackByExp); 20260 trackByIdExpFn = function(key, value, index) { 20261 // assign key, value, and $index to the locals so that they can be used in hash functions 20262 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 20263 hashFnLocals[valueIdentifier] = value; 20264 hashFnLocals.$index = index; 20265 return trackByExpGetter($scope, hashFnLocals); 20266 }; 20267 } else { 20268 trackByIdArrayFn = function(key, value) { 20269 return hashKey(value); 20270 }; 20271 trackByIdObjFn = function(key) { 20272 return key; 20273 }; 20274 } 20275 20276 match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); 20277 if (!match) { 20278 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 20279 lhs); 20280 } 20281 valueIdentifier = match[3] || match[1]; 20282 keyIdentifier = match[2]; 20283 20284 // Store a list of elements from previous run. This is a hash where key is the item from the 20285 // iterator, and the value is objects with following properties. 20286 // - scope: bound scope 20287 // - element: previous element. 20288 // - index: position 20289 var lastBlockMap = {}; 20290 20291 //watch props 20292 $scope.$watchCollection(rhs, function ngRepeatAction(collection){ 20293 var index, length, 20294 previousNode = $element[0], // current position of the node 20295 nextNode, 20296 // Same as lastBlockMap but it has the current state. It will become the 20297 // lastBlockMap on the next iteration. 20298 nextBlockMap = {}, 20299 arrayLength, 20300 childScope, 20301 key, value, // key/value of iteration 20302 trackById, 20303 trackByIdFn, 20304 collectionKeys, 20305 block, // last object information {scope, element, id} 20306 nextBlockOrder = [], 20307 elementsToRemove; 20308 20309 20310 if (isArrayLike(collection)) { 20311 collectionKeys = collection; 20312 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 20313 } else { 20314 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 20315 // if object, extract keys, sort them and use to determine order of iteration over obj props 20316 collectionKeys = []; 20317 for (key in collection) { 20318 if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { 20319 collectionKeys.push(key); 20320 } 20321 } 20322 collectionKeys.sort(); 20323 } 20324 20325 arrayLength = collectionKeys.length; 20326 20327 // locate existing items 20328 length = nextBlockOrder.length = collectionKeys.length; 20329 for(index = 0; index < length; index++) { 20330 key = (collection === collectionKeys) ? index : collectionKeys[index]; 20331 value = collection[key]; 20332 trackById = trackByIdFn(key, value, index); 20333 assertNotHasOwnProperty(trackById, '`track by` id'); 20334 if(lastBlockMap.hasOwnProperty(trackById)) { 20335 block = lastBlockMap[trackById]; 20336 delete lastBlockMap[trackById]; 20337 nextBlockMap[trackById] = block; 20338 nextBlockOrder[index] = block; 20339 } else if (nextBlockMap.hasOwnProperty(trackById)) { 20340 // restore lastBlockMap
20341 forEach(nextBlockOrder, function(block) { 20342 if (block && block.scope) lastBlockMap[block.id] = block; 20343 }); 20344 // This is a duplicate and we need to throw an error 20345 throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}", 20346 expression, trackById); 20347 } else { 20348 // new never before seen block 20349 nextBlockOrder[index] = { id: trackById }; 20350 nextBlockMap[trackById] = false; 20351 } 20352 } 20353 20354 // remove existing items 20355 for (key in lastBlockMap) { 20356 // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn 20357 if (lastBlockMap.hasOwnProperty(key)) { 20358 block = lastBlockMap[key]; 20359 elementsToRemove = getBlockElements(block.clone); 20360 $animate.leave(elementsToRemove); 20361 forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); 20362 block.scope.$destroy(); 20363 } 20364 } 20365 20366 // we are not using forEach for perf reasons (trying to avoid #call) 20367 for (index = 0, length = collectionKeys.length; index < length; index++) { 20368 key = (collection === collectionKeys) ? index : collectionKeys[index]; 20369 value = collection[key]; 20370 block = nextBlockOrder[index]; 20371 if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]); 20372 20373 if (block.scope) { 20374 // if we have already seen this object, then we need to reuse the 20375 // associated scope/element 20376 childScope = block.scope; 20377 20378 nextNode = previousNode; 20379 do { 20380 nextNode = nextNode.nextSibling; 20381 } while(nextNode && nextNode[NG_REMOVED]); 20382 20383 if (getBlockStart(block) != nextNode) { 20384 // existing item which got moved 20385 $animate.move(getBlockElements(block.clone), null, jqLite(previousNode)); 20386 } 20387 previousNode = getBlockEnd(block); 20388 } else { 20389 // new item which we don't know about 20390 childScope = $scope.$new(); 20391 } 20392 20393 childScope[valueIdentifier] = value; 20394 if (keyIdentifier) childScope[keyIdentifier] = key; 20395 childScope.$index = index; 20396 childScope.$first = (index === 0); 20397 childScope.$last = (index === (arrayLength - 1)); 20398 childScope.$middle = !(childScope.$first || childScope.$last); 20399 // jshint bitwise: false 20400 childScope.$odd = !(childScope.$even = (index&1) === 0); 20401 // jshint bitwise: true 20402 20403 if (!block.scope) { 20404 $transclude(childScope, function(clone) { 20405 clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); 20406 $animate.enter(clone, null, jqLite(previousNode)); 20407 previousNode = clone; 20408 block.scope = childScope; 20409 // Note: We only need the first/last node of the cloned nodes. 20410 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 20411 // by a directive with templateUrl when its template arrives. 20412 block.clone = clone; 20413 nextBlockMap[block.id] = block; 20414 }); 20415 } 20416 } 20417 lastBlockMap = nextBlockMap; 20418 }); 20419 } 20420 }; 20421 20422 function getBlockStart(block) { 20423 return block.clone[0]; 20424 } 20425 20426 function getBlockEnd(block) { 20427 return block.clone[block.clone.length - 1]; 20428 } 20429}]; 20430 20431/** 20432 * @ngdoc directive 20433 * @name ngShow 20434 * 20435 * @description 20436 * The `ngShow` directive shows or hides the given HTML element based on the expression 20437 * provided to the ngShow attribute. The element is shown or hidden by removing or adding 20438 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 20439 * in AngularJS and sets the display style to none (using an !important flag). 20440 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 20441 * 20442 * ```html 20443 * <!-- when $scope.myValue is truthy (element is visible) --> 20444 * <div ng-show="myValue"></div> 20445 * 20446 * <!-- when $scope.myValue is falsy (element is hidden) --> 20447 * <div ng-show="myValue" class="ng-hide"></div> 20448 * ``` 20449 * 20450 * When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute 20451 * on the element causing it to become hidden. When true, the ng-hide CSS class is removed 20452 * from the element causing the element not to appear hidden. 20453 * 20454 * <div class="alert alert-warning"> 20455 * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):<br /> 20456 * "f" / "0" / "false" / "no" / "n" / "[]" 20457 * </div> 20458 * 20459 * ## Why is !important used? 20460 * 20461 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 20462 * can be easily overridden by heavier selectors. For example, something as simple 20463 * as changing the display style on a HTML list item would make hidden elements appear visible.
20464 * This also becomes a bigger issue when dealing with CSS frameworks. 20465 * 20466 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 20467 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 20468 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 20469 * 20470 * ### Overriding .ng-hide 20471 * 20472 * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change 20473 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 20474 * class in CSS: 20475 * 20476 * ```css 20477 * .ng-hide { 20478 * //this is just another form of hiding an element 20479 * display:block!important; 20480 * position:absolute; 20481 * top:-9999px; 20482 * left:-9999px; 20483 * } 20484 * ``` 20485 * 20486 * By default you don't need to override in CSS anything and the animations will work around the display style. 20487 * 20488 * ## A note about animations with ngShow 20489 * 20490 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 20491 * is true and false. This system works like the animation system present with ngClass except that 20492 * you must also include the !important flag to override the display property 20493 * so that you can perform an animation when the element is hidden during the time of the animation. 20494 * 20495 * ```css 20496 * // 20497 * //a working example can be found at the bottom of this page 20498 * // 20499 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 20500 * transition:0.5s linear all; 20501 * } 20502 * 20503 * .my-element.ng-hide-add { ... } 20504 * .my-element.ng-hide-add.ng-hide-add-active { ... } 20505 * .my-element.ng-hide-remove { ... } 20506 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 20507 * ``` 20508 * 20509 * 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 20510 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 20511 * 20512 * @animations 20513 * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible 20514 * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden 20515 * 20516 * @element ANY 20517 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 20518 * then the element is shown or hidden respectively. 20519 * 20520 * @example 20521 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20522 <file name="index.html"> 20523 Click me: <input type="checkbox" ng-model="checked"><br/> 20524 <div> 20525 Show: 20526 <div class="check-element animate-show" ng-show="checked"> 20527 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 20528 </div> 20529 </div> 20530 <div> 20531 Hide: 20532 <div class="check-element animate-show" ng-hide="checked"> 20533 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 20534 </div> 20535 </div> 20536 </file> 20537 <file name="glyphicons.css"> 20538 @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); 20539 </file> 20540 <file name="animations.css"> 20541 .animate-show { 20542 -webkit-transition:all linear 0.5s; 20543 transition:all linear 0.5s; 20544 line-height:20px; 20545 opacity:1; 20546 padding:10px; 20547 border:1px solid black; 20548 background:white; 20549 } 20550 20551 .animate-show.ng-hide { 20552 line-height:0; 20553 opacity:0; 20554 padding:0 10px; 20555 } 20556 20557 .check-element { 20558 padding:10px; 20559 border:1px solid black; 20560 background:white; 20561 } 20562 </file> 20563 <file name="protractor.js" type="protractor"> 20564 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 20565 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 20566 20567 it('should check ng-show / ng-hide', function() { 20568 expect(thumbsUp.isDisplayed()).toBeFalsy(); 20569 expect(thumbsDown.isDisplayed()).toBeTruthy(); 20570
20571 element(by.model('checked')).click(); 20572 20573 expect(thumbsUp.isDisplayed()).toBeTruthy(); 20574 expect(thumbsDown.isDisplayed()).toBeFalsy(); 20575 }); 20576 </file> 20577 </example> 20578 */ 20579var ngShowDirective = ['$animate', function($animate) { 20580 return function(scope, element, attr) { 20581 scope.$watch(attr.ngShow, function ngShowWatchAction(value){ 20582 $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); 20583 }); 20584 }; 20585}]; 20586 20587 20588/** 20589 * @ngdoc directive 20590 * @name ngHide 20591 * 20592 * @description 20593 * The `ngHide` directive shows or hides the given HTML element based on the expression 20594 * provided to the ngHide attribute. The element is shown or hidden by removing or adding 20595 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 20596 * in AngularJS and sets the display style to none (using an !important flag). 20597 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 20598 * 20599 * ```html 20600 * <!-- when $scope.myValue is truthy (element is hidden) --> 20601 * <div ng-hide="myValue" class="ng-hide"></div> 20602 * 20603 * <!-- when $scope.myValue is falsy (element is visible) --> 20604 * <div ng-hide="myValue"></div> 20605 * ``` 20606 * 20607 * When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute 20608 * on the element causing it to become hidden. When false, the ng-hide CSS class is removed 20609 * from the element causing the element not to appear hidden. 20610 * 20611 * <div class="alert alert-warning"> 20612 * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):<br /> 20613 * "f" / "0" / "false" / "no" / "n" / "[]" 20614 * </div> 20615 * 20616 * ## Why is !important used? 20617 * 20618 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 20619 * can be easily overridden by heavier selectors. For example, something as simple 20620 * as changing the display style on a HTML list item would make hidden elements appear visible. 20621 * This also becomes a bigger issue when dealing with CSS frameworks. 20622 * 20623 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 20624 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 20625 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 20626 * 20627 * ### Overriding .ng-hide 20628 * 20629 * By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change 20630 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 20631 * class in CSS: 20632 * 20633 * ```css 20634 * .ng-hide { 20635 * //this is just another form of hiding an element 20636 * display:block!important; 20637 * position:absolute; 20638 * top:-9999px; 20639 * left:-9999px; 20640 * } 20641 * ``` 20642 * 20643 * By default you don't need to override in CSS anything and the animations will work around the display style. 20644 * 20645 * ## A note about animations with ngHide 20646 * 20647 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 20648 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 20649 * CSS class is added and removed for you instead of your own CSS class. 20650 * 20651 * ```css 20652 * // 20653 * //a working example can be found at the bottom of this page 20654 * // 20655 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 20656 * transition:0.5s linear all; 20657 * } 20658 * 20659 * .my-element.ng-hide-add { ... } 20660 * .my-element.ng-hide-add.ng-hide-add-active { ... } 20661 * .my-element.ng-hide-remove { ... } 20662 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 20663 * ``` 20664 * 20665 * 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 20666 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 20667 * 20668 * @animations 20669 * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden 20670 * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible 20671 * 20672 * @element ANY 20673 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 20674 * the element is shown or hidden respectively. 20675 * 20676 * @example 20677 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20678 <file name="index.html"> 20679 Click me: <input type="checkbox" ng-model="checked"><br/> 20680 <div> 20681 Show: 20682 <div class="check-element animate-hide" ng-show="checked">
20683 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 20684 </div> 20685 </div> 20686 <div> 20687 Hide: 20688 <div class="check-element animate-hide" ng-hide="checked"> 20689 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 20690 </div> 20691 </div> 20692 </file> 20693 <file name="glyphicons.css"> 20694 @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); 20695 </file> 20696 <file name="animations.css"> 20697 .animate-hide { 20698 -webkit-transition:all linear 0.5s; 20699 transition:all linear 0.5s; 20700 line-height:20px; 20701 opacity:1; 20702 padding:10px; 20703 border:1px solid black; 20704 background:white; 20705 } 20706 20707 .animate-hide.ng-hide { 20708 line-height:0; 20709 opacity:0; 20710 padding:0 10px; 20711 } 20712 20713 .check-element { 20714 padding:10px; 20715 border:1px solid black; 20716 background:white; 20717 } 20718 </file> 20719 <file name="protractor.js" type="protractor"> 20720 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 20721 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 20722 20723 it('should check ng-show / ng-hide', function() { 20724 expect(thumbsUp.isDisplayed()).toBeFalsy(); 20725 expect(thumbsDown.isDisplayed()).toBeTruthy(); 20726 20727 element(by.model('checked')).click(); 20728 20729 expect(thumbsUp.isDisplayed()).toBeTruthy(); 20730 expect(thumbsDown.isDisplayed()).toBeFalsy(); 20731 }); 20732 </file> 20733 </example> 20734 */ 20735var ngHideDirective = ['$animate', function($animate) { 20736 return function(scope, element, attr) { 20737 scope.$watch(attr.ngHide, function ngHideWatchAction(value){ 20738 $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); 20739 }); 20740 }; 20741}]; 20742 20743/** 20744 * @ngdoc directive 20745 * @name ngStyle 20746 * @restrict AC 20747 * 20748 * @description 20749 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 20750 * 20751 * @element ANY 20752 * @param {expression} ngStyle 20753 * 20754 * {@link guide/expression Expression} which evals to an 20755 * object whose keys are CSS style names and values are corresponding values for those CSS 20756 * keys. 20757 * 20758 * Since some CSS style names are not valid keys for an object, they must be quoted. 20759 * See the 'background-color' style in the example below. 20760 * 20761 * @example 20762 <example> 20763 <file name="index.html"> 20764 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 20765 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 20766 <input type="button" value="clear" ng-click="myStyle={}"> 20767 <br/> 20768 <span ng-style="myStyle">Sample Text</span> 20769 <pre>myStyle={{myStyle}}</pre> 20770 </file> 20771 <file name="style.css"> 20772 span { 20773 color: black; 20774 } 20775 </file> 20776 <file name="protractor.js" type="protractor"> 20777 var colorSpan = element(by.css('span')); 20778 20779 it('should check ng-style', function() { 20780 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 20781 element(by.css('input[value=\'set color\']')).click(); 20782 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 20783 element(by.css('input[value=clear]')).click(); 20784 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 20785 }); 20786 </file> 20787 </example> 20788 */ 20789var ngStyleDirective = ngDirective(function(scope, element, attr) { 20790 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 20791 if (oldStyles && (newStyles !== oldStyles)) { 20792 forEach(oldStyles, function(val, style) { element.css(style, '');}); 20793 } 20794 if (newStyles) element.css(newStyles); 20795 }, true); 20796}); 20797 20798/** 20799 * @ngdoc directive 20800 * @name ngSwitch 20801 * @restrict EA 20802 * 20803 * @description 20804 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 20805 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 20806 * as specified in the template. 20807 * 20808 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 20809 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 20810 * matches the value obtained from the evaluated expression. In other words, you define a container element 20811 * (where you place the directive), place an expression on the **`on="..."` attribute** 20812 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 20813 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 20814 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 20815 * attribute is displayed. 20816 * 20817 * <div class="alert alert-info"> 20818 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 20819 * as literal string values to match against. 20820 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
20820omeVal"` not against the 20821 * value of the expression `$scope.someVal`. 20822 * </div> 20823 20824 * @animations 20825 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 20826 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 20827 * 20828 * @usage 20829 * 20830 * ``` 20831 * <ANY ng-switch="expression"> 20832 * <ANY ng-switch-when="matchValue1">...</ANY> 20833 * <ANY ng-switch-when="matchValue2">...</ANY> 20834 * <ANY ng-switch-default>...</ANY> 20835 * </ANY> 20836 * ``` 20837 * 20838 * 20839 * @scope 20840 * @priority 800 20841 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>. 20842 * On child elements add: 20843 * 20844 * * `ngSwitchWhen`: the case statement to match against. If match then this 20845 * case will be displayed. If the same match appears multiple times, all the 20846 * elements will be displayed. 20847 * * `ngSwitchDefault`: the default case when no other case match. If there 20848 * are multiple default cases, all of them will be displayed when no other 20849 * case match. 20850 * 20851 * 20852 * @example 20853 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20854 <file name="index.html"> 20855 <div ng-controller="Ctrl"> 20856 <select ng-model="selection" ng-options="item for item in items"> 20857 </select> 20858 <tt>selection={{selection}}</tt> 20859 <hr/> 20860 <div class="animate-switch-container" 20861 ng-switch on="selection"> 20862 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 20863 <div class="animate-switch" ng-switch-when="home">Home Span</div> 20864 <div class="animate-switch" ng-switch-default>default</div> 20865 </div> 20866 </div> 20867 </file> 20868 <file name="script.js"> 20869 function Ctrl($scope) { 20870 $scope.items = ['settings', 'home', 'other']; 20871 $scope.selection = $scope.items[0]; 20872 } 20873 </file> 20874 <file name="animations.css"> 20875 .animate-switch-container { 20876 position:relative; 20877 background:white; 20878 border:1px solid black; 20879 height:40px; 20880 overflow:hidden; 20881 } 20882 20883 .animate-switch { 20884 padding:10px; 20885 } 20886 20887 .animate-switch.ng-animate { 20888 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 20889 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 20890 20891 position:absolute; 20892 top:0; 20893 left:0; 20894 right:0; 20895 bottom:0; 20896 } 20897 20898 .animate-switch.ng-leave.ng-leave-active, 20899 .animate-switch.ng-enter { 20900 top:-50px; 20901 } 20902 .animate-switch.ng-leave, 20903 .animate-switch.ng-enter.ng-enter-active { 20904 top:0; 20905 } 20906 </file> 20907 <file name="protractor.js" type="protractor"> 20908 var switchElem = element(by.css('[ng-switch]')); 20909 var select = element(by.model('selection')); 20910 20911 it('should start in settings', function() { 20912 expect(switchElem.getText()).toMatch(/Settings Div/); 20913 }); 20914 it('should change to home', function() { 20915 select.element.all(by.css('option')).get(1).click(); 20916 expect(switchElem.getText()).toMatch(/Home Span/); 20917 }); 20918 it('should select default', function() { 20919 select.element.all(by.css('option')).get(2).click(); 20920 expect(switchElem.getText()).toMatch(/default/); 20921 }); 20922 </file> 20923 </example> 20924 */ 20925var ngSwitchDirective = ['$animate', function($animate) { 20926 return { 20927 restrict: 'EA', 20928 require: 'ngSwitch', 20929 20930 // asks for $scope to fool the BC controller module 20931 controller: ['$scope', function ngSwitchController() { 20932 this.cases = {}; 20933 }], 20934 link: function(scope, element, attr, ngSwitchController) { 20935 var watchExpr = attr.ngSwitch || attr.on, 20936 selectedTranscludes = [], 20937 selectedElements = [], 20938 previousElements = [], 20939 selectedScopes = []; 20940 20941 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 20942 var i, ii; 20943 for (i = 0, ii = previousElements.length; i < ii; ++i) { 20944 previousElements[i].remove(); 20945 } 20946 previousElements.length = 0; 20947 20948 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 20949 var selected = selectedElements[i]; 20950 selectedScopes[i].$destroy(); 20951 previousElements[i] = selected; 20952 $animate.leave(selected, function() { 20953 previousElements.splice(i, 1); 20954 }); 20955 } 20956 20957 selectedElements.length = 0; 20958 selectedScopes.length = 0; 20959 20960 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 20961 scope.$eval(attr.change);
20962 forEach(selectedTranscludes, function(selectedTransclude) { 20963 var selectedScope = scope.$new(); 20964 selectedScopes.push(selectedScope); 20965 selectedTransclude.transclude(selectedScope, function(caseElement) { 20966 var anchor = selectedTransclude.element; 20967 20968 selectedElements.push(caseElement); 20969 $animate.enter(caseElement, anchor.parent(), anchor); 20970 }); 20971 }); 20972 } 20973 }); 20974 } 20975 }; 20976}]; 20977 20978var ngSwitchWhenDirective = ngDirective({ 20979 transclude: 'element', 20980 priority: 800, 20981 require: '^ngSwitch', 20982 link: function(scope, element, attrs, ctrl, $transclude) { 20983 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 20984 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 20985 } 20986}); 20987 20988var ngSwitchDefaultDirective = ngDirective({ 20989 transclude: 'element', 20990 priority: 800, 20991 require: '^ngSwitch', 20992 link: function(scope, element, attr, ctrl, $transclude) { 20993 ctrl.cases['?'] = (ctrl.cases['?'] || []); 20994 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 20995 } 20996}); 20997 20998/** 20999 * @ngdoc directive 21000 * @name ngTransclude 21001 * @restrict AC 21002 * 21003 * @description 21004 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 21005 * 21006 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 21007 * 21008 * @element ANY 21009 * 21010 * @example 21011 <example module="transclude"> 21012 <file name="index.html"> 21013 <script> 21014 function Ctrl($scope) { 21015 $scope.title = 'Lorem Ipsum'; 21016 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 21017 } 21018 21019 angular.module('transclude', []) 21020 .directive('pane', function(){ 21021 return { 21022 restrict: 'E', 21023 transclude: true, 21024 scope: { title:'@' }, 21025 template: '<div style="border: 1px solid black;">' + 21026 '<div style="background-color: gray">{{title}}</div>' + 21027 '<div ng-transclude></div>' + 21028 '</div>' 21029 }; 21030 }); 21031 </script> 21032 <div ng-controller="Ctrl"> 21033 <input ng-model="title"><br> 21034 <textarea ng-model="text"></textarea> <br/> 21035 <pane title="{{title}}">{{text}}</pane> 21036 </div> 21037 </file> 21038 <file name="protractor.js" type="protractor"> 21039 it('should have transcluded', function() { 21040 var titleElement = element(by.model('title')); 21041 titleElement.clear(); 21042 titleElement.sendKeys('TITLE'); 21043 var textElement = element(by.model('text')); 21044 textElement.clear(); 21045 textElement.sendKeys('TEXT'); 21046 expect(element(by.binding('title')).getText()).toEqual('TITLE'); 21047 expect(element(by.binding('text')).getText()).toEqual('TEXT'); 21048 }); 21049 </file> 21050 </example> 21051 * 21052 */ 21053var ngTranscludeDirective = ngDirective({ 21054 link: function($scope, $element, $attrs, controller, $transclude) { 21055 if (!$transclude) { 21056 throw minErr('ngTransclude')('orphan', 21057 'Illegal use of ngTransclude directive in the template! ' + 21058 'No parent directive that requires a transclusion found. ' + 21059 'Element: {0}', 21060 startingTag($element)); 21061 } 21062 21063 $transclude(function(clone) { 21064 $element.empty(); 21065 $element.append(clone); 21066 }); 21067 } 21068}); 21069 21070/** 21071 * @ngdoc directive 21072 * @name script 21073 * @restrict E 21074 * 21075 * @description 21076 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 21077 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 21078 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 21079 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 21080 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 21081 * 21082 * @param {string} type Must be set to `'text/ng-template'`. 21083 * @param {string} id Cache name of the template. 21084 * 21085 * @example 21086 <example> 21087 <file name="index.html"> 21088 <script type="text/ng-template" id="/tpl.html"> 21089 Content of the template. 21090 </script> 21091 21092 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 21093 <div id="tpl-content" ng-include src="currentTpl"></div> 21094 </file> 21095 <file name="protractor.js" type="protractor"> 21096 it('should load template defined inside script tag', function() {
21097 element(by.css('#tpl-link')).click(); 21098 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 21099 }); 21100 </file> 21101 </example> 21102 */ 21103var scriptDirective = ['$templateCache', function($templateCache) { 21104 return { 21105 restrict: 'E', 21106 terminal: true, 21107 compile: function(element, attr) { 21108 if (attr.type == 'text/ng-template') { 21109 var templateUrl = attr.id, 21110 // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent 21111 text = element[0].text; 21112 21113 $templateCache.put(templateUrl, text); 21114 } 21115 } 21116 }; 21117}]; 21118 21119var ngOptionsMinErr = minErr('ngOptions'); 21120/** 21121 * @ngdoc directive 21122 * @name select 21123 * @restrict E 21124 * 21125 * @description 21126 * HTML `SELECT` element with angular data-binding. 21127 * 21128 * # `ngOptions` 21129 * 21130 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 21131 * elements for the `<select>` element using the array or object obtained by evaluating the 21132 * `ngOptions` comprehension_expression. 21133 * 21134 * When an item in the `<select>` menu is selected, the array element or object property 21135 * represented by the selected option will be bound to the model identified by the `ngModel` 21136 * directive. 21137 * 21138 * <div class="alert alert-warning"> 21139 * **Note:** `ngModel` compares by reference, not value. This is important when binding to an 21140 * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). 21141 * </div> 21142 * 21143 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 21144 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 21145 * option. See example below for demonstration. 21146 * 21147 * <div class="alert alert-warning"> 21148 * **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead 21149 * of {@link ng.directive:ngRepeat ngRepeat} when you want the 21150 * `select` model to be bound to a non-string value. This is because an option element can only 21151 * be bound to string values at present. 21152 * </div> 21153 * 21154 * @param {string} ngModel Assignable angular expression to data-bind to. 21155 * @param {string=} name Property name of the form under which the control is published. 21156 * @param {string=} required The control is considered valid only if value is entered. 21157 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21158 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21159 * `required` when you want to data-bind to the `required` attribute. 21160 * @param {comprehension_expression=} ngOptions in one of the following forms: 21161 * 21162 * * for array data sources: 21163 * * `label` **`for`** `value` **`in`** `array` 21164 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 21165 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 21166 * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 21167 * * for object data sources: 21168 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 21169 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 21170 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 21171 * * `select` **`as`** `label` **`group by`** `group` 21172 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 21173 * 21174 * Where: 21175 * 21176 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 21177 * * `value`: local variable which will refer to each item in the `array` or each property value 21178 * of `object` during iteration. 21179 * * `key`: local variable which will refer to a property name in `object` during iteration. 21180 * * `label`: The result of this expression will be the label for `<option>` element. The 21181 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 21182 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 21183 * element. If not specified, `select` expression will default to `value`. 21184 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 21185 * DOM element. 21186 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 21187 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 21188 * `value` variable (e.g. `value.propertyName`). 21189 * 21190 * @example 21191 <example> 21192 <file name="index.html"> 21193 <script> 21194 function MyCntrl($scope) { 21195 $scope.colors = [ 21196 {name:'black', shade:'dark'}, 21197 {name:'white', shade:'light'}, 21198 {name:'red', shade:'dark'}, 21199 {name:'blue', shade:'dark'}, 21200 {name:'yellow', shade:'light'} 21201 ]; 21202 $scope.myColor = $scope.colors[2]; // red 21203 } 21204 </script> 21205 <div ng-controller="MyCntrl"> 21206 <ul> 21207 <li ng-repeat="color in colors"> 21208 Name: <input ng-model="color.name"> 21209 [<a href ng-click="colors.splice($index, 1)">X</a>] 21210 </li> 21211 <li> 21212 [<a href ng-click="colors.push({})">add</a>] 21213 </li> 21214 </ul> 21215 <hr/> 21216 Color (null not allowed): 21217 <select ng-model="myColor" ng-options="color.name for color in colors"></select><br> 21218 21219 Color (null allowed):
21220 <span class="nullable"> 21221 <select ng-model="myColor" ng-options="color.name for color in colors"> 21222 <option value="">-- choose color --</option> 21223 </select> 21224 </span><br/> 21225 21226 Color grouped by shade: 21227 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 21228 </select><br/> 21229 21230 21231 Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br> 21232 <hr/> 21233 Currently selected: {{ {selected_color:myColor} }} 21234 <div style="border:solid 1px black; height:20px" 21235 ng-style="{'background-color':myColor.name}"> 21236 </div> 21237 </div> 21238 </file> 21239 <file name="protractor.js" type="protractor"> 21240 it('should check ng-options', function() { 21241 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 21242 element.all(by.select('myColor')).first().click(); 21243 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 21244 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 21245 element(by.css('.nullable select[ng-model="myColor"]')).click(); 21246 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 21247 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 21248 }); 21249 </file> 21250 </example> 21251 */ 21252 21253var ngOptionsDirective = valueFn({ terminal: true }); 21254// jshint maxlen: false 21255var selectDirective = ['$compile', '$parse', function($compile, $parse) { 21256 //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 21257 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]+?))?$/, 21258 nullModelCtrl = {$setViewValue: noop}; 21259// jshint maxlen: 100 21260 21261 return { 21262 restrict: 'E', 21263 require: ['select', '?ngModel'], 21264 controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 21265 var self = this, 21266 optionsMap = {}, 21267 ngModelCtrl = nullModelCtrl, 21268 nullOption, 21269 unknownOption; 21270 21271 21272 self.databound = $attrs.ngModel; 21273 21274 21275 self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { 21276 ngModelCtrl = ngModelCtrl_; 21277 nullOption = nullOption_; 21278 unknownOption = unknownOption_; 21279 }; 21280 21281 21282 self.addOption = function(value) { 21283 assertNotHasOwnProperty(value, '"option value"'); 21284 optionsMap[value] = true; 21285 21286 if (ngModelCtrl.$viewValue == value) { 21287 $element.val(value); 21288 if (unknownOption.parent()) unknownOption.remove(); 21289 } 21290 }; 21291 21292 21293 self.removeOption = function(value) { 21294 if (this.hasOption(value)) { 21295 delete optionsMap[value]; 21296 if (ngModelCtrl.$viewValue == value) { 21297 this.renderUnknownOption(value); 21298 } 21299 } 21300 }; 21301 21302 21303 self.renderUnknownOption = function(val) { 21304 var unknownVal = '? ' + hashKey(val) + ' ?'; 21305 unknownOption.val(unknownVal); 21306 $element.prepend(unknownOption); 21307 $element.val(unknownVal); 21308 unknownOption.prop('selected', true); // needed for IE 21309 }; 21310 21311 21312 self.hasOption = function(value) { 21313 return optionsMap.hasOwnProperty(value); 21314 }; 21315 21316 $scope.$on('$destroy', function() { 21317 // disable unknown option so that we don't do work when the whole select is being destroyed 21318 self.renderUnknownOption = noop; 21319 }); 21320 }], 21321 21322 link: function(scope, element, attr, ctrls) { 21323 // if ngModel is not defined, we don't need to do anything 21324 if (!ctrls[1]) return; 21325 21326 var selectCtrl = ctrls[0], 21327 ngModelCtrl = ctrls[1], 21328 multiple = attr.multiple, 21329 optionsExp = attr.ngOptions, 21330 nullOption = false, // if false, user will not be able to select it (used by ngOptions) 21331 emptyOption, 21332 // we can't just jqLite('<option>') since jqLite is not smart enough 21333 // to create it in <select> and IE barfs otherwise. 21334 optionTemplate = jqLite(document.createElement('option')), 21335 optGroupTemplate =jqLite(document.createElement('optgroup')), 21336 unknownOption = optionTemplate.clone(); 21337 21338 // find "null" option 21339 for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) { 21340 if (children[i].value === '') { 21341 emptyOption = nullOption = children.eq(i); 21342 break; 21343 } 21344 } 21345 21346 selectCtrl.init(ngModelCtrl, nullOption, unknownOption); 21347 21348 // required validator 21349 if (multiple) { 21350 ngModelCtrl.$isEmpty = function(value) { 21351 return !value || value.length === 0; 21352 }; 21353 } 21354 21355 if (optionsExp) setupAsOptions(scope, element, ngModelCtrl); 21356 else if (multiple) setupAsMultiple(scope, element, ngModelCtrl); 21357 else setupAsSingle(scope, element, ngModelCtrl, selectCtrl); 21358 21359
21360 //////////////////////////// 21361 21362 21363 21364 function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) { 21365 ngModelCtrl.$render = function() { 21366 var viewValue = ngModelCtrl.$viewValue; 21367 21368 if (selectCtrl.hasOption(viewValue)) { 21369 if (unknownOption.parent()) unknownOption.remove(); 21370 selectElement.val(viewValue); 21371 if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy 21372 } else { 21373 if (isUndefined(viewValue) && emptyOption) { 21374 selectElement.val(''); 21375 } else { 21376 selectCtrl.renderUnknownOption(viewValue); 21377 } 21378 } 21379 }; 21380 21381 selectElement.on('change', function() { 21382 scope.$apply(function() { 21383 if (unknownOption.parent()) unknownOption.remove(); 21384 ngModelCtrl.$setViewValue(selectElement.val()); 21385 }); 21386 }); 21387 } 21388 21389 function setupAsMultiple(scope, selectElement, ctrl) { 21390 var lastView; 21391 ctrl.$render = function() { 21392 var items = new HashMap(ctrl.$viewValue); 21393 forEach(selectElement.find('option'), function(option) { 21394 option.selected = isDefined(items.get(option.value)); 21395 }); 21396 }; 21397 21398 // we have to do it on each watch since ngModel watches reference, but 21399 // we need to work of an array, so we need to see if anything was inserted/removed 21400 scope.$watch(function selectMultipleWatch() { 21401 if (!equals(lastView, ctrl.$viewValue)) { 21402 lastView = shallowCopy(ctrl.$viewValue); 21403 ctrl.$render(); 21404 } 21405 }); 21406 21407 selectElement.on('change', function() { 21408 scope.$apply(function() { 21409 var array = []; 21410 forEach(selectElement.find('option'), function(option) { 21411 if (option.selected) { 21412 array.push(option.value); 21413 } 21414 }); 21415 ctrl.$setViewValue(array); 21416 }); 21417 }); 21418 } 21419 21420 function setupAsOptions(scope, selectElement, ctrl) { 21421 var match; 21422 21423 if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) { 21424 throw ngOptionsMinErr('iexp', 21425 "Expected expression in form of " + 21426 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 21427 " but got '{0}'. Element: {1}", 21428 optionsExp, startingTag(selectElement)); 21429 } 21430 21431 var displayFn = $parse(match[2] || match[1]), 21432 valueName = match[4] || match[6], 21433 keyName = match[5], 21434 groupByFn = $parse(match[3] || ''), 21435 valueFn = $parse(match[2] ? match[1] : valueName), 21436 valuesFn = $parse(match[7]), 21437 track = match[8], 21438 trackFn = track ? $parse(match[8]) : null, 21439 // This is an array of array of existing option groups in DOM. 21440 // We try to reuse these if possible 21441 // - optionGroupsCache[0] is the options with no option group 21442 // - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element 21443 optionGroupsCache = [[{element: selectElement, label:''}]]; 21444 21445 if (nullOption) { 21446 // compile the element since there might be bindings in it 21447 $compile(nullOption)(scope); 21448 21449 // remove the class, which is added automatically because we recompile the element and it 21450 // becomes the compilation root 21451 nullOption.removeClass('ng-scope'); 21452 21453 // we need to remove it before calling selectElement.empty() because otherwise IE will 21454 // remove the label from the element. wtf? 21455 nullOption.remove(); 21456 } 21457 21458 // clear contents, we'll add what's needed based on the model 21459 selectElement.empty(); 21460 21461 selectElement.on('change', function() { 21462 scope.$apply(function() { 21463 var optionGroup, 21464 collection = valuesFn(scope) || [], 21465 locals = {}, 21466 key, value, optionElement, index, groupIndex, length, groupLength, trackIndex; 21467 21468 if (multiple) { 21469 value = []; 21470 for (groupIndex = 0, groupLength = optionGroupsCache.length; 21471 groupIndex < groupLength; 21472 groupIndex++) { 21473 // list of options for that group. (first item has the parent) 21474 optionGroup = optionGroupsCache[groupIndex]; 21475 21476 for(index = 1, length = optionGroup.length; index < length; index++) { 21477 if ((optionElement = optionGroup[index].element)[0].selected) { 21478 key = optionElement.val(); 21479 if (keyName) locals[keyName] = key; 21480 if (trackFn) { 21481 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 21482 locals[valueName] = collection[trackIndex]; 21483 if (trackFn(scope, locals) == key) break; 21484 } 21485 } else { 21486 locals[valueName] = collection[key]; 21487 } 21488 value.push(valueFn(scope, locals)); 21489 } 21490 } 21491 } 21492 } else { 21493 key = selectElement.val(); 21494 if (key == '?') { 21495 value = undefined; 21496 } else if (key === ''){
21497 value = null; 21498 } else { 21499 if (trackFn) { 21500 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 21501 locals[valueName] = collection[trackIndex]; 21502 if (trackFn(scope, locals) == key) { 21503 value = valueFn(scope, locals); 21504 break; 21505 } 21506 } 21507 } else { 21508 locals[valueName] = collection[key]; 21509 if (keyName) locals[keyName] = key; 21510 value = valueFn(scope, locals); 21511 } 21512 } 21513 // Update the null option's selected property here so $render cleans it up correctly 21514 if (optionGroupsCache[0].length > 1) { 21515 if (optionGroupsCache[0][1].id !== key) { 21516 optionGroupsCache[0][1].selected = false; 21517 } 21518 } 21519 } 21520 ctrl.$setViewValue(value); 21521 }); 21522 }); 21523 21524 ctrl.$render = render; 21525 21526 // TODO(vojta): can't we optimize this ? 21527 scope.$watch(render); 21528 21529 function render() { 21530 // Temporary location for the option groups before we render them 21531 var optionGroups = {'':[]}, 21532 optionGroupNames = [''], 21533 optionGroupName, 21534 optionGroup, 21535 option, 21536 existingParent, existingOptions, existingOption, 21537 modelValue = ctrl.$modelValue, 21538 values = valuesFn(scope) || [], 21539 keys = keyName ? sortedKeys(values) : values, 21540 key, 21541 groupLength, length, 21542 groupIndex, index, 21543 locals = {}, 21544 selected, 21545 selectedSet = false, // nothing is selected yet 21546 lastElement, 21547 element, 21548 label; 21549 21550 if (multiple) { 21551 if (trackFn && isArray(modelValue)) { 21552 selectedSet = new HashMap([]); 21553 for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) { 21554 locals[valueName] = modelValue[trackIndex]; 21555 selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]); 21556 } 21557 } else { 21558 selectedSet = new HashMap(modelValue); 21559 } 21560 } 21561 21562 // We now build up the list of options we need (we merge later) 21563 for (index = 0; length = keys.length, index < length; index++) { 21564 21565 key = index; 21566 if (keyName) { 21567 key = keys[index]; 21568 if ( key.charAt(0) === '$' ) continue; 21569 locals[keyName] = key; 21570 } 21571 21572 locals[valueName] = values[key]; 21573 21574 optionGroupName = groupByFn(scope, locals) || ''; 21575 if (!(optionGroup = optionGroups[optionGroupName])) { 21576 optionGroup = optionGroups[optionGroupName] = []; 21577 optionGroupNames.push(optionGroupName); 21578 } 21579 if (multiple) { 21580 selected = isDefined( 21581 selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals)) 21582 ); 21583 } else { 21584 if (trackFn) { 21585 var modelCast = {}; 21586 modelCast[valueName] = modelValue; 21587 selected = trackFn(scope, modelCast) === trackFn(scope, locals); 21588 } else { 21589 selected = modelValue === valueFn(scope, locals); 21590 } 21591 selectedSet = selectedSet || selected; // see if at least one item is selected 21592 } 21593 label = displayFn(scope, locals); // what will be seen by the user 21594 21595 // doing displayFn(scope, locals) || '' overwrites zero values 21596 label = isDefined(label) ? label : ''; 21597 optionGroup.push({ 21598 // either the index into array or key from object 21599 id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index), 21600 label: label, 21601 selected: selected // determine if we should be selected 21602 }); 21603 } 21604 if (!multiple) { 21605 if (nullOption || modelValue === null) { 21606 // insert null option if we have a placeholder, or the model is null 21607 optionGroups[''].unshift({id:'', label:'', selected:!selectedSet}); 21608 } else if (!selectedSet) { 21609 // option could not be found, we have to insert the undefined item 21610 optionGroups[''].unshift({id:'?', label:'', selected:true}); 21611 } 21612 } 21613 21614 // Now we need to update the list of DOM nodes to match the optionGroups we computed above 21615 for (groupIndex = 0, groupLength = optionGroupNames.length; 21616 groupIndex < groupLength; 21617 groupIndex++) { 21618 // current option group name or '' if no group 21619 optionGroupName = optionGroupNames[groupIndex]; 21620 21621 // list of options for that group. (first item has the parent) 21622 optionGroup = optionGroups[optionGroupName]; 21623 21624 if (optionGroupsCache.length <= groupIndex) { 21625 // we need to grow the optionGroups 21626 existingParent = { 21627 element: optGroupTemplate.clone().attr('label', optionGroupName), 21628 label: optionGroup.label 21629 }; 21630 existingOptions = [existingParent]; 21631 optionGroupsCache.push(existingOptions); 21632 selectElement.append(existingParent.element); 21633 } else { 21634 existingOptions = optionGroupsCache[groupIndex]; 21635 existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element 21636 21637 // update the OPTGROUP label if not the same. 21638 if (existingParent.label != optionGroupName) { 21639 existingParent.element.attr('label', existingParent.label = optionGroupName); 21640 } 21641 } 21642 21643 lastElement = null; // start at the beginning 21644 for(index = 0, length = optionGroup.length; index < length; index++) { 21645 option = optionGroup[index]; 21646 if ((existingOption = existingOptions[index+1])) { 21647 // reuse elements 21648 lastElement = existingOption.element; 21649 if (existingOption.label !== option.label) { 21650 lastElement.text(existingOption.label = option.label); 21651 } 21652 if (existingOption.id !== option.id) { 21653 lastElement.val(existingOption.id = option.id); 21654 } 21655 // lastElement.prop('selected') provided by jQuery has side-effects 21656 if (existingOption.selected !== option.selected) {
21657 lastElement.prop('selected', (existingOption.selected = option.selected)); 21658 } 21659 } else { 21660 // grow elements 21661 21662 // if it's a null option 21663 if (option.id === '' && nullOption) { 21664 // put back the pre-compiled element 21665 element = nullOption; 21666 } else { 21667 // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but 21668 // in this version of jQuery on some browser the .text() returns a string 21669 // rather then the element. 21670 (element = optionTemplate.clone()) 21671 .val(option.id) 21672 .prop('selected', option.selected) 21673 .text(option.label); 21674 } 21675 21676 existingOptions.push(existingOption = { 21677 element: element, 21678 label: option.label, 21679 id: option.id, 21680 selected: option.selected 21681 }); 21682 if (lastElement) { 21683 lastElement.after(element); 21684 } else { 21685 existingParent.element.append(element); 21686 } 21687 lastElement = element; 21688 } 21689 } 21690 // remove any excessive OPTIONs in a group 21691 index++; // increment since the existingOptions[0] is parent element not OPTION 21692 while(existingOptions.length > index) { 21693 existingOptions.pop().element.remove(); 21694 } 21695 } 21696 // remove any excessive OPTGROUPs from select 21697 while(optionGroupsCache.length > groupIndex) { 21698 optionGroupsCache.pop()[0].element.remove(); 21699 } 21700 } 21701 } 21702 } 21703 }; 21704}]; 21705 21706var optionDirective = ['$interpolate', function($interpolate) { 21707 var nullSelectCtrl = { 21708 addOption: noop, 21709 removeOption: noop 21710 }; 21711 21712 return { 21713 restrict: 'E', 21714 priority: 100, 21715 compile: function(element, attr) { 21716 if (isUndefined(attr.value)) { 21717 var interpolateFn = $interpolate(element.text(), true); 21718 if (!interpolateFn) { 21719 attr.$set('value', element.text()); 21720 } 21721 } 21722 21723 return function (scope, element, attr) { 21724 var selectCtrlName = '$selectController', 21725 parent = element.parent(), 21726 selectCtrl = parent.data(selectCtrlName) || 21727 parent.parent().data(selectCtrlName); // in case we are in optgroup 21728 21729 if (selectCtrl && selectCtrl.databound) { 21730 // For some reason Opera defaults to true and if not overridden this messes up the repeater. 21731 // We don't want the view to drive the initialization of the model anyway. 21732 element.prop('selected', false); 21733 } else { 21734 selectCtrl = nullSelectCtrl; 21735 } 21736 21737 if (interpolateFn) { 21738 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 21739 attr.$set('value', newVal); 21740 if (newVal !== oldVal) selectCtrl.removeOption(oldVal); 21741 selectCtrl.addOption(newVal); 21742 }); 21743 } else { 21744 selectCtrl.addOption(attr.value); 21745 } 21746 21747 element.on('$destroy', function() { 21748 selectCtrl.removeOption(attr.value); 21749 }); 21750 }; 21751 } 21752 }; 21753}]; 21754 21755var styleDirective = valueFn({ 21756 restrict: 'E', 21757 terminal: true 21758}); 21759 21760 if (window.angular.bootstrap) { 21761 //AngularJS is already loaded, so we can return here... 21762 console.log('WARNING: Tried to load angular more than once.'); 21763 return; 21764 } 21765 21766 //try to bind to jquery now so that one can write angular.element().read() 21767 //but we will rebind on bootstrap again. 21768 bindJQuery(); 21769 21770 publishExternalAPI(angular); 21771 21772 jqLite(document).ready(function() { 21773 angularInit(document, bootstrap); 21774 }); 21775 21776})(window, document); 21777 21778!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.