1/** 2 * @license AngularJS v1.2.13 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(string, string, ...): Error} 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.13/' + 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 -_angular, 93 -angularModule, 94 -nodeName_, 95 -uid, 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 -isBoolean, 128 -trim, 129 -isElement, 130 -makeMap, 131 -map, 132 -size, 133 -includes, 134 -indexOf, 135 -arrayRemove, 136 -isLeafNode, 137 -copy, 138 -shallowCopy, 139 -equals, 140 -csp, 141 -concat, 142 -sliceArgs, 143 -bind, 144 -toJsonReplacer, 145 -toJson, 146 -fromJson, 147 -toBoolean, 148 -startingTag, 149 -tryDecodeURIComponent, 150 -parseKeyValue, 151 -toKeyValue, 152 -encodeUriSegment, 153 -encodeUriQuery, 154 -angularInit, 155 -bootstrap, 156 -snake_case, 157 -bindJQuery, 158 -assertArg, 159 -assertArgFn,
160 -assertNotHasOwnProperty, 161 -getter, 162 -getBlockElements, 163 -hasOwnProperty, 164 165*/ 166 167//////////////////////////////////// 168 169/** 170 * @ngdoc function 171 * @name angular.lowercase 172 * @function 173 * 174 * @description Converts the specified string to lowercase. 175 * @param {string} string String to be converted to lowercase. 176 * @returns {string} Lowercased string. 177 */ 178var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; 179var hasOwnProperty = Object.prototype.hasOwnProperty; 180 181/** 182 * @ngdoc function 183 * @name angular.uppercase 184 * @function 185 * 186 * @description Converts the specified string to uppercase. 187 * @param {string} string String to be converted to uppercase. 188 * @returns {string} Uppercased string. 189 */ 190var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; 191 192 193var manualLowercase = function(s) { 194 /* jshint bitwise: false */ 195 return isString(s) 196 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 197 : s; 198}; 199var manualUppercase = function(s) { 200 /* jshint bitwise: false */ 201 return isString(s) 202 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 203 : s; 204}; 205 206 207// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 208// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 209// with correct but slower alternatives. 210if ('i' !== 'I'.toLowerCase()) { 211 lowercase = manualLowercase; 212 uppercase = manualUppercase; 213} 214 215 216var /** holds major version number for IE or NaN for real browsers */ 217 msie, 218 jqLite, // delay binding since jQuery could be loaded after us. 219 jQuery, // delay binding 220 slice = [].slice, 221 push = [].push, 222 toString = Object.prototype.toString, 223 ngMinErr = minErr('ng'), 224 225 226 _angular = window.angular, 227 /** @name angular */ 228 angular = window.angular || (window.angular = {}), 229 angularModule, 230 nodeName_, 231 uid = ['0', '0', '0']; 232 233/** 234 * IE 11 changed the format of the UserAgent string. 235 * See http://msdn.microsoft.com/en-us/library/ms537503.aspx 236 */ 237msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); 238if (isNaN(msie)) { 239 msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); 240} 241 242 243/** 244 * @private 245 * @param {*} obj 246 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 247 * String ...) 248 */ 249function isArrayLike(obj) { 250 if (obj == null || isWindow(obj)) { 251 return false; 252 } 253 254 var length = obj.length; 255 256 if (obj.nodeType === 1 && length) { 257 return true; 258 } 259 260 return isString(obj) || isArray(obj) || length === 0 || 261 typeof length === 'number' && length > 0 && (length - 1) in obj; 262} 263 264/** 265 * @ngdoc function 266 * @name angular.forEach 267 * @function 268 * 269 * @description 270 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 271 * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` 272 * is the value of an object property or an array element and `key` is the object property key or 273 * array element index. Specifying a `context` for the function is optional. 274 * 275 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 276 * using the `hasOwnProperty` method. 277 * 278 <pre> 279 var values = {name: 'misko', gender: 'male'}; 280 var log = []; 281 angular.forEach(values, function(value, key){ 282 this.push(key + ': ' + value); 283 }, log); 284 expect(log).toEqual(['name: misko', 'gender: male']); 285 </pre> 286 * 287 * @param {Object|Array} obj Object to iterate over. 288 * @param {Function} iterator Iterator function. 289 * @param {Object=} context Object to become context (`this`) for the iterator function. 290 * @returns {Object|Array} Reference to `obj`. 291 */ 292function forEach(obj, iterator, context) { 293 var key; 294 if (obj) { 295 if (isFunction(obj)){ 296 for (key in obj) { 297 // Need to check if hasOwnProperty exists, 298 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 299 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 300 iterator.call(context, obj[key], key); 301 } 302 } 303 }
303 else if (obj.forEach && obj.forEach !== forEach) { 304 obj.forEach(iterator, context); 305 } else if (isArrayLike(obj)) { 306 for (key = 0; key < obj.length; key++) 307 iterator.call(context, obj[key], key); 308 } else { 309 for (key in obj) { 310 if (obj.hasOwnProperty(key)) { 311 iterator.call(context, obj[key], key); 312 } 313 } 314 } 315 } 316 return obj; 317} 318 319function sortedKeys(obj) { 320 var keys = []; 321 for (var key in obj) { 322 if (obj.hasOwnProperty(key)) { 323 keys.push(key); 324 } 325 } 326 return keys.sort(); 327} 328 329function forEachSorted(obj, iterator, context) { 330 var keys = sortedKeys(obj); 331 for ( var i = 0; i < keys.length; i++) { 332 iterator.call(context, obj[keys[i]], keys[i]); 333 } 334 return keys; 335} 336 337 338/** 339 * when using forEach the params are value, key, but it is often useful to have key, value. 340 * @param {function(string, *)} iteratorFn 341 * @returns {function(*, string)} 342 */ 343function reverseParams(iteratorFn) { 344 return function(value, key) { iteratorFn(key, value); }; 345} 346 347/** 348 * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric 349 * characters such as '012ABC'. The reason why we are not using simply a number counter is that 350 * the number string gets longer over time, and it can also overflow, where as the nextId 351 * will grow much slower, it is a string, and it will never overflow. 352 * 353 * @returns an unique alpha-numeric string 354 */ 355function nextUid() { 356 var index = uid.length; 357 var digit; 358 359 while(index) { 360 index--;
361 digit = uid[index].charCodeAt(0); 362 if (digit == 57 /*'9'*/) { 363 uid[index] = 'A'; 364 return uid.join(''); 365 } 366 if (digit == 90 /*'Z'*/) { 367 uid[index] = '0'; 368 } else { 369 uid[index] = String.fromCharCode(digit + 1); 370 return uid.join(''); 371 } 372 } 373 uid.unshift('0'); 374 return uid.join(''); 375} 376 377 378/** 379 * Set or clear the hashkey for an object. 380 * @param obj object 381 * @param h the hashkey (!truthy to delete the hashkey) 382 */ 383function setHashKey(obj, h) { 384 if (h) { 385 obj.$$hashKey = h; 386 } 387 else { 388 delete obj.$$hashKey; 389 } 390} 391 392/** 393 * @ngdoc function 394 * @name angular.extend 395 * @function 396 * 397 * @description 398 * Extends the destination object `dst` by copying all of the properties from the `src` object(s) 399 * to `dst`. You can specify multiple `src` objects. 400 * 401 * @param {Object} dst Destination object. 402 * @param {...Object} src Source object(s). 403 * @returns {Object} Reference to `dst`. 404 */ 405function extend(dst) { 406 var h = dst.$$hashKey; 407 forEach(arguments, function(obj){ 408 if (obj !== dst) { 409 forEach(obj, function(value, key){ 410 dst[key] = value; 411 }); 412 } 413 }); 414 415 setHashKey(dst,h); 416 return dst; 417} 418 419function int(str) { 420 return parseInt(str, 10); 421} 422 423 424function inherit(parent, extra) { 425 return extend(new (extend(function() {}, {prototype:parent}))(), extra); 426} 427 428/** 429 * @ngdoc function 430 * @name angular.noop 431 * @function 432 * 433 * @description 434 * A function that performs no operations. This function can be useful when writing code in the 435 * functional style. 436 <pre> 437 function foo(callback) { 438 var result = calculateResult(); 439 (callback || angular.noop)(result); 440 } 441 </pre> 442 */ 443function noop() {} 444noop.$inject = []; 445 446 447/** 448 * @ngdoc function 449 * @name angular.identity 450 * @function 451 * 452 * @description 453 * A function that returns its first argument. This function is useful when writing code in the 454 * functional style. 455 * 456 <pre> 457 function transformer(transformationFn, value) { 458 return (transformationFn || angular.identity)(value); 459 }; 460 </pre> 461 */ 462function identity($) {return $;} 463identity.$inject = []; 464 465 466function valueFn(value) {return function() {return value;};} 467 468/** 469 * @ngdoc function 470 * @name angular.isUndefined 471 * @function 472 * 473 * @description 474 * Determines if a reference is undefined. 475 * 476 * @param {*} value Reference to check. 477 * @returns {boolean} True if `value` is undefined. 478 */ 479function isUndefined(value){return typeof value === 'undefined';} 480 481 482/** 483 * @ngdoc function 484 * @name angular.isDefined 485 * @function 486 * 487 * @description 488 * Determines if a reference is defined. 489 * 490 * @param {*} value Reference to check. 491 * @returns {boolean} True if `value` is defined. 492 */ 493function isDefined(value){return typeof value !== 'undefined';} 494 495 496/** 497 * @ngdoc function 498 * @name angular.isObject 499 * @function 500 * 501 * @description 502 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 503 * considered to be objects. 504 * 505 * @param {*} value Reference to check. 506 * @returns {boolean} True if `value` is an `Object` but not `null`. 507 */ 508function isObject(value){return value != null && typeof value === 'object';} 509 510 511/** 512 * @ngdoc function 513 * @name angular.isString 514 * @function 515 * 516 * @description 517 * Determines if a reference is a `String`. 518 * 519 * @param {*} value Reference to check. 520 * @returns {boolean} True if `value` is a `String`. 521 */ 522function isString(value){return typeof value === 'string';} 523 524 525/** 526 * @ngdoc function 527 * @name angular.isNumber 528 * @function 529 * 530 * @description 531 * Determines if a reference is a `Number`. 532 * 533 * @param {*} value Reference to check. 534 * @returns {boolean} True if `value` is a `Number`. 535 */ 536function isNumber(value){return typeof value === 'number';} 537 538 539/** 540 * @ngdoc function 541 * @name angular.isDate 542 * @function 543 * 544 * @description 545 * Determines if a value is a date. 546 * 547 * @param {*} value Reference to check. 548 * @returns {boolean} True if `value` is a `Date`. 549 */ 550function isDate(value){ 551 return toString.call(value) === '[object Date]'; 552} 553 554 555/** 556 * @ngdoc function 557 * @name angular.isArray 558 * @function 559 * 560 * @description 561 * Determines if a reference is an `Array`. 562 * 563 * @param {*} value Reference to check. 564 * @returns {boolean} True if `value` is an `Array`. 565 */ 566function isArray(value) { 567 return toString.call(value) === '[object Array]'; 568} 569 570 571/** 572 * @ngdoc function 573 * @name angular.isFunction 574 * @function 575 * 576 * @description 577 * Determines if a reference is a `Function`. 578 * 579 * @param {*} value Reference to check. 580 * @returns {boolean} True if `value` is a `Function`. 581 */ 582function isFunction(value){return typeof value === 'function';} 583 584 585/** 586 * Determines if a value is a regular expression object. 587 * 588 * @private 589 * @param {*} value Reference to check. 590 * @returns {boolean} True if `value` is a `RegExp`. 591 */ 592function isRegExp(value) { 593 return toString.call(value) === '[object RegExp]'; 594} 595 596 597/** 598 * Checks if `obj` is a window object. 599 * 600 * @private 601 * @param {*} obj Object to check 602 * @returns {boolean} True if `obj` is a window obj. 603 */ 604function isWindow(obj) { 605 return obj && obj.document && obj.location && obj.alert && obj.setInterval; 606} 607 608 609function isScope(obj) { 610 return obj && obj.$evalAsync && obj.$watch; 611} 612 613 614function isFile(obj) { 615 return toString.call(obj) === '[object File]'; 616} 617 618 619function isBoolean(value) { 620 return typeof value === 'boolean'; 621} 622 623 624var trim = (function() { 625 // native trim is way faster: http://jsperf.com/angular-trim-test 626 // but IE doesn't have it... :-( 627 // TODO: we should move this into IE/ES5 polyfill 628 if (!String.prototype.trim) { 629 return function(value) { 630 return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value; 631 }; 632 } 633 return function(value) { 634 return isString(value) ? value.trim() : value; 635 }; 636})(); 637 638 639/** 640 * @ngdoc function 641 * @name angular.isElement 642 * @function 643 * 644 * @description 645 * Determines if a reference is a DOM element (or wrapped jQuery element). 646 * 647 * @param {*} value Reference to check. 648 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 649 */ 650function isElement(node) { 651 return !!(node && 652 (node.nodeName // we are a direct element 653 || (node.on && node.find))); // we have an on and find method part of jQuery API 654} 655 656/** 657 * @param str 'key1,key2,...' 658 * @returns {object} in the form of {key1:true, key2:true, ...} 659 */ 660function makeMap(str){ 661 var obj = {}, items = str.split(","), i; 662 for ( i = 0; i < items.length; i++ ) 663 obj[ items[i] ] = true; 664 return obj;
665} 666 667 668if (msie < 9) { 669 nodeName_ = function(element) { 670 element = element.nodeName ? element : element[0]; 671 return (element.scopeName && element.scopeName != 'HTML') 672 ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; 673 }; 674} else { 675 nodeName_ = function(element) { 676 return element.nodeName ? element.nodeName : element[0].nodeName; 677 }; 678} 679 680 681function map(obj, iterator, context) { 682 var results = []; 683 forEach(obj, function(value, index, list) { 684 results.push(iterator.call(context, value, index, list)); 685 }); 686 return results; 687} 688 689 690/** 691 * @description 692 * Determines the number of elements in an array, the number of properties an object has, or 693 * the length of a string. 694 * 695 * Note: This function is used to augment the Object type in Angular expressions. See 696 * {@link angular.Object} for more information about Angular arrays. 697 * 698 * @param {Object|Array|string} obj Object, array, or string to inspect. 699 * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object 700 * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. 701 */ 702function size(obj, ownPropsOnly) { 703 var count = 0, key; 704 705 if (isArray(obj) || isString(obj)) { 706 return obj.length; 707 } else if (isObject(obj)){ 708 for (key in obj) 709 if (!ownPropsOnly || obj.hasOwnProperty(key)) 710 count++; 711 } 712 713 return count; 714} 715 716 717function includes(array, obj) { 718 return indexOf(array, obj) != -1; 719} 720 721function indexOf(array, obj) { 722 if (array.indexOf) return array.indexOf(obj); 723 724 for (var i = 0; i < array.length; i++) { 725 if (obj === array[i]) return i; 726 } 727 return -1; 728} 729 730function arrayRemove(array, value) { 731 var index = indexOf(array, value); 732 if (index >=0) 733 array.splice(index, 1); 734 return value; 735} 736 737function isLeafNode (node) { 738 if (node) { 739 switch (node.nodeName) { 740 case "OPTION": 741 case "PRE": 742 case "TITLE": 743 return true; 744 } 745 } 746 return false; 747} 748 749/** 750 * @ngdoc function 751 * @name angular.copy 752 * @function 753 * 754 * @description 755 * Creates a deep copy of `source`, which should be an object or an array. 756 * 757 * * If no destination is supplied, a copy of the object or array is created. 758 * * If a destination is provided, all of its elements (for array) or properties (for objects) 759 * are deleted and then all elements/properties from the source are copied to it. 760 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 761 * * If `source` is identical to 'destination' an exception will be thrown. 762 * 763 * @param {*} source The source that will be used to make a copy. 764 * Can be any type, including primitives, `null`, and `undefined`. 765 * @param {(Object|Array)=} destination Destination into which the source is copied. If 766 * provided, must be of the same type as `source`. 767 * @returns {*} The copy or updated `destination`, if `destination` was specified. 768 * 769 * @example 770 <doc:example> 771 <doc:source> 772 <div ng-controller="Controller"> 773 <form novalidate class="simple-form"> 774 Name: <input type="text" ng-model="user.name" /><br /> 775 E-mail: <input type="email" ng-model="user.email" /><br /> 776 Gender: <input type="radio" ng-model="user.gender" value="male" />male 777 <input type="radio" ng-model="user.gender" value="female" />female<br /> 778 <button ng-click="reset()">RESET</button> 779 <button ng-click="update(user)">SAVE</button> 780 </form> 781 <pre>form = {{user | json}}</pre> 782 <pre>master = {{master | json}}</pre> 783 </div> 784 785 <script> 786 function Controller($scope) { 787 $scope.master= {}; 788 789 $scope.update = function(user) { 790 // Example with 1 argument 791 $scope.master= angular.copy(user); 792 }; 793 794 $scope.reset = function() { 795 // Example with 2 arguments 796 angular.copy($scope.master, $scope.user); 797 }; 798 799 $scope.reset(); 800 } 801 </script> 802 </doc:source> 803 </doc:example> 804 */ 805function copy(source, destination){ 806 if (isWindow(source) || isScope(source)) { 807 throw ngMinErr('cpws',
808 "Can't copy! Making copies of Window or Scope instances is not supported."); 809 } 810 811 if (!destination) { 812 destination = source; 813 if (source) { 814 if (isArray(source)) { 815 destination = copy(source, []); 816 } else if (isDate(source)) { 817 destination = new Date(source.getTime()); 818 } else if (isRegExp(source)) { 819 destination = new RegExp(source.source); 820 } else if (isObject(source)) { 821 destination = copy(source, {}); 822 } 823 } 824 } else { 825 if (source === destination) throw ngMinErr('cpi', 826 "Can't copy! Source and destination are identical."); 827 if (isArray(source)) { 828 destination.length = 0; 829 for ( var i = 0; i < source.length; i++) { 830 destination.push(copy(source[i])); 831 } 832 } else { 833 var h = destination.$$hashKey; 834 forEach(destination, function(value, key){ 835 delete destination[key]; 836 }); 837 for ( var key in source) { 838 destination[key] = copy(source[key]); 839 } 840 setHashKey(destination,h); 841 } 842 } 843 return destination; 844} 845 846/** 847 * Create a shallow copy of an object 848 */ 849function shallowCopy(src, dst) { 850 dst = dst || {}; 851 852 for(var key in src) { 853 // shallowCopy is only ever called by $compile nodeLinkFn, which has control over src 854 // so we don't need to worry about using our custom hasOwnProperty here 855 if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { 856 dst[key] = src[key]; 857 } 858 } 859 860 return dst; 861} 862 863 864/** 865 * @ngdoc function 866 * @name angular.equals 867 * @function 868 * 869 * @description 870 * Determines if two objects or two values are equivalent. Supports value types, regular 871 * expressions, arrays and objects. 872 * 873 * Two objects or values are considered equivalent if at least one of the following is true: 874 * 875 * * Both objects or values pass `===` comparison. 876 * * Both objects or values are of the same type and all of their properties are equal by 877 * comparing them with `angular.equals`. 878 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 879 * * Both values represent the same regular expression (In JavasScript, 880 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 881 * representation matches). 882 * 883 * During a property comparison, properties of `function` type and properties with names 884 * that begin with `$` are ignored. 885 * 886 * Scope and DOMWindow objects are being compared only by identify (`===`). 887 * 888 * @param {*} o1 Object or value to compare. 889 * @param {*} o2 Object or value to compare. 890 * @returns {boolean} True if arguments are equal. 891 */ 892function equals(o1, o2) { 893 if (o1 === o2) return true; 894 if (o1 === null || o2 === null) return false; 895 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 896 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 897 if (t1 == t2) { 898 if (t1 == 'object') { 899 if (isArray(o1)) { 900 if (!isArray(o2)) return false; 901 if ((length = o1.length) == o2.length) { 902 for(key=0; key<length; key++) { 903 if (!equals(o1[key], o2[key])) return false; 904 } 905 return true; 906 } 907 } else if (isDate(o1)) { 908 return isDate(o2) && o1.getTime() == o2.getTime(); 909 } else if (isRegExp(o1) && isRegExp(o2)) { 910 return o1.toString() == o2.toString(); 911 } else { 912 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false; 913 keySet = {}; 914 for(key in o1) { 915 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 916 if (!equals(o1[key], o2[key])) return false; 917 keySet[key] = true; 918 } 919 for(key in o2) { 920 if (!keySet.hasOwnProperty(key) && 921 key.charAt(0) !== '$' && 922 o2[key] !== undefined && 923 !isFunction(o2[key])) return false; 924 } 925 return true; 926 } 927 } 928 } 929 return false; 930} 931 932 933function csp() { 934 return (document.securityPolicy && document.securityPolicy.isActive) || 935 (document.querySelector && 936 !!(document.querySelector('[ng-csp]') || document.querySelector('[data-ng-csp]'))); 937} 938 939 940function concat(array1, array2, index) { 941 return array1.concat(slice.call(array2, index)); 942} 943 944function sliceArgs(args, startIndex) { 945 return slice.call(args, startIndex || 0); 946} 947 948
949/* jshint -W101 */ 950/** 951 * @ngdoc function 952 * @name angular.bind 953 * @function 954 * 955 * @description 956 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 957 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 958 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 959 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 960 * 961 * @param {Object} self Context which `fn` should be evaluated in. 962 * @param {function()} fn Function to be bound. 963 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 964 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 965 */ 966/* jshint +W101 */ 967function bind(self, fn) { 968 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 969 if (isFunction(fn) && !(fn instanceof RegExp)) { 970 return curryArgs.length 971 ? function() { 972 return arguments.length 973 ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) 974 : fn.apply(self, curryArgs); 975 } 976 : function() { 977 return arguments.length 978 ? fn.apply(self, arguments) 979 : fn.call(self); 980 }; 981 } else { 982 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 983 return fn; 984 } 985} 986 987 988function toJsonReplacer(key, value) { 989 var val = value; 990 991 if (typeof key === 'string' && key.charAt(0) === '$') { 992 val = undefined; 993 } else if (isWindow(value)) { 994 val = '$WINDOW'; 995 } else if (value && document === value) { 996 val = '$DOCUMENT'; 997 } else if (isScope(value)) { 998 val = '$SCOPE'; 999 } 1000 1001 return val; 1002} 1003 1004 1005/** 1006 * @ngdoc function 1007 * @name angular.toJson 1008 * @function 1009 * 1010 * @description 1011 * Serializes input into a JSON-formatted string. Properties with leading $ characters will be 1012 * stripped since angular uses this notation internally. 1013 * 1014 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1015 * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace. 1016 * @returns {string|undefined} JSON-ified string representing `obj`. 1017 */ 1018function toJson(obj, pretty) { 1019 if (typeof obj === 'undefined') return undefined; 1020 return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); 1021} 1022 1023 1024/** 1025 * @ngdoc function 1026 * @name angular.fromJson 1027 * @function 1028 * 1029 * @description 1030 * Deserializes a JSON string. 1031 * 1032 * @param {string} json JSON string to deserialize. 1033 * @returns {Object|Array|Date|string|number} Deserialized thingy. 1034 */ 1035function fromJson(json) { 1036 return isString(json) 1037 ? JSON.parse(json) 1038 : json; 1039} 1040 1041 1042function toBoolean(value) { 1043 if (typeof value === 'function') { 1044 value = true; 1045 } else if (value && value.length !== 0) { 1046 var v = lowercase("" + value); 1047 value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); 1048 } else { 1049 value = false; 1050 } 1051 return value; 1052} 1053 1054/** 1055 * @returns {string} Returns the string representation of the element. 1056 */ 1057function startingTag(element) { 1058 element = jqLite(element).clone(); 1059 try { 1060 // turns out IE does not let you set .html() on elements which 1061 // are not allowed to have children. So we just ignore it. 1062 element.empty(); 1063 } catch(e) {} 1064 // As Per DOM Standards 1065 var TEXT_NODE = 3; 1066 var elemHtml = jqLite('<div>').append(element).html(); 1067 try { 1068 return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : 1069 elemHtml. 1070 match(/^(<[^>]+>)/)[1]. 1071 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1072 } catch(e) { 1073 return lowercase(elemHtml); 1074 } 1075 1076} 1077 1078 1079///////////////////////////////////////////////// 1080 1081/** 1082 * Tries to decode the URI component without throwing an exception. 1083 * 1084 * @private 1085 * @param str value potential URI component to check. 1086 * @returns {boolean} True if `value` can be decoded 1087 * with the decodeURIComponent function. 1088 */ 1089function tryDecodeURIComponent(value) { 1090 try { 1091 return decodeURIComponent(value); 1092 } catch(e) {
1093 // Ignore any invalid uri component 1094 } 1095} 1096 1097 1098/** 1099 * Parses an escaped url query string into key-value pairs. 1100 * @returns Object.<(string|boolean)> 1101 */ 1102function parseKeyValue(/**string*/keyValue) { 1103 var obj = {}, key_value, key; 1104 forEach((keyValue || "").split('&'), function(keyValue){ 1105 if ( keyValue ) { 1106 key_value = keyValue.split('='); 1107 key = tryDecodeURIComponent(key_value[0]); 1108 if ( isDefined(key) ) { 1109 var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; 1110 if (!obj[key]) { 1111 obj[key] = val; 1112 } else if(isArray(obj[key])) { 1113 obj[key].push(val); 1114 } else { 1115 obj[key] = [obj[key],val]; 1116 } 1117 } 1118 } 1119 }); 1120 return obj; 1121} 1122 1123function toKeyValue(obj) { 1124 var parts = []; 1125 forEach(obj, function(value, key) { 1126 if (isArray(value)) { 1127 forEach(value, function(arrayValue) { 1128 parts.push(encodeUriQuery(key, true) + 1129 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1130 }); 1131 } else { 1132 parts.push(encodeUriQuery(key, true) + 1133 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1134 } 1135 }); 1136 return parts.length ? parts.join('&') : ''; 1137} 1138 1139 1140/** 1141 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1142 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1143 * segments: 1144 * segment = *pchar 1145 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1146 * pct-encoded = "%" HEXDIG HEXDIG 1147 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1148 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1149 * / "*" / "+" / "," / ";" / "=" 1150 */ 1151function encodeUriSegment(val) { 1152 return encodeUriQuery(val, true). 1153 replace(/%26/gi, '&'). 1154 replace(/%3D/gi, '='). 1155 replace(/%2B/gi, '+'); 1156} 1157 1158 1159/** 1160 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1161 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1162 * encoded per http://tools.ietf.org/html/rfc3986: 1163 * query = *( pchar / "/" / "?" ) 1164 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1165 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1166 * pct-encoded = "%" HEXDIG HEXDIG 1167 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1168 * / "*" / "+" / "," / ";" / "=" 1169 */ 1170function encodeUriQuery(val, pctEncodeSpaces) { 1171 return encodeURIComponent(val). 1172 replace(/%40/gi, '@'). 1173 replace(/%3A/gi, ':'). 1174 replace(/%24/g, '$'). 1175 replace(/%2C/gi, ','). 1176 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1177} 1178 1179 1180/** 1181 * @ngdoc directive 1182 * @name ng.directive:ngApp 1183 * 1184 * @element ANY 1185 * @param {angular.Module} ngApp an optional application 1186 * {@link angular.module module} name to load. 1187 * 1188 * @description 1189 * 1190 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1191 * designates the **root element** of the application and is typically placed near the root element 1192 * of the page - e.g. on the `<body>` or `<html>` tags. 1193 * 1194 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` 1195 * found in the document will be used to define the root element to auto-bootstrap as an 1196 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1197 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1198 * 1199 * You can specify an **AngularJS module** to be used as the root module for the application. This 1200 * module will be loaded into the {@link AUTO.$injector} when the application is bootstrapped and 1201 * should contain the application code needed or have dependencies on other modules that will 1202 * contain the code. See {@link angular.module} for more information. 1203 * 1204 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1205 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1206 * would not be resolved to `3`. 1207 * 1208 * `ngApp` is the easiest, and most common, way to bootstrap an application. 1209 * 1210 <example module="ngAppDemo"> 1211 <file name="index.html"> 1212 <div ng-controller="ngAppDemoController"> 1213 I can add: {{a}} + {{b}} = {{ a+b }} 1214 </div> 1215 </file> 1216 <file name="script.js"> 1217 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1218 $scope.a = 1; 1219 $scope.b = 2; 1220 }); 1221 </file> 1222 </example> 1223 * 1224 */ 1225function angularInit(element, bootstrap) { 1226 var elements = [element], 1227 appElement, 1228 module, 1229 names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], 1230 NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; 1231 1232 function append(element) { 1233 element && elements.push(element); 1234 } 1235 1236 forEach(names, function(name) { 1237 names[name] = true; 1238 append(document.getElementById(name)); 1239 name = name.replace(':', '\\:'); 1240 if (element.querySelectorAll) {
1241 forEach(element.querySelectorAll('.' + name), append); 1242 forEach(element.querySelectorAll('.' + name + '\\:'), append); 1243 forEach(element.querySelectorAll('[' + name + ']'), append); 1244 } 1245 }); 1246 1247 forEach(elements, function(element) { 1248 if (!appElement) { 1249 var className = ' ' + element.className + ' '; 1250 var match = NG_APP_CLASS_REGEXP.exec(className); 1251 if (match) { 1252 appElement = element; 1253 module = (match[2] || '').replace(/\s+/g, ','); 1254 } else { 1255 forEach(element.attributes, function(attr) { 1256 if (!appElement && names[attr.name]) { 1257 appElement = element; 1258 module = attr.value; 1259 } 1260 }); 1261 } 1262 } 1263 }); 1264 if (appElement) { 1265 bootstrap(appElement, module ? [module] : []); 1266 } 1267} 1268 1269/** 1270 * @ngdoc function 1271 * @name angular.bootstrap 1272 * @description 1273 * Use this function to manually start up angular application. 1274 * 1275 * See: {@link guide/bootstrap Bootstrap} 1276 * 1277 * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. 1278 * They must use {@link api/ng.directive:ngApp ngApp}. 1279 * 1280 * @param {Element} element DOM element which is the root of angular application. 1281 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1282 * Each item in the array should be the name of a predefined module or a (DI annotated) 1283 * function that will be invoked by the injector as a run block. 1284 * See: {@link angular.module modules} 1285 * @returns {AUTO.$injector} Returns the newly created injector for this app. 1286 */ 1287function bootstrap(element, modules) { 1288 var doBootstrap = function() { 1289 element = jqLite(element); 1290 1291 if (element.injector()) { 1292 var tag = (element[0] === document) ? 'document' : startingTag(element); 1293 throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); 1294 } 1295 1296 modules = modules || []; 1297 modules.unshift(['$provide', function($provide) { 1298 $provide.value('$rootElement', element); 1299 }]); 1300 modules.unshift('ng'); 1301 var injector = createInjector(modules); 1302 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', 1303 function(scope, element, compile, injector, animate) { 1304 scope.$apply(function() { 1305 element.data('$injector', injector); 1306 compile(element)(scope); 1307 }); 1308 }] 1309 ); 1310 return injector; 1311 }; 1312 1313 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1314 1315 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1316 return doBootstrap(); 1317 } 1318 1319 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1320 angular.resumeBootstrap = function(extraModules) { 1321 forEach(extraModules, function(module) { 1322 modules.push(module); 1323 }); 1324 doBootstrap(); 1325 }; 1326} 1327 1328var SNAKE_CASE_REGEXP = /[A-Z]/g; 1329function snake_case(name, separator){ 1330 separator = separator || '_'; 1331 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1332 return (pos ? separator : '') + letter.toLowerCase(); 1333 }); 1334} 1335 1336function bindJQuery() { 1337 // bind to jQuery if present; 1338 jQuery = window.jQuery; 1339 // reset to jQuery or default to us. 1340 if (jQuery) { 1341 jqLite = jQuery; 1342 extend(jQuery.fn, { 1343 scope: JQLitePrototype.scope, 1344 isolateScope: JQLitePrototype.isolateScope, 1345 controller: JQLitePrototype.controller, 1346 injector: JQLitePrototype.injector, 1347 inheritedData: JQLitePrototype.inheritedData 1348 }); 1349 // Method signature: 1350 // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) 1351 jqLitePatchJQueryRemove('remove', true, true, false); 1352 jqLitePatchJQueryRemove('empty', false, false, false); 1353 jqLitePatchJQueryRemove('html', false, false, true); 1354 } else { 1355 jqLite = JQLite; 1356 } 1357 angular.element = jqLite; 1358} 1359 1360/** 1361 * throw error if the argument is falsy. 1362 */ 1363function assertArg(arg, name, reason) { 1364 if (!arg) { 1365 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1366 } 1367 return arg; 1368} 1369 1370function assertArgFn(arg, name, acceptArrayAnnotation) { 1371 if (acceptArrayAnnotation && isArray(arg)) { 1372 arg = arg[arg.length - 1]; 1373 } 1374 1375 assertArg(isFunction(arg), name, 'not a function, got ' + 1376 (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));
vendor: 5,258 bytes, lines 1377-1535
1377 return arg; 1378} 1379 1380/** 1381 * throw error if the name given is hasOwnProperty 1382 * @param {String} name the name to test 1383 * @param {String} context the context in which the name is used, such as module or directive 1384 */ 1385function assertNotHasOwnProperty(name, context) { 1386 if (name === 'hasOwnProperty') { 1387 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1388 } 1389} 1390 1391/** 1392 * Return the value accessible from the object by path. Any undefined traversals are ignored 1393 * @param {Object} obj starting object 1394 * @param {string} path path to traverse 1395 * @param {boolean=true} bindFnToScope 1396 * @returns value as accessible by path 1397 */ 1398//TODO(misko): this function needs to be removed 1399function getter(obj, path, bindFnToScope) { 1400 if (!path) return obj; 1401 var keys = path.split('.'); 1402 var key; 1403 var lastInstance = obj; 1404 var len = keys.length; 1405 1406 for (var i = 0; i < len; i++) { 1407 key = keys[i]; 1408 if (obj) { 1409 obj = (lastInstance = obj)[key]; 1410 } 1411 } 1412 if (!bindFnToScope && isFunction(obj)) { 1413 return bind(lastInstance, obj); 1414 } 1415 return obj; 1416} 1417 1418/** 1419 * Return the DOM siblings between the first and last node in the given array. 1420 * @param {Array} array like object 1421 * @returns jQlite object containing the elements 1422 */ 1423function getBlockElements(nodes) { 1424 var startNode = nodes[0], 1425 endNode = nodes[nodes.length - 1]; 1426 if (startNode === endNode) { 1427 return jqLite(startNode); 1428 } 1429 1430 var element = startNode; 1431 var elements = [element]; 1432 1433 do { 1434 element = element.nextSibling; 1435 if (!element) break; 1436 elements.push(element); 1437 } while (element !== endNode); 1438 1439 return jqLite(elements); 1440} 1441 1442/** 1443 * @ngdoc interface 1444 * @name angular.Module 1445 * @description 1446 * 1447 * Interface for configuring angular {@link angular.module modules}. 1448 */ 1449 1450function setupModuleLoader(window) { 1451 1452 var $injectorMinErr = minErr('$injector'); 1453 var ngMinErr = minErr('ng'); 1454 1455 function ensure(obj, name, factory) { 1456 return obj[name] || (obj[name] = factory()); 1457 } 1458 1459 var angular = ensure(window, 'angular', Object); 1460 1461 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1462 angular.$$minErr = angular.$$minErr || minErr; 1463 1464 return ensure(angular, 'module', function() { 1465 /** @type {Object.<string, angular.Module>} */ 1466 var modules = {}; 1467 1468 /** 1469 * @ngdoc function 1470 * @name angular.module 1471 * @description 1472 * 1473 * The `angular.module` is a global place for creating, registering and retrieving Angular 1474 * modules. 1475 * All modules (angular core or 3rd party) that should be available to an application must be 1476 * registered using this mechanism. 1477 * 1478 * When passed two or more arguments, a new module is created. If passed only one argument, an 1479 * existing module (the name passed as the first argument to `module`) is retrieved. 1480 * 1481 * 1482 * # Module 1483 * 1484 * A module is a collection of services, directives, filters, and configuration information. 1485 * `angular.module` is used to configure the {@link AUTO.$injector $injector}. 1486 * 1487 * <pre> 1488 * // Create a new module 1489 * var myModule = angular.module('myModule', []); 1490 * 1491 * // register a new service 1492 * myModule.value('appName', 'MyCoolApp'); 1493 * 1494 * // configure existing services inside initialization blocks. 1495 * myModule.config(function($locationProvider) { 1496 * // Configure existing providers 1497 * $locationProvider.hashPrefix('!'); 1498 * }); 1499 * </pre> 1500 * 1501 * Then you can create an injector and load your modules like this: 1502 * 1503 * <pre> 1504 * var injector = angular.injector(['ng', 'MyModule']) 1505 * </pre> 1506 * 1507 * However it's more likely that you'll just use 1508 * {@link ng.directive:ngApp ngApp} or 1509 * {@link angular.bootstrap} to simplify this process for you. 1510 * 1511 * @param {!string} name The name of the module to create or retrieve. 1512 * @param {Array.<string>=} requires If specified then new module is being created. If 1513 * unspecified then the the module is being retrieved for further configuration. 1514 * @param {Function} configFn Optional configuration function for the module. Same as 1515 * {@link angular.Module#methods_config Module#config()}. 1516 * @returns {module} new module with the {@link angular.Module} api. 1517 */ 1518 return function module(name, requires, configFn) { 1519 var assertNotHasOwnProperty = function(name, context) { 1520 if (name === 'hasOwnProperty') { 1521 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 1522 } 1523 }; 1524 1525 assertNotHasOwnProperty(name, 'module'); 1526 if (requires && modules.hasOwnProperty(name)) { 1527 modules[name] = null; 1528 } 1529 return ensure(modules, name, function() { 1530 if (!requires) { 1531 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 1532 "the module name or forgot to load it. If registering a module ensure that you " + 1533 "specify the dependencies as the second argument.", name); 1534 } 1535
1536 /** @type {!Array.<Array.<*>>} */ 1537 var invokeQueue = []; 1538 1539 /** @type {!Array.<Function>} */ 1540 var runBlocks = []; 1541 1542 var config = invokeLater('$injector', 'invoke'); 1543 1544 /** @type {angular.Module} */ 1545 var moduleInstance = { 1546 // Private state 1547 _invokeQueue: invokeQueue, 1548 _runBlocks: runBlocks, 1549 1550 /** 1551 * @ngdoc property 1552 * @name angular.Module#requires 1553 * @propertyOf angular.Module 1554 * @returns {Array.<string>} List of module names which must be loaded before this module. 1555 * @description 1556 * Holds the list of modules which the injector will load before the current module is 1557 * loaded. 1558 */ 1559 requires: requires, 1560 1561 /** 1562 * @ngdoc property 1563 * @name angular.Module#name 1564 * @propertyOf angular.Module 1565 * @returns {string} Name of the module. 1566 * @description 1567 */ 1568 name: name, 1569 1570 1571 /** 1572 * @ngdoc method 1573 * @name angular.Module#provider 1574 * @methodOf angular.Module 1575 * @param {string} name service name 1576 * @param {Function} providerType Construction function for creating new instance of the 1577 * service. 1578 * @description 1579 * See {@link AUTO.$provide#provider $provide.provider()}. 1580 */ 1581 provider: invokeLater('$provide', 'provider'), 1582 1583 /** 1584 * @ngdoc method 1585 * @name angular.Module#factory 1586 * @methodOf angular.Module 1587 * @param {string} name service name 1588 * @param {Function} providerFunction Function for creating new instance of the service. 1589 * @description 1590 * See {@link AUTO.$provide#factory $provide.factory()}. 1591 */ 1592 factory: invokeLater('$provide', 'factory'), 1593 1594 /** 1595 * @ngdoc method 1596 * @name angular.Module#service 1597 * @methodOf angular.Module 1598 * @param {string} name service name 1599 * @param {Function} constructor A constructor function that will be instantiated. 1600 * @description 1601 * See {@link AUTO.$provide#service $provide.service()}. 1602 */ 1603 service: invokeLater('$provide', 'service'), 1604 1605 /** 1606 * @ngdoc method 1607 * @name angular.Module#value 1608 * @methodOf angular.Module 1609 * @param {string} name service name 1610 * @param {*} object Service instance object. 1611 * @description 1612 * See {@link AUTO.$provide#value $provide.value()}. 1613 */ 1614 value: invokeLater('$provide', 'value'), 1615 1616 /** 1617 * @ngdoc method 1618 * @name angular.Module#constant 1619 * @methodOf angular.Module 1620 * @param {string} name constant name 1621 * @param {*} object Constant value. 1622 * @description 1623 * Because the constant are fixed, they get applied before other provide methods. 1624 * See {@link AUTO.$provide#constant $provide.constant()}. 1625 */ 1626 constant: invokeLater('$provide', 'constant', 'unshift'), 1627 1628 /** 1629 * @ngdoc method 1630 * @name angular.Module#animation 1631 * @methodOf angular.Module 1632 * @param {string} name animation name 1633 * @param {Function} animationFactory Factory function for creating new instance of an 1634 * animation. 1635 * @description 1636 * 1637 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 1638 * 1639 * 1640 * Defines an animation hook that can be later used with 1641 * {@link ngAnimate.$animate $animate} service and directives that use this service. 1642 * 1643 * <pre> 1644 * module.animation('.animation-name', function($inject1, $inject2) { 1645 * return { 1646 * eventName : function(element, done) { 1647 * //code to run the animation 1648 * //once complete, then run done() 1649 * return function cancellationFunction(element) { 1650 * //code to cancel the animation 1651 * } 1652 * } 1653 * } 1654 * }) 1655 * </pre> 1656 * 1657 * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and 1658 * {@link ngAnimate ngAnimate module} for more information. 1659 */ 1660 animation: invokeLater('$animateProvider', 'register'), 1661 1662 /** 1663 * @ngdoc method 1664 * @name angular.Module#filter 1665 * @methodOf angular.Module 1666 * @param {string} name Filter name. 1667 * @param {Function} filterFactory Factory function for creating new instance of filter.
1668 * @description 1669 * See {@link ng.$filterProvider#register $filterProvider.register()}. 1670 */ 1671 filter: invokeLater('$filterProvider', 'register'), 1672 1673 /** 1674 * @ngdoc method 1675 * @name angular.Module#controller 1676 * @methodOf angular.Module 1677 * @param {string|Object} name Controller name, or an object map of controllers where the 1678 * keys are the names and the values are the constructors. 1679 * @param {Function} constructor Controller constructor function. 1680 * @description 1681 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 1682 */ 1683 controller: invokeLater('$controllerProvider', 'register'), 1684 1685 /** 1686 * @ngdoc method 1687 * @name angular.Module#directive 1688 * @methodOf angular.Module 1689 * @param {string|Object} name Directive name, or an object map of directives where the 1690 * keys are the names and the values are the factories. 1691 * @param {Function} directiveFactory Factory function for creating new instance of 1692 * directives. 1693 * @description 1694 * See {@link ng.$compileProvider#methods_directive $compileProvider.directive()}. 1695 */ 1696 directive: invokeLater('$compileProvider', 'directive'), 1697 1698 /** 1699 * @ngdoc method 1700 * @name angular.Module#config 1701 * @methodOf angular.Module 1702 * @param {Function} configFn Execute this function on module load. Useful for service 1703 * configuration. 1704 * @description 1705 * Use this method to register work which needs to be performed on module loading. 1706 */ 1707 config: config, 1708 1709 /** 1710 * @ngdoc method 1711 * @name angular.Module#run 1712 * @methodOf angular.Module 1713 * @param {Function} initializationFn Execute this function after injector creation. 1714 * Useful for application initialization. 1715 * @description 1716 * Use this method to register work which should be performed when the injector is done 1717 * loading all modules. 1718 */ 1719 run: function(block) { 1720 runBlocks.push(block); 1721 return this; 1722 } 1723 }; 1724 1725 if (configFn) { 1726 config(configFn); 1727 } 1728 1729 return moduleInstance; 1730 1731 /** 1732 * @param {string} provider 1733 * @param {string} method 1734 * @param {String=} insertMethod 1735 * @returns {angular.Module} 1736 */ 1737 function invokeLater(provider, method, insertMethod) { 1738 return function() { 1739 invokeQueue[insertMethod || 'push']([provider, method, arguments]); 1740 return moduleInstance; 1741 }; 1742 } 1743 }); 1744 }; 1745 }); 1746 1747} 1748 1749/* global 1750 angularModule: true, 1751 version: true, 1752 1753 $LocaleProvider, 1754 $CompileProvider, 1755 1756 htmlAnchorDirective, 1757 inputDirective, 1758 inputDirective, 1759 formDirective, 1760 scriptDirective, 1761 selectDirective, 1762 styleDirective, 1763 optionDirective, 1764 ngBindDirective, 1765 ngBindHtmlDirective, 1766 ngBindTemplateDirective, 1767 ngClassDirective, 1768 ngClassEvenDirective, 1769 ngClassOddDirective, 1770 ngCspDirective, 1771 ngCloakDirective, 1772 ngControllerDirective, 1773 ngFormDirective, 1774 ngHideDirective, 1775 ngIfDirective, 1776 ngIncludeDirective, 1777 ngIncludeFillContentDirective, 1778 ngInitDirective, 1779 ngNonBindableDirective, 1780 ngPluralizeDirective, 1781 ngRepeatDirective, 1782 ngShowDirective, 1783 ngStyleDirective, 1784 ngSwitchDirective, 1785 ngSwitchWhenDirective, 1786 ngSwitchDefaultDirective, 1787 ngOptionsDirective, 1788 ngTranscludeDirective, 1789 ngModelDirective, 1790 ngListDirective, 1791 ngChangeDirective, 1792 requiredDirective, 1793 requiredDirective, 1794 ngValueDirective, 1795 ngAttributeAliasDirectives, 1796 ngEventDirectives, 1797 1798 $AnchorScrollProvider, 1799 $AnimateProvider, 1800 $BrowserProvider,
1801 $CacheFactoryProvider, 1802 $ControllerProvider, 1803 $DocumentProvider, 1804 $ExceptionHandlerProvider, 1805 $FilterProvider, 1806 $InterpolateProvider, 1807 $IntervalProvider, 1808 $HttpProvider, 1809 $HttpBackendProvider, 1810 $LocationProvider, 1811 $LogProvider, 1812 $ParseProvider, 1813 $RootScopeProvider, 1814 $QProvider, 1815 $$SanitizeUriProvider, 1816 $SceProvider, 1817 $SceDelegateProvider, 1818 $SnifferProvider, 1819 $TemplateCacheProvider, 1820 $TimeoutProvider, 1821 $WindowProvider 1822*/ 1823 1824 1825/** 1826 * @ngdoc property 1827 * @name angular.version 1828 * @description 1829 * An object that contains information about the current AngularJS version. This object has the 1830 * following properties: 1831 * 1832 * - `full` â `{string}` â Full version string, such as "0.9.18". 1833 * - `major` â `{number}` â Major version number, such as "0". 1834 * - `minor` â `{number}` â Minor version number, such as "9". 1835 * - `dot` â `{number}` â Dot version number, such as "18". 1836 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 1837 */ 1838var version = { 1839 full: '1.2.13', // all of these placeholder strings will be replaced by grunt's 1840 major: 1, // package task 1841 minor: 2, 1842 dot: 13, 1843 codeName: 'romantic-transclusion' 1844}; 1845 1846 1847function publishExternalAPI(angular){ 1848 extend(angular, { 1849 'bootstrap': bootstrap, 1850 'copy': copy, 1851 'extend': extend, 1852 'equals': equals, 1853 'element': jqLite, 1854 'forEach': forEach, 1855 'injector': createInjector, 1856 'noop':noop, 1857 'bind':bind, 1858 'toJson': toJson, 1859 'fromJson': fromJson, 1860 'identity':identity, 1861 'isUndefined': isUndefined, 1862 'isDefined': isDefined, 1863 'isString': isString, 1864 'isFunction': isFunction, 1865 'isObject': isObject, 1866 'isNumber': isNumber, 1867 'isElement': isElement, 1868 'isArray': isArray, 1869 'version': version, 1870 'isDate': isDate, 1871 'lowercase': lowercase, 1872 'uppercase': uppercase, 1873 'callbacks': {counter: 0}, 1874 '$$minErr': minErr, 1875 '$$csp': csp 1876 }); 1877 1878 angularModule = setupModuleLoader(window); 1879 try { 1880 angularModule('ngLocale'); 1881 } catch (e) { 1882 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 1883 } 1884 1885 angularModule('ng', ['ngLocale'], ['$provide', 1886 function ngModule($provide) { 1887 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 1888 $provide.provider({ 1889 $$sanitizeUri: $$SanitizeUriProvider 1890 }); 1891 $provide.provider('$compile', $CompileProvider). 1892 directive({ 1893 a: htmlAnchorDirective, 1894 input: inputDirective, 1895 textarea: inputDirective, 1896 form: formDirective, 1897 script: scriptDirective, 1898 select: selectDirective, 1899 style: styleDirective, 1900 option: optionDirective, 1901 ngBind: ngBindDirective, 1902 ngBindHtml: ngBindHtmlDirective, 1903 ngBindTemplate: ngBindTemplateDirective, 1904 ngClass: ngClassDirective, 1905 ngClassEven: ngClassEvenDirective, 1906 ngClassOdd: ngClassOddDirective, 1907 ngCloak: ngCloakDirective, 1908 ngController: ngControllerDirective, 1909 ngForm: ngFormDirective, 1910 ngHide: ngHideDirective, 1911 ngIf: ngIfDirective, 1912 ngInclude: ngIncludeDirective, 1913 ngInit: ngInitDirective, 1914 ngNonBindable: ngNonBindableDirective, 1915 ngPluralize: ngPluralizeDirective, 1916 ngRepeat: ngRepeatDirective, 1917 ngShow: ngShowDirective, 1918 ngStyle: ngStyleDirective, 1919 ngSwitch: ngSwitchDirective, 1920 ngSwitchWhen: ngSwitchWhenDirective, 1921 ngSwitchDefault: ngSwitchDefaultDirective, 1922 ngOptions: ngOptionsDirective, 1923 ngTransclude: ngTranscludeDirective, 1924 ngModel: ngModelDirective, 1925 ngList: ngListDirective, 1926 ngChange: ngChangeDirective, 1927 required: requiredDirective, 1928 ngRequired: requiredDirective, 1929 ngValue: ngValueDirective 1930 }). 1931 directive({ 1932 ngInclude: ngIncludeFillContentDirective 1933 }). 1934 directive(ngAttributeAliasDirectives). 1935 directive(ngEventDirectives); 1936 $provide.provider({ 1937 $anchorScroll: $AnchorScrollProvider, 1938 $animate: $AnimateProvider, 1939 $browser: $BrowserProvider,
1940 $cacheFactory: $CacheFactoryProvider, 1941 $controller: $ControllerProvider, 1942 $document: $DocumentProvider, 1943 $exceptionHandler: $ExceptionHandlerProvider, 1944 $filter: $FilterProvider, 1945 $interpolate: $InterpolateProvider, 1946 $interval: $IntervalProvider, 1947 $http: $HttpProvider, 1948 $httpBackend: $HttpBackendProvider, 1949 $location: $LocationProvider, 1950 $log: $LogProvider, 1951 $parse: $ParseProvider, 1952 $rootScope: $RootScopeProvider, 1953 $q: $QProvider, 1954 $sce: $SceProvider, 1955 $sceDelegate: $SceDelegateProvider, 1956 $sniffer: $SnifferProvider, 1957 $templateCache: $TemplateCacheProvider, 1958 $timeout: $TimeoutProvider, 1959 $window: $WindowProvider 1960 }); 1961 } 1962 ]); 1963} 1964 1965/* global 1966 1967 -JQLitePrototype, 1968 -addEventListenerFn, 1969 -removeEventListenerFn, 1970 -BOOLEAN_ATTR 1971*/ 1972 1973////////////////////////////////// 1974//JQLite 1975////////////////////////////////// 1976 1977/** 1978 * @ngdoc function 1979 * @name angular.element 1980 * @function 1981 * 1982 * @description 1983 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 1984 * 1985 * If jQuery is available, `angular.element` is an alias for the 1986 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 1987 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." 1988 * 1989 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows 1990 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most 1991 * commonly needed functionality with the goal of having a very small footprint.</div> 1992 * 1993 * To use jQuery, simply load it before `DOMContentLoaded` event fired. 1994 * 1995 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or 1996 * jqLite; they are never raw DOM references.</div> 1997 * 1998 * ## Angular's jqLite 1999 * jqLite provides only the following jQuery methods: 2000 * 2001 * - [`addClass()`](http://api.jquery.com/addClass/) 2002 * - [`after()`](http://api.jquery.com/after/) 2003 * - [`append()`](http://api.jquery.com/append/) 2004 * - [`attr()`](http://api.jquery.com/attr/) 2005 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2006 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2007 * - [`clone()`](http://api.jquery.com/clone/) 2008 * - [`contents()`](http://api.jquery.com/contents/) 2009 * - [`css()`](http://api.jquery.com/css/) 2010 * - [`data()`](http://api.jquery.com/data/) 2011 * - [`empty()`](http://api.jquery.com/empty/) 2012 * - [`eq()`](http://api.jquery.com/eq/) 2013 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2014 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2015 * - [`html()`](http://api.jquery.com/html/) 2016 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2017 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2018 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors 2019 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2020 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2021 * - [`prepend()`](http://api.jquery.com/prepend/) 2022 * - [`prop()`](http://api.jquery.com/prop/) 2023 * - [`ready()`](http://api.jquery.com/ready/) 2024 * - [`remove()`](http://api.jquery.com/remove/) 2025 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2026 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2027 * - [`removeData()`](http://api.jquery.com/removeData/) 2028 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2029 * - [`text()`](http://api.jquery.com/text/) 2030 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2031 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2032 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces 2033 * - [`val()`](http://api.jquery.com/val/) 2034 * - [`wrap()`](http://api.jquery.com/wrap/) 2035 * 2036 * ## jQuery/jqLite Extras 2037 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2038 * 2039 * ### Events 2040 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
2040pis and fires this event 2041 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2042 * element before it is removed. 2043 * 2044 * ### Methods 2045 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2046 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2047 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2048 * `'ngModel'`). 2049 * - `injector()` - retrieves the injector of the current element or its parent. 2050 * - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current 2051 * element or its parent. 2052 * - `isolateScope()` - retrieves an isolate {@link api/ng.$rootScope.Scope scope} if one is attached directly to the 2053 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 2054 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2055 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2056 * parent element is reached. 2057 * 2058 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2059 * @returns {Object} jQuery object. 2060 */ 2061 2062var jqCache = JQLite.cache = {}, 2063 jqName = JQLite.expando = 'ng-' + new Date().getTime(), 2064 jqId = 1, 2065 addEventListenerFn = (window.document.addEventListener 2066 ? function(element, type, fn) {element.addEventListener(type, fn, false);} 2067 : function(element, type, fn) {element.attachEvent('on' + type, fn);}), 2068 removeEventListenerFn = (window.document.removeEventListener 2069 ? function(element, type, fn) {element.removeEventListener(type, fn, false); } 2070 : function(element, type, fn) {element.detachEvent('on' + type, fn); }); 2071 2072/* 2073 * !!! This is an undocumented "private" function !!! 2074 */ 2075var jqData = JQLite._data = function(node) { 2076 //jQuery always returns an object on cache miss 2077 return this.cache[node[this.expando]] || {}; 2078}; 2079 2080function jqNextId() { return ++jqId; } 2081 2082 2083var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2084var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2085var jqLiteMinErr = minErr('jqLite'); 2086 2087/** 2088 * Converts snake_case to camelCase. 2089 * Also there is special case for Moz prefix starting with upper case letter. 2090 * @param name Name to normalize 2091 */ 2092function camelCase(name) { 2093 return name. 2094 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2095 return offset ? letter.toUpperCase() : letter; 2096 }). 2097 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2098} 2099 2100///////////////////////////////////////////// 2101// jQuery mutation patch 2102// 2103// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a 2104// $destroy event on all DOM nodes being removed. 2105// 2106///////////////////////////////////////////// 2107 2108function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { 2109 var originalJqFn = jQuery.fn[name]; 2110 originalJqFn = originalJqFn.$original || originalJqFn; 2111 removePatch.$original = originalJqFn; 2112 jQuery.fn[name] = removePatch; 2113 2114 function removePatch(param) { 2115 // jshint -W040 2116 var list = filterElems && param ? [this.filter(param)] : [this], 2117 fireEvent = dispatchThis, 2118 set, setIndex, setLength, 2119 element, childIndex, childLength, children; 2120 2121 if (!getterIfNoArguments || param != null) { 2122 while(list.length) { 2123 set = list.shift(); 2124 for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { 2125 element = jqLite(set[setIndex]); 2126 if (fireEvent) { 2127 element.triggerHandler('$destroy'); 2128 } else { 2129 fireEvent = !fireEvent; 2130 } 2131 for(childIndex = 0, childLength = (children = element.children()).length; 2132 childIndex < childLength; 2133 childIndex++) { 2134 list.push(jQuery(children[childIndex])); 2135 } 2136 } 2137 } 2138 } 2139 return originalJqFn.apply(this, arguments); 2140 } 2141} 2142 2143///////////////////////////////////////////// 2144function JQLite(element) { 2145 if (element instanceof JQLite) { 2146 return element; 2147 } 2148 if (isString(element)) { 2149 element = trim(element); 2150 } 2151 if (!(this instanceof JQLite)) { 2152 if (isString(element) && element.charAt(0) != '<') { 2153 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2154 } 2155 return new JQLite(element); 2156 } 2157 2158 if (isString(element)) { 2159 var div = document.createElement('div'); 2160 // Read about the NoScope elements here:
2161 // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx 2162 div.innerHTML = '<div> </div>' + element; // IE insanity to make NoScope elements work! 2163 div.removeChild(div.firstChild); // remove the superfluous div 2164 jqLiteAddNodes(this, div.childNodes); 2165 var fragment = jqLite(document.createDocumentFragment()); 2166 fragment.append(this); // detach the elements from the temporary DOM div. 2167 } else { 2168 jqLiteAddNodes(this, element); 2169 } 2170} 2171 2172function jqLiteClone(element) { 2173 return element.cloneNode(true); 2174} 2175 2176function jqLiteDealoc(element){ 2177 jqLiteRemoveData(element); 2178 for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { 2179 jqLiteDealoc(children[i]); 2180 } 2181} 2182 2183function jqLiteOff(element, type, fn, unsupported) { 2184 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2185 2186 var events = jqLiteExpandoStore(element, 'events'), 2187 handle = jqLiteExpandoStore(element, 'handle'); 2188 2189 if (!handle) return; //no listeners registered 2190 2191 if (isUndefined(type)) { 2192 forEach(events, function(eventHandler, type) { 2193 removeEventListenerFn(element, type, eventHandler); 2194 delete events[type]; 2195 }); 2196 } else { 2197 forEach(type.split(' '), function(type) { 2198 if (isUndefined(fn)) { 2199 removeEventListenerFn(element, type, events[type]); 2200 delete events[type]; 2201 } else { 2202 arrayRemove(events[type] || [], fn); 2203 } 2204 }); 2205 } 2206} 2207 2208function jqLiteRemoveData(element, name) { 2209 var expandoId = element[jqName], 2210 expandoStore = jqCache[expandoId]; 2211 2212 if (expandoStore) { 2213 if (name) { 2214 delete jqCache[expandoId].data[name]; 2215 return; 2216 } 2217 2218 if (expandoStore.handle) { 2219 expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); 2220 jqLiteOff(element); 2221 } 2222 delete jqCache[expandoId]; 2223 element[jqName] = undefined; // ie does not allow deletion of attributes on elements. 2224 } 2225} 2226 2227function jqLiteExpandoStore(element, key, value) { 2228 var expandoId = element[jqName], 2229 expandoStore = jqCache[expandoId || -1]; 2230 2231 if (isDefined(value)) { 2232 if (!expandoStore) { 2233 element[jqName] = expandoId = jqNextId(); 2234 expandoStore = jqCache[expandoId] = {}; 2235 } 2236 expandoStore[key] = value; 2237 } else { 2238 return expandoStore && expandoStore[key]; 2239 } 2240} 2241 2242function jqLiteData(element, key, value) { 2243 var data = jqLiteExpandoStore(element, 'data'), 2244 isSetter = isDefined(value), 2245 keyDefined = !isSetter && isDefined(key), 2246 isSimpleGetter = keyDefined && !isObject(key); 2247 2248 if (!data && !isSimpleGetter) { 2249 jqLiteExpandoStore(element, 'data', data = {}); 2250 } 2251 2252 if (isSetter) { 2253 data[key] = value; 2254 } else { 2255 if (keyDefined) { 2256 if (isSimpleGetter) { 2257 // don't create data in this case. 2258 return data && data[key]; 2259 } else { 2260 extend(data, key); 2261 } 2262 } else { 2263 return data; 2264 } 2265 } 2266} 2267 2268function jqLiteHasClass(element, selector) { 2269 if (!element.getAttribute) return false; 2270 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2271 indexOf( " " + selector + " " ) > -1); 2272} 2273 2274function jqLiteRemoveClass(element, cssClasses) { 2275 if (cssClasses && element.setAttribute) { 2276 forEach(cssClasses.split(' '), function(cssClass) { 2277 element.setAttribute('class', trim( 2278 (" " + (element.getAttribute('class') || '') + " ") 2279 .replace(/[\n\t]/g, " ") 2280 .replace(" " + trim(cssClass) + " ", " ")) 2281 ); 2282 }); 2283 } 2284} 2285 2286function jqLiteAddClass(element, cssClasses) { 2287 if (cssClasses && element.setAttribute) { 2288 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 2289 .replace(/[\n\t]/g, " "); 2290 2291 forEach(cssClasses.split(' '), function(cssClass) { 2292 cssClass = trim(cssClass); 2293 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 2294 existingClasses += cssClass + ' '; 2295 } 2296 });
2297 2298 element.setAttribute('class', trim(existingClasses)); 2299 } 2300} 2301 2302function jqLiteAddNodes(root, elements) { 2303 if (elements) { 2304 elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) 2305 ? elements 2306 : [ elements ]; 2307 for(var i=0; i < elements.length; i++) { 2308 root.push(elements[i]); 2309 } 2310 } 2311} 2312 2313function jqLiteController(element, name) { 2314 return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); 2315} 2316 2317function jqLiteInheritedData(element, name, value) { 2318 element = jqLite(element); 2319 2320 // if element is the document object work with the html element instead 2321 // this makes $(document).scope() possible 2322 if(element[0].nodeType == 9) { 2323 element = element.find('html'); 2324 } 2325 var names = isArray(name) ? name : [name]; 2326 2327 while (element.length) { 2328 2329 for (var i = 0, ii = names.length; i < ii; i++) { 2330 if ((value = element.data(names[i])) !== undefined) return value; 2331 } 2332 element = element.parent(); 2333 } 2334} 2335 2336function jqLiteEmpty(element) { 2337 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2338 jqLiteDealoc(childNodes[i]); 2339 } 2340 while (element.firstChild) { 2341 element.removeChild(element.firstChild); 2342 } 2343} 2344 2345////////////////////////////////////////// 2346// Functions which are declared directly. 2347////////////////////////////////////////// 2348var JQLitePrototype = JQLite.prototype = { 2349 ready: function(fn) { 2350 var fired = false; 2351 2352 function trigger() { 2353 if (fired) return; 2354 fired = true; 2355 fn(); 2356 } 2357 2358 // check if document already is loaded 2359 if (document.readyState === 'complete'){ 2360 setTimeout(trigger); 2361 } else { 2362 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 2363 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 2364 // jshint -W064 2365 JQLite(window).on('load', trigger); // fallback to window.onload for others 2366 // jshint +W064 2367 } 2368 }, 2369 toString: function() { 2370 var value = []; 2371 forEach(this, function(e){ value.push('' + e);}); 2372 return '[' + value.join(', ') + ']'; 2373 }, 2374 2375 eq: function(index) { 2376 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 2377 }, 2378 2379 length: 0, 2380 push: push, 2381 sort: [].sort, 2382 splice: [].splice 2383}; 2384 2385////////////////////////////////////////// 2386// Functions iterating getter/setters. 2387// these functions return self on setter and 2388// value on get. 2389////////////////////////////////////////// 2390var BOOLEAN_ATTR = {}; 2391forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 2392 BOOLEAN_ATTR[lowercase(value)] = value; 2393}); 2394var BOOLEAN_ELEMENTS = {}; 2395forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 2396 BOOLEAN_ELEMENTS[uppercase(value)] = true; 2397}); 2398 2399function getBooleanAttrName(element, name) { 2400 // check dom last since we will most likely fail on name 2401 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 2402 2403 // booleanAttr is here twice to minimize DOM access 2404 return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; 2405} 2406 2407forEach({ 2408 data: jqLiteData, 2409 inheritedData: jqLiteInheritedData, 2410 2411 scope: function(element) { 2412 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2413 return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 2414 }, 2415 2416 isolateScope: function(element) { 2417 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2418 return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); 2419 }, 2420 2421 controller: jqLiteController , 2422 2423 injector: function(element) { 2424 return jqLiteInheritedData(element, '$injector'); 2425 }, 2426 2427 removeAttr: function(element,name) { 2428 element.removeAttribute(name); 2429 }, 2430 2431 hasClass: jqLiteHasClass, 2432 2433 css: function(element, name, value) { 2434 name = camelCase(name); 2435 2436 if (isDefined(value)) { 2437 element.style[name] = value; 2438 } else { 2439 var val; 2440 2441 if (msie <= 8) { 2442 // this is some IE specific weirdness that jQuery 1.6.4 does not sure why 2443 val = element.currentStyle && element.currentStyle[name]; 2444 if (val === '') val = 'auto'; 2445 } 2446 2447 val = val || element.style[name]; 2448 2449 if (msie <= 8) { 2450 // jquery weirdness :-/ 2451 val = (val === '') ? undefined : val; 2452 } 2453 2454 return val; 2455 } 2456 }, 2457 2458 attr: function(element, name, value){ 2459 var lowercasedName = lowercase(name); 2460 if (BOOLEAN_ATTR[lowercasedName]) { 2461 if (isDefined(value)) { 2462 if (!!value) { 2463 element[name] = true; 2464 element.setAttribute(name, lowercasedName); 2465 } else { 2466 element[name] = false; 2467 element.removeAttribute(lowercasedName); 2468 } 2469 } else { 2470 return (element[name] || 2471 (element.attributes.getNamedItem(name)|| noop).specified) 2472 ? lowercasedName 2473 : undefined; 2474 } 2475 } else if (isDefined(value)) { 2476 element.setAttribute(name, value); 2477 } else if (element.getAttribute) { 2478 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 2479 // some elements (e.g. Document) don't have get attribute, so return undefined 2480 var ret = element.getAttribute(name, 2); 2481 // normalize non-existing attributes to undefined (as jQuery) 2482 return ret === null ? undefined : ret; 2483 } 2484 }, 2485 2486 prop: function(element, name, value) { 2487 if (isDefined(value)) { 2488 element[name] = value; 2489 } else { 2490 return element[name]; 2491 } 2492 }, 2493 2494 text: (function() { 2495 var NODE_TYPE_TEXT_PROPERTY = []; 2496 if (msie < 9) { 2497 NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ 2498 NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ 2499 } else { 2500 NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ 2501 NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ 2502 } 2503 getText.$dv = ''; 2504 return getText; 2505 2506 function getText(element, value) { 2507 var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; 2508 if (isUndefined(value)) { 2509 return textProp ? element[textProp] : ''; 2510 } 2511 element[textProp] = value; 2512 } 2513 })(), 2514 2515 val: function(element, value) { 2516 if (isUndefined(value)) { 2517 if (nodeName_(element) === 'SELECT' && element.multiple) { 2518 var result = [];
2519 forEach(element.options, function (option) { 2520 if (option.selected) { 2521 result.push(option.value || option.text); 2522 } 2523 }); 2524 return result.length === 0 ? null : result; 2525 } 2526 return element.value; 2527 } 2528 element.value = value; 2529 }, 2530 2531 html: function(element, value) { 2532 if (isUndefined(value)) { 2533 return element.innerHTML; 2534 } 2535 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2536 jqLiteDealoc(childNodes[i]); 2537 } 2538 element.innerHTML = value; 2539 }, 2540 2541 empty: jqLiteEmpty 2542}, function(fn, name){ 2543 /** 2544 * Properties: writes return selection, reads return first value 2545 */ 2546 JQLite.prototype[name] = function(arg1, arg2) { 2547 var i, key; 2548 2549 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 2550 // in a way that survives minification. 2551 // jqLiteEmpty takes no arguments but is a setter. 2552 if (fn !== jqLiteEmpty && 2553 (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { 2554 if (isObject(arg1)) { 2555 2556 // we are a write, but the object properties are the key/values 2557 for (i = 0; i < this.length; i++) { 2558 if (fn === jqLiteData) { 2559 // data() takes the whole object in jQuery 2560 fn(this[i], arg1); 2561 } else { 2562 for (key in arg1) { 2563 fn(this[i], key, arg1[key]); 2564 } 2565 } 2566 } 2567 // return self for chaining 2568 return this; 2569 } else { 2570 // we are a read, so read the first child. 2571 var value = fn.$dv; 2572 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 2573 var jj = (value === undefined) ? Math.min(this.length, 1) : this.length; 2574 for (var j = 0; j < jj; j++) { 2575 var nodeValue = fn(this[j], arg1, arg2); 2576 value = value ? value + nodeValue : nodeValue; 2577 } 2578 return value; 2579 } 2580 } else { 2581 // we are a write, so apply to all children 2582 for (i = 0; i < this.length; i++) { 2583 fn(this[i], arg1, arg2); 2584 } 2585 // return self for chaining 2586 return this; 2587 } 2588 }; 2589}); 2590 2591function createEventHandler(element, events) { 2592 var eventHandler = function (event, type) { 2593 if (!event.preventDefault) { 2594 event.preventDefault = function() { 2595 event.returnValue = false; //ie 2596 }; 2597 } 2598 2599 if (!event.stopPropagation) { 2600 event.stopPropagation = function() { 2601 event.cancelBubble = true; //ie 2602 }; 2603 } 2604 2605 if (!event.target) { 2606 event.target = event.srcElement || document; 2607 } 2608 2609 if (isUndefined(event.defaultPrevented)) { 2610 var prevent = event.preventDefault; 2611 event.preventDefault = function() { 2612 event.defaultPrevented = true; 2613 prevent.call(event); 2614 }; 2615 event.defaultPrevented = false; 2616 } 2617 2618 event.isDefaultPrevented = function() { 2619 return event.defaultPrevented || event.returnValue === false; 2620 }; 2621 2622 // Copy event handlers in case event handlers array is modified during execution. 2623 var eventHandlersCopy = shallowCopy(events[type || event.type] || []); 2624 2625 forEach(eventHandlersCopy, function(fn) { 2626 fn.call(element, event); 2627 }); 2628 2629 // Remove monkey-patched methods (IE), 2630 // as they would cause memory leaks in IE8. 2631 if (msie <= 8) { 2632 // IE7/8 does not allow to delete property on native object 2633 event.preventDefault = null; 2634 event.stopPropagation = null; 2635 event.isDefaultPrevented = null; 2636 } else { 2637 // It shouldn't affect normal browsers (native methods are defined on prototype). 2638 delete event.preventDefault; 2639 delete event.stopPropagation; 2640 delete event.isDefaultPrevented; 2641 } 2642 }; 2643 eventHandler.elem = element; 2644 return eventHandler; 2645} 2646 2647////////////////////////////////////////// 2648// Functions iterating traversal. 2649// These functions chain results into a single 2650// selector.
2651////////////////////////////////////////// 2652forEach({ 2653 removeData: jqLiteRemoveData, 2654 2655 dealoc: jqLiteDealoc, 2656 2657 on: function onFn(element, type, fn, unsupported){ 2658 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 2659 2660 var events = jqLiteExpandoStore(element, 'events'), 2661 handle = jqLiteExpandoStore(element, 'handle'); 2662 2663 if (!events) jqLiteExpandoStore(element, 'events', events = {}); 2664 if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); 2665 2666 forEach(type.split(' '), function(type){ 2667 var eventFns = events[type]; 2668 2669 if (!eventFns) { 2670 if (type == 'mouseenter' || type == 'mouseleave') { 2671 var contains = document.body.contains || document.body.compareDocumentPosition ? 2672 function( a, b ) { 2673 // jshint bitwise: false 2674 var adown = a.nodeType === 9 ? a.documentElement : a, 2675 bup = b && b.parentNode; 2676 return a === bup || !!( bup && bup.nodeType === 1 && ( 2677 adown.contains ? 2678 adown.contains( bup ) : 2679 a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 2680 )); 2681 } : 2682 function( a, b ) { 2683 if ( b ) { 2684 while ( (b = b.parentNode) ) { 2685 if ( b === a ) { 2686 return true; 2687 } 2688 } 2689 } 2690 return false; 2691 }; 2692 2693 events[type] = []; 2694 2695 // Refer to jQuery's implementation of mouseenter & mouseleave 2696 // Read about mouseenter and mouseleave: 2697 // http://www.quirksmode.org/js/events_mouse.html#link8 2698 var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; 2699 2700 onFn(element, eventmap[type], function(event) { 2701 var target = this, related = event.relatedTarget; 2702 // For mousenter/leave call the handler if related is outside the target. 2703 // NB: No relatedTarget if the mouse left/entered the browser window 2704 if ( !related || (related !== target && !contains(target, related)) ){ 2705 handle(event, type); 2706 } 2707 }); 2708 2709 } else { 2710 addEventListenerFn(element, type, handle); 2711 events[type] = []; 2712 } 2713 eventFns = events[type]; 2714 } 2715 eventFns.push(fn); 2716 }); 2717 }, 2718 2719 off: jqLiteOff, 2720 2721 one: function(element, type, fn) { 2722 element = jqLite(element); 2723 2724 //add the listener twice so that when it is called 2725 //you can remove the original function and still be 2726 //able to call element.off(ev, fn) normally 2727 element.on(type, function onFn() { 2728 element.off(type, fn); 2729 element.off(type, onFn); 2730 }); 2731 element.on(type, fn); 2732 }, 2733 2734 replaceWith: function(element, replaceNode) { 2735 var index, parent = element.parentNode; 2736 jqLiteDealoc(element); 2737 forEach(new JQLite(replaceNode), function(node){ 2738 if (index) { 2739 parent.insertBefore(node, index.nextSibling); 2740 } else { 2741 parent.replaceChild(node, element); 2742 } 2743 index = node; 2744 }); 2745 }, 2746 2747 children: function(element) { 2748 var children = []; 2749 forEach(element.childNodes, function(element){ 2750 if (element.nodeType === 1) 2751 children.push(element); 2752 }); 2753 return children; 2754 }, 2755 2756 contents: function(element) { 2757 return element.childNodes || []; 2758 }, 2759 2760 append: function(element, node) { 2761 forEach(new JQLite(node), function(child){ 2762 if (element.nodeType === 1 || element.nodeType === 11) { 2763 element.appendChild(child); 2764 } 2765 }); 2766 }, 2767 2768 prepend: function(element, node) { 2769 if (element.nodeType === 1) { 2770 var index = element.firstChild; 2771 forEach(new JQLite(node), function(child){ 2772 element.insertBefore(child, index); 2773 }); 2774 } 2775 }, 2776 2777 wrap: function(element, wrapNode) { 2778 wrapNode = jqLite(wrapNode)[0]; 2779 var parent = element.parentNode; 2780 if (parent) { 2781 parent.replaceChild(wrapNode, element); 2782 } 2783 wrapNode.appendChild(element); 2784 }, 2785 2786 remove: function(element) { 2787 jqLiteDealoc(element); 2788 var parent = element.parentNode; 2789 if (parent) parent.removeChild(element); 2790 }, 2791 2792 after: function(element, newElement) { 2793 var index = element, parent = element.parentNode;
2794 forEach(new JQLite(newElement), function(node){ 2795 parent.insertBefore(node, index.nextSibling); 2796 index = node; 2797 }); 2798 }, 2799 2800 addClass: jqLiteAddClass, 2801 removeClass: jqLiteRemoveClass, 2802 2803 toggleClass: function(element, selector, condition) { 2804 if (isUndefined(condition)) { 2805 condition = !jqLiteHasClass(element, selector); 2806 } 2807 (condition ? jqLiteAddClass : jqLiteRemoveClass)(element, selector); 2808 }, 2809 2810 parent: function(element) { 2811 var parent = element.parentNode; 2812 return parent && parent.nodeType !== 11 ? parent : null; 2813 }, 2814 2815 next: function(element) { 2816 if (element.nextElementSibling) { 2817 return element.nextElementSibling; 2818 } 2819 2820 // IE8 doesn't have nextElementSibling 2821 var elm = element.nextSibling; 2822 while (elm != null && elm.nodeType !== 1) { 2823 elm = elm.nextSibling; 2824 } 2825 return elm; 2826 }, 2827 2828 find: function(element, selector) { 2829 if (element.getElementsByTagName) { 2830 return element.getElementsByTagName(selector); 2831 } else { 2832 return []; 2833 } 2834 }, 2835 2836 clone: jqLiteClone, 2837 2838 triggerHandler: function(element, eventName, eventData) { 2839 var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; 2840 2841 eventData = eventData || []; 2842 2843 var event = [{ 2844 preventDefault: noop, 2845 stopPropagation: noop 2846 }]; 2847 2848 forEach(eventFns, function(fn) { 2849 fn.apply(element, event.concat(eventData)); 2850 }); 2851 } 2852}, function(fn, name){ 2853 /** 2854 * chaining functions 2855 */ 2856 JQLite.prototype[name] = function(arg1, arg2, arg3) { 2857 var value; 2858 for(var i=0; i < this.length; i++) { 2859 if (isUndefined(value)) { 2860 value = fn(this[i], arg1, arg2, arg3); 2861 if (isDefined(value)) { 2862 // any function which returns a value needs to be wrapped 2863 value = jqLite(value); 2864 } 2865 } else { 2866 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 2867 } 2868 } 2869 return isDefined(value) ? value : this; 2870 }; 2871 2872 // bind legacy bind/unbind to on/off 2873 JQLite.prototype.bind = JQLite.prototype.on; 2874 JQLite.prototype.unbind = JQLite.prototype.off; 2875}); 2876 2877/** 2878 * Computes a hash of an 'obj'. 2879 * Hash of a: 2880 * string is string 2881 * number is number as string 2882 * object is either result of calling $$hashKey function on the object or uniquely generated id, 2883 * that is also assigned to the $$hashKey property of the object. 2884 * 2885 * @param obj 2886 * @returns {string} hash string such that the same input will have the same hash string. 2887 * The resulting string key is in 'type:hashKey' format. 2888 */ 2889function hashKey(obj) { 2890 var objType = typeof obj, 2891 key; 2892 2893 if (objType == 'object' && obj !== null) { 2894 if (typeof (key = obj.$$hashKey) == 'function') { 2895 // must invoke on object to keep the right this 2896 key = obj.$$hashKey(); 2897 } else if (key === undefined) { 2898 key = obj.$$hashKey = nextUid(); 2899 } 2900 } else { 2901 key = obj; 2902 } 2903 2904 return objType + ':' + key; 2905} 2906 2907/** 2908 * HashMap which can use objects as keys 2909 */ 2910function HashMap(array){ 2911 forEach(array, this.put, this); 2912} 2913HashMap.prototype = { 2914 /** 2915 * Store key value pair 2916 * @param key key to store can be any type 2917 * @param value value to store can be any type 2918 */ 2919 put: function(key, value) { 2920 this[hashKey(key)] = value; 2921 }, 2922 2923 /** 2924 * @param key 2925 * @returns the value for the key 2926 */ 2927 get: function(key) { 2928 return this[hashKey(key)]; 2929 }, 2930 2931 /** 2932 * Remove the key/value pair 2933 * @param key 2934 */ 2935 remove: function(key) { 2936 var value = this[key = hashKey(key)]; 2937 delete this[key]; 2938 return value; 2939 } 2940}; 2941 2942/** 2943 * @ngdoc function 2944 * @name angular.injector 2945 * @function 2946 * 2947 * @description 2948 * Creates an injector function that can be used for retrieving services as well as for 2949 * dependency injection (see {@link guide/di dependency injection}). 2950 * 2951 2952 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 2953 * {@link angular.module}. The `ng` module must be explicitly added. 2954 * @returns {function()} Injector function. See {@link AUTO.$injector $injector}. 2955 * 2956 * @example 2957 * Typical usage 2958 * <pre> 2959 * // create an injector 2960 * var $injector = angular.injector(['ng']); 2961 * 2962 * // use the injector to kick off your application 2963 * // use the type inference to auto inject arguments, or use impl
2963icit injection 2964 * $injector.invoke(function($rootScope, $compile, $document){ 2965 * $compile($document)($rootScope); 2966 * $rootScope.$digest(); 2967 * }); 2968 * </pre> 2969 * 2970 * Sometimes you want to get access to the injector of a currently running Angular app 2971 * from outside Angular. Perhaps, you want to inject and compile some markup after the 2972 * application has been bootstrapped. You can do this using extra `injector()` added 2973 * to JQuery/jqLite elements. See {@link angular.element}. 2974 * 2975 * *This is fairly rare but could be the case if a third party library is injecting the 2976 * markup.* 2977 * 2978 * In the following example a new block of HTML containing a `ng-controller` 2979 * directive is added to the end of the document body by JQuery. We then compile and link 2980 * it into the current AngularJS scope. 2981 * 2982 * <pre> 2983 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 2984 * $(document.body).append($div); 2985 * 2986 * angular.element(document).injector().invoke(function($compile) { 2987 * var scope = angular.element($div).scope(); 2988 * $compile($div)(scope); 2989 * }); 2990 * </pre> 2991 */ 2992 2993 2994/** 2995 * @ngdoc overview 2996 * @name AUTO 2997 * @description 2998 * 2999 * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}. 3000 */ 3001 3002var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; 3003var FN_ARG_SPLIT = /,/; 3004var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3005var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3006var $injectorMinErr = minErr('$injector'); 3007function annotate(fn) { 3008 var $inject, 3009 fnText, 3010 argDecl, 3011 last; 3012 3013 if (typeof fn == 'function') { 3014 if (!($inject = fn.$inject)) { 3015 $inject = []; 3016 if (fn.length) { 3017 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3018 argDecl = fnText.match(FN_ARGS); 3019 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ 3020 arg.replace(FN_ARG, function(all, underscore, name){ 3021 $inject.push(name); 3022 }); 3023 }); 3024 } 3025 fn.$inject = $inject; 3026 } 3027 } else if (isArray(fn)) { 3028 last = fn.length - 1; 3029 assertArgFn(fn[last], 'fn'); 3030 $inject = fn.slice(0, last); 3031 } else { 3032 assertArgFn(fn, 'fn', true); 3033 } 3034 return $inject; 3035} 3036 3037/////////////////////////////////////// 3038 3039/** 3040 * @ngdoc object 3041 * @name AUTO.$injector 3042 * @function 3043 * 3044 * @description 3045 * 3046 * `$injector` is used to retrieve object instances as defined by 3047 * {@link AUTO.$provide provider}, instantiate types, invoke methods, 3048 * and load modules. 3049 * 3050 * The following always holds true: 3051 * 3052 * <pre> 3053 * var $injector = angular.injector(); 3054 * expect($injector.get('$injector')).toBe($injector); 3055 * expect($injector.invoke(function($injector){ 3056 * return $injector; 3057 * }).toBe($injector); 3058 * </pre> 3059 * 3060 * # Injection Function Annotation 3061 * 3062 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3063 * following are all valid ways of annotating function with injection arguments and are equivalent. 3064 * 3065 * <pre> 3066 * // inferred (only works if code not minified/obfuscated) 3067 * $injector.invoke(function(serviceA){}); 3068 * 3069 * // annotated 3070 * function explicit(serviceA) {}; 3071 * explicit.$inject = ['serviceA']; 3072 * $injector.invoke(explicit); 3073 * 3074 * // inline 3075 * $injector.invoke(['serviceA', function(serviceA){}]); 3076 * </pre> 3077 * 3078 * ## Inference 3079 * 3080 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3081 * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with 3082 * minification, and obfuscation tools since these tools change the argument names. 3083 * 3084 * ## `$inject` Annotation 3085 * By adding a `$inject` property onto a function the injection parameters can be specified. 3086 * 3087 * ## Inline 3088 * As an array of injection names, where the last item in the array is the function to call. 3089 */ 3090 3091/** 3092 * @ngdoc method 3093 * @name AUTO.$injector#get 3094 * @methodOf AUTO.$injector 3095 * 3096 * @description 3097 * Return an instance of the service. 3098 * 3099 * @param {string} name The name of the instance to retrieve. 3100 * @return {*} The instance. 3101 */ 3102 3103/** 3104 * @ngdoc method 3105 * @name AUTO.$injector#invoke 3106 * @methodOf AUTO.$injector 3107 * 3108 * @description 3109 * Invoke the method and supply the method arguments from the `$injector`. 3110 * 3111 * @param {!function} fn The function to invoke. Function parameters are injected according to the 3112 * {@link guide/di $inject Annotation} rules. 3113 * @param {Object=} self The `this` for the invoked method. 3114 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3115 * object first, before the `$injector` is consulted. 3116 * @returns {*} the value returned by the invoked `fn` function. 3117 */ 3118 3119/** 3120 * @ngdoc method 3121 * @name AUTO.$injector#has 3122 * @methodOf AUTO.$injector 3123 * 3124 * @description 3125 * Allows the user to query if the particular service exist. 3126 * 3127 * @param {string} Name of the service to query. 3128 * @returns {boolean} returns true if injector has given service. 3129 */ 3130 3131/** 3132 * @ngdoc method 3133 * @name AUTO.$injector#instantiate 3134 * @methodOf AUTO.$injector 3135 * @description 3136 * Create a new instance of JS type. The method takes a constructor function invokes the new 3137 * operator and supplies all of the arguments to the constructor function as specified by the 3138 * constructor annotation. 3139 * 3140 * @param {function} Type Annotated constructor function. 3141 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3142 * object first, before the `$injector` is consulted. 3143 * @returns {Object} new instance of `Type`. 3144 */ 3145 3146/** 3147 * @ngdoc method 3148 * @name AUTO.$injector#annotate 3149 * @methodOf AUTO.$injector 3150 * 3151 * @description 3152 * Returns an array of service names which the function is requesting for injection. This API is 3153 * used by the injector to determine which services need to be injected into the function when the 3154 * function is invoked. There are three ways in which the function can be annotated with the needed 3155 * dependencies. 3156 * 3157 * # Argument names 3158 * 3159 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3160 * by converting the function into a string using `toString()` method and extracting the argument 3161 * names. 3162 * <pre> 3163 * // Given 3164 * function MyController($scope, $route) { 3165 * // ... 3166 * } 3167 * 3168 * // Then 3169 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3170 * </pre> 3171 * 3172 * This method does not work with code minification / obfuscation. For this reason the following 3173 * annotation strategies are supported. 3174 * 3175 * # The `$inject` property 3176 * 3177 * If a function has an `$inject` property and its value is an array of strings, then the strings 3178 * represent names of services to be injected into the function. 3179 * <pre> 3180 * // Given 3181 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 3182 * // ... 3183 * } 3184 * // Define function dependencies 3185 * MyController['$inject'] = ['$scope', '$route']; 3186 * 3187 * // Then 3188 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3189 * </pre> 3190 * 3191 * # The array notation 3192 * 3193 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 3194 * is very inconvenient. In these situations using the array notation to specify the dependencies in 3195 * a way that survives minification is a better choice: 3196 * 3197 * <pre> 3198 * // We wish to write this (not minification / obfuscation safe) 3199 * injector.invoke(function($compile, $rootScope) { 3200 * // ... 3201 * }); 3202 * 3203 * // We are forced to write break inlining 3204 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 3205 * // ... 3206 * }; 3207 * tmpFn.$inject = ['$compile', '$rootScope']; 3208 * injector.invoke(tmpFn); 3209 * 3210 * // To better support inline function the inline annotation is supported 3211 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 3212 * // ... 3213 * }]); 3214 * 3215 * // Therefore 3216 * expect(injector.annotate( 3217 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 3218 * ).toEqual(['$compile', '$rootScope']); 3219 * </pre> 3220 * 3221 * @param {function|Array.<string|Function>} fn Function for which dependent service names need to 3222 * be retrieved as described above. 3223 * 3224 * @returns {Array.<string>} The names of the services which the function requires. 3225 */ 3226 3227 3228 3229 3230/** 3231 * @ngdoc object 3232 * @name AUTO.$provide 3233 * 3234 * @description 3235 * 3236 * The {@link AUTO.$provide $provide} service has a number of methods for registering components 3237 * with the {@link AUTO.$injector $injector}. Many of these functions are also exposed on 3238 * {@link angular.Module}. 3239 * 3240 * An Angular **service** is a singleton object created by a **service factory**. These **service 3241 * factories** are functions which, in turn, are created by a **service provider**. 3242 * The **service providers** are constructor functions. When instantiated they must contain a 3243 * property called `$get`, which holds the **service factory** function. 3244 * 3245 * When you request a service, the {@link AUTO.$injector $injector} is responsible for finding the 3246 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 3247 * function to get the instance of the **service**. 3248 * 3249 * Often services have no configuration options and there is no need to add methods to the service 3250 * provider. The provider will be no more than a constructor function with a `$get` property. For 3251 * these cases the {@link AUTO.$provide $provide} service has additional helper methods to register 3252 * services without specifying a provider. 3253 * 3254 * * {@link AUTO.$provide#methods_provider provider(provider)} - registers a **service provider** with the 3255 * {@link AUTO.$injector $injector} 3256 * * {@link AUTO.$provide#methods_constant constant(obj)} - registers a value/object that can be accessed by 3257 * providers and services. 3258 * * {@link AUTO.$provide#methods_value value(obj)} - registers a value/object that can only be accessed by 3259 * services, not providers. 3260 * * {@link AUTO.$provide#methods_factory factory(fn)} - registers a service **factory function**, `fn`, 3261 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 3262 * given factory function. 3263 * * {@link AUTO.$provide#methods_service service(class)} - registers a **constructor function**, `class` that 3264 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 3265 * a new object using the given constructor function. 3266 * 3267 * See the individual methods for more information and examples. 3268 */ 3269 3270/** 3271 * @ngdoc method 3272 * @name AUTO.$provide#provider 3273 * @methodOf AUTO.$provide 3274 * @description 3275 * 3276 * Register a **provider function** with the {@link AUTO.$injector $injector}. Provider functions 3277 * are constructor functions, whose instances are responsible for "providing" a factory for a 3278 * service. 3279 * 3280 * Service provider names start with the name of the service they provide followed by `Provider`. 3281 * For example, the {@link ng.$log $log} service has a provider called 3282 * {@link ng.$logProvider $logProvider}. 3283 * 3284 * Service provider objects can have additional methods which allow configuration of the provider 3285 * and its service. Importantly, you can configure what kind of service is created by the `$get` 3286 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 3287 * method {@link ng.$logProvider#debugEnabled debugEnabled} 3288 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 3289 * console or not. 3290 * 3291 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 3292 'Provider'` key. 3293 * @param {(Object|function())} provider If the provider is: 3294 * 3295 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 3296 * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created. 3297 * - `Constructor`: a new instance of the provider will be created using 3298 * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`. 3299 * 3300 * @returns {Object} registered provider instance 3301 3302 * @example 3303 * 3304 * The following example shows how to create a simple event tracking service and register it using 3305 * {@link AUTO.$provide#methods_provider $provide.provider()}. 3306 * 3307 * <pre> 3308 * // Define the eventTracker provider 3309 * function EventTrackerProvider() { 3310 * var trackingUrl = '/track'; 3311 * 3312 * // A provider method for configuring where the tracked events should been saved 3313 * this.setTrackingUrl = function(url) { 3314 * trackingUrl = url; 3315 * }; 3316 * 3317 * // The service factory function 3318 * this.$get = ['$http', function($http) { 3319 * var trackedEvents = {}; 3320 * return { 3321 * // Call this to track an event 3322 * event: function(event) { 3323 * var count = trackedEvents[event] || 0; 3324 * count += 1; 3325 * trackedEvents[event] = count; 3326 * return count; 3327 * }, 3328 * // Call this to save the tracked events to the trackingUrl 3329 * save: function() { 3330 * $http.post(trackingUrl, trackedEvents); 3331 * } 3332 * }; 3333 * }]; 3334 * } 3335 * 3336 * describe('eventTracker', function() { 3337 * var postSpy; 3338 * 3339 * beforeEach(module(function($provide) { 3340 * // Register the eventTracker provider 3341 * $provide.provider('eventTracker', EventTrackerProvider); 3342 * })); 3343 * 3344 * beforeEach(module(function(eventTrackerProvider) { 3345 * // Configure eventTracker provider 3346 * eventTrackerProvider.setTrackingUrl('/custom-track'); 3347 * })); 3348 * 3349 * it('tracks events', inject(function(eventTracker) { 3350 * expect(eventTracker.event('login')).toEqual(1); 3351 * expect(eventTracker.event('login')).toEqual(2); 3352 * })); 3353 * 3354 * it('saves to the tracking url', inject(function(eventTracker, $http) { 3355 * postSpy = spyOn($http, 'post'); 3356 * eventTracker.event('login'); 3357 * eventTracker.save(); 3358 * expect(postSpy).toHaveBeenCalled(); 3359 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 3360 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 3361 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 3362 * })); 3363 * }); 3364 * </pre> 3365 */ 3366 3367/** 3368 * @ngdoc method 3369 * @name AUTO.$provide#factory 3370 * @methodOf AUTO.$provide 3371 * @description 3372 * 3373 * Register a **service factory**, which will be called to return the service instance. 3374 * This is short for registering a service where its provider consists of only a `$get` property, 3375 * which is the given service factory function. 3376 * You should use {@link AUTO.$provide#factory $provide.factory(getFn)} if you do not need to 3377 * configure your service in a provider. 3378 * 3379 * @param {string} name The name of the instance. 3380 * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand 3381 * for `$provide.provider(name, {$get: $getFn})`. 3382 * @returns {Object} registered provider instance 3383 * 3384 * @example 3385 * Here is an example of registering a service 3386 * <pre> 3387 * $provide.factory('ping', ['$http', function($http) { 3388 * return function ping() { 3389 * return $http.send('/ping'); 3390 * }; 3391 * }]); 3392 * </pre> 3393 * You would then inject and use this service like this: 3394 * <pre> 3395 * someModule.controller('Ctrl', ['ping', function(ping) { 3396 * ping(); 3397 * }]); 3398 * </pre> 3399 */ 3400 3401 3402/** 3403 * @ngdoc method 3404 * @name AUTO.$provide#service 3405 * @methodOf AUTO.$provide 3406 * @description 3407 * 3408 * Register a **service constructor**, which will be invoked with `new` to create the service 3409 * instance. 3410 * This is short for registering a service where its provider's `$get` property is the service 3411 * constructor function that will be used to instantiate the service instance. 3412 * 3413 * You should use {@link AUTO.$provide#methods_service $provide.service(class)} if you define your service 3414 * as a type/class. 3415 * 3416 * @param {string} name The name of the instance. 3417 * @param {Function} constructor A class (constructor function) that will be instantiated. 3418 * @returns {Object} registered provider instance 3419 * 3420 * @example 3421 * Here is an example of registering a service using 3422 * {@link AUTO.$provide#methods_service $provide.service(class)}. 3423 * <pre> 3424 * var Ping = function($http) { 3425 * this.$http = $http; 3426 * }; 3427 * 3428 * Ping.$inject = ['$http']; 3429 * 3430 * Ping.prototype.send = function() { 3431 * return this.$http.get('/ping'); 3432 * }; 3433 * $provide.service('ping', Ping); 3434 * </pre> 3435 * You would then inject and use this service like this: 3436 * <pre> 3437 * someModule.controller('Ctrl', ['ping', function(ping) { 3438 * ping.send(); 3439 * }]); 3440 * </pre> 3441 */ 3442 3443 3444/** 3445 * @ngdoc method 3446 * @name AUTO.$provide#value 3447 * @methodOf AUTO.$provide 3448 * @description 3449 * 3450 * Register a **value service** with the {@link AUTO.$injector $injector}, such as a string, a 3451 * number, an array, an object or a function. This is short for registering a service where its 3452 * provider's `$get` property is a factory function that takes no arguments and returns the **value 3453 * service**. 3454 * 3455 * Value services are similar to constant services, except that they cannot be injected into a 3456 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 3457 * an Angular 3458 * {@link AUTO.$provide#decorator decorator}. 3459 * 3460 * @param {string} name The name of the instance. 3461 * @param {*} value The value. 3462 * @returns {Object} registered provider instance 3463 * 3464 * @example 3465 * Here are some examples of creating value services. 3466 * <pre> 3467 * $provide.value('ADMIN_USER', 'admin'); 3468 *
3469 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 3470 * 3471 * $provide.value('halfOf', function(value) { 3472 * return value / 2; 3473 * }); 3474 * </pre> 3475 */ 3476 3477 3478/** 3479 * @ngdoc method 3480 * @name AUTO.$provide#constant 3481 * @methodOf AUTO.$provide 3482 * @description 3483 * 3484 * Register a **constant service**, such as a string, a number, an array, an object or a function, 3485 * with the {@link AUTO.$injector $injector}. Unlike {@link AUTO.$provide#value value} it can be 3486 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 3487 * be overridden by an Angular {@link AUTO.$provide#decorator decorator}. 3488 * 3489 * @param {string} name The name of the constant. 3490 * @param {*} value The constant value. 3491 * @returns {Object} registered instance 3492 * 3493 * @example 3494 * Here a some examples of creating constants: 3495 * <pre> 3496 * $provide.constant('SHARD_HEIGHT', 306); 3497 * 3498 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 3499 * 3500 * $provide.constant('double', function(value) { 3501 * return value * 2; 3502 * }); 3503 * </pre> 3504 */ 3505 3506 3507/** 3508 * @ngdoc method 3509 * @name AUTO.$provide#decorator 3510 * @methodOf AUTO.$provide 3511 * @description 3512 * 3513 * Register a **service decorator** with the {@link AUTO.$injector $injector}. A service decorator 3514 * intercepts the creation of a service, allowing it to override or modify the behaviour of the 3515 * service. The object returned by the decorator may be the original service, or a new service 3516 * object which replaces or wraps and delegates to the original service. 3517 * 3518 * @param {string} name The name of the service to decorate. 3519 * @param {function()} decorator This function will be invoked when the service needs to be 3520 * instantiated and should return the decorated service instance. The function is called using 3521 * the {@link AUTO.$injector#invoke injector.invoke} method and is therefore fully injectable. 3522 * Local injection arguments: 3523 * 3524 * * `$delegate` - The original service instance, which can be monkey patched, configured, 3525 * decorated or delegated to. 3526 * 3527 * @example 3528 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 3529 * calls to {@link ng.$log#error $log.warn()}. 3530 * <pre> 3531 * $provide.decorator('$log', ['$delegate', function($delegate) { 3532 * $delegate.warn = $delegate.error; 3533 * return $delegate; 3534 * }]); 3535 * </pre> 3536 */ 3537 3538 3539function createInjector(modulesToLoad) { 3540 var INSTANTIATING = {}, 3541 providerSuffix = 'Provider', 3542 path = [], 3543 loadedModules = new HashMap(), 3544 providerCache = { 3545 $provide: { 3546 provider: supportObject(provider), 3547 factory: supportObject(factory), 3548 service: supportObject(service), 3549 value: supportObject(value), 3550 constant: supportObject(constant), 3551 decorator: decorator 3552 } 3553 }, 3554 providerInjector = (providerCache.$injector = 3555 createInternalInjector(providerCache, function() { 3556 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 3557 })), 3558 instanceCache = {}, 3559 instanceInjector = (instanceCache.$injector = 3560 createInternalInjector(instanceCache, function(servicename) { 3561 var provider = providerInjector.get(servicename + providerSuffix); 3562 return instanceInjector.invoke(provider.$get, provider); 3563 })); 3564 3565 3566 forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); 3567 3568 return instanceInjector; 3569 3570 //////////////////////////////////// 3571 // $provider 3572 //////////////////////////////////// 3573 3574 function supportObject(delegate) { 3575 return function(key, value) { 3576 if (isObject(key)) { 3577 forEach(key, reverseParams(delegate)); 3578 } else { 3579 return delegate(key, value); 3580 } 3581 }; 3582 } 3583 3584 function provider(name, provider_) { 3585 assertNotHasOwnProperty(name, 'service'); 3586 if (isFunction(provider_) || isArray(provider_)) { 3587 provider_ = providerInjector.instantiate(provider_); 3588 } 3589 if (!provider_.$get) { 3590 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 3591 } 3592 return providerCache[name + providerSuffix] = provider_; 3593 } 3594 3595 function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } 3596 3597 function service(name, constructor) { 3598 return factory(name, ['$injector', function($injector) { 3599 return $injector.instantiate(constructor); 3600 }]); 3601 } 3602 3603 function value(name, val) { return factory(name, valueFn(val)); } 3604 3605 function constant(name, value) { 3606 assertNotHasOwnProperty(name, 'constant'); 3607 providerCache[name] = value; 3608 instanceCache[name] = value; 3609 } 3610 3611 function decorator(serviceName, decorFn) { 3612 var origProvider = providerInjector.get(serviceName + providerSuffix), 3613 orig$get = origProvider.$get; 3614 3615 origProvider.$get = function() { 3616 var origInstance = instanceInjector.invoke(orig$get, origProvider); 3617 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 3618 }; 3619 } 3620 3621 //////////////////////////////////// 3622 // Module Loading 3623 //////////////////////////////////// 3624 function loadModules(modulesToLoad){ 3625 var runBlocks = [], moduleFn, invokeQueue, i, ii;
3626 forEach(modulesToLoad, function(module) { 3627 if (loadedModules.get(module)) return; 3628 loadedModules.put(module, true); 3629 3630 try { 3631 if (isString(module)) { 3632 moduleFn = angularModule(module); 3633 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 3634 3635 for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { 3636 var invokeArgs = invokeQueue[i], 3637 provider = providerInjector.get(invokeArgs[0]); 3638 3639 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 3640 } 3641 } else if (isFunction(module)) { 3642 runBlocks.push(providerInjector.invoke(module)); 3643 } else if (isArray(module)) { 3644 runBlocks.push(providerInjector.invoke(module)); 3645 } else { 3646 assertArgFn(module, 'module'); 3647 } 3648 } catch (e) { 3649 if (isArray(module)) { 3650 module = module[module.length - 1]; 3651 } 3652 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 3653 // Safari & FF's stack traces don't contain error.message content 3654 // unlike those of Chrome and IE 3655 // So if stack doesn't contain message, we create a new string that contains both. 3656 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 3657 /* jshint -W022 */ 3658 e = e.message + '\n' + e.stack; 3659 } 3660 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 3661 module, e.stack || e.message || e); 3662 } 3663 }); 3664 return runBlocks; 3665 } 3666 3667 //////////////////////////////////// 3668 // internal Injector 3669 //////////////////////////////////// 3670 3671 function createInternalInjector(cache, factory) { 3672 3673 function getService(serviceName) { 3674 if (cache.hasOwnProperty(serviceName)) { 3675 if (cache[serviceName] === INSTANTIATING) { 3676 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- ')); 3677 } 3678 return cache[serviceName]; 3679 } else { 3680 try { 3681 path.unshift(serviceName); 3682 cache[serviceName] = INSTANTIATING; 3683 return cache[serviceName] = factory(serviceName); 3684 } catch (err) { 3685 if (cache[serviceName] === INSTANTIATING) { 3686 delete cache[serviceName]; 3687 } 3688 throw err; 3689 } finally { 3690 path.shift(); 3691 } 3692 } 3693 } 3694 3695 function invoke(fn, self, locals){ 3696 var args = [], 3697 $inject = annotate(fn), 3698 length, i, 3699 key; 3700 3701 for(i = 0, length = $inject.length; i < length; i++) { 3702 key = $inject[i]; 3703 if (typeof key !== 'string') { 3704 throw $injectorMinErr('itkn', 3705 'Incorrect injection token! Expected service name as string, got {0}', key); 3706 } 3707 args.push( 3708 locals && locals.hasOwnProperty(key) 3709 ? locals[key] 3710 : getService(key) 3711 ); 3712 } 3713 if (!fn.$inject) { 3714 // this means that we must be an array. 3715 fn = fn[length]; 3716 } 3717 3718 // http://jsperf.com/angularjs-invoke-apply-vs-switch 3719 // #5388 3720 return fn.apply(self, args); 3721 } 3722 3723 function instantiate(Type, locals) { 3724 var Constructor = function() {}, 3725 instance, returnedValue; 3726 3727 // Check if Type is annotated and use just the given function at n-1 as parameter 3728 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 3729 Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; 3730 instance = new Constructor(); 3731 returnedValue = invoke(Type, instance, locals); 3732 3733 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 3734 } 3735 3736 return { 3737 invoke: invoke, 3738 instantiate: instantiate, 3739 get: getService, 3740 annotate: annotate, 3741 has: function(name) { 3742 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 3743 } 3744 }; 3745 } 3746} 3747 3748/** 3749 * @ngdoc function 3750 * @name ng.$anchorScroll 3751 * @requires $window 3752 * @requires $location 3753 * @requires $rootScope 3754 * 3755 * @description 3756 * When called, it checks current value of `$location.hash()` and scroll to related element, 3757 * according to rules specified in 3758 * {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}. 3759 * 3760 * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. 3761 * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. 3762 * 3763 * @example 3764 <example> 3765 <file name="index.html"> 3766 <div id="scrollArea" ng-controller="ScrollCtrl"> 3767 <a ng-click="gotoBottom()">Go to bottom</a> 3768 <a id="bottom"></a> You're at the bottom! 3769 </div> 3770 </file> 3771 <file name="script.js"> 3772 function ScrollCtrl($scope, $location, $anchorScroll) { 3773 $scope.gotoBottom = function (){ 3774 // set the location.hash to the id of
3775 // the element you wish to scroll to. 3776 $location.hash('bottom'); 3777 3778 // call $anchorScroll() 3779 $anchorScroll(); 3780 } 3781 } 3782 </file> 3783 <file name="style.css"> 3784 #scrollArea { 3785 height: 350px; 3786 overflow: auto; 3787 } 3788 3789 #bottom { 3790 display: block; 3791 margin-top: 2000px; 3792 } 3793 </file> 3794 </example> 3795 */ 3796function $AnchorScrollProvider() { 3797 3798 var autoScrollingEnabled = true; 3799 3800 this.disableAutoScrolling = function() { 3801 autoScrollingEnabled = false; 3802 }; 3803 3804 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 3805 var document = $window.document; 3806 3807 // helper function to get first anchor from a NodeList 3808 // can't use filter.filter, as it accepts only instances of Array 3809 // and IE can't convert NodeList to an array using [].slice 3810 // TODO(vojta): use filter if we change it to accept lists as well 3811 function getFirstAnchor(list) { 3812 var result = null; 3813 forEach(list, function(element) { 3814 if (!result && lowercase(element.nodeName) === 'a') result = element; 3815 }); 3816 return result; 3817 } 3818 3819 function scroll() { 3820 var hash = $location.hash(), elm; 3821 3822 // empty hash, scroll to the top of the page 3823 if (!hash) $window.scrollTo(0, 0); 3824 3825 // element with given id 3826 else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); 3827 3828 // first anchor with given name :-D 3829 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); 3830 3831 // no element and hash == 'top', scroll to the top of the page 3832 else if (hash === 'top') $window.scrollTo(0, 0); 3833 } 3834 3835 // does not scroll when user clicks on anchor link that is currently on 3836 // (no url change, no $location.hash() change), browser native does scroll 3837 if (autoScrollingEnabled) { 3838 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 3839 function autoScrollWatchAction() { 3840 $rootScope.$evalAsync(scroll); 3841 }); 3842 } 3843 3844 return scroll; 3845 }]; 3846} 3847 3848var $animateMinErr = minErr('$animate'); 3849 3850/** 3851 * @ngdoc object 3852 * @name ng.$animateProvider 3853 * 3854 * @description 3855 * Default implementation of $animate that doesn't perform any animations, instead just 3856 * synchronously performs DOM 3857 * updates and calls done() callbacks. 3858 * 3859 * In order to enable animations the ngAnimate module has to be loaded. 3860 * 3861 * To see the functional implementation check out src/ngAnimate/animate.js 3862 */ 3863var $AnimateProvider = ['$provide', function($provide) { 3864 3865 3866 this.$$selectors = {}; 3867 3868 3869 /** 3870 * @ngdoc function 3871 * @name ng.$animateProvider#register 3872 * @methodOf ng.$animateProvider 3873 * 3874 * @description 3875 * Registers a new injectable animation factory function. The factory function produces the 3876 * animation object which contains callback functions for each event that is expected to be 3877 * animated. 3878 * 3879 * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` 3880 * must be called once the element animation is complete. If a function is returned then the 3881 * animation service will use this function to cancel the animation whenever a cancel event is 3882 * triggered. 3883 * 3884 * 3885 *<pre> 3886 * return { 3887 * eventFn : function(element, done) { 3888 * //code to run the animation 3889 * //once complete, then run done() 3890 * return function cancellationFunction() { 3891 * //code to cancel the animation 3892 * } 3893 * } 3894 * } 3895 *</pre> 3896 * 3897 * @param {string} name The name of the animation. 3898 * @param {function} factory The factory function that will be executed to return the animation 3899 * object. 3900 */ 3901 this.register = function(name, factory) { 3902 var key = name + '-animation'; 3903 if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', 3904 "Expecting class selector starting with '.' got '{0}'.", name); 3905 this.$$selectors[name.substr(1)] = key; 3906 $provide.factory(key, factory); 3907 }; 3908 3909 /** 3910 * @ngdoc function 3911 * @name ng.$animateProvider#classNameFilter 3912 * @methodOf ng.$animateProvider 3913 * 3914 * @description 3915 * Sets and/or returns the CSS class regular expression that is checked when performing 3916 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 3917 * therefore enable $animate to attempt to perform an animation on any element. 3918 * When setting the classNameFilter value, animations will only be performed on elements 3919 * that successfully match the filter expression. This in turn can boost performance 3920 * for low-powered devices as well as applications containing a lot of structural operations. 3921 * @param {RegExp=} expression The className expression which will be checked against all animations 3922 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 3923 */ 3924 this.classNameFilter = function(expression) { 3925 if(arguments.length === 1) { 3926 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 3927 } 3928 return this.$$classNameFilter; 3929 }; 3930 3931 this.$get = ['$timeout', function($timeout) { 3932 3933 /** 3934 * 3935 * @ngdoc object 3936 * @name ng.$animate
3937 * @description The $animate service provides rudimentary DOM manipulation functions to 3938 * insert, remove and move elements within the DOM, as well as adding and removing classes. 3939 * This service is the core service used by the ngAnimate $animator service which provides 3940 * high-level animation hooks for CSS and JavaScript. 3941 * 3942 * $animate is available in the AngularJS core, however, the ngAnimate module must be included 3943 * to enable full out animation support. Otherwise, $animate will only perform simple DOM 3944 * manipulation operations. 3945 * 3946 * To learn more about enabling animation support, click here to visit the {@link ngAnimate 3947 * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service 3948 * page}. 3949 */ 3950 return { 3951 3952 /** 3953 * 3954 * @ngdoc function 3955 * @name ng.$animate#enter 3956 * @methodOf ng.$animate 3957 * @function 3958 * @description Inserts the element into the DOM either after the `after` element or within 3959 * the `parent` element. Once complete, the done() callback will be fired (if provided). 3960 * @param {jQuery/jqLite element} element the element which will be inserted into the DOM 3961 * @param {jQuery/jqLite element} parent the parent element which will append the element as 3962 * a child (if the after element is not present) 3963 * @param {jQuery/jqLite element} after the sibling element which will append the element 3964 * after itself 3965 * @param {function=} done callback function that will be called after the element has been 3966 * inserted into the DOM 3967 */ 3968 enter : function(element, parent, after, done) { 3969 if (after) { 3970 after.after(element); 3971 } else { 3972 if (!parent || !parent[0]) { 3973 parent = after.parent(); 3974 } 3975 parent.append(element); 3976 } 3977 done && $timeout(done, 0, false); 3978 }, 3979 3980 /** 3981 * 3982 * @ngdoc function 3983 * @name ng.$animate#leave 3984 * @methodOf ng.$animate 3985 * @function 3986 * @description Removes the element from the DOM. Once complete, the done() callback will be 3987 * fired (if provided). 3988 * @param {jQuery/jqLite element} element the element which will be removed from the DOM 3989 * @param {function=} done callback function that will be called after the element has been 3990 * removed from the DOM 3991 */ 3992 leave : function(element, done) { 3993 element.remove(); 3994 done && $timeout(done, 0, false); 3995 }, 3996 3997 /** 3998 * 3999 * @ngdoc function 4000 * @name ng.$animate#move 4001 * @methodOf ng.$animate 4002 * @function 4003 * @description Moves the position of the provided element within the DOM to be placed 4004 * either after the `after` element or inside of the `parent` element. Once complete, the 4005 * done() callback will be fired (if provided). 4006 * 4007 * @param {jQuery/jqLite element} element the element which will be moved around within the 4008 * DOM 4009 * @param {jQuery/jqLite element} parent the parent element where the element will be 4010 * inserted into (if the after element is not present) 4011 * @param {jQuery/jqLite element} after the sibling element where the element will be 4012 * positioned next to 4013 * @param {function=} done the callback function (if provided) that will be fired after the 4014 * element has been moved to its new position 4015 */ 4016 move : function(element, parent, after, done) { 4017 // Do not remove element before insert. Removing will cause data associated with the 4018 // element to be dropped. Insert will implicitly do the remove. 4019 this.enter(element, parent, after, done); 4020 }, 4021 4022 /** 4023 * 4024 * @ngdoc function 4025 * @name ng.$animate#addClass 4026 * @methodOf ng.$animate 4027 * @function 4028 * @description Adds the provided className CSS class value to the provided element. Once 4029 * complete, the done() callback will be fired (if provided). 4030 * @param {jQuery/jqLite element} element the element which will have the className value 4031 * added to it 4032 * @param {string} className the CSS class which will be added to the element 4033 * @param {function=} done the callback function (if provided) that will be fired after the 4034 * className value has been added to the element 4035 */ 4036 addClass : function(element, className, done) { 4037 className = isString(className) ? 4038 className : 4039 isArray(className) ? className.join(' ') : '';
4040 forEach(element, function (element) { 4041 jqLiteAddClass(element, className); 4042 }); 4043 done && $timeout(done, 0, false); 4044 }, 4045 4046 /** 4047 * 4048 * @ngdoc function 4049 * @name ng.$animate#removeClass 4050 * @methodOf ng.$animate 4051 * @function 4052 * @description Removes the provided className CSS class value from the provided element. 4053 * Once complete, the done() callback will be fired (if provided). 4054 * @param {jQuery/jqLite element} element the element which will have the className value 4055 * removed from it 4056 * @param {string} className the CSS class which will be removed from the element 4057 * @param {function=} done the callback function (if provided) that will be fired after the 4058 * className value has been removed from the element 4059 */ 4060 removeClass : function(element, className, done) { 4061 className = isString(className) ? 4062 className : 4063 isArray(className) ? className.join(' ') : ''; 4064 forEach(element, function (element) { 4065 jqLiteRemoveClass(element, className); 4066 }); 4067 done && $timeout(done, 0, false); 4068 }, 4069 4070 /** 4071 * 4072 * @ngdoc function 4073 * @name ng.$animate#setClass 4074 * @methodOf ng.$animate 4075 * @function 4076 * @description Adds and/or removes the given CSS classes to and from the element. 4077 * Once complete, the done() callback will be fired (if provided). 4078 * @param {jQuery/jqLite element} element the element which will it's CSS classes changed 4079 * removed from it 4080 * @param {string} add the CSS classes which will be added to the element 4081 * @param {string} remove the CSS class which will be removed from the element 4082 * @param {function=} done the callback function (if provided) that will be fired after the 4083 * CSS classes have been set on the element 4084 */ 4085 setClass : function(element, add, remove, done) { 4086 forEach(element, function (element) { 4087 jqLiteAddClass(element, add); 4088 jqLiteRemoveClass(element, remove); 4089 }); 4090 done && $timeout(done, 0, false); 4091 }, 4092 4093 enabled : noop 4094 }; 4095 }]; 4096}]; 4097 4098/** 4099 * ! This is a private undocumented service ! 4100 * 4101 * @name ng.$browser 4102 * @requires $log 4103 * @description 4104 * This object has two goals: 4105 * 4106 * - hide all the global state in the browser caused by the window object 4107 * - abstract away all the browser specific features and inconsistencies 4108 * 4109 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 4110 * service, which can be used for convenient testing of the application without the interaction with 4111 * the real browser apis. 4112 */ 4113/** 4114 * @param {object} window The global window object. 4115 * @param {object} document jQuery wrapped document. 4116 * @param {function()} XHR XMLHttpRequest constructor. 4117 * @param {object} $log console.log or an object with the same interface. 4118 * @param {object} $sniffer $sniffer service 4119 */ 4120function Browser(window, document, $log, $sniffer) { 4121 var self = this, 4122 rawDocument = document[0], 4123 location = window.location, 4124 history = window.history, 4125 setTimeout = window.setTimeout, 4126 clearTimeout = window.clearTimeout, 4127 pendingDeferIds = {}; 4128 4129 self.isMock = false; 4130 4131 var outstandingRequestCount = 0; 4132 var outstandingRequestCallbacks = []; 4133 4134 // TODO(vojta): remove this temporary api 4135 self.$$completeOutstandingRequest = completeOutstandingRequest; 4136 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 4137 4138 /** 4139 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 4140 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 4141 */ 4142 function completeOutstandingRequest(fn) { 4143 try { 4144 fn.apply(null, sliceArgs(arguments, 1)); 4145 } finally { 4146 outstandingRequestCount--; 4147 if (outstandingRequestCount === 0) { 4148 while(outstandingRequestCallbacks.length) { 4149 try { 4150 outstandingRequestCallbacks.pop()(); 4151 } catch (e) { 4152 $log.error(e); 4153 } 4154 } 4155 } 4156 } 4157 } 4158 4159 /** 4160 * @private 4161 * Note: this method is used only by scenario runner 4162 * TODO(vojta): prefix this method with $$ ? 4163 * @param {function()} callback Function that will be called when no outstanding request 4164 */ 4165 self.notifyWhenNoOutstandingRequests = function(callback) { 4166 // force browser to execute all pollFns - this is needed so that cookies and other pollers fire 4167 // at some deterministic time in respect to the test runner's actions. Leaving things up to the 4168 // regular poller would result in flaky tests.
4169 forEach(pollFns, function(pollFn){ pollFn(); }); 4170 4171 if (outstandingRequestCount === 0) { 4172 callback(); 4173 } else { 4174 outstandingRequestCallbacks.push(callback); 4175 } 4176 }; 4177 4178 ////////////////////////////////////////////////////////////// 4179 // Poll Watcher API 4180 ////////////////////////////////////////////////////////////// 4181 var pollFns = [], 4182 pollTimeout; 4183 4184 /** 4185 * @name ng.$browser#addPollFn 4186 * @methodOf ng.$browser 4187 * 4188 * @param {function()} fn Poll function to add 4189 * 4190 * @description 4191 * Adds a function to the list of functions that poller periodically executes, 4192 * and starts polling if not started yet. 4193 * 4194 * @returns {function()} the added function 4195 */ 4196 self.addPollFn = function(fn) { 4197 if (isUndefined(pollTimeout)) startPoller(100, setTimeout); 4198 pollFns.push(fn); 4199 return fn; 4200 }; 4201 4202 /** 4203 * @param {number} interval How often should browser call poll functions (ms) 4204 * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. 4205 * 4206 * @description 4207 * Configures the poller to run in the specified intervals, using the specified 4208 * setTimeout fn and kicks it off. 4209 */ 4210 function startPoller(interval, setTimeout) { 4211 (function check() { 4212 forEach(pollFns, function(pollFn){ pollFn(); }); 4213 pollTimeout = setTimeout(check, interval); 4214 })(); 4215 } 4216 4217 ////////////////////////////////////////////////////////////// 4218 // URL API 4219 ////////////////////////////////////////////////////////////// 4220 4221 var lastBrowserUrl = location.href, 4222 baseElement = document.find('base'), 4223 newLocation = null; 4224 4225 /** 4226 * @name ng.$browser#url 4227 * @methodOf ng.$browser 4228 * 4229 * @description 4230 * GETTER: 4231 * Without any argument, this method just returns current value of location.href. 4232 * 4233 * SETTER: 4234 * With at least one argument, this method sets url to new value. 4235 * If html5 history api supported, pushState/replaceState is used, otherwise 4236 * location.href/location.replace is used. 4237 * Returns its own instance to allow chaining 4238 * 4239 * NOTE: this api is intended for use only by the $location service. Please use the 4240 * {@link ng.$location $location service} to change url. 4241 * 4242 * @param {string} url New url (when used as setter) 4243 * @param {boolean=} replace Should new url replace current history record ? 4244 */ 4245 self.url = function(url, replace) { 4246 // Android Browser BFCache causes location, history reference to become stale. 4247 if (location !== window.location) location = window.location; 4248 if (history !== window.history) history = window.history; 4249 4250 // setter 4251 if (url) { 4252 if (lastBrowserUrl == url) return; 4253 lastBrowserUrl = url; 4254 if ($sniffer.history) { 4255 if (replace) history.replaceState(null, '', url); 4256 else { 4257 history.pushState(null, '', url); 4258 // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 4259 baseElement.attr('href', baseElement.attr('href')); 4260 } 4261 } else { 4262 newLocation = url; 4263 if (replace) { 4264 location.replace(url); 4265 } else { 4266 location.href = url; 4267 } 4268 } 4269 return self; 4270 // getter 4271 } else { 4272 // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href 4273 // methods not updating location.href synchronously. 4274 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 4275 return newLocation || location.href.replace(/%27/g,"'"); 4276 } 4277 }; 4278 4279 var urlChangeListeners = [], 4280 urlChangeInit = false; 4281 4282 function fireUrlChange() { 4283 newLocation = null; 4284 if (lastBrowserUrl == self.url()) return; 4285 4286 lastBrowserUrl = self.url(); 4287 forEach(urlChangeListeners, function(listener) { 4288 listener(self.url()); 4289 }); 4290 } 4291 4292 /** 4293 * @name ng.$browser#onUrlChange 4294 * @methodOf ng.$browser 4295 * @TODO(vojta): refactor to use node's syntax for events 4296 * 4297 * @description 4298 * Register callback function that will be called, when url changes. 4299 * 4300 * It's only called when the url is changed from outside of angular: 4301 * - user types different url into address bar 4302 * - user clicks on history (forward/back) button 4303 * - user clicks on a link 4304 * 4305 * It's not called when url is changed by $browser.url() method 4306 * 4307 * The listener gets called with new url as parameter. 4308 * 4309 * NOTE: this api is intended for use only by the $location service. Please use the 4310 * {@link ng.$location $location service} to monitor url changes in angular apps. 4311 * 4312 * @param {function(string)} listener Listener function to be called when url changes. 4313 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 4314 */ 4315 self.onUrlChange = function(callback) { 4316 if (!urlChangeInit) { 4317 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 4318 // don't fire popstate when user change the address bar and don't fire hashchange when url 4319 // changed by push/replaceState 4320 4321 // html5 history api - popstate event 4322 if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); 4323 // hashchange event 4324 if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); 4325 // polling 4326 else self.addPollFn(fireUrlChange); 4327 4328 urlChangeInit = true; 4329 } 4330 4331 urlChangeListeners.push(callback); 4332 return callback; 4333 }; 4334 4335 ////////////////////////////////////////////////////////////// 4336 // Misc API 4337 ////////////////////////////////////////////////////////////// 4338 4339 /** 4340 * @name ng.$browser#baseHref 4341 * @methodOf ng.$browser 4342 * 4343 * @description 4344 * Returns current <base href> 4345 * (always relative - without domain) 4346 * 4347 * @returns {string=} current <base href> 4348 */ 4349 self.baseHref = function() { 4350 var href = baseElement.attr('href'); 4351 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 4352 }; 4353 4354 ////////////////////////////////////////////////////////////// 4355 // Cookies API 4356 ////////////////////////////////////////////////////////////// 4357 var lastCookies = {}; 4358 var lastCookieString = ''; 4359 var cookiePath = self.baseHref(); 4360 4361 /** 4362 * @name ng.$browser#cookies 4363 * @methodOf ng.$browser 4364 * 4365 * @param {string=} name Cookie name 4366 * @param {string=} value Cookie value 4367 * 4368 * @description 4369 * The cookies method provides a 'private' low level access to browser cookies. 4370 * It is not meant to be used directly, use the $cookie service instead. 4371 * 4372 * The return values vary depending on the arguments that the method was called with as follows: 4373 * 4374 * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify 4375 * it 4376 * - cookies(name, value) -> set name to value, if value is undefined delete the cookie 4377 * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that 4378 * way) 4379 * 4380 * @returns {Object} Hash of all cookies (if called without any parameter) 4381 */ 4382 self.cookies = function(name, value) { 4383 /* global escape: false, unescape: false */ 4384 var cookieLength, cookieArray, cookie, i, index; 4385 4386 if (name) { 4387 if (value === undefined) { 4388 rawDocument.cookie = escape(name) + "=;
4388path=" + cookiePath + 4389 ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; 4390 } else { 4391 if (isString(value)) { 4392 cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + 4393 ';path=' + cookiePath).length + 1; 4394 4395 // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: 4396 // - 300 cookies 4397 // - 20 cookies per unique domain 4398 // - 4096 bytes per cookie 4399 if (cookieLength > 4096) { 4400 $log.warn("Cookie '"+ name + 4401 "' possibly not set or overflowed because it was too large ("+ 4402 cookieLength + " > 4096 bytes)!"); 4403 } 4404 } 4405 } 4406 } else { 4407 if (rawDocument.cookie !== lastCookieString) { 4408 lastCookieString = rawDocument.cookie; 4409 cookieArray = lastCookieString.split("; "); 4410 lastCookies = {}; 4411 4412 for (i = 0; i < cookieArray.length; i++) { 4413 cookie = cookieArray[i]; 4414 index = cookie.indexOf('='); 4415 if (index > 0) { //ignore nameless cookies 4416 name = unescape(cookie.substring(0, index)); 4417 // the first value that is seen for a cookie is the most 4418 // specific one. values for the same cookie name that 4419 // follow are for less specific paths. 4420 if (lastCookies[name] === undefined) { 4421 lastCookies[name] = unescape(cookie.substring(index + 1)); 4422 } 4423 } 4424 } 4425 } 4426 return lastCookies; 4427 } 4428 }; 4429 4430 4431 /** 4432 * @name ng.$browser#defer 4433 * @methodOf ng.$browser 4434 * @param {function()} fn A function, who's execution should be deferred. 4435 * @param {number=} [delay=0] of milliseconds to defer the function execution. 4436 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 4437 * 4438 * @description 4439 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 4440 * 4441 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 4442 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 4443 * via `$browser.defer.flush()`. 4444 * 4445 */ 4446 self.defer = function(fn, delay) { 4447 var timeoutId; 4448 outstandingRequestCount++; 4449 timeoutId = setTimeout(function() { 4450 delete pendingDeferIds[timeoutId]; 4451 completeOutstandingRequest(fn); 4452 }, delay || 0); 4453 pendingDeferIds[timeoutId] = true; 4454 return timeoutId; 4455 }; 4456 4457 4458 /** 4459 * @name ng.$browser#defer.cancel 4460 * @methodOf ng.$browser.defer 4461 * 4462 * @description 4463 * Cancels a deferred task identified with `deferId`. 4464 * 4465 * @param {*} deferId Token returned by the `$browser.defer` function. 4466 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 4467 * canceled. 4468 */ 4469 self.defer.cancel = function(deferId) { 4470 if (pendingDeferIds[deferId]) { 4471 delete pendingDeferIds[deferId]; 4472 clearTimeout(deferId); 4473 completeOutstandingRequest(noop); 4474 return true; 4475 } 4476 return false; 4477 }; 4478 4479} 4480 4481function $BrowserProvider(){ 4482 this.$get = ['$window', '$log', '$sniffer', '$document', 4483 function( $window, $log, $sniffer, $document){ 4484 return new Browser($window, $document, $log, $sniffer); 4485 }]; 4486} 4487 4488/** 4489 * @ngdoc object 4490 * @name ng.$cacheFactory 4491 * 4492 * @description 4493 * Factory that constructs cache objects and gives access to them. 4494 * 4495 * <pre> 4496 * 4497 * var cache = $cacheFactory('cacheId'); 4498 * expect($cacheFactory.get('cacheId')).toBe(cache); 4499 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 4500 * 4501 * cache.put("key", "value"); 4502 * cache.put("another key", "another value"); 4503 * 4504 * // We've specified no options on creation 4505 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 4506 * 4507 * </pre> 4508 * 4509 * 4510 * @param {string} cacheId Name or id of the newly created cache. 4511 * @param {object=} options Options object that specifies the cache behavior. Properties: 4512 * 4513 * - `{number=}` `capacity` â turns the cache into LRU cache. 4514 * 4515 * @returns {object} Newly created cache object with the following set of methods: 4516 * 4517 * - `{object}` `info()` â Returns id, size, and options of cache. 4518 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 4519 * it. 4520 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 4521 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 4522 * - `{void}` `removeAll()` â Removes all cached values. 4523 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 4524 * 4525 */
4526function $CacheFactoryProvider() { 4527 4528 this.$get = function() { 4529 var caches = {}; 4530 4531 function cacheFactory(cacheId, options) { 4532 if (cacheId in caches) { 4533 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 4534 } 4535 4536 var size = 0, 4537 stats = extend({}, options, {id: cacheId}), 4538 data = {}, 4539 capacity = (options && options.capacity) || Number.MAX_VALUE, 4540 lruHash = {}, 4541 freshEnd = null, 4542 staleEnd = null; 4543 4544 return caches[cacheId] = { 4545 4546 put: function(key, value) { 4547 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 4548 4549 refresh(lruEntry); 4550 4551 if (isUndefined(value)) return; 4552 if (!(key in data)) size++; 4553 data[key] = value; 4554 4555 if (size > capacity) { 4556 this.remove(staleEnd.key); 4557 } 4558 4559 return value; 4560 }, 4561 4562 4563 get: function(key) { 4564 var lruEntry = lruHash[key]; 4565 4566 if (!lruEntry) return; 4567 4568 refresh(lruEntry); 4569 4570 return data[key]; 4571 }, 4572 4573 4574 remove: function(key) { 4575 var lruEntry = lruHash[key]; 4576 4577 if (!lruEntry) return; 4578 4579 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 4580 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 4581 link(lruEntry.n,lruEntry.p); 4582 4583 delete lruHash[key]; 4584 delete data[key]; 4585 size--; 4586 }, 4587 4588 4589 removeAll: function() { 4590 data = {}; 4591 size = 0; 4592 lruHash = {}; 4593 freshEnd = staleEnd = null; 4594 }, 4595 4596 4597 destroy: function() { 4598 data = null; 4599 stats = null; 4600 lruHash = null; 4601 delete caches[cacheId]; 4602 }, 4603 4604 4605 info: function() { 4606 return extend({}, stats, {size: size}); 4607 } 4608 }; 4609 4610 4611 /** 4612 * makes the `entry` the freshEnd of the LRU linked list 4613 */ 4614 function refresh(entry) { 4615 if (entry != freshEnd) { 4616 if (!staleEnd) { 4617 staleEnd = entry; 4618 } else if (staleEnd == entry) { 4619 staleEnd = entry.n;
vendor: 5,054 bytes, lines 4620-4789
4620 } 4621 4622 link(entry.n, entry.p); 4623 link(entry, freshEnd); 4624 freshEnd = entry; 4625 freshEnd.n = null; 4626 } 4627 } 4628 4629 4630 /** 4631 * bidirectionally links two entries of the LRU linked list 4632 */ 4633 function link(nextEntry, prevEntry) { 4634 if (nextEntry != prevEntry) { 4635 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 4636 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 4637 } 4638 } 4639 } 4640 4641 4642 /** 4643 * @ngdoc method 4644 * @name ng.$cacheFactory#info 4645 * @methodOf ng.$cacheFactory 4646 * 4647 * @description 4648 * Get information about all the of the caches that have been created 4649 * 4650 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 4651 */ 4652 cacheFactory.info = function() { 4653 var info = {}; 4654 forEach(caches, function(cache, cacheId) { 4655 info[cacheId] = cache.info(); 4656 }); 4657 return info; 4658 }; 4659 4660 4661 /** 4662 * @ngdoc method 4663 * @name ng.$cacheFactory#get 4664 * @methodOf ng.$cacheFactory 4665 * 4666 * @description 4667 * Get access to a cache object by the `cacheId` used when it was created. 4668 * 4669 * @param {string} cacheId Name or id of a cache to access. 4670 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 4671 */ 4672 cacheFactory.get = function(cacheId) { 4673 return caches[cacheId]; 4674 }; 4675 4676 4677 return cacheFactory; 4678 }; 4679} 4680 4681/** 4682 * @ngdoc object 4683 * @name ng.$templateCache 4684 * 4685 * @description 4686 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 4687 * can load templates directly into the cache in a `script` tag, or by consuming the 4688 * `$templateCache` service directly. 4689 * 4690 * Adding via the `script` tag: 4691 * <pre> 4692 * <html ng-app> 4693 * <head> 4694 * <script type="text/ng-template" id="templateId.html"> 4695 * This is the content of the template 4696 * </script> 4697 * </head> 4698 * ... 4699 * </html> 4700 * </pre> 4701 * 4702 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 4703 * the document, but it must be below the `ng-app` definition. 4704 * 4705 * Adding via the $templateCache service: 4706 * 4707 * <pre> 4708 * var myApp = angular.module('myApp', []); 4709 * myApp.run(function($templateCache) { 4710 * $templateCache.put('templateId.html', 'This is the content of the template'); 4711 * }); 4712 * </pre> 4713 * 4714 * To retrieve the template later, simply use it in your HTML: 4715 * <pre> 4716 * <div ng-include=" 'templateId.html' "></div> 4717 * </pre> 4718 * 4719 * or get it via Javascript: 4720 * <pre> 4721 * $templateCache.get('templateId.html') 4722 * </pre> 4723 * 4724 * See {@link ng.$cacheFactory $cacheFactory}. 4725 * 4726 */ 4727function $TemplateCacheProvider() { 4728 this.$get = ['$cacheFactory', function($cacheFactory) { 4729 return $cacheFactory('templates'); 4730 }]; 4731} 4732 4733/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 4734 * 4735 * DOM-related variables: 4736 * 4737 * - "node" - DOM Node 4738 * - "element" - DOM Element or Node 4739 * - "$node" or "$element" - jqLite-wrapped node or element 4740 * 4741 * 4742 * Compiler related stuff: 4743 * 4744 * - "linkFn" - linking fn of a single directive 4745 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 4746 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 4747 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 4748 */ 4749 4750 4751/** 4752 * @ngdoc function 4753 * @name ng.$compile 4754 * @function 4755 * 4756 * @description 4757 * Compiles an HTML string or DOM into a template and produces a template function, which 4758 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 4759 * 4760 * The compilation is a process of walking the DOM tree and matching DOM elements to 4761 * {@link ng.$compileProvider#methods_directive directives}. 4762 * 4763 * <div class="alert alert-warning"> 4764 * **Note:** This document is an in-depth reference of all directive options. 4765 * For a gentle introduction to directives with examples of common use cases, 4766 * see the {@link guide/directive directive guide}. 4767 * </div> 4768 * 4769 * ## Comprehensive Directive API 4770 * 4771 * There are many different options for a directive. 4772 * 4773 * The difference resides in the return value of the factory function. 4774 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 4775 * or just the `postLink` function (all other properties will have the default values). 4776 * 4777 * <div class="alert alert-success"> 4778 * **Best Practice:** It's recommended to use the "directive definition object" form. 4779 * </div> 4780 * 4781 * Here's an example directive declared with a Directive Definition Object: 4782 * 4783 * <pre> 4784 * var myModule = angular.module(...); 4785 * 4786 * myModule.directive('directiveName', function factory(injectables) { 4787 * var directiveDefinitionObject = { 4788 * priority: 0, 4789 * template: '<div></div>
4789', // or // function(tElement, tAttrs) { ... }, 4790 * // or 4791 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 4792 * replace: false, 4793 * transclude: false, 4794 * restrict: 'A', 4795 * scope: false, 4796 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 4797 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 4798 * compile: function compile(tElement, tAttrs, transclude) { 4799 * return { 4800 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 4801 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 4802 * } 4803 * // or 4804 * // return function postLink( ... ) { ... } 4805 * }, 4806 * // or 4807 * // link: { 4808 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 4809 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 4810 * // } 4811 * // or 4812 * // link: function postLink( ... ) { ... } 4813 * }; 4814 * return directiveDefinitionObject; 4815 * }); 4816 * </pre> 4817 * 4818 * <div class="alert alert-warning"> 4819 * **Note:** Any unspecified options will use the default value. You can see the default values below. 4820 * </div> 4821 * 4822 * Therefore the above can be simplified as: 4823 * 4824 * <pre> 4825 * var myModule = angular.module(...); 4826 * 4827 * myModule.directive('directiveName', function factory(injectables) { 4828 * var directiveDefinitionObject = { 4829 * link: function postLink(scope, iElement, iAttrs) { ... } 4830 * }; 4831 * return directiveDefinitionObject; 4832 * // or 4833 * // return function postLink(scope, iElement, iAttrs) { ... } 4834 * }); 4835 * </pre> 4836 * 4837 * 4838 * 4839 * ### Directive Definition Object 4840 * 4841 * The directive definition object provides instructions to the {@link api/ng.$compile 4842 * compiler}. The attributes are: 4843 * 4844 * #### `priority` 4845 * When there are multiple directives defined on a single DOM element, sometimes it 4846 * is necessary to specify the order in which the directives are applied. The `priority` is used 4847 * to sort the directives before their `compile` functions get called. Priority is defined as a 4848 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 4849 * are also run in priority order, but post-link functions are run in reverse order. The order 4850 * of directives with the same priority is undefined. The default priority is `0`. 4851 * 4852 * #### `terminal` 4853 * If set to true then the current `priority` will be the last set of directives 4854 * which will execute (any directives at the current priority will still execute 4855 * as the order of execution on same `priority` is undefined). 4856 * 4857 * #### `scope` 4858 * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the 4859 * same element request a new scope, only one new scope is created. The new scope rule does not 4860 * apply for the root of the template since the root of the template always gets a new scope. 4861 * 4862 * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from 4863 * normal scope in that it does not prototypically inherit from the parent scope. This is useful 4864 * when creating reusable components, which should not accidentally read or modify data in the 4865 * parent scope. 4866 * 4867 * The 'isolate' scope takes an object hash which defines a set of local scope properties 4868 * derived from the parent scope. These local properties are useful for aliasing values for 4869 * templates. Locals definition is a hash of local scope property to its source: 4870 * 4871 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is 4872 * always a string since DOM attributes are strings. If no `attr` name is specified then the 4873 * attribute name is assumed to be the same as the local name. 4874 * Given `<widget my-attr="hello {{name}}">` and widget definition 4875 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 4876 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 4877 * `localName` property on the widget scope. The `name` is read from the parent scope (not 4878 * component scope). 4879 * 4880 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 4881 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 4882 * name is specified then the attribute name is assumed to be the same as the local name. 4883 * Given `<widget my-attr="parentModel">` and widget definition of 4884 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 4885 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 4886 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 4887 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 4888 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. 4889 * 4890 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 4891 * If no `attr` name is specified then the attribute name is assumed to be the same as the 4892 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 4893 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 4894 * a function wrapper for the `count = count + value` expression. Often it's desirable to 4895 * pass data from the isolated scope via an expression and to the parent scope, this can be 4896 * done by passing a map of local variable names and values into the expression wrapper fn. 4897 * For example, if the expression is `increment(amount)` then we can specify the amount value 4898 * by calling the `localFn` as `localFn({amount: 22})`. 4899 * 4900 * 4901 * 4902 * #### `controller` 4903 * Controller constructor function. The controller is instantiated before the 4904 * pre-linking phase and it is shared with other directives (see 4905 * `require` attribute). This allows the directives to communicate with each other and augment 4906 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 4907 * 4908 * * `$scope` - Current scope associated with the element 4909 * * `$element` - Current element 4910 * * `$attrs` - Current attributes object for the element 4911 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. 4912 * The scope can be overridden by an optional first argument. 4913 * `function([scope], cloneLinkingFn)`. 4914 * 4915 * 4916 * #### `require` 4917 * Require another directive and inject its controller as the fourth argument to the linking function. The
4918 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 4919 * injected argument will be an array in corresponding order. If no such directive can be 4920 * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: 4921 * 4922 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 4923 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 4924 * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found. 4925 * * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the 4926 * `link` fn if not found. 4927 * 4928 * 4929 * #### `controllerAs` 4930 * Controller alias at the directive scope. An alias for the controller so it 4931 * can be referenced at the directive template. The directive needs to define a scope for this 4932 * configuration to be used. Useful in the case when directive is used as component. 4933 * 4934 * 4935 * #### `restrict` 4936 * String of subset of `EACM` which restricts the directive to a specific directive 4937 * declaration style. If omitted, the default (attributes only) is used. 4938 * 4939 * * `E` - Element name: `<my-directive></my-directive>` 4940 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 4941 * * `C` - Class: `<div class="my-directive: exp;"></div>` 4942 * * `M` - Comment: `<!-- directive: my-directive exp -->` 4943 * 4944 * 4945 * #### `template` 4946 * replace the current element with the contents of the HTML. The replacement process 4947 * migrates all of the attributes / classes from the old element to the new one. See the 4948 * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive 4949 * Directives Guide} for an example. 4950 * 4951 * You can specify `template` as a string representing the template or as a function which takes 4952 * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and 4953 * returns a string value representing the template. 4954 * 4955 * 4956 * #### `templateUrl` 4957 * Same as `template` but the template is loaded from the specified URL. Because 4958 * the template loading is asynchronous the compilation/linking is suspended until the template 4959 * is loaded. 4960 * 4961 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 4962 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 4963 * a string value representing the url. In either case, the template URL is passed through {@link 4964 * api/ng.$sce#methods_getTrustedResourceUrl $sce.getTrustedResourceUrl}. 4965 * 4966 * 4967 * #### `replace` 4968 * specify where the template should be inserted. Defaults to `false`. 4969 * 4970 * * `true` - the template will replace the current element. 4971 * * `false` - the template will replace the contents of the current element. 4972 * 4973 * 4974 * #### `transclude` 4975 * compile the content of the element and make it available to the directive. 4976 * Typically used with {@link api/ng.directive:ngTransclude 4977 * ngTransclude}. The advantage of transclusion is that the linking function receives a 4978 * transclusion function which is pre-bound to the correct scope. In a typical setup the widget 4979 * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` 4980 * scope. This makes it possible for the widget to have private state, and the transclusion to 4981 * be bound to the parent (pre-`isolate`) scope. 4982 * 4983 * * `true` - transclude the content of the directive. 4984 * * `'element'` - transclude the whole element including any directives defined at lower priority. 4985 * 4986 * 4987 * #### `compile` 4988 * 4989 * <pre> 4990 * function compile(tElement, tAttrs, transclude) { ... } 4991 * </pre> 4992 * 4993 * The compile function deals with transforming the template DOM. Since most directives do not do 4994 * template transformation, it is not used often. Examples that require compile functions are 4995 * directives that transform template DOM, such as {@link 4996 * api/ng.directive:ngRepeat ngRepeat}, or load the contents 4997 * asynchronously, such as {@link api/ngRoute.directive:ngView ngView}. The 4998 * compile function takes the following arguments. 4999 * 5000 * * `tElement` - template element - The element where the directive has been declared. It is 5001 * safe to do template transformation on the element and child elements only. 5002 * 5003 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 5004 * between all directive compile functions. 5005 * 5006 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 5007 * 5008 * <div class="alert alert-warning"> 5009 * **Note:** The template instance and the link instance may be different objects if the template has 5010 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 5011 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
5012 * should be done in a linking function rather than in a compile function. 5013 * </div> 5014 * 5015 * <div class="alert alert-error"> 5016 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 5017 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 5018 * to the link function instead. 5019 * </div> 5020 5021 * A compile function can have a return value which can be either a function or an object. 5022 * 5023 * * returning a (post-link) function - is equivalent to registering the linking function via the 5024 * `link` property of the config object when the compile function is empty. 5025 * 5026 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 5027 * control when a linking function should be called during the linking phase. See info about 5028 * pre-linking and post-linking functions below. 5029 * 5030 * 5031 * #### `link` 5032 * This property is used only if the `compile` property is not defined. 5033 * 5034 * <pre> 5035 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 5036 * </pre> 5037 * 5038 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 5039 * executed after the template has been cloned. This is where most of the directive logic will be 5040 * put. 5041 * 5042 * * `scope` - {@link api/ng.$rootScope.Scope Scope} - The scope to be used by the 5043 * directive for registering {@link api/ng.$rootScope.Scope#methods_$watch watches}. 5044 * 5045 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 5046 * manipulate the children of the element only in `postLink` function since the children have 5047 * already been linked. 5048 * 5049 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 5050 * between all directive linking functions. 5051 * 5052 * * `controller` - a controller instance - A controller instance if at least one directive on the 5053 * element defines a controller. The controller is shared among all the directives, which allows 5054 * the directives to use the controllers as a communication channel. 5055 * 5056 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 5057 * The scope can be overridden by an optional first argument. This is the same as the `$transclude` 5058 * parameter of directive controllers. 5059 * `function([scope], cloneLinkingFn)`. 5060 * 5061 * 5062 * #### Pre-linking function 5063 * 5064 * Executed before the child elements are linked. Not safe to do DOM transformation since the 5065 * compiler linking function will fail to locate the correct elements for linking. 5066 * 5067 * #### Post-linking function 5068 * 5069 * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. 5070 * 5071 * <a name="Attributes"></a> 5072 * ### Attributes 5073 * 5074 * The {@link api/ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 5075 * `link()` or `compile()` functions. It has a variety of uses. 5076 * 5077 * accessing *Normalized attribute names:* 5078 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 5079 * the attributes object allows for normalized access to 5080 * the attributes. 5081 * 5082 * * *Directive inter-communication:* All directives share the same instance of the attributes 5083 * object which allows the directives to use the attributes object as inter directive 5084 * communication. 5085 * 5086 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 5087 * allowing other directives to read the interpolated value. 5088 * 5089 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 5090 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 5091 * the only way to easily get the actual value because during the linking phase the interpolation 5092 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 5093 * 5094 * <pre> 5095 * function linkingFn(scope, elm, attrs, ctrl) { 5096 * // get the attribute value 5097 * console.log(attrs.ngModel); 5098 * 5099 * // change the attribute 5100 * attrs.$set('ngModel', 'new value'); 5101 * 5102 * // observe changes to interpolated attribute 5103 * attrs.$observe('ngModel', function(value) { 5104 * console.log('ngModel has changed value to ' + value); 5105 * }); 5106 * } 5107 * </pre> 5108 * 5109 * Below is an example using `$compileProvider`. 5110 * 5111 * <div class="alert alert-warning"> 5112 * **Note**: Typically directives are registered with `module.directive`. The example below is 5113 * to illustrate how `$compile` works. 5114 * </div> 5115 * 5116 <doc:example module="compile"> 5117 <doc:source> 5118 <script>
5119 angular.module('compile', [], function($compileProvider) { 5120 // configure new 'compile' directive by passing a directive 5121 // factory function. The factory function injects the '$compile' 5122 $compileProvider.directive('compile', function($compile) { 5123 // directive factory creates a link function 5124 return function(scope, element, attrs) { 5125 scope.$watch( 5126 function(scope) { 5127 // watch the 'compile' expression for changes 5128 return scope.$eval(attrs.compile); 5129 }, 5130 function(value) { 5131 // when the 'compile' expression changes 5132 // assign it into the current DOM 5133 element.html(value); 5134 5135 // compile the new DOM and link it to the current 5136 // scope. 5137 // NOTE: we only compile .childNodes so that 5138 // we don't get into infinite loop compiling ourselves 5139 $compile(element.contents())(scope); 5140 } 5141 ); 5142 }; 5143 }) 5144 }); 5145 5146 function Ctrl($scope) { 5147 $scope.name = 'Angular'; 5148 $scope.html = 'Hello {{name}}'; 5149 } 5150 </script> 5151 <div ng-controller="Ctrl"> 5152 <input ng-model="name"> <br> 5153 <textarea ng-model="html"></textarea> <br> 5154 <div compile="html"></div> 5155 </div> 5156 </doc:source> 5157 <doc:protractor> 5158 it('should auto compile', function() { 5159 var textarea = $('textarea'); 5160 var output = $('div[compile]'); 5161 // The initial state reads 'Hello Angular'. 5162 expect(output.getText()).toBe('Hello Angular'); 5163 textarea.clear(); 5164 textarea.sendKeys('{{name}}!'); 5165 expect(output.getText()).toBe('Angular!'); 5166 }); 5167 </doc:protractor> 5168 </doc:example> 5169 5170 * 5171 * 5172 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 5173 * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives. 5174 * @param {number} maxPriority only apply directives lower then given priority (Only effects the 5175 * root element(s), not their children) 5176 * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template 5177 * (a DOM element/tree) to a scope. Where: 5178 * 5179 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 5180 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 5181 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 5182 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 5183 * called as: <br> `cloneAttachFn(clonedElement, scope)` where: 5184 * 5185 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 5186 * * `scope` - is the current scope with which the linking function is working with. 5187 * 5188 * Calling the linking function returns the element of the template. It is either the original 5189 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 5190 * 5191 * After linking the view is not updated until after a call to $digest which typically is done by 5192 * Angular automatically. 5193 * 5194 * If you need access to the bound view, there are two ways to do it: 5195 * 5196 * - If you are not asking the linking function to clone the template, create the DOM element(s) 5197 * before you send them to the compiler and keep this reference around. 5198 * <pre> 5199 * var element = $compile('<p>{{total}}</p>')(scope); 5200 * </pre> 5201 * 5202 * - if on the other hand, you need the element to be cloned, the view reference from the original 5203 * example would not point to the clone, but rather to the original template that was cloned. In 5204 * this case, you can access the clone via the cloneAttachFn: 5205 * <pre> 5206 * var templateElement = angular.element('<p>{{total}}</p>'), 5207 * scope = ....; 5208 * 5209 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 5210 * //attach the clone to DOM document at the right place 5211 * }); 5212 * 5213 * //now we have reference to the cloned DOM via `clonedElement` 5214 * </pre> 5215 * 5216 * 5217 * For information on how the compiler works, see the 5218 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 5219 */ 5220 5221var $compileMinErr = minErr('$compile'); 5222 5223/** 5224 * @ngdoc service 5225 * @name ng.$compileProvider 5226 * @function 5227 * 5228 * @description 5229 */
vendor: 4,092 bytes, lines 5230-5322
5230$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 5231function $CompileProvider($provide, $$sanitizeUriProvider) { 5232 var hasDirectives = {}, 5233 Suffix = 'Directive', 5234 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, 5235 CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/, 5236 TABLE_CONTENT_REGEXP = /^<\s*(tr|th|td|tbody)(\s+[^>]*)?>/i; 5237 5238 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 5239 // The assumption is that future DOM event attribute names will begin with 5240 // 'on' and be composed of only English letters. 5241 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 5242 5243 /** 5244 * @ngdoc function 5245 * @name ng.$compileProvider#directive 5246 * @methodOf ng.$compileProvider 5247 * @function 5248 * 5249 * @description 5250 * Register a new directive with the compiler. 5251 * 5252 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 5253 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 5254 * names and the values are the factories. 5255 * @param {function|Array} directiveFactory An injectable directive factory function. See 5256 * {@link guide/directive} for more info. 5257 * @returns {ng.$compileProvider} Self for chaining. 5258 */ 5259 this.directive = function registerDirective(name, directiveFactory) { 5260 assertNotHasOwnProperty(name, 'directive'); 5261 if (isString(name)) { 5262 assertArg(directiveFactory, 'directiveFactory'); 5263 if (!hasDirectives.hasOwnProperty(name)) { 5264 hasDirectives[name] = []; 5265 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 5266 function($injector, $exceptionHandler) { 5267 var directives = []; 5268 forEach(hasDirectives[name], function(directiveFactory, index) { 5269 try { 5270 var directive = $injector.invoke(directiveFactory); 5271 if (isFunction(directive)) { 5272 directive = { compile: valueFn(directive) }; 5273 } else if (!directive.compile && directive.link) { 5274 directive.compile = valueFn(directive.link); 5275 } 5276 directive.priority = directive.priority || 0; 5277 directive.index = index; 5278 directive.name = directive.name || name; 5279 directive.require = directive.require || (directive.controller && directive.name); 5280 directive.restrict = directive.restrict || 'A'; 5281 directives.push(directive); 5282 } catch (e) { 5283 $exceptionHandler(e); 5284 } 5285 }); 5286 return directives; 5287 }]); 5288 } 5289 hasDirectives[name].push(directiveFactory); 5290 } else { 5291 forEach(name, reverseParams(registerDirective)); 5292 } 5293 return this; 5294 }; 5295 5296 5297 /** 5298 * @ngdoc function 5299 * @name ng.$compileProvider#aHrefSanitizationWhitelist 5300 * @methodOf ng.$compileProvider 5301 * @function 5302 * 5303 * @description 5304 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5305 * urls during a[href] sanitization. 5306 * 5307 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5308 * 5309 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 5310 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 5311 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 5312 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5313 * 5314 * @param {RegExp=} regexp New regexp to whitelist urls with. 5315 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5316 * chaining otherwise. 5317 */ 5318 this.aHrefSanitizationWhitelist = function(regexp) { 5319 if (isDefined(regexp)) { 5320 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 5321 return this; 5322 } else {
5323 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 5324 } 5325 }; 5326 5327 5328 /** 5329 * @ngdoc function 5330 * @name ng.$compileProvider#imgSrcSanitizationWhitelist 5331 * @methodOf ng.$compileProvider 5332 * @function 5333 * 5334 * @description 5335 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5336 * urls during img[src] sanitization. 5337 * 5338 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5339 * 5340 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 5341 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 5342 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 5343 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5344 * 5345 * @param {RegExp=} regexp New regexp to whitelist urls with. 5346 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5347 * chaining otherwise. 5348 */ 5349 this.imgSrcSanitizationWhitelist = function(regexp) { 5350 if (isDefined(regexp)) { 5351 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 5352 return this; 5353 } else { 5354 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 5355 } 5356 }; 5357 5358 this.$get = [ 5359 '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', 5360 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 5361 function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, 5362 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 5363 5364 var Attributes = function(element, attr) { 5365 this.$$element = element; 5366 this.$attr = attr || {}; 5367 }; 5368 5369 Attributes.prototype = { 5370 $normalize: directiveNormalize, 5371 5372 5373 /** 5374 * @ngdoc function 5375 * @name ng.$compile.directive.Attributes#$addClass 5376 * @methodOf ng.$compile.directive.Attributes 5377 * @function 5378 * 5379 * @description 5380 * Adds the CSS class value specified by the classVal parameter to the element. If animations 5381 * are enabled then an animation will be triggered for the class addition. 5382 * 5383 * @param {string} classVal The className value that will be added to the element 5384 */ 5385 $addClass : function(classVal) { 5386 if(classVal && classVal.length > 0) { 5387 $animate.addClass(this.$$element, classVal); 5388 } 5389 }, 5390 5391 /** 5392 * @ngdoc function 5393 * @name ng.$compile.directive.Attributes#$removeClass 5394 * @methodOf ng.$compile.directive.Attributes 5395 * @function 5396 * 5397 * @description 5398 * Removes the CSS class value specified by the classVal parameter from the element. If 5399 * animations are enabled then an animation will be triggered for the class removal. 5400 * 5401 * @param {string} classVal The className value that will be removed from the element 5402 */ 5403 $removeClass : function(classVal) { 5404 if(classVal && classVal.length > 0) { 5405 $animate.removeClass(this.$$element, classVal); 5406 } 5407 }, 5408 5409 /** 5410 * @ngdoc function 5411 * @name ng.$compile.directive.Attributes#$updateClass 5412 * @methodOf ng.$compile.directive.Attributes 5413 * @function 5414 * 5415 * @description 5416 * Adds and removes the appropriate CSS class values to the element based on the difference 5417 * between the new and old CSS class values (specified as newClasses and oldClasses). 5418 * 5419 * @param {string} newClasses The current CSS className value 5420 * @param {string} oldClasses The former CSS className value 5421 */ 5422 $updateClass : function(newClasses, oldClasses) { 5423 var toAdd = tokenDifference(newClasses, oldClasses); 5424 var toRemove = tokenDifference(oldClasses, newClasses); 5425 5426 if(toAdd.length === 0) { 5427 $animate.removeClass(this.$$element, toRemove); 5428 } else if(toRemove.length === 0) { 5429 $animate.addClass(this.$$element, toAdd); 5430 } else { 5431 $animate.setClass(this.$$element, toAdd, toRemove); 5432 } 5433 }, 5434 5435 /** 5436 * Set a normalized attribute on the element in a way such that all directives 5437 * can share the attribute. This function properly handles boolean attributes. 5438 * @param {string} key Normalized key. (ie ngAttribute) 5439 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 5440 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 5441 * Defaults to true. 5442 * @param {string=} attrName Optional none normalized name. Defaults to key. 5443 */ 5444 $set: function(key, value, writeAttr, attrName) { 5445 // TODO: decide whether or not to throw an error if "class" 5446 //is set through this function since it may cause $updateClass to 5447 //become unstable. 5448 5449 var booleanKey = getBooleanAttrName(this.$$element[0], key), 5450 normalizedVal, 5451 nodeName; 5452 5453 if (booleanKey) { 5454 this.$$element.prop(key, value); 5455 attrName = booleanKey; 5456 } 5457 5458 this[key] = value; 5459 5460 // translate normalized key to actual key 5461 if (attrName) { 5462 this.$attr[key] = attrName; 5463 } else { 5464 attrName = this.$attr[key]; 5465 if (!attrName) { 5466 this.$attr[key] = attrName = snake_case(key, '-'); 5467 } 5468 } 5469 5470 nodeName = nodeName_(this.$$element); 5471 5472 // sanitize a[href] and img[src] values 5473 if ((nodeName === 'A' && key === 'href') || 5474 (nodeName === 'IMG' && key === 'src')) { 5475 this[key] = value = $$sanitizeUri(value, key === 'src'); 5476 } 5477 5478 if (writeAttr !== false) { 5479 if (value === null || value === undefined) { 5480 this.$$element.removeAttr(attrName); 5481 } else { 5482 this.$$element.attr(attrName, value); 5483 } 5484 } 5485 5486 // fire observers 5487 var $$observers = this.$$observers;
5488 $$observers && forEach($$observers[key], function(fn) { 5489 try { 5490 fn(value); 5491 } catch (e) { 5492 $exceptionHandler(e); 5493 } 5494 }); 5495 }, 5496 5497 5498 /** 5499 * @ngdoc function 5500 * @name ng.$compile.directive.Attributes#$observe 5501 * @methodOf ng.$compile.directive.Attributes 5502 * @function 5503 * 5504 * @description 5505 * Observes an interpolated attribute. 5506 * 5507 * The observer function will be invoked once during the next `$digest` following 5508 * compilation. The observer is then invoked whenever the interpolated value 5509 * changes. 5510 * 5511 * @param {string} key Normalized key. (ie ngAttribute) . 5512 * @param {function(interpolatedValue)} fn Function that will be called whenever 5513 the interpolated value of the attribute changes. 5514 * See the {@link guide/directive#Attributes Directives} guide for more info. 5515 * @returns {function()} the `fn` parameter. 5516 */ 5517 $observe: function(key, fn) { 5518 var attrs = this, 5519 $$observers = (attrs.$$observers || (attrs.$$observers = {})), 5520 listeners = ($$observers[key] || ($$observers[key] = [])); 5521 5522 listeners.push(fn); 5523 $rootScope.$evalAsync(function() { 5524 if (!listeners.$$inter) { 5525 // no one registered attribute interpolation function, so lets call it manually 5526 fn(attrs[key]); 5527 } 5528 }); 5529 return fn; 5530 } 5531 }; 5532 5533 var startSymbol = $interpolate.startSymbol(), 5534 endSymbol = $interpolate.endSymbol(), 5535 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 5536 ? identity 5537 : function denormalizeTemplate(template) { 5538 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 5539 }, 5540 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 5541 5542 5543 return compile; 5544 5545 //================================ 5546 5547 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 5548 previousCompileContext) { 5549 if (!($compileNodes instanceof jqLite)) { 5550 // jquery always rewraps, whereas we need to preserve the original selector so that we can 5551 // modify it. 5552 $compileNodes = jqLite($compileNodes); 5553 } 5554 // We can not compile top level text elements since text nodes can be merged and we will 5555 // not be able to attach scope data to them, so we will wrap them in <span> 5556 forEach($compileNodes, function(node, index){ 5557 if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { 5558 $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0]; 5559 } 5560 }); 5561 var compositeLinkFn = 5562 compileNodes($compileNodes, transcludeFn, $compileNodes, 5563 maxPriority, ignoreDirective, previousCompileContext); 5564 safeAddClass($compileNodes, 'ng-scope'); 5565 return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){ 5566 assertArg(scope, 'scope'); 5567 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 5568 // and sometimes changes the structure of the DOM. 5569 var $linkNode = cloneConnectFn 5570 ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! 5571 : $compileNodes; 5572 5573 forEach(transcludeControllers, function(instance, name) { 5574 $linkNode.data('$' + name + 'Controller', instance); 5575 }); 5576 5577 // Attach scope only to non-text nodes. 5578 for(var i = 0, ii = $linkNode.length; i<ii; i++) { 5579 var node = $linkNode[i], 5580 nodeType = node.nodeType; 5581 if (nodeType === 1 /* element */ || nodeType === 9 /* document */) { 5582 $linkNode.eq(i).data('$scope', scope); 5583 } 5584 } 5585 5586 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 5587 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode); 5588 return $linkNode; 5589 }; 5590 } 5591 5592 function safeAddClass($element, className) {
5593 try { 5594 $element.addClass(className); 5595 } catch(e) { 5596 // ignore, since it means that we are trying to set class on 5597 // SVG element, where class name is read-only. 5598 } 5599 } 5600 5601 /** 5602 * Compile function matches each node in nodeList against the directives. Once all directives 5603 * for a particular node are collected their compile functions are executed. The compile 5604 * functions return values - the linking functions - are combined into a composite linking 5605 * function, which is the a linking function for the node. 5606 * 5607 * @param {NodeList} nodeList an array of nodes or NodeList to compile 5608 * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the 5609 * scope argument is auto-generated to the new child of the transcluded parent scope. 5610 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 5611 * the rootElement must be set the jqLite collection of the compile root. This is 5612 * needed so that the jqLite collection items can be replaced with widgets. 5613 * @param {number=} maxPriority Max directive priority. 5614 * @returns {?function} A composite linking function of all of the matched directives or null. 5615 */ 5616 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 5617 previousCompileContext) { 5618 var linkFns = [], 5619 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound; 5620 5621 for (var i = 0; i < nodeList.length; i++) { 5622 attrs = new Attributes(); 5623 5624 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 5625 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 5626 ignoreDirective); 5627 5628 nodeLinkFn = (directives.length) 5629 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 5630 null, [], [], previousCompileContext) 5631 : null; 5632 5633 if (nodeLinkFn && nodeLinkFn.scope) { 5634 safeAddClass(jqLite(nodeList[i]), 'ng-scope'); 5635 } 5636 5637 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 5638 !(childNodes = nodeList[i].childNodes) || 5639 !childNodes.length) 5640 ? null 5641 : compileNodes(childNodes, 5642 nodeLinkFn ? nodeLinkFn.transclude : transcludeFn); 5643 5644 linkFns.push(nodeLinkFn, childLinkFn); 5645 linkFnFound = linkFnFound || nodeLinkFn || childLinkFn; 5646 //use the previous context only for the first element in the virtual group 5647 previousCompileContext = null; 5648 } 5649 5650 // return a linking function if we have found anything, null otherwise 5651 return linkFnFound ? compositeLinkFn : null; 5652 5653 function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) { 5654 var nodeLinkFn, childLinkFn, node, $node, childScope, childTranscludeFn, i, ii, n; 5655 5656 // copy nodeList so that linking doesn't break due to live list updates. 5657 var nodeListLength = nodeList.length, 5658 stableNodeList = new Array(nodeListLength); 5659 for (i = 0; i < nodeListLength; i++) { 5660 stableNodeList[i] = nodeList[i]; 5661 } 5662 5663 for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) { 5664 node = stableNodeList[n]; 5665 nodeLinkFn = linkFns[i++]; 5666 childLinkFn = linkFns[i++]; 5667 $node = jqLite(node); 5668 5669 if (nodeLinkFn) { 5670 if (nodeLinkFn.scope) { 5671 childScope = scope.$new(); 5672 $node.data('$scope', childScope); 5673 } else { 5674 childScope = scope; 5675 } 5676 childTranscludeFn = nodeLinkFn.transclude; 5677 if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) { 5678 nodeLinkFn(childLinkFn, childScope, node, $rootElement, 5679 createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn) 5680 ); 5681 } else { 5682 nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn);
5683 } 5684 } else if (childLinkFn) { 5685 childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn); 5686 } 5687 } 5688 } 5689 } 5690 5691 function createBoundTranscludeFn(scope, transcludeFn) { 5692 return function boundTranscludeFn(transcludedScope, cloneFn, controllers) { 5693 var scopeCreated = false; 5694 5695 if (!transcludedScope) { 5696 transcludedScope = scope.$new(); 5697 transcludedScope.$$transcluded = true; 5698 scopeCreated = true; 5699 } 5700 5701 var clone = transcludeFn(transcludedScope, cloneFn, controllers); 5702 if (scopeCreated) { 5703 clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy)); 5704 } 5705 return clone; 5706 }; 5707 } 5708 5709 /** 5710 * Looks for directives on the given node and adds them to the directive collection which is 5711 * sorted. 5712 * 5713 * @param node Node to search. 5714 * @param directives An array to which the directives are added to. This array is sorted before 5715 * the function returns. 5716 * @param attrs The shared attrs object which is used to populate the normalized attributes. 5717 * @param {number=} maxPriority Max directive priority. 5718 */ 5719 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 5720 var nodeType = node.nodeType, 5721 attrsMap = attrs.$attr, 5722 match, 5723 className; 5724 5725 switch(nodeType) { 5726 case 1: /* Element */ 5727 // use the node name: <directive> 5728 addDirective(directives, 5729 directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); 5730 5731 // iterate over the attributes 5732 for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes, 5733 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 5734 var attrStartName = false; 5735 var attrEndName = false; 5736 5737 attr = nAttrs[j]; 5738 if (!msie || msie >= 8 || attr.specified) { 5739 name = attr.name; 5740 // support ngAttr attribute binding 5741 ngAttrName = directiveNormalize(name); 5742 if (NG_ATTR_BINDING.test(ngAttrName)) { 5743 name = snake_case(ngAttrName.substr(6), '-'); 5744 } 5745 5746 var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); 5747 if (ngAttrName === directiveNName + 'Start') { 5748 attrStartName = name; 5749 attrEndName = name.substr(0, name.length - 5) + 'end'; 5750 name = name.substr(0, name.length - 6); 5751 } 5752 5753 nName = directiveNormalize(name.toLowerCase()); 5754 attrsMap[nName] = name; 5755 attrs[nName] = value = trim(attr.value); 5756 if (getBooleanAttrName(node, nName)) { 5757 attrs[nName] = true; // presence means true 5758 } 5759 addAttrInterpolateDirective(node, directives, value, nName); 5760 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 5761 attrEndName); 5762 } 5763 } 5764 5765 // use class as directive 5766 className = node.className; 5767 if (isString(className) && className !== '') { 5768 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 5769 nName = directiveNormalize(match[2]); 5770 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 5771 attrs[nName] = trim(match[3]); 5772 } 5773 className = className.substr(match.index + match[0].length); 5774 } 5775 } 5776 break; 5777 case 3: /* Text Node */ 5778 addTextInterpolateDirective(directives, node.nodeValue); 5779 break; 5780 case 8: /* Comment */ 5781 try { 5782 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 5783 if (match) { 5784 nName = directiveNormalize(match[1]); 5785 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 5786 attrs[nName] = trim(match[2]); 5787 } 5788 } 5789 } catch (e) { 5790 // turns out that under some circumstances IE9 throws errors when one attempts to read 5791 // comment's node value. 5792 // Just ignore it and continue. (Can't seem to reproduce in test case.) 5793 } 5794 break; 5795 } 5796 5797 directives.sort(byPriority); 5798 return directives; 5799 } 5800 5801 /**
5802 * Given a node with an directive-start it collects all of the siblings until it finds 5803 * directive-end. 5804 * @param node 5805 * @param attrStart 5806 * @param attrEnd 5807 * @returns {*} 5808 */ 5809 function groupScan(node, attrStart, attrEnd) { 5810 var nodes = []; 5811 var depth = 0; 5812 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 5813 var startNode = node; 5814 do { 5815 if (!node) { 5816 throw $compileMinErr('uterdir', 5817 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 5818 attrStart, attrEnd); 5819 } 5820 if (node.nodeType == 1 /** Element **/) { 5821 if (node.hasAttribute(attrStart)) depth++; 5822 if (node.hasAttribute(attrEnd)) depth--; 5823 } 5824 nodes.push(node); 5825 node = node.nextSibling; 5826 } while (depth > 0); 5827 } else { 5828 nodes.push(node); 5829 } 5830 5831 return jqLite(nodes); 5832 } 5833 5834 /** 5835 * Wrapper for linking function which converts normal linking function into a grouped 5836 * linking function. 5837 * @param linkFn 5838 * @param attrStart 5839 * @param attrEnd 5840 * @returns {Function} 5841 */ 5842 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 5843 return function(scope, element, attrs, controllers, transcludeFn) { 5844 element = groupScan(element[0], attrStart, attrEnd); 5845 return linkFn(scope, element, attrs, controllers, transcludeFn); 5846 }; 5847 } 5848 5849 /** 5850 * Once the directives have been collected, their compile functions are executed. This method 5851 * is responsible for inlining directive templates as well as terminating the application 5852 * of the directives if the terminal directive has been reached. 5853 * 5854 * @param {Array} directives Array of collected directives to execute their compile function. 5855 * this needs to be pre-sorted by priority order. 5856 * @param {Node} compileNode The raw DOM node to apply the compile functions to 5857 * @param {Object} templateAttrs The shared attribute function 5858 * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the 5859 * scope argument is auto-generated to the new 5860 * child of the transcluded parent scope. 5861 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 5862 * argument has the root jqLite array so that we can replace nodes 5863 * on it. 5864 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 5865 * compiling the transclusion. 5866 * @param {Array.<Function>} preLinkFns 5867 * @param {Array.<Function>} postLinkFns 5868 * @param {Object} previousCompileContext Context used for previous compilation of the current 5869 * node 5870 * @returns linkFn 5871 */ 5872 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 5873 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 5874 previousCompileContext) { 5875 previousCompileContext = previousCompileContext || {}; 5876 5877 var terminalPriority = -Number.MAX_VALUE, 5878 newScopeDirective, 5879 controllerDirectives = previousCompileContext.controllerDirectives, 5880 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 5881 templateDirective = previousCompileContext.templateDirective, 5882 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 5883 hasTranscludeDirective = false, 5884 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 5885 $compileNode = templateAttrs.$$element = jqLite(compileNode), 5886 directive, 5887 directiveName, 5888 $template, 5889 replaceDirective = originalReplaceDirective, 5890 childTranscludeFn = transcludeFn, 5891 linkFn, 5892 directiveValue; 5893 5894 // executes all directives on the current element 5895 for(var i = 0, ii = directives.length; i < ii; i++) { 5896 directive = directives[i]; 5897 var attrStart = directive.$$start; 5898 var attrEnd = directive.$$end; 5899 5900 // collect multiblock sections 5901 if (attrStart) { 5902 $compileNode = groupScan(compileNode, attrStart, attrEnd); 5903 } 5904 $template = undefined; 5905 5906 if (terminalPriority > directive.priority) { 5907 break; // prevent further processing of directives 5908 } 5909 5910 if (directiveValue = directive.scope) { 5911 newScopeDirective = newScopeDirective || directive; 5912 5913 // skip the check for directives with async templates, we'll check the derived sync 5914 // directive when the template arrives 5915 if (!directive.templateUrl) { 5916 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 5917 $compileNode); 5918 if (isObject(directiveValue)) { 5919 newIsolateScopeDirective = directive; 5920 } 5921 } 5922 } 5923 5924 directiveName = directive.name; 5925 5926 if (!directive.templateUrl && directive.controller) { 5927 directiveValue = directive.controller; 5928 controllerDirectives = controllerDirectives || {}; 5929 assertNoDuplicate("'" + directiveName + "' controller", 5930 controllerDirectives[directiveName], directive, $compileNode); 5931 controllerDirectives[directiveName] = directive; 5932 } 5933 5934 if (directiveValue = directive.transclude) { 5935 hasTranscludeDirective = true; 5936 5937 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 5938 // This option should only be used by directives that know how to safely handle element transclusion, 5939 // where the transcluded nodes are added or replaced after linking. 5940 if (!directive.$$tlb) { 5941 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 5942 nonTlbTranscludeDirective = directive; 5943 } 5944 5945 if (directiveValue == 'element') { 5946 hasElementTranscludeDirective = true; 5947 terminalPriority = directive.priority; 5948 $template = groupScan(compileNode, attrStart, attrEnd); 5949 $compileNode = templateAttrs.$$element = 5950 jqLite(document.createComment(' ' + directiveName + ': ' + 5951 templateAttrs[directiveName] + ' ')); 5952 compileNode = $compileNode[0]; 5953 replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); 5954 5955 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 5956 replaceDirective && replaceDirective.name, { 5957 // Don't pass in: 5958 // - controllerDirectives - otherwise we'll create duplicates controllers 5959 // - newIsolateScopeDirective or templateDirective - comb
5959ining templates with 5960 // element transclusion doesn't make sense. 5961 // 5962 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 5963 // on the same element more than once. 5964 nonTlbTranscludeDirective: nonTlbTranscludeDirective 5965 }); 5966 } else { 5967 $template = jqLite(jqLiteClone(compileNode)).contents(); 5968 $compileNode.empty(); // clear contents 5969 childTranscludeFn = compile($template, transcludeFn); 5970 } 5971 } 5972 5973 if (directive.template) { 5974 assertNoDuplicate('template', templateDirective, directive, $compileNode); 5975 templateDirective = directive; 5976 5977 directiveValue = (isFunction(directive.template)) 5978 ? directive.template($compileNode, templateAttrs) 5979 : directive.template; 5980 5981 directiveValue = denormalizeTemplate(directiveValue); 5982 5983 if (directive.replace) { 5984 replaceDirective = directive; 5985 $template = directiveTemplateContents(directiveValue); 5986 compileNode = $template[0]; 5987 5988 if ($template.length != 1 || compileNode.nodeType !== 1) { 5989 throw $compileMinErr('tplrt', 5990 "Template for directive '{0}' must have exactly one root element. {1}", 5991 directiveName, ''); 5992 } 5993 5994 replaceWith(jqCollection, $compileNode, compileNode); 5995 5996 var newTemplateAttrs = {$attr: {}}; 5997 5998 // combine directives from the original node and from the template: 5999 // - take the array of directives for this element 6000 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 6001 // - collect directives from the template and sort them by priority 6002 // - combine directives as: processed + template + unprocessed 6003 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 6004 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 6005 6006 if (newIsolateScopeDirective) { 6007 markDirectivesAsIsolate(templateDirectives); 6008 } 6009 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 6010 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 6011 6012 ii = directives.length; 6013 } else { 6014 $compileNode.html(directiveValue); 6015 } 6016 } 6017 6018 if (directive.templateUrl) { 6019 assertNoDuplicate('template', templateDirective, directive, $compileNode); 6020 templateDirective = directive; 6021 6022 if (directive.replace) { 6023 replaceDirective = directive; 6024 } 6025 6026 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 6027 templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, { 6028 controllerDirectives: controllerDirectives, 6029 newIsolateScopeDirective: newIsolateScopeDirective, 6030 templateDirective: templateDirective, 6031 nonTlbTranscludeDirective: nonTlbTranscludeDirective 6032 }); 6033 ii = directives.length; 6034 } else if (directive.compile) { 6035 try { 6036 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 6037 if (isFunction(linkFn)) { 6038 addLinkFns(null, linkFn, attrStart, attrEnd); 6039 } else if (linkFn) { 6040 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 6041 } 6042 } catch (e) { 6043 $exceptionHandler(e, startingTag($compileNode)); 6044 } 6045 } 6046 6047 if (directive.terminal) { 6048 nodeLinkFn.terminal = true; 6049 terminalPriority = Math.max(terminalPriority, directive.priority); 6050 } 6051 6052 } 6053 6054 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 6055 nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn; 6056 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 6057
6058 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 6059 return nodeLinkFn; 6060 6061 //////////////////// 6062 6063 function addLinkFns(pre, post, attrStart, attrEnd) { 6064 if (pre) { 6065 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 6066 pre.require = directive.require; 6067 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6068 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 6069 } 6070 preLinkFns.push(pre); 6071 } 6072 if (post) { 6073 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 6074 post.require = directive.require; 6075 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6076 post = cloneAndAnnotateFn(post, {isolateScope: true}); 6077 } 6078 postLinkFns.push(post); 6079 } 6080 } 6081 6082 6083 function getControllers(require, $element, elementControllers) { 6084 var value, retrievalMethod = 'data', optional = false; 6085 if (isString(require)) { 6086 while((value = require.charAt(0)) == '^' || value == '?') { 6087 require = require.substr(1); 6088 if (value == '^') { 6089 retrievalMethod = 'inheritedData'; 6090 } 6091 optional = optional || value == '?'; 6092 } 6093 value = null; 6094 6095 if (elementControllers && retrievalMethod === 'data') { 6096 value = elementControllers[require]; 6097 } 6098 value = value || $element[retrievalMethod]('$' + require + 'Controller'); 6099 6100 if (!value && !optional) { 6101 throw $compileMinErr('ctreq', 6102 "Controller '{0}', required by directive '{1}', can't be found!", 6103 require, directiveName); 6104 } 6105 return value; 6106 } else if (isArray(require)) { 6107 value = []; 6108 forEach(require, function(require) { 6109 value.push(getControllers(require, $element, elementControllers)); 6110 }); 6111 } 6112 return value; 6113 } 6114 6115 6116 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 6117 var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; 6118 6119 if (compileNode === linkNode) { 6120 attrs = templateAttrs; 6121 } else { 6122 attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); 6123 } 6124 $element = attrs.$$element; 6125 6126 if (newIsolateScopeDirective) { 6127 var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; 6128 var $linkNode = jqLite(linkNode); 6129 6130 isolateScope = scope.$new(true); 6131 6132 if (templateDirective && (templateDirective === newIsolateScopeDirective.$$originalDirective)) { 6133 $linkNode.data('$isolateScope', isolateScope) ; 6134 } else { 6135 $linkNode.data('$isolateScopeNoTemplate', isolateScope); 6136 } 6137 6138 6139 6140 safeAddClass($linkNode, 'ng-isolate-scope'); 6141 6142 forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { 6143 var match = definition.match(LOCAL_REGEXP) || [], 6144 attrName = match[3] || scopeName, 6145 optional = (match[2] == '?'), 6146 mode = match[1], // @, =, or & 6147 lastValue, 6148 parentGet, parentSet, compare; 6149 6150 isolateScope.$$isolateBindings[scopeName] = mode + attrName; 6151 6152 switch (mode) { 6153 6154 case '@': 6155 attrs.$observe(attrName, function(value) { 6156 isolateScope[scopeName] = value; 6157 }); 6158 attrs.$$observers[attrName].$$scope = scope; 6159 if( attrs[attrName] ) { 6160 // If the attribute has been provided then we trigger an interpolation to ensure 6161 // the value is there for use in the link fn 6162 isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); 6163 } 6164 break; 6165 6166 case '=': 6167 if (optional && !attrs[attrName]) { 6168 return; 6169 } 6170 parentGet = $parse(attrs[attrName]); 6171 if (parentGet.literal) { 6172 compare = equals; 6173 } else { 6174 compare = function(a,b) { return a === b; }; 6175 } 6176 parentSet = parentGet.assign || function() { 6177 // reset the change, or we will throw this exception on every $digest
6178 lastValue = isolateScope[scopeName] = parentGet(scope); 6179 throw $compileMinErr('nonassign', 6180 "Expression '{0}' used with directive '{1}' is non-assignable!", 6181 attrs[attrName], newIsolateScopeDirective.name); 6182 }; 6183 lastValue = isolateScope[scopeName] = parentGet(scope); 6184 isolateScope.$watch(function parentValueWatch() { 6185 var parentValue = parentGet(scope); 6186 if (!compare(parentValue, isolateScope[scopeName])) { 6187 // we are out of sync and need to copy 6188 if (!compare(parentValue, lastValue)) { 6189 // parent changed and it has precedence 6190 isolateScope[scopeName] = parentValue; 6191 } else { 6192 // if the parent can be assigned then do so 6193 parentSet(scope, parentValue = isolateScope[scopeName]); 6194 } 6195 } 6196 return lastValue = parentValue; 6197 }, null, parentGet.literal); 6198 break; 6199 6200 case '&': 6201 parentGet = $parse(attrs[attrName]); 6202 isolateScope[scopeName] = function(locals) { 6203 return parentGet(scope, locals); 6204 }; 6205 break; 6206 6207 default: 6208 throw $compileMinErr('iscp', 6209 "Invalid isolate scope definition for directive '{0}'." + 6210 " Definition: {... {1}: '{2}' ...}", 6211 newIsolateScopeDirective.name, scopeName, definition); 6212 } 6213 }); 6214 } 6215 transcludeFn = boundTranscludeFn && controllersBoundTransclude; 6216 if (controllerDirectives) { 6217 forEach(controllerDirectives, function(directive) { 6218 var locals = { 6219 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 6220 $element: $element, 6221 $attrs: attrs, 6222 $transclude: transcludeFn 6223 }, controllerInstance; 6224 6225 controller = directive.controller; 6226 if (controller == '@') { 6227 controller = attrs[directive.name]; 6228 } 6229 6230 controllerInstance = $controller(controller, locals); 6231 // For directives with element transclusion the element is a comment, 6232 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 6233 // clean up (http://bugs.jquery.com/ticket/8335). 6234 // Instead, we save the controllers for the element in a local hash and attach to .data 6235 // later, once we have the actual element. 6236 elementControllers[directive.name] = controllerInstance; 6237 if (!hasElementTranscludeDirective) { 6238 $element.data('$' + directive.name + 'Controller', controllerInstance); 6239 } 6240 6241 if (directive.controllerAs) { 6242 locals.$scope[directive.controllerAs] = controllerInstance; 6243 } 6244 }); 6245 } 6246 6247 // PRELINKING 6248 for(i = 0, ii = preLinkFns.length; i < ii; i++) { 6249 try { 6250 linkFn = preLinkFns[i]; 6251 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6252 linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); 6253 } catch (e) { 6254 $exceptionHandler(e, startingTag($element)); 6255 } 6256 } 6257 6258 // RECURSION 6259 // We only pass the isolate scope, if the isolate directive has a template, 6260 // otherwise the child elements do not belong to the isolate directive. 6261 var scopeToChild = scope; 6262 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 6263 scopeToChild = isolateScope; 6264 } 6265 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
6266 6267 // POSTLINKING 6268 for(i = postLinkFns.length - 1; i >= 0; i--) { 6269 try { 6270 linkFn = postLinkFns[i]; 6271 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6272 linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); 6273 } catch (e) { 6274 $exceptionHandler(e, startingTag($element)); 6275 } 6276 } 6277 6278 // This is the function that is injected as `$transclude`. 6279 function controllersBoundTransclude(scope, cloneAttachFn) { 6280 var transcludeControllers; 6281 6282 // no scope passed 6283 if (arguments.length < 2) { 6284 cloneAttachFn = scope; 6285 scope = undefined; 6286 } 6287 6288 if (hasElementTranscludeDirective) { 6289 transcludeControllers = elementControllers; 6290 } 6291 6292 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); 6293 } 6294 } 6295 } 6296 6297 function markDirectivesAsIsolate(directives) { 6298 // mark all directives as needing isolate scope. 6299 for (var j = 0, jj = directives.length; j < jj; j++) { 6300 directives[j] = inherit(directives[j], {$$isolateScope: true}); 6301 } 6302 } 6303 6304 /** 6305 * looks up the directive and decorates it with exception handling and proper parameters. We 6306 * call this the boundDirective. 6307 * 6308 * @param {string} name name of the directive to look up. 6309 * @param {string} location The directive must be found in specific format. 6310 * String containing any of theses characters: 6311 * 6312 * * `E`: element name 6313 * * `A': attribute 6314 * * `C`: class 6315 * * `M`: comment 6316 * @returns true if directive was added. 6317 */ 6318 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 6319 endAttrName) { 6320 if (name === ignoreDirective) return null; 6321 var match = null; 6322 if (hasDirectives.hasOwnProperty(name)) { 6323 for(var directive, directives = $injector.get(name + Suffix), 6324 i = 0, ii = directives.length; i<ii; i++) { 6325 try { 6326 directive = directives[i]; 6327 if ( (maxPriority === undefined || maxPriority > directive.priority) && 6328 directive.restrict.indexOf(location) != -1) { 6329 if (startAttrName) { 6330 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 6331 } 6332 tDirectives.push(directive); 6333 match = directive; 6334 } 6335 } catch(e) { $exceptionHandler(e); } 6336 } 6337 } 6338 return match; 6339 } 6340 6341 6342 /** 6343 * When the element is replaced with HTML template then the new attributes 6344 * on the template need to be merged with the existing attributes in the DOM. 6345 * The desired effect is to have both of the attributes present. 6346 * 6347 * @param {object} dst destination attributes (original DOM) 6348 * @param {object} src source attributes (from the directive template) 6349 */ 6350 function mergeTemplateAttributes(dst, src) { 6351 var srcAttr = src.$attr, 6352 dstAttr = dst.$attr, 6353 $element = dst.$$element; 6354 6355 // reapply the old attributes to the new element 6356 forEach(dst, function(value, key) { 6357 if (key.charAt(0) != '$') { 6358 if (src[key]) { 6359 value += (key === 'style' ? ';' : ' ') + src[key]; 6360 } 6361 dst.$set(key, value, true, srcAttr[key]); 6362 } 6363 }); 6364 6365 // copy the new attributes on the old attrs object 6366 forEach(src, function(value, key) { 6367 if (key == 'class') { 6368 safeAddClass($element, value); 6369 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 6370 } else if (key == 'style') { 6371 $element.attr('style', $element.attr('style') + ';' + value); 6372 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 6373 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 6374 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 6375 // have an attribute like "has-own-property" or "data-has-own-property", etc. 6376 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 6377 dst[key] = value; 6378 dstAttr[key] = srcAttr[key]; 6379 } 6380 }); 6381 } 6382 6383 6384 function directiveTemplateContents(template) { 6385 var type; 6386 template = trim(template); 6387 if ((type = TABLE_CONTENT_REGEXP.exec(template))) { 6388 type = type[1].toLowerCase(); 6389 var table = jqLite('<table>' + template + '</table>'), 6390 tbody = table.children('tbody'), 6391 leaf = /(td|th)/.test(type) && table.find('tr'); 6392 if (tbody.length && type !== 'tbody') { 6393 table = tbody; 6394 } 6395 if (leaf && leaf.length) { 6396 table = leaf; 6397 } 6398 return table.contents(); 6399 } 6400 return jqLite('<div>' + 6401 template + 6402 '</div>').contents(); 6403 } 6404 6405 6406 function compileTemplateUrl(directives, $compileNode, tAttrs, 6407 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 6408 var linkQueue = [], 6409 afterTemplateNodeLinkFn, 6410 afterTemplateChildLinkFn, 6411 beforeTemplateCompileNode = $compileNode[0], 6412 origAsyncDirective = directives.shift(), 6413 // The fact that we have to copy and patch the directive seems wrong! 6414 derivedSyncDirective = extend({}, origAsyncDirective, { 6415 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 6416 }), 6417 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 6418 ? origAsyncDirective.templateUrl($compileNode, tAttrs
6418) 6419 : origAsyncDirective.templateUrl; 6420 6421 $compileNode.empty(); 6422 6423 $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). 6424 success(function(content) { 6425 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 6426 6427 content = denormalizeTemplate(content); 6428 6429 if (origAsyncDirective.replace) { 6430 $template = directiveTemplateContents(content); 6431 compileNode = $template[0]; 6432 6433 if ($template.length != 1 || compileNode.nodeType !== 1) { 6434 throw $compileMinErr('tplrt', 6435 "Template for directive '{0}' must have exactly one root element. {1}", 6436 origAsyncDirective.name, templateUrl); 6437 } 6438 6439 tempTemplateAttrs = {$attr: {}}; 6440 replaceWith($rootElement, $compileNode, compileNode); 6441 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 6442 6443 if (isObject(origAsyncDirective.scope)) { 6444 markDirectivesAsIsolate(templateDirectives); 6445 } 6446 directives = templateDirectives.concat(directives); 6447 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 6448 } else { 6449 compileNode = beforeTemplateCompileNode; 6450 $compileNode.html(content); 6451 } 6452 6453 directives.unshift(derivedSyncDirective); 6454 6455 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 6456 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 6457 previousCompileContext); 6458 forEach($rootElement, function(node, i) { 6459 if (node == compileNode) { 6460 $rootElement[i] = $compileNode[0]; 6461 } 6462 }); 6463 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 6464 6465 6466 while(linkQueue.length) { 6467 var scope = linkQueue.shift(), 6468 beforeTemplateLinkNode = linkQueue.shift(), 6469 linkRootElement = linkQueue.shift(), 6470 boundTranscludeFn = linkQueue.shift(), 6471 linkNode = $compileNode[0]; 6472 6473 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 6474 var oldClasses = beforeTemplateLinkNode.className; 6475 6476 if (!(previousCompileContext.hasElementTranscludeDirective && 6477 origAsyncDirective.replace)) { 6478 // it was cloned therefore we have to clone as well. 6479 linkNode = jqLiteClone(compileNode); 6480 } 6481 6482 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 6483 6484 // Copy in CSS classes from original node 6485 safeAddClass(jqLite(linkNode), oldClasses); 6486 } 6487 if (afterTemplateNodeLinkFn.transclude) { 6488 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude); 6489 } else { 6490 childBoundTranscludeFn = boundTranscludeFn; 6491 } 6492 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 6493 childBoundTranscludeFn); 6494 } 6495 linkQueue = null; 6496 }). 6497 error(function(response, code, headers, config) { 6498 throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); 6499 }); 6500 6501 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 6502 if (linkQueue) { 6503 linkQueue.push(scope); 6504 linkQueue.push(node); 6505 linkQueue.push(rootElement); 6506 linkQueue.push(boundTranscludeFn); 6507 } else { 6508 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn); 6509 } 6510 }; 6511 } 6512 6513 6514 /** 6515 * Sorting function for bound directives. 6516 */ 6517 function byPriority(a, b) { 6518 var diff = b.priority - a.priority; 6519 if (diff !== 0) return diff; 6520 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 6521 return a.index - b.index; 6522 } 6523 6524 6525 function assertNoDuplicate(what, previousDirective, directive, element) { 6526 if (previousDirective) { 6527 throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', 6528 previousDirective.name, directive.name, what, startingTag(element)); 6529 } 6530 } 6531 6532 6533 function addTextInterpolateDirective(directives, text) { 6534 var interpolateFn = $interpolate(text, true); 6535 if (interpolateFn) { 6536 directives.push({ 6537 priority: 0, 6538 compile: valueFn(function textInterpolateLinkFn(scope, node) { 6539 var parent = node.parent(), 6540 bindings = parent.data('$binding') || []; 6541 bindings.push(interpolateFn); 6542 safeAddClass(parent.data('$binding', bindings), 'ng-binding'); 6543 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 6544 node[0].nodeValue = value; 6545 }); 6546 }) 6547 }); 6548 } 6549 } 6550 6551 6552 function getTrustedContext(node, attrNormalizedName) { 6553 if (attrNormalizedName == "srcdoc") { 6554 return $sce.HTML; 6555 } 6556 var tag = nodeName_(node); 6557 // maction[xlink:href] can source SVG. It's not limited to <maction>. 6558 if (attrNormalizedName == "xlinkHref" || 6559 (tag == "FORM" && attrNormalizedName == "action") || 6560 (tag != "IMG" && (attrNormalizedName == "src" || 6561 attrNormalizedName == "ngSrc"))) { 6562 return $sce.RESOURCE_URL; 6563 } 6564 } 6565 6566 6567 function addAttrInterpolateDirective(node, directives, value, name) { 6568 var interpolateFn = $interpolate(value, true); 6569 6570 // no interpolation found -> ignore 6571 if (!interpolateFn) return; 6572 6573 6574 if (name === "multiple" && nodeName_(node) === "SELECT") { 6575 throw $compileMinErr("selmulti", 6576 "Binding to the 'multiple' attribute is not supported. Element: {0}", 6577 startingTag(node));
6578 } 6579 6580 directives.push({ 6581 priority: 100, 6582 compile: function() { 6583 return { 6584 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 6585 var $$observers = (attr.$$observers || (attr.$$observers = {})); 6586 6587 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 6588 throw $compileMinErr('nodomevents', 6589 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 6590 "ng- versions (such as ng-click instead of onclick) instead."); 6591 } 6592 6593 // we need to interpolate again, in case the attribute value has been updated 6594 // (e.g. by another directive's compile function) 6595 interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); 6596 6597 // if attribute was updated so that there is no interpolation going on we don't want to 6598 // register any observers 6599 if (!interpolateFn) return; 6600 6601 // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the 6602 // actual attr value 6603 attr[name] = interpolateFn(scope); 6604 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 6605 (attr.$$observers && attr.$$observers[name].$$scope || scope). 6606 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 6607 //special case for class attribute addition + removal 6608 //so that class changes can tap into the animation 6609 //hooks provided by the $animate service. Be sure to 6610 //skip animations when the first digest occurs (when 6611 //both the new and the old values are the same) since 6612 //the CSS classes are the non-interpolated values 6613 if(name === 'class' && newValue != oldValue) { 6614 attr.$updateClass(newValue, oldValue); 6615 } else { 6616 attr.$set(name, newValue); 6617 } 6618 }); 6619 } 6620 }; 6621 } 6622 }); 6623 } 6624 6625 6626 /** 6627 * This is a special jqLite.replaceWith, which can replace items which 6628 * have no parents, provided that the containing jqLite collection is provided. 6629 * 6630 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 6631 * in the root of the tree. 6632 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 6633 * the shell, but replace its DOM node reference. 6634 * @param {Node} newNode The new DOM node. 6635 */ 6636 function replaceWith($rootElement, elementsToRemove, newNode) { 6637 var firstElementToRemove = elementsToRemove[0], 6638 removeCount = elementsToRemove.length, 6639 parent = firstElementToRemove.parentNode, 6640 i, ii; 6641 6642 if ($rootElement) { 6643 for(i = 0, ii = $rootElement.length; i < ii; i++) { 6644 if ($rootElement[i] == firstElementToRemove) { 6645 $rootElement[i++] = newNode; 6646 for (var j = i, j2 = j + removeCount - 1, 6647 jj = $rootElement.length; 6648 j < jj; j++, j2++) { 6649 if (j2 < jj) { 6650 $rootElement[j] = $rootElement[j2]; 6651 } else { 6652 delete $rootElement[j]; 6653 } 6654 } 6655 $rootElement.length -= removeCount - 1; 6656 break; 6657 } 6658 } 6659 } 6660 6661 if (parent) { 6662 parent.replaceChild(newNode, firstElementToRemove); 6663 } 6664 var fragment = document.createDocumentFragment(); 6665 fragment.appendChild(firstElementToRemove); 6666 newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; 6667 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 6668 var element = elementsToRemove[k]; 6669 jqLite(element).remove(); // must do this way to clean up expando 6670 fragment.appendChild(element); 6671 delete elementsToRemove[k]; 6672 } 6673 6674 elementsToRemove[0] = newNode; 6675 elementsToRemove.length = 1; 6676 } 6677 6678 6679 function cloneAndAnnotateFn(fn, annotation) { 6680 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 6681 } 6682 }]; 6683} 6684 6685var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; 6686/** 6687 * Converts all accepted directives format into proper directive name. 6688 * All of these will become 'myDirective': 6689 * my:Directive 6690 * my-directive 6691 * x-my-directive 6692 * data-my:directive 6693 * 6694 * Also there is special case for Moz prefix starting with upper case letter. 6695 * @param name Name to normalize 6696 */ 6697function directiveNormalize(name) { 6698 return camelCase(name.replace(PREFIX_REGEXP, '')); 6699} 6700 6701/** 6702 * @ngdoc object 6703 * @name ng.$compile.directive.Attributes 6704 * 6705 * @description 6706 * A shared object between directive compile / linking functions which contains
6706normalized DOM 6707 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 6708 * needed since all of these are treated as equivalent in Angular: 6709 * 6710 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 6711 */ 6712 6713/** 6714 * @ngdoc property 6715 * @name ng.$compile.directive.Attributes#$attr 6716 * @propertyOf ng.$compile.directive.Attributes 6717 * @returns {object} A map of DOM element attribute names to the normalized name. This is 6718 * needed to do reverse lookup from normalized name back to actual name. 6719 */ 6720 6721 6722/** 6723 * @ngdoc function 6724 * @name ng.$compile.directive.Attributes#$set 6725 * @methodOf ng.$compile.directive.Attributes 6726 * @function 6727 * 6728 * @description 6729 * Set DOM element attribute value. 6730 * 6731 * 6732 * @param {string} name Normalized element attribute name of the property to modify. The name is 6733 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 6734 * property to the original name. 6735 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 6736 */ 6737 6738 6739 6740/** 6741 * Closure compiler type information 6742 */ 6743 6744function nodesetLinkingFn( 6745 /* angular.Scope */ scope, 6746 /* NodeList */ nodeList, 6747 /* Element */ rootElement, 6748 /* function(Function) */ boundTranscludeFn 6749){} 6750 6751function directiveLinkingFn( 6752 /* nodesetLinkingFn */ nodesetLinkingFn, 6753 /* angular.Scope */ scope, 6754 /* Node */ node, 6755 /* Element */ rootElement, 6756 /* function(Function) */ boundTranscludeFn 6757){} 6758 6759function tokenDifference(str1, str2) { 6760 var values = '', 6761 tokens1 = str1.split(/\s+/), 6762 tokens2 = str2.split(/\s+/); 6763 6764 outer: 6765 for(var i = 0; i < tokens1.length; i++) { 6766 var token = tokens1[i]; 6767 for(var j = 0; j < tokens2.length; j++) { 6768 if(token == tokens2[j]) continue outer; 6769 } 6770 values += (values.length > 0 ? ' ' : '') + token; 6771 } 6772 return values; 6773} 6774 6775/** 6776 * @ngdoc object 6777 * @name ng.$controllerProvider 6778 * @description 6779 * The {@link ng.$controller $controller service} is used by Angular to create new 6780 * controllers. 6781 * 6782 * This provider allows controller registration via the 6783 * {@link ng.$controllerProvider#methods_register register} method. 6784 */ 6785function $ControllerProvider() { 6786 var controllers = {}, 6787 CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 6788 6789 6790 /** 6791 * @ngdoc function 6792 * @name ng.$controllerProvider#register 6793 * @methodOf ng.$controllerProvider 6794 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 6795 * the names and the values are the constructors. 6796 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 6797 * annotations in the array notation). 6798 */ 6799 this.register = function(name, constructor) { 6800 assertNotHasOwnProperty(name, 'controller'); 6801 if (isObject(name)) { 6802 extend(controllers, name); 6803 } else { 6804 controllers[name] = constructor; 6805 } 6806 }; 6807 6808 6809 this.$get = ['$injector', '$window', function($injector, $window) { 6810 6811 /** 6812 * @ngdoc function 6813 * @name ng.$controller 6814 * @requires $injector 6815 * 6816 * @param {Function|string} constructor If called with a function then it's considered to be the 6817 * controller constructor function. Otherwise it's considered to be a string which is used 6818 * to retrieve the controller constructor using the following steps: 6819 * 6820 * * check if a controller with given name is registered via `$controllerProvider` 6821 * * check if evaluating the string on the current scope returns a constructor 6822 * * check `window[constructor]` on the global `window` object 6823 * 6824 * @param {Object} locals Injection locals for Controller. 6825 * @return {Object} Instance of given controller. 6826 * 6827 * @description 6828 * `$controller` service is responsible for instantiating controllers. 6829 * 6830 * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into 6831 * a service, so that one can override this service with {@link https://gist.github.com/1649788 6832 * BC version}. 6833 */ 6834 return function(expression, locals) { 6835 var instance, match, constructor, identifier; 6836 6837 if(isString(expression)) { 6838 match = expression.match(CNTRL_REG), 6839 constructor = match[1], 6840 identifier = match[3]; 6841 expression = controllers.hasOwnProperty(constructor) 6842 ? controllers[constructor] 6843 : getter(locals.$scope, constructor, true) || getter($window, constructor, true); 6844 6845 assertArgFn(expression, constructor, true); 6846 } 6847 6848 instance = $injector.instantiate(expression, locals); 6849 6850 if (identifier) { 6851 if (!(locals && typeof locals.$scope == 'object')) { 6852 throw minErr('$controller')('noscp', 6853 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 6854 constructor || expression.name, identifier); 6855 } 6856 6857 locals.$scope[identifier] = instance; 6858 } 6859 6860 return instance; 6861 }; 6862 }]; 6863} 6864 6865/** 6866 * @ngdoc object 6867 * @name ng.$document 6868 * @requires $window 6869 * 6870 * @description 6871 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 6872 */ 6873function $DocumentProvider(){ 6874 this.$get = ['$window', function(window){ 6875 return jqLite(window.document); 6876 }]; 6877} 6878 6879/** 6880 * @ngdoc function 6881 * @name ng.$exceptionHandler 6882 * @requires $log 6883 * 6884 * @description 6885 * Any uncaught exception in angular expressions is delegated to this service. 6886 * The default implementation simply delegates to `$log.error` which logs it into 6887 * the browser console. 6888 * 6889 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 6890 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 6891 * 6892 * ## Example: 6893 * 6894 * <pre> 6895 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { 6896 * return function (exception, cause) { 6897 * exception.message += ' (caused by "' + cause + '")'; 6898 * throw exception; 6899 * }; 6900 * }); 6901 * </pre> 6902 * 6903 * This example will override the normal action of `$exceptionHandler`, to make angular
6904 * exceptions fail hard when they happen, instead of just logging to the console. 6905 * 6906 * @param {Error} exception Exception associated with the error. 6907 * @param {string=} cause optional information about the context in which 6908 * the error was thrown. 6909 * 6910 */ 6911function $ExceptionHandlerProvider() { 6912 this.$get = ['$log', function($log) { 6913 return function(exception, cause) { 6914 $log.error.apply($log, arguments); 6915 }; 6916 }]; 6917} 6918 6919/** 6920 * Parse headers into key value object 6921 * 6922 * @param {string} headers Raw headers as a string 6923 * @returns {Object} Parsed headers as key value object 6924 */ 6925function parseHeaders(headers) { 6926 var parsed = {}, key, val, i; 6927 6928 if (!headers) return parsed; 6929 6930 forEach(headers.split('\n'), function(line) { 6931 i = line.indexOf(':'); 6932 key = lowercase(trim(line.substr(0, i))); 6933 val = trim(line.substr(i + 1)); 6934 6935 if (key) { 6936 if (parsed[key]) { 6937 parsed[key] += ', ' + val; 6938 } else { 6939 parsed[key] = val; 6940 } 6941 } 6942 }); 6943 6944 return parsed; 6945} 6946 6947 6948/** 6949 * Returns a function that provides access to parsed headers. 6950 * 6951 * Headers are lazy parsed when first requested. 6952 * @see parseHeaders 6953 * 6954 * @param {(string|Object)} headers Headers to provide access to. 6955 * @returns {function(string=)} Returns a getter function which if called with: 6956 * 6957 * - if called with single an argument returns a single header value or null
6958 * - if called with no arguments returns an object containing all headers. 6959 */ 6960function headersGetter(headers) { 6961 var headersObj = isObject(headers) ? headers : undefined; 6962 6963 return function(name) { 6964 if (!headersObj) headersObj = parseHeaders(headers); 6965 6966 if (name) { 6967 return headersObj[lowercase(name)] || null; 6968 } 6969 6970 return headersObj; 6971 }; 6972} 6973 6974 6975/** 6976 * Chain all given functions 6977 * 6978 * This function is used for both request and response transforming 6979 * 6980 * @param {*} data Data to transform. 6981 * @param {function(string=)} headers Http headers getter fn. 6982 * @param {(function|Array.<function>)} fns Function or an array of functions. 6983 * @returns {*} Transformed data. 6984 */ 6985function transformData(data, headers, fns) { 6986 if (isFunction(fns)) 6987 return fns(data, headers); 6988 6989 forEach(fns, function(fn) { 6990 data = fn(data, headers); 6991 }); 6992 6993 return data; 6994} 6995 6996 6997function isSuccess(status) { 6998 return 200 <= status && status < 300; 6999} 7000 7001 7002function $HttpProvider() { 7003 var JSON_START = /^\s*(\[|\{[^\{])/, 7004 JSON_END = /[\}\]]\s*$/, 7005 PROTECTION_PREFIX = /^\)\]\}',?\n/, 7006 CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; 7007 7008 var defaults = this.defaults = { 7009 // transform incoming response data 7010 transformResponse: [function(data) { 7011 if (isString(data)) { 7012 // strip json vulnerability protection prefix 7013 data = data.replace(PROTECTION_PREFIX, ''); 7014 if (JSON_START.test(data) && JSON_END.test(data)) 7015 data = fromJson(data); 7016 } 7017 return data; 7018 }], 7019 7020 // transform outgoing request data 7021 transformRequest: [function(d) { 7022 return isObject(d) && !isFile(d) ? toJson(d) : d; 7023 }], 7024 7025 // default headers 7026 headers: { 7027 common: { 7028 'Accept': 'application/json, text/plain, */*' 7029 }, 7030 post: copy(CONTENT_TYPE_APPLICATION_JSON), 7031 put: copy(CONTENT_TYPE_APPLICATION_JSON), 7032 patch: copy(CONTENT_TYPE_APPLICATION_JSON) 7033 }, 7034 7035 xsrfCookieName: 'XSRF-TOKEN', 7036 xsrfHeaderName: 'X-XSRF-TOKEN' 7037 }; 7038 7039 /** 7040 * Are ordered by request, i.e. they are applied in the same order as the 7041 * array, on request, but reverse order, on response. 7042 */ 7043 var interceptorFactories = this.interceptors = []; 7044 7045 /** 7046 * For historical reasons, response interceptors are ordered by the order in which 7047 * they are applied to the response. (This is the opposite of interceptorFactories) 7048 */ 7049 var responseInterceptorFactories = this.responseInterceptors = []; 7050 7051 this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', 7052 function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { 7053 7054 var defaultCache = $cacheFactory('$http'); 7055 7056 /** 7057 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 7058 * The reversal is needed so that we can build up the interception chain around the 7059 * server request. 7060 */ 7061 var reversedInterceptors = []; 7062 7063 forEach(interceptorFactories, function(interceptorFactory) { 7064 reversedInterceptors.unshift(isString(interceptorFactory) 7065 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 7066 }); 7067 7068 forEach(responseInterceptorFactories, function(interceptorFactory, index) { 7069 var responseFn = isString(interceptorFactory) 7070 ? $injector.get(interceptorFactory) 7071 : $injector.invoke(interceptorFactory); 7072 7073 /** 7074 * Response interceptors go before "around" interceptors (no real reason, just 7075 * had to pick one.) But they are already reversed, so we can't use unshift, hence 7076 * the splice. 7077 */ 7078 reversedInterceptors.splice(index, 0, { 7079 response: function(response) { 7080 return responseFn($q.when(response)); 7081 }, 7082 responseError: function(response) { 7083 return responseFn($q.reject(response)); 7084 } 7085 }); 7086 }); 7087 7088 7089 /** 7090 * @ngdoc function 7091 * @name ng.$http 7092 * @requires $httpBackend 7093 * @requires $browser 7094 * @requires $cacheFactory 7095 * @requires $rootScope 7096 * @requires $q 7097 * @requires $injector 7098 *
7099 * @description 7100 * The `$http` service is a core Angular service that facilitates communication with the remote 7101 * HTTP servers via the browser's {@link https://developer.mozilla.org/en/xmlhttprequest 7102 * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}. 7103 * 7104 * For unit testing applications that use `$http` service, see 7105 * {@link ngMock.$httpBackend $httpBackend mock}. 7106 * 7107 * For a higher level of abstraction, please check out the {@link ngResource.$resource 7108 * $resource} service. 7109 * 7110 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 7111 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 7112 * it is important to familiarize yourself with these APIs and the guarantees they provide. 7113 * 7114 * 7115 * # General usage 7116 * The `$http` service is a function which takes a single argument â a configuration object â 7117 * that is used to generate an HTTP request and returns a {@link ng.$q promise} 7118 * with two $http specific methods: `success` and `error`. 7119 * 7120 * <pre> 7121 * $http({method: 'GET', url: '/someUrl'}). 7122 * success(function(data, status, headers, config) { 7123 * // this callback will be called asynchronously 7124 * // when the response is available 7125 * }). 7126 * error(function(data, status, headers, config) { 7127 * // called asynchronously if an error occurs 7128 * // or server returns response with an error status. 7129 * }); 7130 * </pre> 7131 * 7132 * Since the returned value of calling the $http function is a `promise`, you can also use 7133 * the `then` method to register callbacks, and these callbacks will receive a single argument â 7134 * an object representing the response. See the API signature and type info below for more 7135 * details. 7136 * 7137 * A response status code between 200 and 299 is considered a success status and 7138 * will result in the success callback being called. Note that if the response is a redirect, 7139 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 7140 * called for such responses. 7141 * 7142 * # Writing Unit Tests that use $http 7143 * When unit testing (using {@link api/ngMock ngMock}), it is necessary to call 7144 * {@link api/ngMock.$httpBackend#methods_flush $httpBackend.flush()} to flush each pending 7145 * request using trained responses. 7146 * 7147 * ``` 7148 * $httpBackend.expectGET(...); 7149 * $http.get(...); 7150 * $httpBackend.flush(); 7151 * ``` 7152 * 7153 * # Shortcut methods 7154 * 7155 * Since all invocations of the $http service require passing in an HTTP method and URL, and 7156 * POST/PUT requests require request data to be provided as well, shortcut methods 7157 * were created: 7158 * 7159 * <pre> 7160 * $http.get('/someUrl').success(successCallback); 7161 * $http.post('/someUrl', data).success(successCallback); 7162 * </pre> 7163 * 7164 * Complete list of shortcut methods: 7165 * 7166 * - {@link ng.$http#methods_get $http.get} 7167 * - {@link ng.$http#methods_head $http.head} 7168 * - {@link ng.$http#methods_post $http.post} 7169 * - {@link ng.$http#methods_put $http.put} 7170 * - {@link ng.$http#methods_delete $http.delete} 7171 * - {@link ng.$http#methods_jsonp $http.jsonp} 7172 * 7173 * 7174 * # Setting HTTP Headers 7175 * 7176 * The $http service will automatically add certain HTTP headers to all requests. These defaults 7177 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 7178 * object, which currently contains this default configuration: 7179 * 7180 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 7181 * - `Accept: application/json, text/plain, * / *` 7182 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 7183 * - `Content-Type: application/json` 7184 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 7185 * - `Content-Type: application/json` 7186 *
7187 * To add or overwrite these defaults, simply add or remove a property from these configuration 7188 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 7189 * with the lowercased HTTP method name as the key, e.g. 7190 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. 7191 * 7192 * The defaults can also be set at runtime via the `$http.defaults` object in the same 7193 * fashion. For example: 7194 * 7195 * ``` 7196 * module.run(function($http) { 7197 * $http.defaults.headers.common.Authentication = 'Basic YmVlcDpib29w' 7198 * }); 7199 * ``` 7200 * 7201 * In addition, you can supply a `headers` property in the config object passed when 7202 * calling `$http(config)`, which overrides the defaults without changing them globally. 7203 * 7204 * 7205 * # Transforming Requests and Responses 7206 * 7207 * Both requests and responses can be transformed using transform functions. By default, Angular 7208 * applies these transformations: 7209 * 7210 * Request transformations: 7211 * 7212 * - If the `data` property of the request configuration object contains an object, serialize it 7213 * into JSON format. 7214 * 7215 * Response transformations: 7216 * 7217 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 7218 * - If JSON response is detected, deserialize it using a JSON parser. 7219 * 7220 * To globally augment or override the default transforms, modify the 7221 * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` 7222 * properties. These properties are by default an array of transform functions, which allows you 7223 * to `push` or `unshift` a new transformation function into the transformation chain. You can 7224 * also decide to completely override any default transformations by assigning your 7225 * transformation functions to these properties directly without the array wrapper. These defaults 7226 * are again available on the $http factory at run-time, which may be useful if you have run-time 7227 * services you wish to be involved in your transformations. 7228 * 7229 * Similarly, to locally override the request/response transforms, augment the 7230 * `transformRequest` and/or `transformResponse` properties of the configuration object passed 7231 * into `$http`. 7232 * 7233 * 7234 * # Caching 7235 * 7236 * To enable caching, set the request configuration `cache` property to `true` (to use default 7237 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 7238 * When the cache is enabled, `$http` stores the response from the server in the specified 7239 * cache. The next time the same request is made, the response is served from the cache without 7240 * sending a request to the server. 7241 * 7242 * Note that even if the response is served from cache, delivery of the data is asynchronous in 7243 * the same way that real requests are. 7244 * 7245 * If there are multiple GET requests for the same URL that should be cached using the same 7246 * cache, but the cache is not populated yet, only one request to the server will be made and 7247 * the remaining requests will be fulfilled using the response from the first request. 7248 * 7249 * You can change the default cache to a new object (built with 7250 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 7251 * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set 7252 * their `cache` property to `true` will now use this cache object. 7253 * 7254 * If you set the default cache to `false` then only requests that specify their own custom 7255 * cache object will be cached. 7256 * 7257 * # Interceptors 7258 * 7259 * Before you start creating interceptors, be sure to understand the 7260 * {@link ng.$q $q and deferred/promise APIs}. 7261 * 7262 * For purposes of global error handling, authentication, or any kind of synchronous or 7263 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 7264 * able to intercept requests before they are handed to the server and 7265 * responses before they are handed over to the application code that 7266 * initiated these requests. The interceptors leverage the {@link ng.$q 7267 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 7268 * 7269 * The interceptors are service factories that are registered with the `$httpProvider` by 7270 * adding them to the `$httpProvider.interceptors` array. The factory is called and 7271 * injected with dependencies (if specified) and returns the interceptor. 7272 * 7273 * There are two kinds of interceptors (and two kinds of rejection interceptors): 7274 * 7275 * * `request`: interceptors get called with http `config` object. The function is free to 7276 * modify the `config` or create a new one. The function needs to return the `config` 7277 * directly or as a promise. 7278 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 7279 * resolved with a rejection. 7280 * * `response`: interceptors get called with http `response` object. The function is free to 7281 * modify the `response` or create a new one. The function needs to return the `response` 7282 * directly or as a promise. 7283 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 7284 * resolved with a rejection. 7285 * 7286 * 7287 * <pre> 7288 * // register the interceptor as a service 7289 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7290 * return { 7291 * // optional method 7292 * 'request': function(config) {
7293 * // do something on success 7294 * return config || $q.when(config); 7295 * }, 7296 * 7297 * // optional method 7298 * 'requestError': function(rejection) { 7299 * // do something on error 7300 * if (canRecover(rejection)) { 7301 * return responseOrNewPromise 7302 * } 7303 * return $q.reject(rejection); 7304 * }, 7305 * 7306 * 7307 * 7308 * // optional method 7309 * 'response': function(response) { 7310 * // do something on success 7311 * return response || $q.when(response); 7312 * }, 7313 * 7314 * // optional method 7315 * 'responseError': function(rejection) { 7316 * // do something on error 7317 * if (canRecover(rejection)) { 7318 * return responseOrNewPromise 7319 * } 7320 * return $q.reject(rejection); 7321 * } 7322 * }; 7323 * }); 7324 * 7325 * $httpProvider.interceptors.push('myHttpInterceptor'); 7326 * 7327 * 7328 * // alternatively, register the interceptor via an anonymous factory 7329 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 7330 * return { 7331 * 'request': function(config) { 7332 * // same as above 7333 * }, 7334 * 7335 * 'response': function(response) { 7336 * // same as above 7337 * } 7338 * }; 7339 * }); 7340 * </pre> 7341 * 7342 * # Response interceptors (DEPRECATED) 7343 * 7344 * Before you start creating interceptors, be sure to understand the 7345 * {@link ng.$q $q and deferred/promise APIs}. 7346 * 7347 * For purposes of global error handling, authentication or any kind of synchronous or 7348 * asynchronous preprocessing of received responses, it is desirable to be able to intercept 7349 * responses for http requests before they are handed over to the application code that 7350 * initiated these requests. The response interceptors leverage the {@link ng.$q 7351 * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. 7352 * 7353 * The interceptors are service factories that are registered with the $httpProvider by 7354 * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and 7355 * injected with dependencies (if specified) and returns the interceptor â a function that 7356 * takes a {@link ng.$q promise} and returns the original or a new promise. 7357 * 7358 * <pre> 7359 * // register the interceptor as a service 7360 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7361 * return function(promise) { 7362 * return promise.then(function(response) { 7363 * // do something on success 7364 * return response; 7365 * }, function(response) { 7366 * // do something on error 7367 * if (canRecover(response)) { 7368 * return responseOrNewPromise 7369 * } 7370 * return $q.reject(response); 7371 * }); 7372 * } 7373 * }); 7374 * 7375 * $httpProvider.responseInterceptors.push('myHttpInterceptor'); 7376 * 7377 * 7378 * // register the interceptor via an anonymous factory 7379 * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) { 7380 * return function(promise) { 7381 * // same as above 7382 * } 7383 * }); 7384 * </pre> 7385 * 7386 * 7387 * # Security Considerations 7388 * 7389 * When designing web applications, consider security threats from: 7390 * 7391 * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx 7392 * JSON vulnerability} 7393 * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} 7394 * 7395 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 7396 * pre-configured with strategies that address these issues, but for this to work backend server 7397 * cooperation is required. 7398 * 7399 * ## JSON Vulnerability Protection 7400 * 7401 * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx 7402 * JSON vulnerability} allows third party website to turn your JSON resource URL into 7403 * {@link http://en.wikipedia.org/wiki/JSONP JSONP} request under some conditions. To 7404 * counter this your server can prefix all JSON requests with following str
7404ing `")]}',\n"`. 7405 * Angular will automatically strip the prefix before processing it as JSON. 7406 * 7407 * For example if your server needs to return: 7408 * <pre> 7409 * ['one','two'] 7410 * </pre> 7411 * 7412 * which is vulnerable to attack, your server can return: 7413 * <pre> 7414 * )]}', 7415 * ['one','two'] 7416 * </pre> 7417 * 7418 * Angular will strip the prefix, before processing the JSON. 7419 * 7420 * 7421 * ## Cross Site Request Forgery (XSRF) Protection 7422 * 7423 * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which 7424 * an unauthorized site can gain your user's private data. Angular provides a mechanism 7425 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 7426 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 7427 * JavaScript that runs on your domain could read the cookie, your server can be assured that 7428 * the XHR came from JavaScript running on your domain. The header will not be set for 7429 * cross-domain requests. 7430 * 7431 * To take advantage of this, your server needs to set a token in a JavaScript readable session 7432 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 7433 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 7434 * that only JavaScript running on your domain could have sent the request. The token must be 7435 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 7436 * making up its own tokens). We recommend that the token is a digest of your site's 7437 * authentication cookie with a {@link https://en.wikipedia.org/wiki/Salt_(cryptography) salt} 7438 * for added security. 7439 * 7440 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 7441 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 7442 * or the per-request config object. 7443 * 7444 * 7445 * @param {object} config Object describing the request to be made and how it should be 7446 * processed. The object has following properties: 7447 * 7448 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 7449 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 7450 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be turned 7451 * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be 7452 * JSONified. 7453 * - **data** â `{string|Object}` â Data to be sent as the request message data. 7454 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 7455 * HTTP headers to send to the server. If the return value of a function is null, the 7456 * header will not be sent. 7457 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 7458 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 7459 * - **transformRequest** â 7460 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 7461 * transform function or an array of such functions. The transform function takes the http 7462 * request body and headers and returns its transformed (typically serialized) version. 7463 * - **transformResponse** â 7464 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 7465 * transform function or an array of such functions. The transform function takes the http 7466 * response body and headers and returns its transformed (typically deserialized) version. 7467 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 7468 * GET request, otherwise if a cache instance built with 7469 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 7470 * caching. 7471 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 7472 * that should abort the request when resolved. 7473 * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the 7474 * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 7475 * requests with credentials} for more information. 7476 * - **responseType** - `{string}` - see {@link 7477 * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. 7478 * 7479 * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the 7480 * standard `then` method and two http specific methods: `success` and `error`. The `then` 7481 * method takes two arguments a success and an error callback which will be called with a 7482 * response object. The `success` and `error` methods take a single argument - a function that 7483 * will be called when the request succeeds or fails respectively. The arguments passed into 7484 * these functions are destructured representation of the response object passed into the 7485 * `then` method. The response object has these properties: 7486 * 7487 * - **data** â `{string|Object}` â The response body transformed with the transform 7488 * functions. 7489 * - **status** â `{number}` â HTTP status code of the response. 7490 * - **headers** â `{function([headerName])}` â Header getter function. 7491 * - **config** â `{Object}` â The configuration object that was used to generate the request. 7492 * 7493 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 7494 * requests. This is primarily meant to be used for debugging purposes. 7495 * 7496 * 7497 * @example 7498<example> 7499<file name="index.html"> 7500 <div ng-controller="FetchCtrl"> 7501 <select ng-model="method"> 7502 <option>GET</option> 7503 <option>JSONP</option> 7504 </select> 7505 <input type="text" ng-model="url" size="80"/> 7506 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 7507 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 7508 <button id="samplejsonpbtn" 7509 ng-click="updateModel('JSONP', 7510 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 7511 Sample JSONP 7512 </button> 7513 <button id="invalidjsonpbtn" 7514 ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 7515 Invalid JSONP 7516 </button> 7517 <pre>http status code: {{status}}</pre> 7518 <pre>http response data: {{data}}</pre> 7519 </div> 7520</file> 7521<file name="script.js"> 7522 function FetchCtrl($scope, $http, $templateCache) { 7523 $scope.method = 'GET'; 7524 $scope.url = 'http-hello.html'; 7525 7526 $scope.fetch = function() { 7527 $scope.code = null; 7528 $scope.response = null; 7529 7530 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 7531 success(function(data, status) { 7532 $scope.status = status; 7533 $scope.data = data; 7534 }). 7535 error(function(data, status) { 7536 $scope.data = data || "Request failed"; 7537 $scope.status = status; 7538 }); 7539 }; 7540 7541 $scope.updateModel = function(method, url) { 7542 $scope.method = method; 7543 $scope.url = url; 7544 }; 7545 } 7546</file> 7547<file name="http-hello.html"> 7548 Hello, $http! 7549</file> 7550<file name="protractorTest.js"> 7551 var status = element(by.binding('status')); 7552 var data = element(by.binding('data')); 7553 var fetchBtn = element(by.id('fetchbtn')); 7554 var sampleGetBtn = element(by.id('samplegetbtn')); 7555 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 7556 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 7557 7558 it('should make an xhr GET request', function() { 7559 sampleGetBtn.click(); 7560 fetchBtn.click(); 7561 expect(status.getText()).toMatch('200'); 7562 expect(data.getText()).toMatch(/Hello, \$http!/) 7563 }); 7564 7565 it('should make a JSONP request to angularjs.org', function() { 7566 sampleJsonpBtn.click(); 7567 fetchBtn.click(); 7568 expect(status.getText()).toMatch('200'); 7569 expect(data.getText()).toMatch(/Super Hero!/); 7570 }); 7571
7572 it('should make JSONP request to invalid URL and invoke the error handler', 7573 function() { 7574 invalidJsonpBtn.click(); 7575 fetchBtn.click(); 7576 expect(status.getText()).toMatch('0'); 7577 expect(data.getText()).toMatch('Request failed'); 7578 }); 7579</file> 7580</example> 7581 */ 7582 function $http(requestConfig) { 7583 var config = { 7584 transformRequest: defaults.transformRequest, 7585 transformResponse: defaults.transformResponse 7586 }; 7587 var headers = mergeHeaders(requestConfig); 7588 7589 extend(config, requestConfig); 7590 config.headers = headers; 7591 config.method = uppercase(config.method); 7592 7593 var xsrfValue = urlIsSameOrigin(config.url) 7594 ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] 7595 : undefined; 7596 if (xsrfValue) { 7597 headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 7598 } 7599 7600 7601 var serverRequest = function(config) { 7602 headers = config.headers; 7603 var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); 7604 7605 // strip content-type if data is undefined 7606 if (isUndefined(config.data)) { 7607 forEach(headers, function(value, header) { 7608 if (lowercase(header) === 'content-type') { 7609 delete headers[header]; 7610 } 7611 }); 7612 } 7613 7614 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 7615 config.withCredentials = defaults.withCredentials; 7616 } 7617 7618 // send request 7619 return sendReq(config, reqData, headers).then(transformResponse, transformResponse); 7620 }; 7621 7622 var chain = [serverRequest, undefined]; 7623 var promise = $q.when(config); 7624 7625 // apply interceptors 7626 forEach(reversedInterceptors, function(interceptor) { 7627 if (interceptor.request || interceptor.requestError) { 7628 chain.unshift(interceptor.request, interceptor.requestError); 7629 } 7630 if (interceptor.response || interceptor.responseError) { 7631 chain.push(interceptor.response, interceptor.responseError); 7632 } 7633 }); 7634 7635 while(chain.length) { 7636 var thenFn = chain.shift(); 7637 var rejectFn = chain.shift(); 7638 7639 promise = promise.then(thenFn, rejectFn); 7640 } 7641 7642 promise.success = function(fn) { 7643 promise.then(function(response) { 7644 fn(response.data, response.status, response.headers, config); 7645 }); 7646 return promise; 7647 }; 7648 7649 promise.error = function(fn) { 7650 promise.then(null, function(response) { 7651 fn(response.data, response.status, response.headers, config); 7652 }); 7653 return promise; 7654 }; 7655 7656 return promise; 7657 7658 function transformResponse(response) { 7659 // make a copy since the response must be cacheable 7660 var resp = extend({}, response, { 7661 data: transformData(response.data, response.headers, config.transformResponse) 7662 }); 7663 return (isSuccess(response.status)) 7664 ? resp 7665 : $q.reject(resp); 7666 } 7667 7668 function mergeHeaders(config) { 7669 var defHeaders = defaults.headers, 7670 reqHeaders = extend({}, config.headers), 7671 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 7672 7673 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 7674 7675 // execute if header value is function 7676 execHeaders(defHeaders); 7677 execHeaders(reqHeaders); 7678 7679 // using for-in instead of forEach to avoid unecessary iteration after header has been found 7680 defaultHeadersIteration: 7681 for (defHeaderName in defHeaders) { 7682 lowercaseDefHeaderName = lowercase(defHeaderName); 7683 7684 for (reqHeaderName in reqHeaders) { 7685 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 7686 continue defaultHeadersIteration; 7687 } 7688 } 7689 7690 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 7691 } 7692 7693 return reqHeaders; 7694 7695 function execHeaders(headers) { 7696 var headerContent; 7697
7698 forEach(headers, function(headerFn, header) { 7699 if (isFunction(headerFn)) { 7700 headerContent = headerFn(); 7701 if (headerContent != null) { 7702 headers[header] = headerContent; 7703 } else { 7704 delete headers[header]; 7705 } 7706 } 7707 }); 7708 } 7709 } 7710 } 7711 7712 $http.pendingRequests = []; 7713 7714 /** 7715 * @ngdoc method 7716 * @name ng.$http#get 7717 * @methodOf ng.$http 7718 * 7719 * @description 7720 * Shortcut method to perform `GET` request. 7721 * 7722 * @param {string} url Relative or absolute URL specifying the destination of the request 7723 * @param {Object=} config Optional configuration object 7724 * @returns {HttpPromise} Future object 7725 */ 7726 7727 /** 7728 * @ngdoc method 7729 * @name ng.$http#delete 7730 * @methodOf ng.$http 7731 * 7732 * @description 7733 * Shortcut method to perform `DELETE` request. 7734 * 7735 * @param {string} url Relative or absolute URL specifying the destination of the request 7736 * @param {Object=} config Optional configuration object 7737 * @returns {HttpPromise} Future object 7738 */ 7739 7740 /** 7741 * @ngdoc method 7742 * @name ng.$http#head 7743 * @methodOf ng.$http 7744 * 7745 * @description 7746 * Shortcut method to perform `HEAD` request. 7747 * 7748 * @param {string} url Relative or absolute URL specifying the destination of the request 7749 * @param {Object=} config Optional configuration object 7750 * @returns {HttpPromise} Future object 7751 */ 7752 7753 /** 7754 * @ngdoc method 7755 * @name ng.$http#jsonp 7756 * @methodOf ng.$http 7757 * 7758 * @description 7759 * Shortcut method to perform `JSONP` request. 7760 * 7761 * @param {string} url Relative or absolute URL specifying the destination of the request. 7762 * Should contain `JSON_CALLBACK` string. 7763 * @param {Object=} config Optional configuration object 7764 * @returns {HttpPromise} Future object 7765 */ 7766 createShortMethods('get', 'delete', 'head', 'jsonp'); 7767 7768 /** 7769 * @ngdoc method 7770 * @name ng.$http#post 7771 * @methodOf ng.$http 7772 * 7773 * @description 7774 * Shortcut method to perform `POST` request. 7775 * 7776 * @param {string} url Relative or absolute URL specifying the destination of the request 7777 * @param {*} data Request content 7778 * @param {Object=} config Optional configuration object 7779 * @returns {HttpPromise} Future object 7780 */ 7781 7782 /** 7783 * @ngdoc method 7784 * @name ng.$http#put 7785 * @methodOf ng.$http 7786 * 7787 * @description 7788 * Shortcut method to perform `PUT` request. 7789 * 7790 * @param {string} url Relative or absolute URL specifying the destination of the request 7791 * @param {*} data Request content 7792 * @param {Object=} config Optional configuration object 7793 * @returns {HttpPromise} Future object 7794 */ 7795 createShortMethodsWithData('post', 'put'); 7796 7797 /** 7798 * @ngdoc property 7799 * @name ng.$http#defaults 7800 * @propertyOf ng.$http 7801 * 7802 * @description 7803 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 7804 * default headers, withCredentials as well as request and response transformations. 7805 * 7806 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 7807 */ 7808 $http.defaults = defaults; 7809 7810 7811 return $http; 7812 7813 7814 function createShortMethods(names) { 7815 forEach(arguments, function(name) { 7816 $http[name] = function(url, config) { 7817 return $http(extend(config || {}, { 7818 method: name, 7819 url: url 7820 })); 7821 }; 7822 }); 7823 } 7824 7825 7826 function createShortMethodsWithData(name) { 7827 forEach(arguments, function(name) { 7828 $http[name] = function(url, data, config) { 7829 return $http(extend(config || {}, { 7830 method: name, 7831 url: url, 7832 data: data 7833 })); 7834 }; 7835 }); 7836 } 7837 7838 7839 /** 7840 * Makes the request. 7841 * 7842 * !!! ACCESSES CLOSURE VARS: 7843 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 7844 */ 7845 function sendReq(config, reqData, reqHeaders) { 7846 var deferred = $q.defer(), 7847 promise = deferred.promise, 7848 cache, 7849 cachedResp, 7850 url = buildUrl(config.url, config.params); 7851 7852 $http.pendingRequests.push(config); 7853 promise.then(removePendingReq, removePendingReq); 7854 7855 7856 if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') { 7857 cache = isObject(config.cache) ? config.cache 7858 : isObject(defaults.cache) ? defaults.cache 7859 : defaultCache; 7860 } 7861 7862 if (cache) { 7863 cachedResp = cache.get(url); 7864 if (isDefined(cachedResp)) { 7865 if (cachedResp.then) { 7866 // cached request has already been sent, but there is no response yet 7867 cachedResp.then(removePendingReq, removePendingReq); 7868 return cachedResp; 7869 } else { 7870 // serving from cache 7871 if (isArray(cachedResp)) { 7872 resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2])); 7873 } else { 7874 resolvePromise(cachedResp, 200, {}); 7875 } 7876 } 7877 } else { 7878 // put the promise for the non-transformed response into cache as a placeholder 7879 cache.put(url, promise); 7880 } 7881 } 7882 7883 // if we won't have the response in cache, send the request to the backend 7884 if (isUndefined(cachedResp)) { 7885 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 7886 config.withCredentials, config.responseType); 7887 } 7888 7889 return promise; 7890 7891 7892 /** 7893 * Callback registered to $httpBackend(): 7894 * - caches the response if desired 7895 * - resolves the raw $http promise 7896 * - calls $apply 7897 */ 7898 function done(status, response, headersString) { 7899 if (cache) { 7900 if (isSuccess(status)) { 7901 cache.put(url, [status, response, parseHeaders(headersString)]); 7902 } else { 7903 // remove promise from the cache 7904 cache.remove(url); 7905 } 7906 } 7907 7908 resolvePromise(response, status, headersString); 7909 if (!$rootScope.$$phase) $rootScope.$apply(); 7910 } 7911 7912 7913 /** 7914 * Resolves the raw $http promise. 7915 */ 7916 function resolvePromise(response, status, headers) { 7917 // normalize internal statuses to 0 7918 status = Math.max(status, 0); 7919 7920 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 7921 data: response, 7922 status: status, 7923 headers: headersGetter(headers), 7924 config: config 7925 }); 7926 } 7927 7928 7929 function removePendingReq() { 7930 var idx = indexOf($http.pendingRequests, config); 7931 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 7932 } 7933 } 7934 7935 7936 function buildUrl(url, params) { 7937 if (!params) return url; 7938 var parts = [];
7939 forEachSorted(params, function(value, key) { 7940 if (value === null || isUndefined(value)) return; 7941 if (!isArray(value)) value = [value]; 7942 7943 forEach(value, function(v) { 7944 if (isObject(v)) { 7945 v = toJson(v); 7946 } 7947 parts.push(encodeUriQuery(key) + '=' + 7948 encodeUriQuery(v)); 7949 }); 7950 }); 7951 return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); 7952 } 7953 7954 7955 }]; 7956} 7957 7958function createXhr(method) { 7959 //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest 7960 //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest 7961 //if it is available 7962 if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) || 7963 !window.XMLHttpRequest)) { 7964 return new window.ActiveXObject("Microsoft.XMLHTTP"); 7965 } else if (window.XMLHttpRequest) { 7966 return new window.XMLHttpRequest(); 7967 } 7968 7969 throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); 7970} 7971 7972/** 7973 * @ngdoc object 7974 * @name ng.$httpBackend 7975 * @requires $browser 7976 * @requires $window 7977 * @requires $document 7978 * 7979 * @description 7980 * HTTP backend used by the {@link ng.$http service} that delegates to 7981 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 7982 * 7983 * You should never need to use this service directly, instead use the higher-level abstractions: 7984 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 7985 * 7986 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 7987 * $httpBackend} which can be trained with responses. 7988 */ 7989function $HttpBackendProvider() { 7990 this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { 7991 return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); 7992 }]; 7993} 7994 7995function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 7996 var ABORTED = -1; 7997 7998 // TODO(vojta): fix the signature 7999 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 8000 var status; 8001 $browser.$$incOutstandingRequestCount(); 8002 url = url || $browser.url(); 8003 8004 if (lowercase(method) == 'jsonp') { 8005 var callbackId = '_' + (callbacks.counter++).toString(36); 8006 callbacks[callbackId] = function(data) { 8007 callbacks[callbackId].data = data; 8008 }; 8009 8010 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 8011 function() { 8012 if (callbacks[callbackId].data) { 8013 completeRequest(callback, 200, callbacks[callbackId].data); 8014 } else { 8015 completeRequest(callback, status || -2); 8016 } 8017 callbacks[callbackId] = angular.noop; 8018 }); 8019 } else { 8020 8021 var xhr = createXhr(method); 8022 8023 xhr.open(method, url, true); 8024 forEach(headers, function(value, key) { 8025 if (isDefined(value)) { 8026 xhr.setRequestHeader(key, value); 8027 } 8028 }); 8029 8030 // In IE6 and 7, this might be called synchronously when xhr.send below is called and the 8031 // response is in the cache. the promise api will ensure that to the app code the api is 8032 // always async 8033 xhr.onreadystatechange = function() { 8034 // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by 8035 // xhrs that are resolved while the app is in the background (see #5426). 8036 // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before 8037 // continuing 8038 // 8039 // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and 8040 // Safari respectively. 8041 if (xhr && xhr.readyState == 4) { 8042 var responseHeaders = null, 8043 response = null; 8044 8045 if(status !== ABORTED) { 8046 responseHeaders = xhr.getAllResponseHeaders(); 8047 8048 // responseText is the old-school way of retrieving response
8048(supported by IE8 & 9) 8049 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 8050 response = ('response' in xhr) ? xhr.response : xhr.responseText; 8051 } 8052 8053 completeRequest(callback, 8054 status || xhr.status, 8055 response, 8056 responseHeaders); 8057 } 8058 }; 8059 8060 if (withCredentials) { 8061 xhr.withCredentials = true; 8062 } 8063 8064 if (responseType) { 8065 try { 8066 xhr.responseType = responseType; 8067 } catch (e) { 8068 // WebKit added support for the json responseType value on 09/03/2013 8069 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 8070 // known to throw when setting the value "json" as the response type. Other older 8071 // browsers implementing the responseType 8072 // 8073 // The json response type can be ignored if not supported, because JSON payloads are 8074 // parsed on the client-side regardless. 8075 if (responseType !== 'json') { 8076 throw e; 8077 } 8078 } 8079 } 8080 8081 xhr.send(post || null); 8082 } 8083 8084 if (timeout > 0) { 8085 var timeoutId = $browserDefer(timeoutRequest, timeout); 8086 } else if (timeout && timeout.then) { 8087 timeout.then(timeoutRequest); 8088 } 8089 8090 8091 function timeoutRequest() { 8092 status = ABORTED; 8093 jsonpDone && jsonpDone(); 8094 xhr && xhr.abort(); 8095 } 8096 8097 function completeRequest(callback, status, response, headersString) { 8098 // cancel timeout and subsequent timeout promise resolution 8099 timeoutId && $browserDefer.cancel(timeoutId); 8100 jsonpDone = xhr = null; 8101 8102 // fix status code when it is 0 (0 status is undocumented). 8103 // Occurs when accessing file resources. 8104 // On Android 4.1 stock browser it occurs while retrieving files from application cache. 8105 status = (status === 0) ? (response ? 200 : 404) : status; 8106 8107 // normalize IE bug (http://bugs.jquery.com/ticket/1450) 8108 status = status == 1223 ? 204 : status; 8109 8110 callback(status, response, headersString); 8111 $browser.$$completeOutstandingRequest(noop); 8112 } 8113 }; 8114 8115 function jsonpReq(url, done) { 8116 // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: 8117 // - fetches local scripts via XHR and evals them 8118 // - adds and immediately removes script elements from the document 8119 var script = rawDocument.createElement('script'), 8120 doneWrapper = function() { 8121 script.onreadystatechange = script.onload = script.onerror = null; 8122 rawDocument.body.removeChild(script); 8123 if (done) done(); 8124 }; 8125 8126 script.type = 'text/javascript'; 8127 script.src = url; 8128 8129 if (msie && msie <= 8) { 8130 script.onreadystatechange = function() { 8131 if (/loaded|complete/.test(script.readyState)) { 8132 doneWrapper(); 8133 } 8134 }; 8135 } else { 8136 script.onload = script.onerror = function() { 8137 doneWrapper(); 8138 }; 8139 } 8140 8141 rawDocument.body.appendChild(script); 8142 return doneWrapper; 8143 } 8144} 8145 8146var $interpolateMinErr = minErr('$interpolate'); 8147 8148/** 8149 * @ngdoc object 8150 * @name ng.$interpolateProvider 8151 * @function 8152 * 8153 * @description 8154 *
8155 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 8156 * 8157 * @example 8158<doc:example module="customInterpolationApp"> 8159<doc:source> 8160<script> 8161 var customInterpolationApp = angular.module('customInterpolationApp', []); 8162 8163 customInterpolationApp.config(function($interpolateProvider) { 8164 $interpolateProvider.startSymbol('//'); 8165 $interpolateProvider.endSymbol('//'); 8166 }); 8167 8168 8169 customInterpolationApp.controller('DemoController', function DemoController() { 8170 this.label = "This binding is brought you by // interpolation symbols."; 8171 }); 8172</script> 8173<div ng-app="App" ng-controller="DemoController as demo"> 8174 //demo.label// 8175</div> 8176</doc:source> 8177<doc:protractor> 8178 it('should interpolate binding with custom symbols', function() { 8179 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 8180 }); 8181</doc:protractor> 8182</doc:example> 8183 */ 8184function $InterpolateProvider() { 8185 var startSymbol = '{{'; 8186 var endSymbol = '}}'; 8187 8188 /** 8189 * @ngdoc method 8190 * @name ng.$interpolateProvider#startSymbol 8191 * @methodOf ng.$interpolateProvider 8192 * @description 8193 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 8194 * 8195 * @param {string=} value new value to set the starting symbol to. 8196 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8197 */ 8198 this.startSymbol = function(value){ 8199 if (value) { 8200 startSymbol = value; 8201 return this; 8202 } else { 8203 return startSymbol; 8204 } 8205 }; 8206 8207 /** 8208 * @ngdoc method 8209 * @name ng.$interpolateProvider#endSymbol 8210 * @methodOf ng.$interpolateProvider 8211 * @description 8212 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8213 * 8214 * @param {string=} value new value to set the ending symbol to. 8215 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8216 */ 8217 this.endSymbol = function(value){ 8218 if (value) { 8219 endSymbol = value; 8220 return this; 8221 } else { 8222 return endSymbol; 8223 } 8224 }; 8225 8226 8227 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 8228 var startSymbolLength = startSymbol.length, 8229 endSymbolLength = endSymbol.length; 8230 8231 /** 8232 * @ngdoc function 8233 * @name ng.$interpolate 8234 * @function 8235 * 8236 * @requires $parse 8237 * @requires $sce 8238 * 8239 * @description 8240 * 8241 * Compiles a string with markup into an interpolation function. This service is used by the 8242 * HTML {@link ng.$compile $compile} service for data binding. See 8243 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 8244 * interpolation markup. 8245 * 8246 * 8247 <pre> 8248 var $interpolate = ...; // injected 8249 var exp = $interpolate('Hello {{name | uppercase}}!'); 8250 expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); 8251 </pre> 8252 * 8253 * 8254 * @param {string} text The text with markup to interpolate. 8255 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 8256 * embedded expression in order to return an interpolation function. Strings with no 8257 * embedded expression will return null for the interpolation function. 8258 * @param {string=} trustedContext when provided, the returned function passes the interpolated 8259 * result through {@link ng.$sce#methods_getTrusted $sce.getTrusted(interpolatedResult, 8260 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 8261 * provides Strict Contextual Escaping for details. 8262 * @returns {function(context)} an interpolation function which is used to compute the 8263 * interpolated string. The function has these parameters: 8264 * 8265 * * `context`: an object against which any expressions embedded in the strings are evaluated 8266 * against. 8267 * 8268 */ 8269 function $interpolate(text, mustHaveExpression, trustedContext) { 8270 var startIndex, 8271 endIndex, 8272 index = 0, 8273 parts = [], 8274 length = text.length, 8275 hasInterpolation = false, 8276 fn, 8277 exp, 8278 concat = []; 8279 8280 while(index < length) { 8281 if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && 8282 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { 8283 (index != startIndex) && parts.push(text.substring(index, startIndex)); 8284 parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); 8285 fn.exp = exp;
8286 index = endIndex + endSymbolLength; 8287 hasInterpolation = true; 8288 } else { 8289 // we did not find anything, so we have to add the remainder to the parts array 8290 (index != length) && parts.push(text.substring(index)); 8291 index = length; 8292 } 8293 } 8294 8295 if (!(length = parts.length)) { 8296 // we added, nothing, must have been an empty string. 8297 parts.push(''); 8298 length = 1; 8299 } 8300 8301 // Concatenating expressions makes it hard to reason about whether some combination of 8302 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 8303 // single expression be used for iframe[src], object[src], etc., we ensure that the value 8304 // that's used is assigned or constructed by some JS code somewhere that is more testable or 8305 // make it obvious that you bound the value to some user controlled value. This helps reduce 8306 // the load when auditing for XSS issues. 8307 if (trustedContext && parts.length > 1) { 8308 throw $interpolateMinErr('noconcat', 8309 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 8310 "interpolations that concatenate multiple expressions when a trusted value is " + 8311 "required. See http://docs.angularjs.org/api/ng.$sce", text); 8312 } 8313 8314 if (!mustHaveExpression || hasInterpolation) { 8315 concat.length = length; 8316 fn = function(context) { 8317 try { 8318 for(var i = 0, ii = length, part; i<ii; i++) { 8319 if (typeof (part = parts[i]) == 'function') { 8320 part = part(context); 8321 if (trustedContext) { 8322 part = $sce.getTrusted(trustedContext, part); 8323 } else { 8324 part = $sce.valueOf(part); 8325 } 8326 if (part === null || isUndefined(part)) { 8327 part = ''; 8328 } else if (typeof part != 'string') { 8329 part = toJson(part); 8330 } 8331 } 8332 concat[i] = part; 8333 } 8334 return concat.join(''); 8335 } 8336 catch(err) { 8337 var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, 8338 err.toString()); 8339 $exceptionHandler(newErr); 8340 } 8341 }; 8342 fn.exp = text; 8343 fn.parts = parts; 8344 return fn; 8345 } 8346 } 8347 8348 8349 /** 8350 * @ngdoc method 8351 * @name ng.$interpolate#startSymbol 8352 * @methodOf ng.$interpolate 8353 * @description 8354 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 8355 * 8356 * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change 8357 * the symbol. 8358 * 8359 * @returns {string} start symbol. 8360 */ 8361 $interpolate.startSymbol = function() { 8362 return startSymbol; 8363 }; 8364 8365 8366 /** 8367 * @ngdoc method 8368 * @name ng.$interpolate#endSymbol 8369 * @methodOf ng.$interpolate 8370 * @description 8371 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8372 * 8373 * Use {@link ng.$interpolateProvider#methods_endSymbol $interpolateProvider#endSymbol} to change 8374 * the symbol. 8375 * 8376 * @returns {string} start symbol. 8377 */ 8378 $interpolate.endSymbol = function() { 8379 return endSymbol; 8380 }; 8381 8382 return $interpolate; 8383 }]; 8384} 8385 8386function $IntervalProvider() { 8387 this.$get = ['$rootScope', '$window', '$q', 8388 function($rootScope, $window, $q) { 8389 var intervals = {}; 8390 8391 8392 /** 8393 * @ngdoc function 8394 * @name ng.$interval 8395 * 8396 * @description 8397 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 8398 * milliseconds. 8399 * 8400 * The return value of registering an interval function is a promise. This promise will be 8401 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 8402 * run indefinitely if `count` is not defined. The value of the notification will be the 8403 * number of iterations that have run. 8404 * To cancel an interval, call `$interval.cancel(promise)`. 8405 * 8406 * In tests you can use {@link ngMock.$interval#methods_flush `$interval.flush(millis)`} to 8407 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 8408 * time. 8409 * 8410 * <div class="alert alert-warning">
8411 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 8412 * with them. In particular they are not automatically destroyed when a controller's scope or a 8413 * directive's element are destroyed. 8414 * You should take this into consideration and make sure to always cancel the interval at the 8415 * appropriate moment. See the example below for more details on how and when to do this. 8416 * </div> 8417 * 8418 * @param {function()} fn A function that should be called repeatedly. 8419 * @param {number} delay Number of milliseconds between each function call. 8420 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 8421 * indefinitely. 8422 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 8423 * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block. 8424 * @returns {promise} A promise which will be notified on each iteration. 8425 * 8426 * @example 8427 <doc:example module="time"> 8428 <doc:source> 8429 <script> 8430 function Ctrl2($scope,$interval) { 8431 $scope.format = 'M/d/yy h:mm:ss a'; 8432 $scope.blood_1 = 100; 8433 $scope.blood_2 = 120; 8434 8435 var stop; 8436 $scope.fight = function() { 8437 // Don't start a new fight if we are already fighting 8438 if ( angular.isDefined(stop) ) return; 8439 8440 stop = $interval(function() { 8441 if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 8442 $scope.blood_1 = $scope.blood_1 - 3; 8443 $scope.blood_2 = $scope.blood_2 - 4; 8444 } else { 8445 $scope.stopFight(); 8446 } 8447 }, 100); 8448 }; 8449 8450 $scope.stopFight = function() { 8451 if (angular.isDefined(stop)) { 8452 $interval.cancel(stop); 8453 stop = undefined; 8454 } 8455 }; 8456 8457 $scope.resetFight = function() { 8458 $scope.blood_1 = 100; 8459 $scope.blood_2 = 120; 8460 } 8461 8462 $scope.$on('$destroy', function() { 8463 // Make sure that the interval is destroyed too 8464 $scope.stopFight(); 8465 }); 8466 } 8467 8468 angular.module('time', []) 8469 // Register the 'myCurrentTime' directive factory method. 8470 // We inject $interval and dateFilter service since the factory method is DI. 8471 .directive('myCurrentTime', function($interval, dateFilter) { 8472 // return the directive link function. (compile function not needed) 8473 return function(scope, element, attrs) { 8474 var format, // date format 8475 stopTime; // so that we can cancel the time updates 8476 8477 // used to update the UI 8478 function updateTime() { 8479 element.text(dateFilter(new Date(), format)); 8480 } 8481 8482 // watch the expression, and update the UI on change. 8483 scope.$watch(attrs.myCurrentTime, function(value) { 8484 format = value; 8485 updateTime(); 8486 }); 8487 8488 stopTime = $interval(updateTime, 1000); 8489 8490 // listen on DOM destroy (removal) event, and cancel the next UI update 8491 // to prevent updating time ofter the DOM element was removed. 8492 element.bind('$destroy', function() { 8493 $interval.cancel(stopTime); 8494 }); 8495 } 8496 }); 8497 </script> 8498 8499 <div> 8500 <div ng-controller="Ctrl2"> 8501 Date format: <input ng-model="format"> <hr/> 8502 Current time is: <span my-current-time="format"></span> 8503 <hr/> 8504 Blood 1 : <font color='red'>{{blood_1}}</font> 8505 Blood 2 : <font color='red'>{{blood_2}}</font> 8506 <button type="button" data-ng-click="fight()">Fight</button> 8507 <button type="button" data-ng-click="stopFight()">StopFight</button> 8508 <button type="button" data-ng-click="resetFight()">resetFight</button> 8509 </div> 8510 </div> 8511 8512 </doc:source> 8513 </doc:example> 8514 */
8515 function interval(fn, delay, count, invokeApply) { 8516 var setInterval = $window.setInterval, 8517 clearInterval = $window.clearInterval, 8518 deferred = $q.defer(), 8519 promise = deferred.promise, 8520 iteration = 0, 8521 skipApply = (isDefined(invokeApply) && !invokeApply); 8522 8523 count = isDefined(count) ? count : 0; 8524 8525 promise.then(null, null, fn); 8526 8527 promise.$$intervalId = setInterval(function tick() { 8528 deferred.notify(iteration++); 8529 8530 if (count > 0 && iteration >= count) { 8531 deferred.resolve(iteration); 8532 clearInterval(promise.$$intervalId); 8533 delete intervals[promise.$$intervalId]; 8534 } 8535 8536 if (!skipApply) $rootScope.$apply(); 8537 8538 }, delay); 8539 8540 intervals[promise.$$intervalId] = deferred; 8541 8542 return promise; 8543 } 8544 8545 8546 /** 8547 * @ngdoc function 8548 * @name ng.$interval#cancel 8549 * @methodOf ng.$interval 8550 * 8551 * @description 8552 * Cancels a task associated with the `promise`. 8553 * 8554 * @param {number} promise Promise returned by the `$interval` function. 8555 * @returns {boolean} Returns `true` if the task was successfully canceled. 8556 */ 8557 interval.cancel = function(promise) { 8558 if (promise && promise.$$intervalId in intervals) { 8559 intervals[promise.$$intervalId].reject('canceled'); 8560 clearInterval(promise.$$intervalId); 8561 delete intervals[promise.$$intervalId]; 8562 return true; 8563 } 8564 return false; 8565 }; 8566 8567 return interval; 8568 }]; 8569} 8570 8571/** 8572 * @ngdoc object 8573 * @name ng.$locale 8574 * 8575 * @description 8576 * $locale service provides localization rules for various Angular components. As of right now the 8577 * only public api is: 8578 * 8579 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 8580 */ 8581function $LocaleProvider(){ 8582 this.$get = function() { 8583 return { 8584 id: 'en-us', 8585 8586 NUMBER_FORMATS: { 8587 DECIMAL_SEP: '.', 8588 GROUP_SEP: ',', 8589 PATTERNS: [ 8590 { // Decimal Pattern 8591 minInt: 1, 8592 minFrac: 0, 8593 maxFrac: 3, 8594 posPre: '', 8595 posSuf: '', 8596 negPre: '-', 8597 negSuf: '', 8598 gSize: 3, 8599 lgSize: 3 8600 },{ //Currency Pattern 8601 minInt: 1, 8602 minFrac: 2, 8603 maxFrac: 2, 8604 posPre: '\u00A4', 8605 posSuf: '', 8606 negPre: '(\u00A4', 8607 negSuf: ')', 8608 gSize: 3, 8609 lgSize: 3 8610 } 8611 ], 8612 CURRENCY_SYM: '$' 8613 }, 8614 8615 DATETIME_FORMATS: { 8616 MONTH: 8617 'January,February,March,April,May,June,July,August,September,October,November,December' 8618 .split(','), 8619 SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), 8620 DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), 8621 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), 8622 AMPMS: ['AM','PM'], 8623 medium: 'MMM d, y h:mm:ss a', 8624 short: 'M/d/yy h:mm a', 8625 fullDate: 'EEEE, MMMM d, y', 8626 longDate: 'MMMM d, y', 8627 mediumDate: 'MMM d, y', 8628 shortDate: 'M/d/yy', 8629 mediumTime: 'h:mm:ss a', 8630 shortTime: 'h:mm a' 8631 }, 8632 8633 pluralCat: function(num) { 8634 if (num === 1) { 8635 return 'one'; 8636 } 8637 return 'other'; 8638 } 8639 }; 8640 }; 8641} 8642 8643var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 8644 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 8645var $locationMinErr = minErr('$location'); 8646 8647 8648/** 8649 * Encode path using encodeUriSegment, ignoring forward slashes 8650 * 8651 * @param {string} path Path to encode 8652 * @returns {string} 8653 */ 8654function encodePath(path) { 8655 var segments = path.split('/'), 8656 i = segments.length; 8657 8658 while (i--) { 8659 segments[i] = encodeUriSegment(segments[i]); 8660 } 8661 8662 return segments.join('/'); 8663} 8664 8665function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
8666 var parsedUrl = urlResolve(absoluteUrl, appBase); 8667 8668 locationObj.$$protocol = parsedUrl.protocol; 8669 locationObj.$$host = parsedUrl.hostname; 8670 locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 8671} 8672 8673 8674function parseAppUrl(relativeUrl, locationObj, appBase) { 8675 var prefixed = (relativeUrl.charAt(0) !== '/'); 8676 if (prefixed) { 8677 relativeUrl = '/' + relativeUrl; 8678 } 8679 var match = urlResolve(relativeUrl, appBase); 8680 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 8681 match.pathname.substring(1) : match.pathname); 8682 locationObj.$$search = parseKeyValue(match.search); 8683 locationObj.$$hash = decodeURIComponent(match.hash); 8684 8685 // make sure path starts with '/'; 8686 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 8687 locationObj.$$path = '/' + locationObj.$$path; 8688 } 8689} 8690 8691 8692/** 8693 * 8694 * @param {string} begin 8695 * @param {string} whole 8696 * @returns {string} returns text from whole after begin or undefined if it does not begin with 8697 * expected string. 8698 */ 8699function beginsWith(begin, whole) { 8700 if (whole.indexOf(begin) === 0) { 8701 return whole.substr(begin.length); 8702 } 8703} 8704 8705 8706function stripHash(url) { 8707 var index = url.indexOf('#'); 8708 return index == -1 ? url : url.substr(0, index); 8709} 8710 8711 8712function stripFile(url) { 8713 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 8714} 8715 8716/* return the server only (scheme://host:port) */ 8717function serverBase(url) { 8718 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 8719} 8720 8721 8722/** 8723 * LocationHtml5Url represents an url 8724 * This object is exposed as $location service when HTML5 mode is enabled and supported 8725 * 8726 * @constructor 8727 * @param {string} appBase application base URL 8728 * @param {string} basePrefix url path prefix 8729 */ 8730function LocationHtml5Url(appBase, basePrefix) { 8731 this.$$html5 = true; 8732 basePrefix = basePrefix || ''; 8733 var appBaseNoFile = stripFile(appBase); 8734 parseAbsoluteUrl(appBase, this, appBase); 8735 8736 8737 /** 8738 * Parse given html5 (regular) url string into properties 8739 * @param {string} newAbsoluteUrl HTML5 url 8740 * @private 8741 */ 8742 this.$$parse = function(url) { 8743 var pathUrl = beginsWith(appBaseNoFile, url); 8744 if (!isString(pathUrl)) { 8745 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 8746 appBaseNoFile); 8747 } 8748 8749 parseAppUrl(pathUrl, this, appBase); 8750 8751 if (!this.$$path) { 8752 this.$$path = '/'; 8753 } 8754 8755 this.$$compose(); 8756 }; 8757 8758 /** 8759 * Compose url and update `absUrl` property 8760 * @private 8761 */ 8762 this.$$compose = function() { 8763 var search = toKeyValue(this.$$search), 8764 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 8765 8766 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 8767 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 8768 }; 8769 8770 this.$$rewrite = function(url) { 8771 var appUrl, prevAppUrl; 8772 8773 if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { 8774 prevAppUrl = appUrl; 8775 if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { 8776 return appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 8777 } else { 8778 return appBase + prevAppUrl; 8779 } 8780 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { 8781 return appBaseNoFile + appUrl; 8782 } else if (appBaseNoFile == url + '/') { 8783 return appBaseNoFile; 8784 } 8785 }; 8786} 8787 8788 8789/** 8790 * LocationHashbangUrl represents url 8791 * This object is exposed as $location service when developer doesn't opt into html5 mode. 8792 * It also serves as the base class for html5 mode fallback on legacy browsers. 8793 * 8794 * @constructor 8795 * @param {string} appBase application base URL 8796 * @param {string} hashPrefix hashbang prefix 8797 */ 8798function LocationHashbangUrl(appBase, hashPrefix) { 8799 var appBaseNoFile = stripFile(appBase); 8800 8801 parseAbsoluteUrl(appBase, this, appBase); 8802 8803 8804 /** 8805 * Parse given hashbang url into properties 8806 * @param {string} url Hashbang url 8807 * @private 8808 */ 8809 this.$$parse = function(url) { 8810 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 8811 var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' 8812 ? beginsWith(hashPrefix, withoutBaseUrl) 8813 : (this.$$html5) 8814 ? withoutBaseUrl 8815 : ''; 8816 8817 if (!isString(withoutHashUrl)) { 8818 throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, 8819 hashPrefix); 8820 } 8821 parseAppUrl(withoutHashUrl, this, appBase); 8822 8823 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 8824 8825 this.$$compose(); 8826 8827 /* 8828 * In Windows, on an anchor node on documents loaded from 8829 * the filesystem, the browser will return a pathname
8830 * prefixed with the drive name ('/C:/path') when a 8831 * pathname without a drive is set: 8832 * * a.setAttribute('href', '/foo') 8833 * * a.pathname === '/C:/foo' //true 8834 * 8835 * Inside of Angular, we're always using pathnames that 8836 * do not include drive names for routing. 8837 */ 8838 function removeWindowsDriveName (path, url, base) { 8839 /* 8840 Matches paths for file protocol on windows, 8841 such as /C:/foo/bar, and captures only /foo/bar. 8842 */ 8843 var windowsFilePathExp = /^\/?.*?:(\/.*)/; 8844 8845 var firstPathSegmentMatch; 8846 8847 //Get the relative path from the input URL. 8848 if (url.indexOf(base) === 0) { 8849 url = url.replace(base, ''); 8850 } 8851 8852 /* 8853 * The input URL intentionally contains a 8854 * first path segment that ends with a colon. 8855 */ 8856 if (windowsFilePathExp.exec(url)) { 8857 return path; 8858 } 8859 8860 firstPathSegmentMatch = windowsFilePathExp.exec(path); 8861 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 8862 } 8863 }; 8864 8865 /** 8866 * Compose hashbang url and update `absUrl` property 8867 * @private 8868 */ 8869 this.$$compose = function() { 8870 var search = toKeyValue(this.$$search), 8871 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 8872 8873 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 8874 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 8875 }; 8876 8877 this.$$rewrite = function(url) { 8878 if(stripHash(appBase) == stripHash(url)) { 8879 return url; 8880 } 8881 }; 8882} 8883 8884 8885/** 8886 * LocationHashbangUrl represents url 8887 * This object is exposed as $location service when html5 history api is enabled but the browser 8888 * does not support it. 8889 * 8890 * @constructor 8891 * @param {string} appBase application base URL 8892 * @param {string} hashPrefix hashbang prefix 8893 */ 8894function LocationHashbangInHtml5Url(appBase, hashPrefix) { 8895 this.$$html5 = true; 8896 LocationHashbangUrl.apply(this, arguments); 8897 8898 var appBaseNoFile = stripFile(appBase); 8899 8900 this.$$rewrite = function(url) { 8901 var appUrl; 8902 8903 if ( appBase == stripHash(url) ) { 8904 return url; 8905 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { 8906 return appBase + hashPrefix + appUrl; 8907 } else if ( appBaseNoFile === url + '/') { 8908 return appBaseNoFile; 8909 } 8910 }; 8911} 8912 8913 8914LocationHashbangInHtml5Url.prototype = 8915 LocationHashbangUrl.prototype = 8916 LocationHtml5Url.prototype = { 8917 8918 /** 8919 * Are we in html5 mode? 8920 * @private 8921 */ 8922 $$html5: false, 8923 8924 /** 8925 * Has any change been replacing ? 8926 * @private 8927 */ 8928 $$replace: false, 8929 8930 /** 8931 * @ngdoc method 8932 * @name ng.$location#absUrl 8933 * @methodOf ng.$location 8934 * 8935 * @description 8936 * This method is getter only. 8937 * 8938 * Return full url representation with all segments encoded according to rules specified in 8939 * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}. 8940 * 8941 * @return {string} full url 8942 */ 8943 absUrl: locationGetter('$$absUrl'), 8944 8945 /** 8946 * @ngdoc method 8947 * @name ng.$location#url 8948 * @methodOf ng.$location 8949 * 8950 * @description 8951 * This method is getter / setter. 8952 * 8953 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 8954 * 8955 * Change path, search and hash, when called with parameter and return `$location`. 8956 * 8957 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 8958 * @param {string=} replace The path that will be changed 8959 * @return {string} url 8960 */ 8961 url: function(url, replace) { 8962 if (isUndefined(url)) 8963 return this.$$url; 8964 8965 var match = PATH_MATCH.exec(url); 8966 if (match[1]) this.path(decodeURIComponent(match[1])); 8967 if (match[2] || match[1]) this.search(match[3] || ''); 8968 this.hash(match[5] || '', replace); 8969 8970 return this; 8971 }, 8972 8973 /** 8974 * @ngdoc method 8975 * @name ng.$location#protocol 8976 * @methodOf ng.$location 8977 * 8978 * @description 8979 * This method is getter only. 8980 * 8981 * Return protocol of current url. 8982 * 8983 * @return {string} protocol of current url 8984 */ 8985 protocol: locationGetter('$$protocol'), 8986 8987 /** 8988 * @ngdoc method 8989 * @name ng.$location#host 8990 * @methodOf ng.$location 8991 * 8992 * @description 8993 * This method is getter only. 8994 * 8995 * Return host of current url. 8996 * 8997 * @return {string} host of current url. 8998 */ 8999 host: locationGetter('$$host'), 9000 9001 /** 9002 * @ngdoc method 9003 * @name ng.$location#port 9004 * @methodOf ng.$location 9005 * 9006 * @description 9007 * This method is getter only. 9008 * 9009 * Return port of current url. 9010 * 9011 * @return {Number} port 9012 */ 9013 port: locationGetter('$$port'), 9014 9015 /** 9016 * @ngdoc method 9017 * @name ng.$location#path 9018 * @methodOf ng.$location 9019 * 9020 * @description 9021 * This method is getter / setter. 9022 * 9023 * Return path of current url when called without any parameter. 9024 * 9025 * Change path when called with parameter and return `$location`. 9026 * 9027 * Note: Path should always begin with forward slash (/), this method will add the forward slash 9028 * if it is missing. 9029 * 9030 * @param {string=} path New path 9031 * @return {string} path 9032 */ 9033 path: locationGetterSetter('$$path', function(path) { 9034 return path.charAt(0) == '/' ? path : '/' + path; 9035 }), 9036 9037 /** 9038 * @ngdoc method 9039 * @name ng.$location#search 9040 * @methodOf ng.$location 9041 * 9042 * @description 9043 * This method is getter / setter. 9044 * 9045 * Return search part (as object) of current url when called without any parameter. 9046 * 9047 * Change search part when called with parameter and return `$location`. 9048 * 9049 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 9050 * hash object. Hash object may contain an array of values, which will be decoded as duplicates in 9051 * the url. 9052 * 9053 * @param {(string|Array<string>)=} paramValue If `search` is a string, then `paramValue` will override only a 9054 * single search parameter. If `paramValue` is an array, it will set the parameter as a 9055 * comma-separated value. If `paramValue` is `null`, the parameter will be deleted. 9056 * 9057 * @return {string} search 9058 */ 9059 search: function(search, paramValue) { 9060 switch (arguments.length) { 9061 case 0: 9062 return this.$$search; 9063 case 1: 9064 if (isString(search)) { 9065 this.$$search = parseKeyValue(search); 9066 } else if (isObject(search)) { 9067 this.$$search = search; 9068 } else { 9069 throw $locationMinErr('isrcharg', 9070 'The first argument of the `$location#search()` call must be a string or an object.'); 9071 } 9072 break; 9073 default: 9074 if (isUndefined(paramValue) || paramValue === null) { 9075 delete this.$$search[search];
vendor: 5,194 bytes, lines 9076-9253
9076 } else { 9077 this.$$search[search] = paramValue; 9078 } 9079 } 9080 9081 this.$$compose(); 9082 return this; 9083 }, 9084 9085 /** 9086 * @ngdoc method 9087 * @name ng.$location#hash 9088 * @methodOf ng.$location 9089 * 9090 * @description 9091 * This method is getter / setter. 9092 * 9093 * Return hash fragment when called without any parameter. 9094 * 9095 * Change hash fragment when called with parameter and return `$location`. 9096 * 9097 * @param {string=} hash New hash fragment 9098 * @return {string} hash 9099 */ 9100 hash: locationGetterSetter('$$hash', identity), 9101 9102 /** 9103 * @ngdoc method 9104 * @name ng.$location#replace 9105 * @methodOf ng.$location 9106 * 9107 * @description 9108 * If called, all changes to $location during current `$digest` will be replacing current history 9109 * record, instead of adding new one. 9110 */ 9111 replace: function() { 9112 this.$$replace = true; 9113 return this; 9114 } 9115}; 9116 9117function locationGetter(property) { 9118 return function() { 9119 return this[property]; 9120 }; 9121} 9122 9123 9124function locationGetterSetter(property, preprocess) { 9125 return function(value) { 9126 if (isUndefined(value)) 9127 return this[property]; 9128 9129 this[property] = preprocess(value); 9130 this.$$compose(); 9131 9132 return this; 9133 }; 9134} 9135 9136 9137/** 9138 * @ngdoc object 9139 * @name ng.$location 9140 * 9141 * @requires $browser 9142 * @requires $sniffer 9143 * @requires $rootElement 9144 * 9145 * @description 9146 * The $location service parses the URL in the browser address bar (based on the 9147 * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL 9148 * available to your application. Changes to the URL in the address bar are reflected into 9149 * $location service and changes to $location are reflected into the browser address bar. 9150 * 9151 * **The $location service:** 9152 * 9153 * - Exposes the current URL in the browser address bar, so you can 9154 * - Watch and observe the URL. 9155 * - Change the URL. 9156 * - Synchronizes the URL with the browser when the user 9157 * - Changes the address bar. 9158 * - Clicks the back or forward button (or clicks a History link). 9159 * - Clicks on a link. 9160 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 9161 * 9162 * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular 9163 * Services: Using $location} 9164 */ 9165 9166/** 9167 * @ngdoc object 9168 * @name ng.$locationProvider 9169 * @description 9170 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 9171 */ 9172function $LocationProvider(){ 9173 var hashPrefix = '', 9174 html5Mode = false; 9175 9176 /** 9177 * @ngdoc property 9178 * @name ng.$locationProvider#hashPrefix 9179 * @methodOf ng.$locationProvider 9180 * @description 9181 * @param {string=} prefix Prefix for hash part (containing path and search) 9182 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9183 */ 9184 this.hashPrefix = function(prefix) { 9185 if (isDefined(prefix)) { 9186 hashPrefix = prefix; 9187 return this; 9188 } else { 9189 return hashPrefix; 9190 } 9191 }; 9192 9193 /** 9194 * @ngdoc property 9195 * @name ng.$locationProvider#html5Mode 9196 * @methodOf ng.$locationProvider 9197 * @description 9198 * @param {boolean=} mode Use HTML5 strategy if available. 9199 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9200 */ 9201 this.html5Mode = function(mode) { 9202 if (isDefined(mode)) { 9203 html5Mode = mode; 9204 return this; 9205 } else { 9206 return html5Mode; 9207 } 9208 }; 9209 9210 /** 9211 * @ngdoc event 9212 * @name ng.$location#$locationChangeStart 9213 * @eventOf ng.$location 9214 * @eventType broadcast on root scope 9215 * @description 9216 * Broadcasted before a URL will change. This change can be prevented by calling 9217 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#methods_$on} for more 9218 * details about event object. Upon successful change 9219 * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. 9220 * 9221 * @param {Object} angularEvent Synthetic event object. 9222 * @param {string} newUrl New URL 9223 * @param {string=} oldUrl URL that was before it was changed. 9224 */ 9225 9226 /** 9227 * @ngdoc event 9228 * @name ng.$location#$locationChangeSuccess 9229 * @eventOf ng.$location 9230 * @eventType broadcast on root scope 9231 * @description 9232 * Broadcasted after a URL was changed. 9233 * 9234 * @param {Object} angularEvent Synthetic event object. 9235 * @param {string} newUrl New URL 9236 * @param {string=} oldUrl URL that was before it was changed. 9237 */ 9238 9239 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', 9240 function( $rootScope, $browser, $sniffer, $rootElement) { 9241 var $location, 9242 LocationMode, 9243 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 9244 initialUrl = $browser.url(), 9245 appBase; 9246 9247 if (html5Mode) { 9248 appBase = serverBase(initialUrl) + (baseHref || '/'); 9249 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 9250 } else { 9251 appBase = stripHash(initialUrl); 9252 LocationMode = LocationHashbangUrl; 9253 }
9254 $location = new LocationMode(appBase, '#' + hashPrefix); 9255 $location.$$parse($location.$$rewrite(initialUrl)); 9256 9257 $rootElement.on('click', function(event) { 9258 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 9259 // currently we open nice url link and redirect then 9260 9261 if (event.ctrlKey || event.metaKey || event.which == 2) return; 9262 9263 var elm = jqLite(event.target); 9264 9265 // traverse the DOM up to find first A tag 9266 while (lowercase(elm[0].nodeName) !== 'a') { 9267 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 9268 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 9269 } 9270 9271 var absHref = elm.prop('href'); 9272 9273 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 9274 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 9275 // an animation. 9276 absHref = urlResolve(absHref.animVal).href; 9277 } 9278 9279 var rewrittenUrl = $location.$$rewrite(absHref); 9280 9281 if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) { 9282 event.preventDefault(); 9283 if (rewrittenUrl != $browser.url()) { 9284 // update location manually 9285 $location.$$parse(rewrittenUrl); 9286 $rootScope.$apply(); 9287 // hack to work around FF6 bug 684208 when scenario runner clicks on links 9288 window.angular['ff-684208-preventDefault'] = true; 9289 } 9290 } 9291 }); 9292 9293 9294 // rewrite hashbang url <> html5 url 9295 if ($location.absUrl() != initialUrl) { 9296 $browser.url($location.absUrl(), true); 9297 } 9298 9299 // update $location when $browser url changes 9300 $browser.onUrlChange(function(newUrl) { 9301 if ($location.absUrl() != newUrl) { 9302 $rootScope.$evalAsync(function() { 9303 var oldUrl = $location.absUrl(); 9304 9305 $location.$$parse(newUrl); 9306 if ($rootScope.$broadcast('$locationChangeStart', newUrl, 9307 oldUrl).defaultPrevented) { 9308 $location.$$parse(oldUrl); 9309 $browser.url(oldUrl); 9310 } else { 9311 afterLocationChange(oldUrl); 9312 } 9313 }); 9314 if (!$rootScope.$$phase) $rootScope.$digest(); 9315 } 9316 }); 9317 9318 // update browser 9319 var changeCounter = 0; 9320 $rootScope.$watch(function $locationWatch() { 9321 var oldUrl = $browser.url(); 9322 var currentReplace = $location.$$replace; 9323 9324 if (!changeCounter || oldUrl != $location.absUrl()) { 9325 changeCounter++; 9326 $rootScope.$evalAsync(function() { 9327 if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). 9328 defaultPrevented) { 9329 $location.$$parse(oldUrl); 9330 } else { 9331 $browser.url($location.absUrl(), currentReplace); 9332 afterLocationChange(oldUrl); 9333 } 9334 }); 9335 } 9336 $location.$$replace = false; 9337 9338 return changeCounter; 9339 }); 9340 9341 return $location; 9342 9343 function afterLocationChange(oldUrl) { 9344 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); 9345 } 9346}]; 9347} 9348 9349/** 9350 * @ngdoc object 9351 * @name ng.$log 9352 * @requires $window 9353 * 9354 * @description 9355 * Simple service for logging. Default implementation safely writes the message 9356 * into the browser's console (if present). 9357 * 9358 * The main purpose of this service is to simplify debugging and troubleshooting. 9359 * 9360 * The default is to log `debug` messages. You can use 9361 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 9362 * 9363 * @example 9364 <example> 9365 <file name="script.js"> 9366 function LogCtrl($scope, $log) { 9367 $scope.$log = $log; 9368 $scope.message = 'Hello World!'; 9369 } 9370 </file> 9371 <file name="index.html"> 9372 <div ng-controller="LogCtrl"> 9373 <p>Reload this page with open console, enter text and hit the log button...</p> 9374 Message: 9375 <input type="text" ng-model="message"/> 9376 <button ng-click="$log.log(message)">log</button> 9377 <button ng-click="$log.warn(message)">warn</button> 9378 <button ng-click="$log.info(message)">info</button> 9379 <button ng-click="$log.error(message)">error</button> 9380 </div> 9381 </file> 9382 </example> 9383 */ 9384 9385/** 9386 * @ngdoc object 9387 * @name ng.$logProvider 9388 * @description 9389 * Use the `$logProvider` to configure how the application logs messages 9390 */ 9391function $LogProvider(){ 9392 var debug = true, 9393 self = this; 9394 9395 /** 9396 * @ngdoc property 9397 * @name ng.$logProvider#debugEnabled 9398 * @methodOf ng.$logProvider 9399 * @description 9400 * @param {boolean=} flag enable or disable debug level messages 9401 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9402 */ 9403 this.debugEnabled = function(flag) { 9404 if (isDefined(flag)) { 9405 debug = flag; 9406 return this; 9407 } else { 9408 return debug; 9409 } 9410 }; 9411 9412 this.$get = ['$window', function($window){ 9413 return { 9414 /** 9415 * @ngdoc method 9416 * @name ng.$log#log 9417 * @methodOf ng.$log 9418 *
9419 * @description 9420 * Write a log message 9421 */ 9422 log: consoleLog('log'), 9423 9424 /** 9425 * @ngdoc method 9426 * @name ng.$log#info 9427 * @methodOf ng.$log 9428 * 9429 * @description 9430 * Write an information message 9431 */ 9432 info: consoleLog('info'), 9433 9434 /** 9435 * @ngdoc method 9436 * @name ng.$log#warn 9437 * @methodOf ng.$log 9438 * 9439 * @description 9440 * Write a warning message 9441 */ 9442 warn: consoleLog('warn'), 9443 9444 /** 9445 * @ngdoc method 9446 * @name ng.$log#error 9447 * @methodOf ng.$log 9448 * 9449 * @description 9450 * Write an error message 9451 */ 9452 error: consoleLog('error'), 9453 9454 /** 9455 * @ngdoc method 9456 * @name ng.$log#debug 9457 * @methodOf ng.$log 9458 * 9459 * @description 9460 * Write a debug message 9461 */ 9462 debug: (function () { 9463 var fn = consoleLog('debug'); 9464 9465 return function() { 9466 if (debug) { 9467 fn.apply(self, arguments); 9468 } 9469 }; 9470 }()) 9471 }; 9472 9473 function formatError(arg) { 9474 if (arg instanceof Error) { 9475 if (arg.stack) { 9476 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 9477 ? 'Error: ' + arg.message + '\n' + arg.stack 9478 : arg.stack; 9479 } else if (arg.sourceURL) { 9480 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 9481 } 9482 } 9483 return arg; 9484 } 9485 9486 function consoleLog(type) { 9487 var console = $window.console || {}, 9488 logFn = console[type] || console.log || noop, 9489 hasApply = false; 9490 9491 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 9492 // The reason behind this is that console.log has type "object" in IE8... 9493 try { 9494 hasApply = !! logFn.apply; 9495 } catch (e) {} 9496 9497 if (hasApply) { 9498 return function() { 9499 var args = []; 9500 forEach(arguments, function(arg) { 9501 args.push(formatError(arg)); 9502 }); 9503 return logFn.apply(console, args); 9504 }; 9505 } 9506 9507 // we are IE which either doesn't have window.console => this is noop and we do nothing, 9508 // or we are IE where console.log doesn't have apply so we log at least first 2 args 9509 return function(arg1, arg2) { 9510 logFn(arg1, arg2 == null ? '' : arg2); 9511 }; 9512 } 9513 }]; 9514} 9515 9516var $parseMinErr = minErr('$parse'); 9517var promiseWarningCache = {}; 9518var promiseWarning; 9519 9520// Sandboxing Angular Expressions 9521// ------------------------------ 9522// Angular expressions are generally considered safe because these expressions only have direct 9523// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by 9524// obtaining a reference to native JS functions such as the Function constructor. 9525// 9526// As an example, consider the following Angular expression: 9527// 9528// {}.toString.constructor(alert("evil JS code")) 9529// 9530// We want to prevent this type of access. For the sake of performance, during the lexing phase we 9531// disallow any "dotted" access to any member named "constructor". 9532// 9533// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor 9534// while evaluating the expression, which is a stronger but more expensive test. Since reflective 9535// calls are expensive anyway, this is not such a big deal compared to static dereferencing. 9536// 9537// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 9538// against the expression language, but not to prevent exploits that were enabled by exposing 9539// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good 9540// practice and therefore we are not even trying to protect against interaction with an object 9541// explicitly exposed in this way. 9542// 9543// A developer could foil the name check by aliasing the Function constructor under a different 9544// name on the scope. 9545// 9546// In general, it is not possible to access a Window object from an angular expression unless a 9547// window or some DOM object that has a reference to window is published onto a Scope. 9548 9549function ensureSafeMemberName(name, fullExpression) { 9550 if (name === "constructor") { 9551 throw $parseMinErr('isecfld', 9552 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', 9553 fullExpression); 9554 } 9555 return name; 9556} 9557 9558function ensureSafeObject(obj, fullExpression) { 9559 // nifty check if obj is Function that is fast and works across iframes and other contexts 9560 if (obj) { 9561 if (obj.constructor === obj) { 9562 throw $parseMinErr('isecfn', 9563 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 9564 fullExpression); 9565 } else if (// isWindow(obj) 9566 obj.document && obj.location && obj.alert && obj.setInterval) { 9567 throw $parseMinErr('isecwindow', 9568 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 9569 fullExpression); 9570 } else if (// isElement(obj) 9571 obj.children && (obj.nodeName || (obj.on && obj.find))) { 9572 throw $parseMinErr('isecdom', 9573 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 9574 fullExpression); 9575 } 9576 } 9577 return obj;
9578} 9579 9580var OPERATORS = { 9581 /* jshint bitwise : false */ 9582 'null':function(){return null;}, 9583 'true':function(){return true;}, 9584 'false':function(){return false;}, 9585 undefined:noop, 9586 '+':function(self, locals, a,b){ 9587 a=a(self, locals); b=b(self, locals); 9588 if (isDefined(a)) { 9589 if (isDefined(b)) { 9590 return a + b; 9591 } 9592 return a; 9593 } 9594 return isDefined(b)?b:undefined;}, 9595 '-':function(self, locals, a,b){ 9596 a=a(self, locals); b=b(self, locals); 9597 return (isDefined(a)?a:0)-(isDefined(b)?b:0); 9598 }, 9599 '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, 9600 '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, 9601 '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, 9602 '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, 9603 '=':noop, 9604 '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, 9605 '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, 9606 '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, 9607 '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, 9608 '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);}, 9609 '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, 9610 '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, 9611 '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, 9612 '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, 9613 '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, 9614 '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, 9615// '|':function(self, locals, a,b){return a|b;}, 9616 '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, 9617 '!':function(self, locals, a){return !a(self, locals);} 9618}; 9619/* jshint bitwise: true */ 9620var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 9621 9622 9623///////////////////////////////////////// 9624 9625 9626/** 9627 * @constructor 9628 */ 9629var Lexer = function (options) { 9630 this.options = options; 9631}; 9632 9633Lexer.prototype = { 9634 constructor: Lexer, 9635 9636 lex: function (text) { 9637 this.text = text; 9638 9639 this.index = 0; 9640 this.ch = undefined; 9641 this.lastCh = ':'; // can start regexp 9642 9643 this.tokens = []; 9644 9645 var token; 9646 var json = []; 9647 9648 while (this.index < this.text.length) { 9649 this.ch = this.text.charAt(this.index); 9650 if (this.is('"\'')) { 9651 this.readString(this.ch); 9652 } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { 9653 this.readNumber(); 9654 } else if (this.isIdent(this.ch)) { 9655 this.readIdent(); 9656 // identifiers can only be if the preceding char was a { or , 9657 if (this.was('{,') && json[0] === '{' && 9658 (token = this.tokens[this.tokens.length - 1])) { 9659 token.json = token.text.indexOf('.') === -1; 9660 } 9661 } else if (this.is('(){}[].,;:?')) { 9662 this.tokens.push({ 9663 index: this.index, 9664 text: this.ch, 9665 json: (this.was(':[,') && this.is('{[')) || this.is('}]:,') 9666 }); 9667 if (this.is('{[')) json.unshift(this.ch); 9668 if (this.is('}]')) json.shift(); 9669 this.index++; 9670 } else if (this.isWhitespace(this.ch)) { 9671 this.index++; 9672 continue; 9673 } else { 9674 var ch2 = this.ch + this.peek(); 9675 var ch3 = ch2 + this.peek(2); 9676 var fn = OPERATORS[this.ch]; 9677 var fn2 = OPERATORS[ch2]; 9678 var fn3 = OPERATORS[ch3]; 9679 if (fn3) { 9680 this.tokens.push({index: this.index, text: ch3, fn: fn3}); 9681 this.index += 3; 9682 } else if (fn2) { 9683 this.tokens.push({index: this.index, text: ch2, fn: fn2}); 9684 this.index += 2; 9685 } else if (fn) { 9686 this.tokens.push({ 9687 index: this.index, 9688 text: this.ch, 9689 fn: fn, 9690 json: (this.was('[,:') && this.is('+-')) 9691 }); 9692 this.index += 1;
9693 } else { 9694 this.throwError('Unexpected next character ', this.index, this.index + 1); 9695 } 9696 } 9697 this.lastCh = this.ch; 9698 } 9699 return this.tokens; 9700 }, 9701 9702 is: function(chars) { 9703 return chars.indexOf(this.ch) !== -1; 9704 }, 9705 9706 was: function(chars) { 9707 return chars.indexOf(this.lastCh) !== -1; 9708 }, 9709 9710 peek: function(i) { 9711 var num = i || 1; 9712 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 9713 }, 9714 9715 isNumber: function(ch) { 9716 return ('0' <= ch && ch <= '9'); 9717 }, 9718 9719 isWhitespace: function(ch) { 9720 // IE treats non-breaking space as \u00A0 9721 return (ch === ' ' || ch === '\r' || ch === '\t' || 9722 ch === '\n' || ch === '\v' || ch === '\u00A0'); 9723 }, 9724 9725 isIdent: function(ch) { 9726 return ('a' <= ch && ch <= 'z' || 9727 'A' <= ch && ch <= 'Z' || 9728 '_' === ch || ch === '$'); 9729 }, 9730 9731 isExpOperator: function(ch) { 9732 return (ch === '-' || ch === '+' || this.isNumber(ch)); 9733 }, 9734 9735 throwError: function(error, start, end) { 9736 end = end || this.index; 9737 var colStr = (isDefined(start) 9738 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 9739 : ' ' + end); 9740 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 9741 error, colStr, this.text); 9742 }, 9743 9744 readNumber: function() { 9745 var number = ''; 9746 var start = this.index; 9747 while (this.index < this.text.length) { 9748 var ch = lowercase(this.text.charAt(this.index)); 9749 if (ch == '.' || this.isNumber(ch)) { 9750 number += ch; 9751 } else { 9752 var peekCh = this.peek(); 9753 if (ch == 'e' && this.isExpOperator(peekCh)) { 9754 number += ch; 9755 } else if (this.isExpOperator(ch) && 9756 peekCh && this.isNumber(peekCh) && 9757 number.charAt(number.length - 1) == 'e') { 9758 number += ch; 9759 } else if (this.isExpOperator(ch) && 9760 (!peekCh || !this.isNumber(peekCh)) && 9761 number.charAt(number.length - 1) == 'e') { 9762 this.throwError('Invalid exponent'); 9763 } else { 9764 break; 9765 } 9766 } 9767 this.index++; 9768 } 9769 number = 1 * number; 9770 this.tokens.push({ 9771 index: start, 9772 text: number, 9773 json: true, 9774 fn: function() { return number; } 9775 }); 9776 }, 9777 9778 readIdent: function() { 9779 var parser = this; 9780 9781 var ident = ''; 9782 var start = this.index; 9783 9784 var lastDot, peekIndex, methodName, ch; 9785 9786 while (this.index < this.text.length) { 9787 ch = this.text.charAt(this.index); 9788 if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { 9789 if (ch === '.') lastDot = this.index; 9790 ident += ch; 9791 } else { 9792 break; 9793 } 9794 this.index++; 9795 } 9796 9797 //check if this is not a method invocation and if it is back out to last dot 9798 if (lastDot) { 9799 peekIndex = this.index; 9800 while (peekIndex < this.text.length) { 9801 ch = this.text.charAt(peekIndex); 9802 if (ch === '(') { 9803 methodName = ident.substr(lastDot - start + 1); 9804 ident = ident.substr(0, lastDot - start); 9805 this.index = peekIndex; 9806 break; 9807 } 9808 if (this.isWhitespace(ch)) { 9809 peekIndex++; 9810 } else { 9811 break; 9812 } 9813 } 9814 } 9815 9816 9817 var token = { 9818 index: start, 9819 text: ident 9820 }; 9821 9822 // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn 9823 if (OPERATORS.hasOwnProperty(ident)) { 9824 token.fn = OPERATORS[ident]; 9825 token.json = OPERATORS[ident]; 9826 } else { 9827 var getter = getterFn(ident, this.options, this.text); 9828 token.fn = extend(function(self, locals) { 9829 return (getter(self, locals)); 9830 }, { 9831 assign: function(self, value) { 9832 return setter(self, ident, value, parser.text, parser.options); 9833 } 9834 }); 9835 } 9836 9837 this.tokens.push(token); 9838 9839 if (methodName) { 9840 this.tokens.push({ 9841 index:lastDot, 9842 text: '.', 9843 json: false 9844 }); 9845 this.tokens.push({ 9846 index: lastDot + 1, 9847 text: methodName, 9848 json: false 9849 }); 9850 } 9851 }, 9852 9853 readString: function(quote) { 9854 var start = this.index; 9855 this.index++; 9856 var string = ''; 9857 var rawString = quote; 9858 var escape = false; 9859 while (this.index < this.text.length) { 9860 var ch = this.text.charAt(this.index); 9861 rawString += ch; 9862 if (escape) { 9863 if (ch === 'u') { 9864 var hex = this.text.substring(this.index + 1, this.index + 5); 9865 if (!hex.match(/[\da-f]{4}/i)) 9866 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 9867 this.index += 4;
9868 string += String.fromCharCode(parseInt(hex, 16)); 9869 } else { 9870 var rep = ESCAPE[ch]; 9871 if (rep) { 9872 string += rep; 9873 } else { 9874 string += ch; 9875 } 9876 } 9877 escape = false; 9878 } else if (ch === '\\') { 9879 escape = true; 9880 } else if (ch === quote) { 9881 this.index++; 9882 this.tokens.push({ 9883 index: start, 9884 text: rawString, 9885 string: string, 9886 json: true, 9887 fn: function() { return string; } 9888 }); 9889 return; 9890 } else { 9891 string += ch; 9892 } 9893 this.index++; 9894 } 9895 this.throwError('Unterminated quote', start); 9896 } 9897}; 9898 9899 9900/** 9901 * @constructor 9902 */ 9903var Parser = function (lexer, $filter, options) { 9904 this.lexer = lexer; 9905 this.$filter = $filter; 9906 this.options = options; 9907}; 9908 9909Parser.ZERO = function () { return 0; }; 9910 9911Parser.prototype = { 9912 constructor: Parser, 9913 9914 parse: function (text, json) { 9915 this.text = text; 9916 9917 //TODO(i): strip all the obsolte json stuff from this file 9918 this.json = json; 9919 9920 this.tokens = this.lexer.lex(text); 9921 9922 if (json) { 9923 // The extra level of aliasing is here, just in case the lexer misses something, so that 9924 // we prevent any accidental execution in JSON. 9925 this.assignment = this.logicalOR; 9926 9927 this.functionCall = 9928 this.fieldAccess = 9929 this.objectIndex = 9930 this.filterChain = function() { 9931 this.throwError('is not valid json', {text: text, index: 0}); 9932 }; 9933 } 9934 9935 var value = json ? this.primary() : this.statements(); 9936 9937 if (this.tokens.length !== 0) { 9938 this.throwError('is an unexpected token', this.tokens[0]); 9939 } 9940 9941 value.literal = !!value.literal; 9942 value.constant = !!value.constant; 9943 9944 return value; 9945 }, 9946 9947 primary: function () { 9948 var primary; 9949 if (this.expect('(')) { 9950 primary = this.filterChain(); 9951 this.consume(')'); 9952 } else if (this.expect('[')) { 9953 primary = this.arrayDeclaration(); 9954 } else if (this.expect('{')) { 9955 primary = this.object(); 9956 } else { 9957 var token = this.expect(); 9958 primary = token.fn; 9959 if (!primary) { 9960 this.throwError('not a primary expression', token); 9961 } 9962 if (token.json) { 9963 primary.constant = true; 9964 primary.literal = true; 9965 } 9966 } 9967 9968 var next, context; 9969 while ((next = this.expect('(', '[', '.'))) { 9970 if (next.text === '(') { 9971 primary = this.functionCall(primary, context); 9972 context = null; 9973 } else if (next.text === '[') { 9974 context = primary; 9975 primary = this.objectIndex(primary); 9976 } else if (next.text === '.') { 9977 context = primary; 9978 primary = this.fieldAccess(primary); 9979 } else { 9980 this.throwError('IMPOSSIBLE'); 9981 } 9982 } 9983 return primary; 9984 }, 9985 9986 throwError: function(msg, token) { 9987 throw $parseMinErr('syntax', 9988 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 9989 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 9990 }, 9991 9992 peekToken: function() { 9993 if (this.tokens.length === 0) 9994 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 9995 return this.tokens[0]; 9996 }, 9997 9998 peek: function(e1, e2, e3, e4) { 9999 if (this.tokens.length > 0) { 10000 var token = this.tokens[0]; 10001 var t = token.text; 10002 if (t === e1 || t === e2 || t === e3 || t === e4 || 10003 (!e1 && !e2 && !e3 && !e4)) { 10004 return token; 10005 } 10006 } 10007 return false; 10008 }, 10009 10010 expect: function(e1, e2, e3, e4){ 10011 var token = this.peek(e1, e2, e3, e4); 10012 if (token) { 10013 if (this.json && !token.json) { 10014 this.throwError('is not valid json', token); 10015 } 10016 this.tokens.shift(); 10017 return token; 10018 } 10019 return false; 10020 }, 10021 10022 consume: function(e1){ 10023 if (!this.expect(e1)) { 10024 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 10025 } 10026 }, 10027 10028 unaryFn: function(fn, right) { 10029 return extend(function(self, locals) { 10030 return fn(self, locals, right); 10031 }, { 10032 constant:right.constant 10033 });
10034 }, 10035 10036 ternaryFn: function(left, middle, right){ 10037 return extend(function(self, locals){ 10038 return left(self, locals) ? middle(self, locals) : right(self, locals); 10039 }, { 10040 constant: left.constant && middle.constant && right.constant 10041 }); 10042 }, 10043 10044 binaryFn: function(left, fn, right) { 10045 return extend(function(self, locals) { 10046 return fn(self, locals, left, right); 10047 }, { 10048 constant:left.constant && right.constant 10049 }); 10050 }, 10051 10052 statements: function() { 10053 var statements = []; 10054 while (true) { 10055 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 10056 statements.push(this.filterChain()); 10057 if (!this.expect(';')) { 10058 // optimize for the common case where there is only one statement. 10059 // TODO(size): maybe we should not support multiple statements? 10060 return (statements.length === 1) 10061 ? statements[0] 10062 : function(self, locals) { 10063 var value; 10064 for (var i = 0; i < statements.length; i++) { 10065 var statement = statements[i]; 10066 if (statement) { 10067 value = statement(self, locals); 10068 } 10069 } 10070 return value; 10071 }; 10072 } 10073 } 10074 }, 10075 10076 filterChain: function() { 10077 var left = this.expression(); 10078 var token; 10079 while (true) { 10080 if ((token = this.expect('|'))) { 10081 left = this.binaryFn(left, token.fn, this.filter()); 10082 } else { 10083 return left; 10084 } 10085 } 10086 }, 10087 10088 filter: function() { 10089 var token = this.expect(); 10090 var fn = this.$filter(token.text); 10091 var argsFn = []; 10092 while (true) { 10093 if ((token = this.expect(':'))) { 10094 argsFn.push(this.expression()); 10095 } else { 10096 var fnInvoke = function(self, locals, input) { 10097 var args = [input]; 10098 for (var i = 0; i < argsFn.length; i++) { 10099 args.push(argsFn[i](self, locals)); 10100 } 10101 return fn.apply(self, args); 10102 }; 10103 return function() { 10104 return fnInvoke; 10105 }; 10106 } 10107 } 10108 }, 10109 10110 expression: function() { 10111 return this.assignment(); 10112 }, 10113 10114 assignment: function() { 10115 var left = this.ternary(); 10116 var right; 10117 var token; 10118 if ((token = this.expect('='))) { 10119 if (!left.assign) { 10120 this.throwError('implies assignment but [' + 10121 this.text.substring(0, token.index) + '] can not be assigned to', token); 10122 } 10123 right = this.ternary(); 10124 return function(scope, locals) { 10125 return left.assign(scope, right(scope, locals), locals); 10126 }; 10127 } 10128 return left; 10129 }, 10130 10131 ternary: function() { 10132 var left = this.logicalOR(); 10133 var middle; 10134 var token; 10135 if ((token = this.expect('?'))) { 10136 middle = this.ternary(); 10137 if ((token = this.expect(':'))) { 10138 return this.ternaryFn(left, middle, this.ternary()); 10139 } else { 10140 this.throwError('expected :', token); 10141 } 10142 } else { 10143 return left; 10144 } 10145 }, 10146 10147 logicalOR: function() { 10148 var left = this.logicalAND(); 10149 var token; 10150 while (true) { 10151 if ((token = this.expect('||'))) { 10152 left = this.binaryFn(left, token.fn, this.logicalAND()); 10153 } else { 10154 return left; 10155 } 10156 } 10157 }, 10158 10159 logicalAND: function() { 10160 var left = this.equality(); 10161 var token; 10162 if ((token = this.expect('&&'))) { 10163 left = this.binaryFn(left, token.fn, this.logicalAND()); 10164 } 10165 return left; 10166 }, 10167 10168 equality: function() { 10169 var left = this.relational(); 10170 var token; 10171 if ((token = this.expect('==','!=','===','!=='))) { 10172 left = this.binaryFn(left, token.fn, this.equality()); 10173 } 10174 return left; 10175 }, 10176 10177 relational: function() { 10178 var left = this.additive(); 10179 var token; 10180 if ((token = this.expect('<', '>', '<=', '>='))) { 10181 left = this.binaryFn(left, token.fn, this.relational()); 10182 } 10183 return left; 10184 }, 10185 10186 additive: function() { 10187 var left = this.multiplicative(); 10188 var token; 10189 while ((token = this.expect('+','-'))) { 10190 left = this.binaryFn(left, token.fn, this.multiplicative()); 10191 } 10192 return left; 10193 }, 10194 10195 multiplicative: function() { 10196 var left = this.unary(); 10197 var token; 10198 while ((token = this.expect('*','/','%'))) { 10199 left = this.binaryFn(left, token.fn, this.unary()); 10200 } 10201 return left; 10202 }, 10203 10204 unary: function() { 10205 var token; 10206 if (this.expect('+')) { 10207 return this.primary();
10208 } else if ((token = this.expect('-'))) { 10209 return this.binaryFn(Parser.ZERO, token.fn, this.unary()); 10210 } else if ((token = this.expect('!'))) { 10211 return this.unaryFn(token.fn, this.unary()); 10212 } else { 10213 return this.primary(); 10214 } 10215 }, 10216 10217 fieldAccess: function(object) { 10218 var parser = this; 10219 var field = this.expect().text; 10220 var getter = getterFn(field, this.options, this.text); 10221 10222 return extend(function(scope, locals, self) { 10223 return getter(self || object(scope, locals)); 10224 }, { 10225 assign: function(scope, value, locals) { 10226 return setter(object(scope, locals), field, value, parser.text, parser.options); 10227 } 10228 }); 10229 }, 10230 10231 objectIndex: function(obj) { 10232 var parser = this; 10233 10234 var indexFn = this.expression(); 10235 this.consume(']'); 10236 10237 return extend(function(self, locals) { 10238 var o = obj(self, locals), 10239 i = indexFn(self, locals), 10240 v, p; 10241 10242 if (!o) return undefined; 10243 v = ensureSafeObject(o[i], parser.text); 10244 if (v && v.then && parser.options.unwrapPromises) { 10245 p = v; 10246 if (!('$$v' in v)) { 10247 p.$$v = undefined; 10248 p.then(function(val) { p.$$v = val; }); 10249 } 10250 v = v.$$v; 10251 } 10252 return v; 10253 }, { 10254 assign: function(self, value, locals) { 10255 var key = indexFn(self, locals); 10256 // prevent overwriting of Function.constructor which would break ensureSafeObject check 10257 var safe = ensureSafeObject(obj(self, locals), parser.text); 10258 return safe[key] = value; 10259 } 10260 }); 10261 }, 10262 10263 functionCall: function(fn, contextGetter) { 10264 var argsFn = []; 10265 if (this.peekToken().text !== ')') { 10266 do { 10267 argsFn.push(this.expression()); 10268 } while (this.expect(',')); 10269 } 10270 this.consume(')'); 10271 10272 var parser = this; 10273 10274 return function(scope, locals) { 10275 var args = []; 10276 var context = contextGetter ? contextGetter(scope, locals) : scope; 10277 10278 for (var i = 0; i < argsFn.length; i++) { 10279 args.push(argsFn[i](scope, locals)); 10280 } 10281 var fnPtr = fn(scope, locals, context) || noop; 10282 10283 ensureSafeObject(context, parser.text); 10284 ensureSafeObject(fnPtr, parser.text); 10285 10286 // IE stupidity! (IE doesn't have apply for some native functions) 10287 var v = fnPtr.apply 10288 ? fnPtr.apply(context, args) 10289 : fnPtr(args[0], args[1], args[2], args[3], args[4]); 10290 10291 return ensureSafeObject(v, parser.text); 10292 }; 10293 }, 10294 10295 // This is used with json array declaration 10296 arrayDeclaration: function () { 10297 var elementFns = []; 10298 var allConstant = true; 10299 if (this.peekToken().text !== ']') { 10300 do { 10301 var elementFn = this.expression(); 10302 elementFns.push(elementFn); 10303 if (!elementFn.constant) { 10304 allConstant = false; 10305 } 10306 } while (this.expect(',')); 10307 } 10308 this.consume(']'); 10309 10310 return extend(function(self, locals) { 10311 var array = []; 10312 for (var i = 0; i < elementFns.length; i++) { 10313 array.push(elementFns[i](self, locals)); 10314 } 10315 return array; 10316 }, { 10317 literal: true, 10318 constant: allConstant 10319 }); 10320 }, 10321 10322 object: function () { 10323 var keyValues = []; 10324 var allConstant = true; 10325 if (this.peekToken().text !== '}') { 10326 do { 10327 var token = this.expect(), 10328 key = token.string || token.text; 10329 this.consume(':'); 10330 var value = this.expression(); 10331 keyValues.push({key: key, value: value}); 10332 if (!value.constant) { 10333 allConstant = false; 10334 } 10335 } while (this.expect(',')); 10336 } 10337 this.consume('}'); 10338 10339 return extend(function(self, locals) { 10340 var object = {}; 10341 for (var i = 0; i < keyValues.length; i++) { 10342 var keyValue = keyValues[i]; 10343 object[keyValue.key] = keyValue.value(self, locals); 10344 } 10345 return object; 10346 }, { 10347 literal: true, 10348 constant: allConstant 10349 }); 10350 } 10351}; 10352 10353 10354////////////////////////////////////////////////// 10355// Parser helper functions 10356////////////////////////////////////////////////// 10357 10358function setter(obj, path, setValue, fullExp, options) { 10359 //needed? 10360 options = options || {}; 10361 10362 var element = path.split('.'), key; 10363 for (var i = 0; element.length > 1; i++) { 10364 key = ensureSafeMemberName(element.shift(), fullExp); 10365 var propertyObj = obj[key]; 10366 if (!propertyObj) { 10367 propertyObj = {}; 10368 obj[key] = propertyObj; 10369 } 10370 obj = propertyObj; 10371 if (obj.then && options.unwrapPromises) { 10372 promiseWarning(fullExp); 10373 if (!("$$v" in obj)) { 10374 (function(promise) { 10375 promise.then(function(val) { promise.$$v = val; }); } 10376 )(obj); 10377 } 10378 if (obj.$$v === undefined) { 10379 obj.$$v = {}; 10380 } 10381 obj = obj.$$v; 10382 } 10383 } 10384 key = ensureSafeMemberName(element.shift(), fullExp); 10385 obj[key] = setValue; 10386 return setValue; 10387} 10388 10389var getterFnCache = {}; 10390 10391/** 10392 * Implementation of the "Black Hole" variant from: 10393 * - http://jsperf.com/angularjs-parse-getter/4 10394 * - http://jsperf.com/path-evaluation-simplified/7 10395 */ 10396function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { 10397 ensureSafeMemberName(key0, fullExp); 10398 ensureSafeMemberName(key1, fullExp); 10399 ensureSafeMemberName(key2, fullExp); 10400 ensureSafeMemberName(key3, fullExp); 10401 ensureSafeMemberName(key4, fullExp); 10402 10403 return !options.unwrapPromises 10404 ? function cspSafeGetter(scope, locals) { 10405 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; 10406 10407 if (pathVal == null) return pathVal; 10408 pathVal = pathVal[key0]; 10409 10410 if (!key1) return pathVal; 10411 if (pathVal == null) return undefined; 10412 pathVal = pathVal[key1]; 10413 10414 if (!key2) return pathVal; 10415 if (pathVal == null) return undefined; 10416 pathVal = pathVal[key2]; 10417 10418 if (!key3) return pathVal; 10419 if (pathVal == null) return undefined; 10420 pathVal = pathVal[key3]; 10421 10422 if (!key4) return pathVal; 10423 if (pathVal == null) return undefined; 10424 pathVal = pathVal[key4]; 10425 10426 return pathVal; 10427 } 10428 : function cspSafePromiseEnabledGetter(scope, locals) { 10429 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, 10430 promise; 10431 10432 if (pathVal == null) return pathVal; 10433 10434 pathVal = pathVal[key0]; 10435 if (pathVal && pathVal.then) { 10436 promiseWarning(fullExp); 10437 if (!("$$v" in pathVal)) { 10438 promise = pathVal; 10439 promise.$$v = undefined; 10440 promise.then(function(val) { promise.$$v = val; }); 10441 } 10442 pathVal = pathVal.$$v; 10443 } 10444 10445 if (!key1) return pathVal; 10446 if (pathVal == null) return undefined; 10447 pathVal = pathVal[key1]; 10448 if (pathVal && pathVal.then) { 10449 promiseWarning(fullExp); 10450 if (!("$$v" in pathVal)) { 10451 promise = pathVal; 10452 promise.$$v = undefined; 10453 promise.then(function(val) { promise.$$v = val; }); 10454 } 10455 pathVal = pathVal.$$v; 10456 } 10457 10458 if (!key2) return pathVal; 10459 if (pathVal == null) return undefined; 10460 pathVal = pathVal[key2]; 10461 if (pathVal && pathVal.then) { 10462 promiseWarning(fullExp); 10463 if (!("$$v" in pathVal)) { 10464 promise = pathVal; 10465 promise.$$v = undefined; 10466 promise.then(function(val) { promise.$$v = val; }); 10467 } 10468 pathVal = pathVal.$$v; 10469 } 10470 10471 if (!key3) return pathVal; 10472 if (pathVal == null) return undefined; 10473 pathVal = pathVal[key3]; 10474 if (pathVal && pathVal.then) { 10475 promiseWarning(fullExp); 10476 if (!("$$v" in pathVal)) { 10477 promise = pathVal; 10478 promise.$$v = undefined; 10479 promise.then(function(val) { promise.$$v = val; }); 10480 } 10481 pathVal = pathVal.$$v; 10482 } 10483 10484 if (!key4) return pathVal; 10485 if (pathVal == null) return undefined; 10486 pathVal = pathVal[key4]; 10487 if (pathVal && pathVal.then) { 10488 promiseWarning(fullExp); 10489 if (!("$$v" in pathVal)) { 10490 promise = pathVal; 10491 promise.$$v = undefined; 10492 promise.then(function(val) { promise.$$v = val; }); 10493 } 10494 pathVal = pathVal.$$v; 10495 } 10496 return pathVal; 10497 }; 10498} 10499 10500function simpleGetterFn1(key0, fullExp) { 10501 ensureSafeMemberName(key0, fullExp); 10502 10503 return function simpleGetterFn1(scope, locals) { 10504 if (scope == null) return undefined; 10505 return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; 10506 }; 10507} 10508 10509function simpleGetterFn2(key0, key1, fullExp) { 10510 ensureSafeMemberName(key0, fullExp); 10511 ensureSafeMemberName(key1, fullExp); 10512 10513 return function simpleGetterFn2(scope, locals) { 10514 if (scope == null) return undefined; 10515 scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; 10516 return scope == null ? undefined : scope[key1]; 10517 }; 10518} 10519 10520function getterFn(path, options, fullExp) { 10521 // Check whether the cache has this getter already. 10522 // We can use hasOwnProperty directly on the cache because we ensure, 10523 // see below, that the cache never stores a path called 'hasOwnProperty' 10524 if (getterFnCache.hasOwnProperty(path)) { 10525 return getterFnCache[path]; 10526 } 10527 10528 var pathKeys = path.split('.'), 10529 pathKeysLength = pathKeys.length, 10530 fn; 10531 10532 // When we have only 1 or 2 tokens, use optimized special case closures. 10533 // http://jsperf.com/angularjs-parse-getter/6 10534 if (!options.unwrapPromises && pathKeysLength === 1) { 10535 fn = simpleGetterFn1(pathKeys[0], fullExp); 10536 } else if (!options.unwrapPromises && pathKeysLength === 2) { 10537 fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp); 10538 } else if (options.csp) { 10539 if (pathKeysLength < 6) { 10540 fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, 10541 options); 10542 } else { 10543 fn = function(scope, locals) { 10544 var i = 0, val; 10545 do { 10546 val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], 10547 pathKeys[i++], fullExp, options)(scope, locals); 10548 10549 locals = undefined; // clear after first iteration 10550 scope = val; 10551 } while (i < pathKeysLength); 10552 return val; 10553 }; 10554 } 10555 } else { 10556 var code = 'var p;\n';
10557 forEach(pathKeys, function(key, index) { 10558 ensureSafeMemberName(key, fullExp); 10559 code += 'if(s == null) return undefined;\n' + 10560 's='+ (index 10561 // we simply dereference 's' on any .dot notation 10562 ? 's' 10563 // but if we are first then we check locals first, and if so read it first 10564 : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + 10565 (options.unwrapPromises 10566 ? 'if (s && s.then) {\n' + 10567 ' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' + 10568 ' if (!("$$v" in s)) {\n' + 10569 ' p=s;\n' + 10570 ' p.$$v = undefined;\n' + 10571 ' p.then(function(v) {p.$$v=v;});\n' + 10572 '}\n' + 10573 ' s=s.$$v\n' + 10574 '}\n' 10575 : ''); 10576 }); 10577 code += 'return s;'; 10578 10579 /* jshint -W054 */ 10580 var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning 10581 /* jshint +W054 */ 10582 evaledFnGetter.toString = valueFn(code); 10583 fn = options.unwrapPromises ? function(scope, locals) { 10584 return evaledFnGetter(scope, locals, promiseWarning); 10585 } : evaledFnGetter; 10586 } 10587 10588 // Only cache the value if it's not going to mess up the cache object 10589 // This is more performant that using Object.prototype.hasOwnProperty.call 10590 if (path !== 'hasOwnProperty') { 10591 getterFnCache[path] = fn; 10592 } 10593 return fn; 10594} 10595 10596/////////////////////////////////// 10597 10598/** 10599 * @ngdoc function 10600 * @name ng.$parse 10601 * @function 10602 * 10603 * @description 10604 * 10605 * Converts Angular {@link guide/expression expression} into a function. 10606 * 10607 * <pre> 10608 * var getter = $parse('user.name'); 10609 * var setter = getter.assign; 10610 * var context = {user:{name:'angular'}}; 10611 * var locals = {user:{name:'local'}}; 10612 * 10613 * expect(getter(context)).toEqual('angular'); 10614 * setter(context, 'newValue'); 10615 * expect(context.user.name).toEqual('newValue'); 10616 * expect(getter(context, locals)).toEqual('local'); 10617 * </pre> 10618 * 10619 * 10620 * @param {string} expression String expression to compile. 10621 * @returns {function(context, locals)} a function which represents the compiled expression: 10622 * 10623 * * `context` â `{object}` â an object against which any expressions embedded in the strings 10624 * are evaluated against (typically a scope object). 10625 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 10626 * `context`. 10627 * 10628 * The returned function also has the following properties: 10629 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 10630 * literal. 10631 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 10632 * constant literals. 10633 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 10634 * set to a function to change its value on the given context. 10635 * 10636 */ 10637 10638 10639/** 10640 * @ngdoc object 10641 * @name ng.$parseProvider 10642 * @function 10643 * 10644 * @description 10645 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 10646 * service. 10647 */ 10648function $ParseProvider() { 10649 var cache = {}; 10650 10651 var $parseOptions = { 10652 csp: false, 10653 unwrapPromises: false, 10654 logPromiseWarnings: true 10655 }; 10656 10657 10658 /** 10659 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 10660 * 10661 * @ngdoc method 10662 * @name ng.$parseProvider#unwrapPromises 10663 * @methodOf ng.$parseProvider 10664 * @description 10665 * 10666 * **This feature is deprecated, see deprecation notes below for more info** 10667 * 10668 * If set to true (default is false), $parse will unwrap promises automatically when a promise is 10669 * found at any part of the expression. In other words, if set to true, the expression will always 10670 * result in a non-promise value. 10671 * 10672 * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, 10673 * the fulfillment value is used in place of the promise while evaluating the expression. 10674 * 10675 * **Deprecation notice** 10676 * 10677 * This is a feature that didn't prove to be wildly useful or popular, primarily because of the 10678 * dichotomy between data access in templates (accessed as raw values) and controller code 10679 * (accessed as promises). 10680 * 10681 * In most code we ended up resolving promises manually in controllers anyway and thus unifying 10682 * the model access there. 10683 * 10684 * Other downsides of automatic promise unwrapping: 10685 *
10686 * - when building components it's often desirable to receive the raw promises 10687 * - adds complexity and slows down expression evaluation 10688 * - makes expression code pre-generation unattractive due to the amount of code that needs to be 10689 * generated 10690 * - makes IDE auto-completion and tool support hard 10691 * 10692 * **Warning Logs** 10693 * 10694 * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a 10695 * promise (to reduce the noise, each expression is logged only once). To disable this logging use 10696 * `$parseProvider.logPromiseWarnings(false)` api. 10697 * 10698 * 10699 * @param {boolean=} value New value. 10700 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 10701 * setter. 10702 */ 10703 this.unwrapPromises = function(value) { 10704 if (isDefined(value)) { 10705 $parseOptions.unwrapPromises = !!value; 10706 return this; 10707 } else { 10708 return $parseOptions.unwrapPromises; 10709 } 10710 }; 10711 10712 10713 /** 10714 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 10715 * 10716 * @ngdoc method 10717 * @name ng.$parseProvider#logPromiseWarnings 10718 * @methodOf ng.$parseProvider 10719 * @description 10720 * 10721 * Controls whether Angular should log a warning on any encounter of a promise in an expression. 10722 * 10723 * The default is set to `true`. 10724 * 10725 * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. 10726 * 10727 * @param {boolean=} value New value. 10728 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 10729 * setter. 10730 */ 10731 this.logPromiseWarnings = function(value) { 10732 if (isDefined(value)) { 10733 $parseOptions.logPromiseWarnings = value; 10734 return this; 10735 } else { 10736 return $parseOptions.logPromiseWarnings; 10737 } 10738 }; 10739 10740 10741 this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { 10742 $parseOptions.csp = $sniffer.csp; 10743 10744 promiseWarning = function promiseWarningFn(fullExp) { 10745 if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; 10746 promiseWarningCache[fullExp] = true; 10747 $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + 10748 'Automatic unwrapping of promises in Angular expressions is deprecated.'); 10749 }; 10750 10751 return function(exp) { 10752 var parsedExpression; 10753 10754 switch (typeof exp) { 10755 case 'string': 10756 10757 if (cache.hasOwnProperty(exp)) { 10758 return cache[exp]; 10759 } 10760 10761 var lexer = new Lexer($parseOptions); 10762 var parser = new Parser(lexer, $filter, $parseOptions); 10763 parsedExpression = parser.parse(exp, false); 10764 10765 if (exp !== 'hasOwnProperty') { 10766 // Only cache the value if it's not going to mess up the cache object 10767 // This is more performant that using Object.prototype.hasOwnProperty.call 10768 cache[exp] = parsedExpression; 10769 } 10770 10771 return parsedExpression; 10772 10773 case 'function': 10774 return exp; 10775 10776 default: 10777 return noop; 10778 } 10779 }; 10780 }]; 10781} 10782 10783/** 10784 * @ngdoc service 10785 * @name ng.$q 10786 * @requires $rootScope 10787 * 10788 * @description 10789 * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). 10790 * 10791 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 10792 * interface for interacting with an object that represents the result of an action that is 10793 * performed asynchronously, and may or may not be finished at any given point in time. 10794 * 10795 * From the perspective of dealing with error handling, deferred and promise APIs are to 10796 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 10797 * 10798 * <pre> 10799 * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` 10800 * // are available in the current lexical scope (they could have been injected or passed in). 10801 * 10802 * function asyncGreet(name) { 10803 * var deferred = $q.defer(); 10804 * 10805 * setTimeout(function() { 10806 * // since this fn executes async in a future turn of the event loop, we need to wrap 10807 * // our code into an $apply call so that the model changes are properly observed. 10808 * scope.$apply(function() { 10809 * deferred.notify('About to greet ' + name + '.'); 10810 * 10811 * if (okToGreet(name)) { 10812 * deferred.resolve('Hello, ' + name + '!'); 10813 * } else { 10814 * deferred.reject('Greeting ' + name + ' is not allowed.'); 10815 * } 10816 * }); 10817 * }, 1000); 10818 * 10819 * return deferred.promise; 10820 * } 10821 * 10822 * var promise = asyncGreet('Robin Hood'); 10823 * promise.then(function(greeting) { 10824 * alert('Success: ' + greeting); 10825 * }, function(reason) { 10826 * alert('Failed: ' + reason); 10827 * }, function(update) { 10828 * alert('Got notification: ' + update); 10829 * }); 10830 * </pre> 10831 * 10832 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 10833 * comes in the way of guarantees that promise and deferred APIs make, see 10834 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 10835 *
10836 * Additionally the promise api allows for composition that is very hard to do with the 10837 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 10838 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 10839 * section on serial or parallel joining of promises. 10840 * 10841 * 10842 * # The Deferred API 10843 * 10844 * A new instance of deferred is constructed by calling `$q.defer()`. 10845 * 10846 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 10847 * that can be used for signaling the successful or unsuccessful completion, as well as the status 10848 * of the task. 10849 * 10850 * **Methods** 10851 * 10852 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 10853 * constructed via `$q.reject`, the promise will be rejected instead. 10854 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 10855 * resolving it with a rejection constructed via `$q.reject`. 10856 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 10857 * multiple times before the promise is either resolved or rejected. 10858 * 10859 * **Properties** 10860 * 10861 * - promise â `{Promise}` â promise object associated with this deferred. 10862 * 10863 * 10864 * # The Promise API 10865 * 10866 * A new promise instance is created when a deferred instance is created and can be retrieved by 10867 * calling `deferred.promise`. 10868 * 10869 * The purpose of the promise object is to allow for interested parties to get access to the result 10870 * of the deferred task when it completes. 10871 * 10872 * **Methods** 10873 * 10874 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 10875 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 10876 * as soon as the result is available. The callbacks are called with a single argument: the result 10877 * or rejection reason. Additionally, the notify callback may be called zero or more times to 10878 * provide a progress indication, before the promise is resolved or rejected. 10879 * 10880 * This method *returns a new promise* which is resolved or rejected via the return value of the 10881 * `successCallback`, `errorCallback`. It also notifies via the return value of the 10882 * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback 10883 * method. 10884 * 10885 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 10886 * 10887 * - `finally(callback)` â allows you to observe either the fulfillment or rejection of a promise, 10888 * but to do so without modifying the final value. This is useful to release resources or do some 10889 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 10890 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 10891 * more information. 10892 * 10893 * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as 10894 * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to 10895 * make your code IE8 compatible. 10896 * 10897 * # Chaining promises 10898 * 10899 * Because calling the `then` method of a promise returns a new derived promise, it is easily 10900 * possible to create a chain of promises: 10901 * 10902 * <pre> 10903 * promiseB = promiseA.then(function(result) { 10904 * return result + 1; 10905 * }); 10906 * 10907 * // promiseB will be resolved immediately after promiseA is resolved and its value 10908 * // will be the result of promiseA incremented by 1 10909 * </pre> 10910 * 10911 * It is possible to create chains of any length and since a promise can be resolved with another 10912 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 10913 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 10914 * $http's response interceptors. 10915 * 10916 * 10917 * # Differences between Kris Kowal's Q and $q 10918 * 10919 * There are two main differences: 10920 * 10921 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 10922 * mechanism in angular, which means faster propagation of resolution or rejection into your 10923 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
10924 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains 10925 * all the important functionality needed for common async tasks. 10926 * 10927 * # Testing 10928 * 10929 * <pre> 10930 * it('should simulate promise', inject(function($q, $rootScope) { 10931 * var deferred = $q.defer(); 10932 * var promise = deferred.promise; 10933 * var resolvedValue; 10934 * 10935 * promise.then(function(value) { resolvedValue = value; }); 10936 * expect(resolvedValue).toBeUndefined(); 10937 * 10938 * // Simulate resolving of promise 10939 * deferred.resolve(123); 10940 * // Note that the 'then' function does not get called synchronously. 10941 * // This is because we want the promise API to always be async, whether or not 10942 * // it got called synchronously or asynchronously. 10943 * expect(resolvedValue).toBeUndefined(); 10944 * 10945 * // Propagate promise resolution to 'then' functions using $apply(). 10946 * $rootScope.$apply(); 10947 * expect(resolvedValue).toEqual(123); 10948 * })); 10949 * </pre> 10950 */ 10951function $QProvider() { 10952 10953 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 10954 return qFactory(function(callback) { 10955 $rootScope.$evalAsync(callback); 10956 }, $exceptionHandler); 10957 }]; 10958} 10959 10960 10961/** 10962 * Constructs a promise manager. 10963 * 10964 * @param {function(function)} nextTick Function for executing functions in the next turn. 10965 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 10966 * debugging purposes. 10967 * @returns {object} Promise manager. 10968 */ 10969function qFactory(nextTick, exceptionHandler) { 10970 10971 /** 10972 * @ngdoc 10973 * @name ng.$q#defer 10974 * @methodOf ng.$q 10975 * @description 10976 * Creates a `Deferred` object which represents a task which will finish in the future. 10977 * 10978 * @returns {Deferred} Returns a new instance of deferred. 10979 */ 10980 var defer = function() { 10981 var pending = [], 10982 value, deferred; 10983 10984 deferred = { 10985 10986 resolve: function(val) { 10987 if (pending) { 10988 var callbacks = pending; 10989 pending = undefined; 10990 value = ref(val); 10991 10992 if (callbacks.length) { 10993 nextTick(function() { 10994 var callback; 10995 for (var i = 0, ii = callbacks.length; i < ii; i++) { 10996 callback = callbacks[i]; 10997 value.then(callback[0], callback[1], callback[2]); 10998 } 10999 }); 11000 } 11001 } 11002 }, 11003 11004 11005 reject: function(reason) { 11006 deferred.resolve(createInternalRejectedPromise(reason)); 11007 }, 11008 11009 11010 notify: function(progress) { 11011 if (pending) { 11012 var callbacks = pending; 11013 11014 if (pending.length) { 11015 nextTick(function() { 11016 var callback; 11017 for (var i = 0, ii = callbacks.length; i < ii; i++) { 11018 callback = callbacks[i]; 11019 callback[2](progress); 11020 } 11021 }); 11022 } 11023 } 11024 }, 11025 11026 11027 promise: { 11028 then: function(callback, errback, progressback) { 11029 var result = defer(); 11030 11031 var wrappedCallback = function(value) { 11032 try { 11033 result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); 11034 } catch(e) { 11035 result.reject(e); 11036 exceptionHandler(e); 11037 } 11038 }; 11039 11040 var wrappedErrback = function(reason) { 11041 try { 11042 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11043 } catch(e) { 11044 result.reject(e); 11045 exceptionHandler(e); 11046 } 11047 }; 11048 11049 var wrappedProgressback = function(progress) { 11050 try { 11051 result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); 11052 } catch(e) { 11053 exceptionHandler(e); 11054 } 11055 }; 11056 11057 if (pending) { 11058 pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); 11059 } else { 11060 value.then(wrappedCallback, wrappedErrback, wrappedProgressback); 11061 } 11062 11063 return result.promise; 11064 }, 11065 11066 "catch": function(callback) { 11067 return this.then(null, callback); 11068 }, 11069 11070 "finally": function(callback) { 11071 11072 function makePromise(value, resolved) { 11073 var result = defer(); 11074 if (resolved) { 11075 result.resolve(value); 11076 } else { 11077 result.reject(value); 11078 } 11079 return result.promise; 11080 } 11081 11082 function handleCallback(value, isResolved) { 11083 var callbackOutput = null; 11084 try { 11085 callbackOutput = (callback ||defaultCallback)(); 11086 } catch(e) { 11087 return makePromise(e, false); 11088 } 11089 if (callbackOutput && isFunction(callbackOutput.then)) { 11090 return callbackOutput.then(function() { 11091 return makePromise(value, isResolved); 11092 }, function(error) { 11093 return makePromise(error, false); 11094 }); 11095 } else { 11096 return makePromise(value, isResolved); 11097 } 11098 } 11099 11100 return this.then(function(value) { 11101 return handleCallback(value, true); 11102 }, function(error) { 11103 return handleCallback(error, false); 11104 }); 11105 } 11106 } 11107 }; 11108 11109 return deferred; 11110 }; 11111 11112 11113 var ref = function(value) { 11114 if (value && isFunction(value.then)) return value; 11115 return { 11116 then: function(callback) { 11117 var result = defer(); 11118 nextTick(function() { 11119 result.resolve(callback(value)); 11120 }); 11121 return result.promise; 11122 } 11123 }; 11124 }; 11125 11126 11127 /** 11128 * @ngdoc 11129 * @name ng.$q#reject 11130 * @methodOf ng.$q 11131 * @description 11132 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 11133 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 11134 * a promise chain, you don't need to worry about it. 11135 * 11136 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 11137 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 11138 * a promise error callback and you want to forward the error to the promise derived from the 11139 * current promise, you have to "rethrow" the error by returning a rejection constructed via 11140 * `reject`. 11141 * 11142 * <pre> 11143 * promiseB = promiseA.then(function(result) { 11144 * // success: do something and resolve promiseB 11145 * // with the old or a new result 11146 * return result; 11147 * }, function(reason) { 11148 * // error: handle the error if possible and
11149 * // resolve promiseB with newPromiseOrValue, 11150 * // otherwise forward the rejection to promiseB 11151 * if (canHandle(reason)) { 11152 * // handle the error and recover 11153 * return newPromiseOrValue; 11154 * } 11155 * return $q.reject(reason); 11156 * }); 11157 * </pre> 11158 * 11159 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 11160 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 11161 */ 11162 var reject = function(reason) { 11163 var result = defer(); 11164 result.reject(reason); 11165 return result.promise; 11166 }; 11167 11168 var createInternalRejectedPromise = function(reason) { 11169 return { 11170 then: function(callback, errback) { 11171 var result = defer(); 11172 nextTick(function() { 11173 try { 11174 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11175 } catch(e) { 11176 result.reject(e); 11177 exceptionHandler(e); 11178 } 11179 }); 11180 return result.promise; 11181 } 11182 }; 11183 }; 11184 11185 11186 /** 11187 * @ngdoc 11188 * @name ng.$q#when 11189 * @methodOf ng.$q 11190 * @description 11191 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 11192 * This is useful when you are dealing with an object that might or might not be a promise, or if 11193 * the promise comes from a source that can't be trusted. 11194 * 11195 * @param {*} value Value or a promise 11196 * @returns {Promise} Returns a promise of the passed value or promise 11197 */ 11198 var when = function(value, callback, errback, progressback) { 11199 var result = defer(), 11200 done; 11201 11202 var wrappedCallback = function(value) { 11203 try { 11204 return (isFunction(callback) ? callback : defaultCallback)(value); 11205 } catch (e) { 11206 exceptionHandler(e); 11207 return reject(e); 11208 } 11209 }; 11210 11211 var wrappedErrback = function(reason) { 11212 try { 11213 return (isFunction(errback) ? errback : defaultErrback)(reason); 11214 } catch (e) { 11215 exceptionHandler(e); 11216 return reject(e); 11217 } 11218 }; 11219 11220 var wrappedProgressback = function(progress) { 11221 try { 11222 return (isFunction(progressback) ? progressback : defaultCallback)(progress); 11223 } catch (e) { 11224 exceptionHandler(e); 11225 } 11226 }; 11227 11228 nextTick(function() { 11229 ref(value).then(function(value) { 11230 if (done) return; 11231 done = true; 11232 result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); 11233 }, function(reason) { 11234 if (done) return; 11235 done = true; 11236 result.resolve(wrappedErrback(reason)); 11237 }, function(progress) { 11238 if (done) return; 11239 result.notify(wrappedProgressback(progress)); 11240 }); 11241 }); 11242 11243 return result.promise; 11244 }; 11245 11246 11247 function defaultCallback(value) { 11248 return value; 11249 } 11250 11251 11252 function defaultErrback(reason) { 11253 return reject(reason); 11254 } 11255 11256 11257 /** 11258 * @ngdoc 11259 * @name ng.$q#all 11260 * @methodOf ng.$q 11261 * @description 11262 * Combines multiple promises into a single promise that is resolved when all of the input 11263 * promises are resolved. 11264 * 11265 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 11266 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 11267 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 11268 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 11269 * with the same rejection value. 11270 */ 11271 function all(promises) { 11272 var deferred = defer(), 11273 counter = 0, 11274 results = isArray(promises) ? [] : {}; 11275 11276 forEach(promises, function(promise, key) { 11277 counter++; 11278 ref(promise).then(function(value) { 11279 if (results.hasOwnProperty(key)) return; 11280 results[key] = value; 11281 if (!(--counter)) deferred.resolve(results); 11282 }, function(reason) { 11283 if (results.hasOwnProperty(key)) return; 11284 deferred.reject(reason); 11285 }); 11286 });
11287 11288 if (counter === 0) { 11289 deferred.resolve(results); 11290 } 11291 11292 return deferred.promise; 11293 } 11294 11295 return { 11296 defer: defer, 11297 reject: reject, 11298 when: when, 11299 all: all 11300 }; 11301} 11302 11303/** 11304 * DESIGN NOTES 11305 * 11306 * The design decisions behind the scope are heavily favored for speed and memory consumption. 11307 * 11308 * The typical use of scope is to watch the expressions, which most of the time return the same 11309 * value as last time so we optimize the operation. 11310 * 11311 * Closures construction is expensive in terms of speed as well as memory: 11312 * - No closures, instead use prototypical inheritance for API 11313 * - Internal state needs to be stored on scope directly, which means that private state is 11314 * exposed as $$____ properties 11315 * 11316 * Loop operations are optimized by using while(count--) { ... } 11317 * - this means that in order to keep the same order of execution as addition we have to add 11318 * items to the array at the beginning (shift) instead of at the end (push) 11319 * 11320 * Child scopes are created and removed often 11321 * - Using an array would be slow since inserts in middle are expensive so we use linked list 11322 * 11323 * There are few watches then a lot of observers. This is why you don't want the observer to be 11324 * implemented in the same way as watch. Watch requires return of initialization function which 11325 * are expensive to construct. 11326 */ 11327 11328 11329/** 11330 * @ngdoc object 11331 * @name ng.$rootScopeProvider 11332 * @description 11333 * 11334 * Provider for the $rootScope service. 11335 */ 11336 11337/** 11338 * @ngdoc function 11339 * @name ng.$rootScopeProvider#digestTtl 11340 * @methodOf ng.$rootScopeProvider 11341 * @description 11342 * 11343 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 11344 * assuming that the model is unstable. 11345 * 11346 * The current default is 10 iterations. 11347 * 11348 * In complex applications it's possible that the dependencies between `$watch`s will result in 11349 * several digest iterations. However if an application needs more than the default 10 digest 11350 * iterations for its model to stabilize then you should investigate what is causing the model to 11351 * continuously change during the digest. 11352 * 11353 * Increasing the TTL could have performance implications, so you should not change it without 11354 * proper justification. 11355 * 11356 * @param {number} limit The number of digest iterations. 11357 */ 11358 11359 11360/** 11361 * @ngdoc object 11362 * @name ng.$rootScope 11363 * @description 11364 * 11365 * Every application has a single root {@link ng.$rootScope.Scope scope}. 11366 * All other scopes are descendant scopes of the root scope. Scopes provide separation 11367 * between the model and the view, via a mechanism for watching the model for changes. 11368 * They also provide an event emission/broadcast and subscription facility. See the 11369 * {@link guide/scope developer guide on scopes}. 11370 */ 11371function $RootScopeProvider(){ 11372 var TTL = 10; 11373 var $rootScopeMinErr = minErr('$rootScope'); 11374 var lastDirtyWatch = null; 11375 11376 this.digestTtl = function(value) { 11377 if (arguments.length) { 11378 TTL = value; 11379 } 11380 return TTL; 11381 }; 11382 11383 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 11384 function( $injector, $exceptionHandler, $parse, $browser) { 11385 11386 /** 11387 * @ngdoc function 11388 * @name ng.$rootScope.Scope 11389 * 11390 * @description 11391 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 11392 * {@link AUTO.$injector $injector}. Child scopes are created using the 11393 * {@link ng.$rootScope.Scope#methods_$new $new()} method. (Most scopes are created automatically when 11394 * compiled HTML template is executed.) 11395 * 11396 * Here is a simple scope snippet to show how you can interact with the scope. 11397 * <pre> 11398 * <file src="./test/ng/rootScopeSpec.js" tag="docs1" /> 11399 * </pre> 11400 * 11401 * # Inheritance 11402 * A scope can inherit from a parent scope, as in this example: 11403 * <pre> 11404 var parent = $rootScope; 11405 var child = parent.$new(); 11406 11407 parent.salutation = "Hello"; 11408 child.name = "World"; 11409 expect(child.salutation).toEqual('Hello'); 11410 11411 child.salutation = "Welcome"; 11412 expect(child.salutation).toEqual('Welcome'); 11413 expect(parent.salutation).toEqual('Hello'); 11414 * </pre> 11415 * 11416 * 11417 * @param {Object.<string, function()>=} providers Map of service factory which need to be 11418 * provided for the current scope. Defaults to {@link ng}. 11419 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 11420 * append/override services provided by `providers`. This is handy 11421 * when unit-testing and having the need to override a default 11422 * service. 11423 * @returns {Object} Newly created scope. 11424 * 11425 */ 11426 function Scope() { 11427 this.$id = nextUid();
11428 this.$$phase = this.$parent = this.$$watchers = 11429 this.$$nextSibling = this.$$prevSibling = 11430 this.$$childHead = this.$$childTail = null; 11431 this['this'] = this.$root = this; 11432 this.$$destroyed = false; 11433 this.$$asyncQueue = []; 11434 this.$$postDigestQueue = []; 11435 this.$$listeners = {}; 11436 this.$$listenerCount = {}; 11437 this.$$isolateBindings = {}; 11438 } 11439 11440 /** 11441 * @ngdoc property 11442 * @name ng.$rootScope.Scope#$id 11443 * @propertyOf ng.$rootScope.Scope 11444 * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for 11445 * debugging. 11446 */ 11447 11448 11449 Scope.prototype = { 11450 constructor: Scope, 11451 /** 11452 * @ngdoc function 11453 * @name ng.$rootScope.Scope#$new 11454 * @methodOf ng.$rootScope.Scope 11455 * @function 11456 * 11457 * @description 11458 * Creates a new child {@link ng.$rootScope.Scope scope}. 11459 * 11460 * The parent scope will propagate the {@link ng.$rootScope.Scope#methods_$digest $digest()} and 11461 * {@link ng.$rootScope.Scope#methods_$digest $digest()} events. The scope can be removed from the 11462 * scope hierarchy using {@link ng.$rootScope.Scope#methods_$destroy $destroy()}. 11463 * 11464 * {@link ng.$rootScope.Scope#methods_$destroy $destroy()} must be called on a scope when it is 11465 * desired for the scope and its child scopes to be permanently detached from the parent and 11466 * thus stop participating in model change detection and listener notification by invoking. 11467 * 11468 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 11469 * parent scope. The scope is isolated, as it can not see parent scope properties. 11470 * When creating widgets, it is useful for the widget to not accidentally read parent 11471 * state. 11472 * 11473 * @returns {Object} The newly created child scope. 11474 * 11475 */ 11476 $new: function(isolate) { 11477 var ChildScope, 11478 child; 11479 11480 if (isolate) { 11481 child = new Scope(); 11482 child.$root = this.$root; 11483 // ensure that there is just one async queue per $rootScope and its children 11484 child.$$asyncQueue = this.$$asyncQueue; 11485 child.$$postDigestQueue = this.$$postDigestQueue; 11486 } else { 11487 ChildScope = function() {}; // should be anonymous; This is so that when the minifier munges 11488 // the name it does not become random set of chars. This will then show up as class 11489 // name in the web inspector. 11490 ChildScope.prototype = this; 11491 child = new ChildScope(); 11492 child.$id = nextUid(); 11493 } 11494 child['this'] = child; 11495 child.$$listeners = {}; 11496 child.$$listenerCount = {}; 11497 child.$parent = this; 11498 child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null; 11499 child.$$prevSibling = this.$$childTail; 11500 if (this.$$childHead) { 11501 this.$$childTail.$$nextSibling = child; 11502 this.$$childTail = child; 11503 } else { 11504 this.$$childHead = this.$$childTail = child; 11505 } 11506 return child; 11507 }, 11508 11509 /** 11510 * @ngdoc function 11511 * @name ng.$rootScope.Scope#$watch 11512 * @methodOf ng.$rootScope.Scope 11513 * @function 11514 * 11515 * @description 11516 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 11517 * 11518 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#methods_$digest 11519 * $digest()} and should return the value that will be watched. (Since 11520 * {@link ng.$rootScope.Scope#methods_$digest $digest()} reruns when it detects changes the 11521 * `watchExpression` can execute multiple times per 11522 * {@link ng.$rootScope.Scope#methods_$digest $digest()} and should be idempotent.) 11523 * - The `listener` is called only when the value from the current `watchExpression` and the 11524 * previous call to `watchExpression` are not equal (with the exception of the initial run, 11525 * see below). The inequality is determined according to 11526 * {@link angular.equals} function. To save the value of the object for later comparison, 11527 * the {@link angular.copy} function is used. It also means that watching complex options 11528 * will have adverse memory and performance implications. 11529 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 11530 * This is achieved by rerunning the watchers until no changes are detected. The rerun 11531 * iteration limit is 10 to prevent an infinite loop deadlock. 11532 * 11533 * 11534 * If you want to be notified whenever {@link ng.$rootScope.Scope#methods_$digest $digest} is called, 11535 * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` 11536 * can execute multiple times per {@link ng.$rootScope.Scope#methods_$digest $digest} cycle when a 11537 * change is detected, be prepared for multiple calls to your listener.) 11538 * 11539 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 11540 * (via {@link ng.$rootScope.Scope#methods_$evalAsync $evalAsync}) to initialize the 11541 * watcher. In rare cases, this is undesirable because the listener is called when the result 11542 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you
11543 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 11544 * listener was called due to initialization. 11545 * 11546 * The example below contains an illustration of using a function as your $watch listener 11547 * 11548 * 11549 * # Example 11550 * <pre> 11551 // let's assume that scope was dependency injected as the $rootScope 11552 var scope = $rootScope; 11553 scope.name = 'misko'; 11554 scope.counter = 0; 11555 11556 expect(scope.counter).toEqual(0); 11557 scope.$watch('name', function(newValue, oldValue) { 11558 scope.counter = scope.counter + 1; 11559 }); 11560 expect(scope.counter).toEqual(0); 11561 11562 scope.$digest(); 11563 // no variable change 11564 expect(scope.counter).toEqual(0); 11565 11566 scope.name = 'adam'; 11567 scope.$digest(); 11568 expect(scope.counter).toEqual(1); 11569 11570 11571 11572 // Using a listener function 11573 var food; 11574 scope.foodCounter = 0; 11575 expect(scope.foodCounter).toEqual(0); 11576 scope.$watch( 11577 // This is the listener function 11578 function() { return food; }, 11579 // This is the change handler 11580 function(newValue, oldValue) { 11581 if ( newValue !== oldValue ) { 11582 // Only increment the counter if the value changed 11583 scope.foodCounter = scope.foodCounter + 1; 11584 } 11585 } 11586 ); 11587 // No digest has been run so the counter will be zero 11588 expect(scope.foodCounter).toEqual(0); 11589 11590 // Run the digest but since food has not changed count will still be zero 11591 scope.$digest(); 11592 expect(scope.foodCounter).toEqual(0); 11593 11594 // Update food and run digest. Now the counter will increment 11595 food = 'cheeseburger'; 11596 scope.$digest(); 11597 expect(scope.foodCounter).toEqual(1); 11598 11599 * </pre> 11600 * 11601 * 11602 * 11603 * @param {(function()|string)} watchExpression Expression that is evaluated on each 11604 * {@link ng.$rootScope.Scope#methods_$digest $digest} cycle. A change in the return value triggers 11605 * a call to the `listener`. 11606 * 11607 * - `string`: Evaluated as {@link guide/expression expression} 11608 * - `function(scope)`: called with current `scope` as a parameter. 11609 * @param {(function()|string)=} listener Callback called whenever the return value of 11610 * the `watchExpression` changes. 11611 * 11612 * - `string`: Evaluated as {@link guide/expression expression} 11613 * - `function(newValue, oldValue, scope)`: called with current and previous values as 11614 * parameters. 11615 * 11616 * @param {boolean=} objectEquality Compare object for equality rather than for reference. 11617 * @returns {function()} Returns a deregistration function for this listener. 11618 */ 11619 $watch: function(watchExp, listener, objectEquality) { 11620 var scope = this, 11621 get = compileToFn(watchExp, 'watch'), 11622 array = scope.$$watchers, 11623 watcher = { 11624 fn: listener, 11625 last: initWatchVal, 11626 get: get, 11627 exp: watchExp, 11628 eq: !!objectEquality 11629 }; 11630 11631 lastDirtyWatch = null; 11632 11633 // in the case user pass string, we need to compile it, do we really need this ? 11634 if (!isFunction(listener)) { 11635 var listenFn = compileToFn(listener || noop, 'listener'); 11636 watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; 11637 } 11638 11639 if (typeof watchExp == 'string' && get.constant) { 11640 var originalFn = watcher.fn; 11641 watcher.fn = function(newVal, oldVal, scope) { 11642 originalFn.call(this, newVal, oldVal, scope); 11643 arrayRemove(array, watcher); 11644 }; 11645 } 11646 11647 if (!array) { 11648 array = scope.$$watchers = []; 11649 } 11650 // we use unshift since we use a while loop in $digest for speed. 11651 // the while loop reads in reverse order. 11652 array.unshift(watcher); 11653 11654 return function() { 11655 arrayRemove(array, watcher); 11656 lastDirtyWatch = null; 11657 }; 11658 }, 11659 11660 11661 /** 11662 * @ngdoc function 11663 * @name ng.$rootScope.Scope#$watchCollection 11664 * @methodOf ng.$rootScope.Scope 11665 * @function 11666 *
11667 * @description 11668 * Shallow watches the properties of an object and fires whenever any of the properties change 11669 * (for arrays, this implies watching the array items; for object maps, this implies watching 11670 * the properties). If a change is detected, the `listener` callback is fired. 11671 * 11672 * - The `obj` collection is observed via standard $watch operation and is examined on every 11673 * call to $digest() to see if any items have been added, removed, or moved. 11674 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 11675 * adding, removing, and moving items belonging to an object or array. 11676 * 11677 * 11678 * # Example 11679 * <pre> 11680 $scope.names = ['igor', 'matias', 'misko', 'james']; 11681 $scope.dataCount = 4; 11682 11683 $scope.$watchCollection('names', function(newNames, oldNames) { 11684 $scope.dataCount = newNames.length; 11685 }); 11686 11687 expect($scope.dataCount).toEqual(4); 11688 $scope.$digest(); 11689 11690 //still at 4 ... no changes 11691 expect($scope.dataCount).toEqual(4); 11692 11693 $scope.names.pop(); 11694 $scope.$digest(); 11695 11696 //now there's been a change 11697 expect($scope.dataCount).toEqual(3); 11698 * </pre> 11699 * 11700 * 11701 * @param {string|Function(scope)} obj Evaluated as {@link guide/expression expression}. The 11702 * expression value should evaluate to an object or an array which is observed on each 11703 * {@link ng.$rootScope.Scope#methods_$digest $digest} cycle. Any shallow change within the 11704 * collection will trigger a call to the `listener`. 11705 * 11706 * @param {function(newCollection, oldCollection, scope)} listener a callback function that is 11707 * fired with both the `newCollection` and `oldCollection` as parameters. 11708 * The `newCollection` object is the newly modified data obtained from the `obj` expression 11709 * and the `oldCollection` object is a copy of the former collection data. 11710 * The `scope` refers to the current scope. 11711 * 11712 * @returns {function()} Returns a de-registration function for this listener. When the 11713 * de-registration function is executed, the internal watch operation is terminated. 11714 */ 11715 $watchCollection: function(obj, listener) { 11716 var self = this; 11717 var oldValue; 11718 var newValue; 11719 var changeDetected = 0; 11720 var objGetter = $parse(obj); 11721 var internalArray = []; 11722 var internalObject = {}; 11723 var oldLength = 0; 11724 11725 function $watchCollectionWatch() { 11726 newValue = objGetter(self); 11727 var newLength, key; 11728 11729 if (!isObject(newValue)) { 11730 if (oldValue !== newValue) { 11731 oldValue = newValue; 11732 changeDetected++; 11733 } 11734 } else if (isArrayLike(newValue)) { 11735 if (oldValue !== internalArray) { 11736 // we are transitioning from something which was not an array into array. 11737 oldValue = internalArray; 11738 oldLength = oldValue.length = 0; 11739 changeDetected++; 11740 } 11741 11742 newLength = newValue.length; 11743 11744 if (oldLength !== newLength) { 11745 // if lengths do not match we need to trigger change notification 11746 changeDetected++; 11747 oldValue.length = oldLength = newLength; 11748 } 11749 // copy the items to oldValue and look for changes. 11750 for (var i = 0; i < newLength; i++) { 11751 if (oldValue[i] !== newValue[i]) { 11752 changeDetected++; 11753 oldValue[i] = newValue[i]; 11754 } 11755 } 11756 } else { 11757 if (oldValue !== internalObject) { 11758 // we are transitioning from something which was not an object into object. 11759 oldValue = internalObject = {}; 11760 oldLength = 0; 11761 changeDetected++; 11762 } 11763 // copy the items to oldValue and look for changes. 11764 newLength = 0; 11765 for (key in newValue) { 11766 if (newValue.hasOwnProperty(key)) { 11767 newLength++; 11768 if (oldValue.hasOwnProperty(key)) { 11769 if (oldValue[key] !== newValue[key]) { 11770 changeDetected++; 11771 oldValue[key] = newValue[key]; 11772 } 11773 } else { 11774 oldLength++; 11775 oldValue[key] = newValue[key]; 11776 changeDetected++; 11777 } 11778 } 11779 } 11780 if (oldLength > newLength) { 11781 // we used to have more keys, need to find them and destroy them. 11782 changeDetected++; 11783 for(key in oldValue) { 11784 if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { 11785 oldLength--; 11786 delete oldValue[key]; 11787 } 11788 } 11789 } 11790 } 11791 return changeDetected; 11792 } 11793 11794 function $watchCollectionAction() { 11795 listener(newValue, oldValue, self); 11796 } 11797 11798 return this.$watch($watchCollectionWatch, $watchCollectionAction); 11799 }, 11800 11801 /** 11802 * @ngdoc function 11803 * @name ng.$rootScope.Scope#$digest 11804 * @methodOf ng.$rootScope.Scope 11805 * @function 11806 *
11807 * @description 11808 * Processes all of the {@link ng.$rootScope.Scope#methods_$watch watchers} of the current scope and 11809 * its children. Because a {@link ng.$rootScope.Scope#methods_$watch watcher}'s listener can change 11810 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#methods_$watch watchers} 11811 * until no more listeners are firing. This means that it is possible to get into an infinite 11812 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 11813 * iterations exceeds 10. 11814 * 11815 * Usually, you don't call `$digest()` directly in 11816 * {@link ng.directive:ngController controllers} or in 11817 * {@link ng.$compileProvider#methods_directive directives}. 11818 * Instead, you should call {@link ng.$rootScope.Scope#methods_$apply $apply()} (typically from within 11819 * a {@link ng.$compileProvider#methods_directive directives}), which will force a `$digest()`. 11820 * 11821 * If you want to be notified whenever `$digest()` is called, 11822 * you can register a `watchExpression` function with 11823 * {@link ng.$rootScope.Scope#methods_$watch $watch()} with no `listener`. 11824 * 11825 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 11826 * 11827 * # Example 11828 * <pre> 11829 var scope = ...; 11830 scope.name = 'misko'; 11831 scope.counter = 0; 11832 11833 expect(scope.counter).toEqual(0); 11834 scope.$watch('name', function(newValue, oldValue) { 11835 scope.counter = scope.counter + 1; 11836 }); 11837 expect(scope.counter).toEqual(0); 11838 11839 scope.$digest(); 11840 // no variable change 11841 expect(scope.counter).toEqual(0); 11842 11843 scope.name = 'adam'; 11844 scope.$digest(); 11845 expect(scope.counter).toEqual(1); 11846 * </pre> 11847 * 11848 */ 11849 $digest: function() { 11850 var watch, value, last, 11851 watchers, 11852 asyncQueue = this.$$asyncQueue, 11853 postDigestQueue = this.$$postDigestQueue, 11854 length, 11855 dirty, ttl = TTL, 11856 next, current, target = this, 11857 watchLog = [], 11858 logIdx, logMsg, asyncTask; 11859 11860 beginPhase('$digest'); 11861 11862 lastDirtyWatch = null; 11863 11864 do { // "while dirty" loop 11865 dirty = false; 11866 current = target; 11867 11868 while(asyncQueue.length) { 11869 try { 11870 asyncTask = asyncQueue.shift(); 11871 asyncTask.scope.$eval(asyncTask.expression); 11872 } catch (e) { 11873 clearPhase(); 11874 $exceptionHandler(e); 11875 } 11876 lastDirtyWatch = null; 11877 } 11878 11879 traverseScopesLoop: 11880 do { // "traverse the scopes" loop 11881 if ((watchers = current.$$watchers)) { 11882 // process our watches 11883 length = watchers.length; 11884 while (length--) { 11885 try { 11886 watch = watchers[length]; 11887 // Most common watches are on primitives, in which case we can short 11888 // circuit it with === operator, only when === fails do we use .equals 11889 if (watch) { 11890 if ((value = watch.get(current)) !== (last = watch.last) && 11891 !(watch.eq 11892 ? equals(value, last) 11893 : (typeof value == 'number' && typeof last == 'number' 11894 && isNaN(value) && isNaN(last)))) { 11895 dirty = true; 11896 lastDirtyWatch = watch; 11897 watch.last = watch.eq ? copy(value) : value; 11898 watch.fn(value, ((last === initWatchVal) ? value : last), current); 11899 if (ttl < 5) { 11900 logIdx = 4 - ttl; 11901 if (!watchLog[logIdx]) watchLog[logIdx] = []; 11902 logMsg = (isFunction(watch.exp)) 11903 ? 'fn: ' + (watch.exp.name || watch.exp.toString()) 11904 : watch.exp; 11905 logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); 11906 watchLog[logIdx].push(logMsg); 11907 } 11908 } else if (watch === lastDirtyWatch) { 11909 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 11910 // have already been tested. 11911 dirty = false; 11912 break traverseScopesLoop; 11913 } 11914 } 11915 } catch (e) { 11916 clearPhase(); 11917 $exceptionHandler(e); 11918 } 11919 } 11920 } 11921 11922 // Insanity Warning: scope depth-first traversal 11923 // yes, this code is a bit crazy, but it works and we have tests to prove it! 11924 // this piece should be kept in sync with the traversal in $broadcast 11925 if (!(next = (current.$$childHead || 11926 (current !== target && current.$$nextSibling)))) { 11927 while(current !== target && !(next = current.$$nextSibling)) { 11928 current = current.$parent; 11929 } 11930 } 11931 } while ((current = next)); 11932 11933 // `break traverseScopesLoop;` takes us to here 11934 11935 if((dirty || asyncQueue.length) && !(ttl--)) { 11936 clearPhase(); 11937 throw $rootScopeMinErr('infdig', 11938 '{0} $digest() iterations reached. Aborting!\n' + 11939 'Watchers fired in the last 5 iterations: {1}', 11940 TTL, toJson(watchLog)); 11941 } 11942 11943 }
11943 while (dirty || asyncQueue.length); 11944 11945 clearPhase(); 11946 11947 while(postDigestQueue.length) { 11948 try { 11949 postDigestQueue.shift()(); 11950 } catch (e) { 11951 $exceptionHandler(e); 11952 } 11953 } 11954 }, 11955 11956 11957 /** 11958 * @ngdoc event 11959 * @name ng.$rootScope.Scope#$destroy 11960 * @eventOf ng.$rootScope.Scope 11961 * @eventType broadcast on scope being destroyed 11962 * 11963 * @description 11964 * Broadcasted when a scope and its children are being destroyed. 11965 * 11966 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 11967 * clean up DOM bindings before an element is removed from the DOM. 11968 */ 11969 11970 /** 11971 * @ngdoc function 11972 * @name ng.$rootScope.Scope#$destroy 11973 * @methodOf ng.$rootScope.Scope 11974 * @function 11975 * 11976 * @description 11977 * Removes the current scope (and all of its children) from the parent scope. Removal implies 11978 * that calls to {@link ng.$rootScope.Scope#methods_$digest $digest()} will no longer 11979 * propagate to the current scope and its children. Removal also implies that the current 11980 * scope is eligible for garbage collection. 11981 * 11982 * The `$destroy()` is usually used by directives such as 11983 * {@link ng.directive:ngRepeat ngRepeat} for managing the 11984 * unrolling of the loop. 11985 * 11986 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 11987 * Application code can register a `$destroy` event handler that will give it a chance to 11988 * perform any necessary cleanup. 11989 * 11990 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 11991 * clean up DOM bindings before an element is removed from the DOM. 11992 */ 11993 $destroy: function() { 11994 // we can't destroy the root scope or a scope that has been already destroyed 11995 if (this.$$destroyed) return; 11996 var parent = this.$parent; 11997 11998 this.$broadcast('$destroy'); 11999 this.$$destroyed = true; 12000 if (this === $rootScope) return; 12001 12002 forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); 12003 12004 if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 12005 if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 12006 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 12007 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 12008 12009 // This is bogus code that works around Chrome's GC leak 12010 // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 12011 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 12012 this.$$childTail = null; 12013 }, 12014 12015 /** 12016 * @ngdoc function 12017 * @name ng.$rootScope.Scope#$eval 12018 * @methodOf ng.$rootScope.Scope 12019 * @function 12020 * 12021 * @description 12022 * Executes the `expression` on the current scope and returns the result. Any exceptions in 12023 * the expression are propagated (uncaught). This is useful when evaluating Angular 12024 * expressions. 12025 * 12026 * # Example 12027 * <pre> 12028 var scope = ng.$rootScope.Scope(); 12029 scope.a = 1; 12030 scope.b = 2; 12031 12032 expect(scope.$eval('a+b')).toEqual(3); 12033 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 12034 * </pre> 12035 * 12036 * @param {(string|function())=} expression An angular expression to be executed. 12037 * 12038 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12039 * - `function(scope)`: execute the function with the current `scope` parameter. 12040 * 12041 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 12042 * @returns {*} The result of evaluating the expression. 12043 */ 12044 $eval: function(expr, locals) { 12045 return $parse(expr)(this, locals); 12046 }, 12047 12048 /** 12049 * @ngdoc function 12050 * @name ng.$rootScope.Scope#$evalAsync 12051 * @methodOf ng.$rootScope.Scope 12052 * @function 12053 *
12054 * @description 12055 * Executes the expression on the current scope at a later point in time. 12056 * 12057 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 12058 * that: 12059 * 12060 * - it will execute after the function that scheduled the evaluation (preferably before DOM 12061 * rendering). 12062 * - at least one {@link ng.$rootScope.Scope#methods_$digest $digest cycle} will be performed after 12063 * `expression` execution. 12064 * 12065 * Any exceptions from the execution of the expression are forwarded to the 12066 * {@link ng.$exceptionHandler $exceptionHandler} service. 12067 * 12068 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 12069 * will be scheduled. However, it is encouraged to always call code that changes the model 12070 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 12071 * 12072 * @param {(string|function())=} expression An angular expression to be executed. 12073 * 12074 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12075 * - `function(scope)`: execute the function with the current `scope` parameter. 12076 * 12077 */ 12078 $evalAsync: function(expr) { 12079 // if we are outside of an $digest loop and this is the first time we are scheduling async 12080 // task also schedule async auto-flush 12081 if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { 12082 $browser.defer(function() { 12083 if ($rootScope.$$asyncQueue.length) { 12084 $rootScope.$digest(); 12085 } 12086 }); 12087 } 12088 12089 this.$$asyncQueue.push({scope: this, expression: expr}); 12090 }, 12091 12092 $$postDigest : function(fn) { 12093 this.$$postDigestQueue.push(fn); 12094 }, 12095 12096 /** 12097 * @ngdoc function 12098 * @name ng.$rootScope.Scope#$apply 12099 * @methodOf ng.$rootScope.Scope 12100 * @function 12101 * 12102 * @description 12103 * `$apply()` is used to execute an expression in angular from outside of the angular 12104 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 12105 * Because we are calling into the angular framework we need to perform proper scope life 12106 * cycle of {@link ng.$exceptionHandler exception handling}, 12107 * {@link ng.$rootScope.Scope#methods_$digest executing watches}. 12108 * 12109 * ## Life cycle 12110 * 12111 * # Pseudo-Code of `$apply()` 12112 * <pre> 12113 function $apply(expr) { 12114 try { 12115 return $eval(expr); 12116 } catch (e) { 12117 $exceptionHandler(e); 12118 } finally { 12119 $root.$digest(); 12120 } 12121 } 12122 * </pre> 12123 * 12124 * 12125 * Scope's `$apply()` method transitions through the following stages: 12126 * 12127 * 1. The {@link guide/expression expression} is executed using the 12128 * {@link ng.$rootScope.Scope#methods_$eval $eval()} method. 12129 * 2. Any exceptions from the execution of the expression are forwarded to the 12130 * {@link ng.$exceptionHandler $exceptionHandler} service. 12131 * 3. The {@link ng.$rootScope.Scope#methods_$watch watch} listeners are fired immediately after the 12132 * expression was executed using the {@link ng.$rootScope.Scope#methods_$digest $digest()} method. 12133 * 12134 * 12135 * @param {(string|function())=} exp An angular expression to be executed. 12136 * 12137 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12138 * - `function(scope)`: execute the function with current `scope` parameter. 12139 * 12140 * @returns {*} The result of evaluating the expression. 12141 */ 12142 $apply: function(expr) { 12143 try { 12144 beginPhase('$apply'); 12145 return this.$eval(expr); 12146 } catch (e) { 12147 $exceptionHandler(e); 12148 } finally { 12149 clearPhase(); 12150 try { 12151 $rootScope.$digest(); 12152 } catch (e) { 12153 $exceptionHandler(e); 12154 throw e; 12155 } 12156 } 12157 }, 12158 12159 /** 12160 * @ngdoc function 12161 * @name ng.$rootScope.Scope#$on 12162 * @methodOf ng.$rootScope.Scope 12163 * @function 12164 *
12165 * @description 12166 * Listens on events of a given type. See {@link ng.$rootScope.Scope#methods_$emit $emit} for 12167 * discussion of event life cycle. 12168 * 12169 * The event listener function format is: `function(event, args...)`. The `event` object 12170 * passed into the listener has the following attributes: 12171 * 12172 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 12173 * `$broadcast`-ed. 12174 * - `currentScope` - `{Scope}`: the current scope which is handling the event. 12175 * - `name` - `{string}`: name of the event. 12176 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 12177 * further event propagation (available only for events that were `$emit`-ed). 12178 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 12179 * to true. 12180 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 12181 * 12182 * @param {string} name Event name to listen on. 12183 * @param {function(event, args...)} listener Function to call when the event is emitted. 12184 * @returns {function()} Returns a deregistration function for this listener. 12185 */ 12186 $on: function(name, listener) { 12187 var namedListeners = this.$$listeners[name]; 12188 if (!namedListeners) { 12189 this.$$listeners[name] = namedListeners = []; 12190 } 12191 namedListeners.push(listener); 12192 12193 var current = this; 12194 do { 12195 if (!current.$$listenerCount[name]) { 12196 current.$$listenerCount[name] = 0; 12197 } 12198 current.$$listenerCount[name]++; 12199 } while ((current = current.$parent)); 12200 12201 var self = this; 12202 return function() { 12203 namedListeners[indexOf(namedListeners, listener)] = null; 12204 decrementListenerCount(self, 1, name); 12205 }; 12206 }, 12207 12208 12209 /** 12210 * @ngdoc function 12211 * @name ng.$rootScope.Scope#$emit 12212 * @methodOf ng.$rootScope.Scope 12213 * @function 12214 * 12215 * @description 12216 * Dispatches an event `name` upwards through the scope hierarchy notifying the 12217 * registered {@link ng.$rootScope.Scope#methods_$on} listeners. 12218 * 12219 * The event life cycle starts at the scope on which `$emit` was called. All 12220 * {@link ng.$rootScope.Scope#methods_$on listeners} listening for `name` event on this scope get 12221 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 12222 * registered listeners along the way. The event will stop propagating if one of the listeners 12223 * cancels it. 12224 * 12225 * Any exception emitted from the {@link ng.$rootScope.Scope#methods_$on listeners} will be passed 12226 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 12227 * 12228 * @param {string} name Event name to emit. 12229 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 12230 * @return {Object} Event object (see {@link ng.$rootScope.Scope#methods_$on}). 12231 */ 12232 $emit: function(name, args) { 12233 var empty = [], 12234 namedListeners, 12235 scope = this, 12236 stopPropagation = false, 12237 event = { 12238 name: name, 12239 targetScope: scope, 12240 stopPropagation: function() {stopPropagation = true;}, 12241 preventDefault: function() { 12242 event.defaultPrevented = true; 12243 }, 12244 defaultPrevented: false 12245 }, 12246 listenerArgs = concat([event], arguments, 1), 12247 i, length; 12248 12249 do { 12250 namedListeners = scope.$$listeners[name] || empty; 12251 event.currentScope = scope; 12252 for (i=0, length=namedListeners.length; i<length; i++) { 12253 12254 // if listeners were deregistered, defragment the array 12255 if (!namedListeners[i]) { 12256 namedListeners.splice(i, 1); 12257 i--; 12258 length--; 12259 continue; 12260 } 12261 try { 12262 //allow all listeners attached to the current scope to run 12263 namedListeners[i].apply(null, listenerArgs); 12264 } catch (e) { 12265 $exceptionHandler(e); 12266 } 12267 } 12268 //if any listener on the current scope stops propagation, prevent bubbling 12269 if (stopPropagation) return event; 12270 //traverse upwards 12271 scope = scope.$parent; 12272 } while (scope); 12273 12274 return event; 12275 }, 12276 12277 12278 /** 12279 * @ngdoc function 12280 * @name ng.$rootScope.Scope#$broadcast 12281 * @methodOf ng.$rootScope.Scope 12282 * @function 12283 *
12284 * @description 12285 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 12286 * registered {@link ng.$rootScope.Scope#methods_$on} listeners. 12287 * 12288 * The event life cycle starts at the scope on which `$broadcast` was called. All 12289 * {@link ng.$rootScope.Scope#methods_$on listeners} listening for `name` event on this scope get 12290 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 12291 * scope and calls all registered listeners along the way. The event cannot be canceled. 12292 * 12293 * Any exception emitted from the {@link ng.$rootScope.Scope#methods_$on listeners} will be passed 12294 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 12295 * 12296 * @param {string} name Event name to broadcast. 12297 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 12298 * @return {Object} Event object, see {@link ng.$rootScope.Scope#methods_$on} 12299 */ 12300 $broadcast: function(name, args) { 12301 var target = this, 12302 current = target, 12303 next = target, 12304 event = { 12305 name: name, 12306 targetScope: target, 12307 preventDefault: function() { 12308 event.defaultPrevented = true; 12309 }, 12310 defaultPrevented: false 12311 }, 12312 listenerArgs = concat([event], arguments, 1), 12313 listeners, i, length; 12314 12315 //down while you can, then up and next sibling or up and next sibling until back at root 12316 while ((current = next)) { 12317 event.currentScope = current; 12318 listeners = current.$$listeners[name] || []; 12319 for (i=0, length = listeners.length; i<length; i++) { 12320 // if listeners were deregistered, defragment the array 12321 if (!listeners[i]) { 12322 listeners.splice(i, 1); 12323 i--; 12324 length--; 12325 continue; 12326 } 12327 12328 try { 12329 listeners[i].apply(null, listenerArgs); 12330 } catch(e) { 12331 $exceptionHandler(e); 12332 } 12333 } 12334 12335 // Insanity Warning: scope depth-first traversal 12336 // yes, this code is a bit crazy, but it works and we have tests to prove it! 12337 // this piece should be kept in sync with the traversal in $digest 12338 // (though it differs due to having the extra check for $$listenerCount) 12339 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 12340 (current !== target && current.$$nextSibling)))) { 12341 while(current !== target && !(next = current.$$nextSibling)) { 12342 current = current.$parent; 12343 } 12344 } 12345 } 12346 12347 return event; 12348 } 12349 }; 12350 12351 var $rootScope = new Scope(); 12352 12353 return $rootScope; 12354 12355 12356 function beginPhase(phase) { 12357 if ($rootScope.$$phase) { 12358 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 12359 } 12360 12361 $rootScope.$$phase = phase; 12362 } 12363 12364 function clearPhase() { 12365 $rootScope.$$phase = null; 12366 } 12367 12368 function compileToFn(exp, name) { 12369 var fn = $parse(exp); 12370 assertArgFn(fn, name); 12371 return fn; 12372 } 12373 12374 function decrementListenerCount(current, count, name) { 12375 do { 12376 current.$$listenerCount[name] -= count; 12377 12378 if (current.$$listenerCount[name] === 0) { 12379 delete current.$$listenerCount[name]; 12380 } 12381 } while ((current = current.$parent)); 12382 } 12383 12384 /** 12385 * function used as an initial value for watchers. 12386 * because it's unique we can easily tell it apart from other values 12387 */ 12388 function initWatchVal() {} 12389 }]; 12390} 12391 12392/** 12393 * @description 12394 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 12395 */ 12396function $$SanitizeUriProvider() { 12397 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 12398 imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//; 12399 12400 /** 12401 * @description 12402 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 12403 * urls during a[href] sanitization. 12404 * 12405 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 12406 * 12407 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 12408 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 12409 * regular expression. If a match is found, the original url is
12409written into the dom. Otherwise, 12410 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 12411 * 12412 * @param {RegExp=} regexp New regexp to whitelist urls with. 12413 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 12414 * chaining otherwise. 12415 */ 12416 this.aHrefSanitizationWhitelist = function(regexp) { 12417 if (isDefined(regexp)) { 12418 aHrefSanitizationWhitelist = regexp; 12419 return this; 12420 } 12421 return aHrefSanitizationWhitelist; 12422 }; 12423 12424 12425 /** 12426 * @description 12427 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 12428 * urls during img[src] sanitization. 12429 * 12430 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 12431 * 12432 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 12433 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 12434 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 12435 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 12436 * 12437 * @param {RegExp=} regexp New regexp to whitelist urls with. 12438 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 12439 * chaining otherwise. 12440 */ 12441 this.imgSrcSanitizationWhitelist = function(regexp) { 12442 if (isDefined(regexp)) { 12443 imgSrcSanitizationWhitelist = regexp; 12444 return this; 12445 } 12446 return imgSrcSanitizationWhitelist; 12447 }; 12448 12449 this.$get = function() { 12450 return function sanitizeUri(uri, isImage) { 12451 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 12452 var normalizedVal; 12453 // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case. 12454 if (!msie || msie >= 8 ) { 12455 normalizedVal = urlResolve(uri).href; 12456 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 12457 return 'unsafe:'+normalizedVal; 12458 } 12459 } 12460 return uri; 12461 }; 12462 }; 12463} 12464 12465var $sceMinErr = minErr('$sce'); 12466 12467var SCE_CONTEXTS = { 12468 HTML: 'html', 12469 CSS: 'css', 12470 URL: 'url', 12471 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 12472 // url. (e.g. ng-include, script src, templateUrl) 12473 RESOURCE_URL: 'resourceUrl', 12474 JS: 'js' 12475}; 12476 12477// Helper functions follow. 12478 12479// Copied from: 12480// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 12481// Prereq: s is a string. 12482function escapeForRegexp(s) { 12483 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 12484 replace(/\x08/g, '\\x08'); 12485} 12486 12487 12488function adjustMatcher(matcher) { 12489 if (matcher === 'self') { 12490 return matcher; 12491 } else if (isString(matcher)) { 12492 // Strings match exactly except for 2 wildcards - '*' and '**'. 12493 // '*' matches any character except those from the set ':/.?&'. 12494 // '**' matches any character (like .* in a RegExp). 12495 // More than 2 *'s raises an error as it's ill defined. 12496 if (matcher.indexOf('***') > -1) { 12497 throw $sceMinErr('iwcard', 12498 'Illegal sequence *** in string matcher. String: {0}', matcher); 12499 } 12500 matcher = escapeForRegexp(matcher). 12501 replace('\\*\\*', '.*'). 12502 replace('\\*', '[^:/.?&;]*'); 12503 return new RegExp('^' + matcher + '$'); 12504 } else if (isRegExp(matcher)) { 12505 // The only other type of matcher allowed is a Regexp. 12506 // Match entire URL / disallow partial matches. 12507 // Flags are reset (i.e. no global, ignoreCase or multiline) 12508 return new RegExp('^' + matcher.source + '$'); 12509 } else { 12510 throw $sceMinErr('imatcher', 12511 'Matchers may only be "self", string patterns or RegExp objects'); 12512 } 12513} 12514 12515 12516function adjustMatchers(matchers) { 12517 var adjustedMatchers = []; 12518 if (isDefined(matchers)) { 12519 forEach(matchers, function(matcher) { 12520 adjustedMatchers.push(adjustMatcher(matcher)); 12521 }); 12522 } 12523 return adjustedMatchers; 12524} 12525 12526 12527/** 12528 * @ngdoc service 12529 * @name ng.$sceDelegate 12530 * @function 12531 * 12532 * @description 12533 * 12534 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 12535 * Contextual Escaping (SCE)} services to AngularJS. 12536 * 12537 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 12538 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 12539 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 12540 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 12541 * work because `$sce` delegates to `$sceDelegate` for these operations. 12542 * 12543 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 12544 * 12545 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
12546 * can override it completely to change the behavior of `$sce`, the common case would 12547 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 12548 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 12549 * templates. Refer {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist 12550 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 12551 * ng.$sceDelegateProvider#methods_resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 12552 */ 12553 12554/** 12555 * @ngdoc object 12556 * @name ng.$sceDelegateProvider 12557 * @description 12558 * 12559 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 12560 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 12561 * that the URLs used for sourcing Angular templates are safe. Refer {@link 12562 * ng.$sceDelegateProvider#methods_resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 12563 * {@link ng.$sceDelegateProvider#methods_resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 12564 * 12565 * For the general details about this service in Angular, read the main page for {@link ng.$sce 12566 * Strict Contextual Escaping (SCE)}. 12567 * 12568 * **Example**: Consider the following case. <a name="example"></a> 12569 * 12570 * - your app is hosted at url `http://myapp.example.com/` 12571 * - but some of your templates are hosted on other domains you control such as 12572 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 12573 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 12574 * 12575 * Here is what a secure configuration for this scenario might look like: 12576 * 12577 * <pre class="prettyprint"> 12578 * angular.module('myApp', []).config(function($sceDelegateProvider) { 12579 * $sceDelegateProvider.resourceUrlWhitelist([ 12580 * // Allow same origin resource loads. 12581 * 'self', 12582 * // Allow loading from our assets domain. Notice the difference between * and **. 12583 * 'http://srv*.assets.example.com/**']); 12584 * 12585 * // The blacklist overrides the whitelist so the open redirect here is blocked. 12586 * $sceDelegateProvider.resourceUrlBlacklist([ 12587 * 'http://myapp.example.com/clickThru**']); 12588 * }); 12589 * </pre> 12590 */ 12591 12592function $SceDelegateProvider() { 12593 this.SCE_CONTEXTS = SCE_CONTEXTS; 12594 12595 // Resource URLs can also be trusted by policy. 12596 var resourceUrlWhitelist = ['self'], 12597 resourceUrlBlacklist = []; 12598 12599 /** 12600 * @ngdoc function 12601 * @name ng.sceDelegateProvider#resourceUrlWhitelist 12602 * @methodOf ng.$sceDelegateProvider 12603 * @function 12604 * 12605 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 12606 * provided. This must be an array or null. A snapshot of this array is used so further 12607 * changes to the array are ignored. 12608 * 12609 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 12610 * allowed in this array. 12611 * 12612 * Note: **an empty whitelist array will block all URLs**! 12613 * 12614 * @return {Array} the currently set whitelist array. 12615 * 12616 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 12617 * same origin resource requests. 12618 * 12619 * @description 12620 * Sets/Gets the whitelist of trusted resource URLs. 12621 */ 12622 this.resourceUrlWhitelist = function (value) { 12623 if (arguments.length) { 12624 resourceUrlWhitelist = adjustMatchers(value); 12625 } 12626 return resourceUrlWhitelist; 12627 }; 12628 12629 /** 12630 * @ngdoc function 12631 * @name ng.sceDelegateProvider#resourceUrlBlacklist 12632 * @methodOf ng.$sceDelegateProvider 12633 * @function 12634 * 12635 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 12636 * provided. This must be an array or null. A snapshot of this array is used so further 12637 * changes to the array are ignored. 12638 * 12639 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 12640 * allowed in this array. 12641 *
12642 * The typical usage for the blacklist is to **block 12643 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 12644 * these would otherwise be trusted but actually return content from the redirected domain. 12645 * 12646 * Finally, **the blacklist overrides the whitelist** and has the final say. 12647 * 12648 * @return {Array} the currently set blacklist array. 12649 * 12650 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 12651 * is no blacklist.) 12652 * 12653 * @description 12654 * Sets/Gets the blacklist of trusted resource URLs. 12655 */ 12656 12657 this.resourceUrlBlacklist = function (value) { 12658 if (arguments.length) { 12659 resourceUrlBlacklist = adjustMatchers(value); 12660 } 12661 return resourceUrlBlacklist; 12662 }; 12663 12664 this.$get = ['$injector', function($injector) { 12665 12666 var htmlSanitizer = function htmlSanitizer(html) { 12667 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 12668 }; 12669 12670 if ($injector.has('$sanitize')) { 12671 htmlSanitizer = $injector.get('$sanitize'); 12672 } 12673 12674 12675 function matchUrl(matcher, parsedUrl) { 12676 if (matcher === 'self') { 12677 return urlIsSameOrigin(parsedUrl); 12678 } else { 12679 // definitely a regex. See adjustMatchers() 12680 return !!matcher.exec(parsedUrl.href); 12681 } 12682 } 12683 12684 function isResourceUrlAllowedByPolicy(url) { 12685 var parsedUrl = urlResolve(url.toString()); 12686 var i, n, allowed = false; 12687 // Ensure that at least one item from the whitelist allows this url. 12688 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 12689 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 12690 allowed = true; 12691 break; 12692 } 12693 } 12694 if (allowed) { 12695 // Ensure that no item from the blacklist blocked this url. 12696 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 12697 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 12698 allowed = false; 12699 break; 12700 } 12701 } 12702 } 12703 return allowed; 12704 } 12705 12706 function generateHolderType(Base) { 12707 var holderType = function TrustedValueHolderType(trustedValue) { 12708 this.$$unwrapTrustedValue = function() { 12709 return trustedValue; 12710 }; 12711 }; 12712 if (Base) { 12713 holderType.prototype = new Base(); 12714 } 12715 holderType.prototype.valueOf = function sceValueOf() { 12716 return this.$$unwrapTrustedValue(); 12717 }; 12718 holderType.prototype.toString = function sceToString() { 12719 return this.$$unwrapTrustedValue().toString(); 12720 }; 12721 return holderType; 12722 } 12723 12724 var trustedValueHolderBase = generateHolderType(), 12725 byType = {}; 12726 12727 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 12728 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 12729 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 12730 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 12731 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 12732 12733 /** 12734 * @ngdoc method 12735 * @name ng.$sceDelegate#trustAs 12736 * @methodOf ng.$sceDelegate 12737 * 12738 * @description 12739 * Returns an object that is trusted by angular for use in specified strict 12740 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 12741 * attribute interpolation, any dom event binding attribute interpolation 12742 * such as for onclick, etc.) that uses the provided value. 12743 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 12744 * 12745 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 12746 * resourceUrl, html, js and css. 12747 * @param {*} value The value that that should be considered trusted/safe. 12748 * @returns {*} A value that can be used to stand in for the provided `value` in places 12749 * where Angular expects a $sce.trustAs() return value. 12750 */ 12751 function trustAs(type, trustedValue) { 12752 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 12753 if (!Constructor) { 12754 throw $sceMinErr('icontext',
vendor: 5,263 bytes, lines 12755-12880
12755 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 12756 type, trustedValue); 12757 } 12758 if (trustedValue === null || trustedValue === undefined || trustedValue === '') { 12759 return trustedValue; 12760 } 12761 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 12762 // mutable objects, we ensure here that the value passed in is actually a string. 12763 if (typeof trustedValue !== 'string') { 12764 throw $sceMinErr('itype', 12765 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 12766 type); 12767 } 12768 return new Constructor(trustedValue); 12769 } 12770 12771 /** 12772 * @ngdoc method 12773 * @name ng.$sceDelegate#valueOf 12774 * @methodOf ng.$sceDelegate 12775 * 12776 * @description 12777 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#methods_trustAs 12778 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 12779 * ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. 12780 * 12781 * If the passed parameter is not a value that had been returned by {@link 12782 * ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}, returns it as-is. 12783 * 12784 * @param {*} value The result of a prior {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`} 12785 * call or anything else. 12786 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#methods_trustAs 12787 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 12788 * `value` unchanged. 12789 */ 12790 function valueOf(maybeTrusted) { 12791 if (maybeTrusted instanceof trustedValueHolderBase) { 12792 return maybeTrusted.$$unwrapTrustedValue(); 12793 } else { 12794 return maybeTrusted; 12795 } 12796 } 12797 12798 /** 12799 * @ngdoc method 12800 * @name ng.$sceDelegate#getTrusted 12801 * @methodOf ng.$sceDelegate 12802 * 12803 * @description 12804 * Takes the result of a {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`} call and 12805 * returns the originally supplied value if the queried context type is a supertype of the 12806 * created type. If this condition isn't satisfied, throws an exception. 12807 * 12808 * @param {string} type The kind of context in which this value is to be used. 12809 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#methods_trustAs 12810 * `$sceDelegate.trustAs`} call. 12811 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#methods_trustAs 12812 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 12813 */ 12814 function getTrusted(type, maybeTrusted) { 12815 if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { 12816 return maybeTrusted; 12817 } 12818 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 12819 if (constructor && maybeTrusted instanceof constructor) { 12820 return maybeTrusted.$$unwrapTrustedValue(); 12821 } 12822 // If we get here, then we may only take one of two actions. 12823 // 1. sanitize the value for the requested type, or 12824 // 2. throw an exception. 12825 if (type === SCE_CONTEXTS.RESOURCE_URL) { 12826 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 12827 return maybeTrusted; 12828 } else { 12829 throw $sceMinErr('insecurl', 12830 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 12831 maybeTrusted.toString()); 12832 } 12833 } else if (type === SCE_CONTEXTS.HTML) { 12834 return htmlSanitizer(maybeTrusted); 12835 } 12836 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 12837 } 12838 12839 return { trustAs: trustAs, 12840 getTrusted: getTrusted, 12841 valueOf: valueOf }; 12842 }]; 12843} 12844 12845 12846/** 12847 * @ngdoc object 12848 * @name ng.$sceProvider 12849 * @description 12850 * 12851 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 12852 * - enable/disable Strict Contextual Escaping (SCE) in a module 12853 * - override the default implementation with a custom delegate 12854 * 12855 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 12856 */ 12857 12858/* jshint maxlen: false*/ 12859 12860/** 12861 * @ngdoc service 12862 * @name ng.$sce 12863 * @function 12864 * 12865 * @description 12866 * 12867 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 12868 * 12869 * # Strict Contextual Escaping 12870 * 12871 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 12872 * contexts to result in a value that is marked as safe to use for that context. One example of 12873 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 12874 * to these contexts as privileged or SCE contexts. 12875 * 12876 * As of version 1.2, Angular ships with SCE enabled by default. 12877 * 12878 * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows 12879 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 12880 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
12881 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 12882 * to the top of your HTML document. 12883 * 12884 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 12885 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 12886 * 12887 * Here's an example of a binding in a privileged context: 12888 * 12889 * <pre class="prettyprint"> 12890 * <input ng-model="userHtml"> 12891 * <div ng-bind-html="userHtml"> 12892 * </pre> 12893 * 12894 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 12895 * disabled, this application allows the user to render arbitrary HTML into the DIV. 12896 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 12897 * bindings. (HTML is just one example of a context where rendering user controlled input creates 12898 * security vulnerabilities.) 12899 * 12900 * For the case of HTML, you might use a library, either on the client side, or on the server side, 12901 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 12902 * 12903 * How would you ensure that every place that used these types of bindings was bound to a value that 12904 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 12905 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 12906 * properties/fields and forgot to update the binding to the sanitized value? 12907 * 12908 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 12909 * determine that something explicitly says it's safe to use a value for binding in that
12910 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 12911 * for those values that you can easily tell are safe - because they were received from your server, 12912 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 12913 * allowing only the files in a specific directory to do this. Ensuring that the internal API 12914 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 12915 * 12916 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#methods_trustAs $sce.trustAs} 12917 * (and shorthand methods such as {@link ng.$sce#methods_trustAsHtml $sce.trustAsHtml}, etc.) to 12918 * obtain values that will be accepted by SCE / privileged contexts. 12919 * 12920 * 12921 * ## How does it work? 12922 * 12923 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#methods_getTrusted 12924 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 12925 * ng.$sce#methods_parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 12926 * {@link ng.$sce#methods_getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 12927 * 12928 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 12929 * ng.$sce#methods_parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 12930 * simplified): 12931 * 12932 * <pre class="prettyprint"> 12933 * var ngBindHtmlDirective = ['$sce', function($sce) { 12934 * return function(scope, element, attr) { 12935 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 12936 * element.html(value || ''); 12937 * }); 12938 * }; 12939 * }]; 12940 * </pre> 12941 * 12942 * ## Impact on loading templates 12943 * 12944 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 12945 * `templateUrl`'s specified by {@link guide/directive directives}. 12946 * 12947 * By default, Angular only loads templates from the same domain and protocol as the application 12948 * document. This is done by calling {@link ng.$sce#methods_getTrustedResourceUrl 12949 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 12950 * protocols, you may either either {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelist 12951 * them} or {@link ng.$sce#methods_trustAsResourceUrl wrap it} into a trusted value. 12952 * 12953 * *Please note*: 12954 * The browser's 12955 * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest 12956 * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing (CORS)} 12957 * policy apply in addition to this and may further restrict whether the template is successfully 12958 * loaded. This means that without the right CORS policy, loading templates from a different domain 12959 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 12960 * browsers. 12961 * 12962 * ## This feels like too much overhead for the developer? 12963 * 12964 * It's important to remember that SCE only applies to interpolation expressions. 12965 * 12966 * If your expressions are constant literals, they're automatically trusted and you don't need to 12967 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 12968 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 12969 * 12970 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 12971 * through {@link ng.$sce#methods_getTrusted $sce.getTrusted}. SCE doesn't play a role here. 12972 * 12973 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 12974 * templates in `ng-include` from your application's domain without having to even know about SCE. 12975 * It blocks loading templates from other domains or loading templates over http from an https 12976 * served document. You can change these by setting your own custom {@link 12977 * ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelists} and {@link 12978 * ng.$sceDelegateProvider#methods_resourceUrlBlacklist blacklists} for matching such URLs. 12979 * 12980 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 12981 * application that's secure and can be audited to verify that with much more ease than bolting 12982 * security onto an application later. 12983 * 12984 * <a name="contexts"></a> 12985 * ## What trusted context types are supported? 12986 * 12987 * | Context | Notes | 12988 * |---------------------|----------------| 12989 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. | 12990 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 12991 * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't consititute an SCE context. | 12992 * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contens 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. | 12993 * | `$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. | 12994 * 12995 * ## Format of items in {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#methods_resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 12996 * 12997 * Each element in these arrays must be one of the following: 12998 * 12999 * - **'self'** 13000 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 13001 * domain** as the application document using the **same protocol**. 13002 * - **String** (except the special value `'self'`) 13003 * - The string is matched against the full *normalized / absolute URL* of the resource 13004 * being tested (substring matches are not good enough.) 13005 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 13006 * match themselves. 13007 * - `*`: matches zero or more occurances of any character other than one of the following 6 13008 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use
13009 * in a whitelist. 13010 * - `**`: matches zero or more occurances of *any* character. As such, it's not 13011 * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. 13012 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 13013 * not have been the intention.) It's usage at the very end of the path is ok. (e.g. 13014 * http://foo.example.com/templates/**). 13015 * - **RegExp** (*see caveat below*) 13016 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 13017 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 13018 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 13019 * have good test coverage.). For instance, the use of `.` in the regex is correct only in a 13020 * small number of cases. A `.` character in the regex used when matching the scheme or a 13021 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 13022 * is highly recommended to use the string patterns and only fall back to regular expressions 13023 * if they as a last resort. 13024 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 13025 * matched against the **entire** *normalized / absolute URL* of the resource being tested 13026 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 13027 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 13028 * - If you are generating your JavaScript from some other templating engine (not 13029 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 13030 * remember to escape your regular expression (and be aware that you might need more than 13031 * one level of escaping depending on your templating engine and the way you interpolated 13032 * the value.) Do make use of your platform's escaping mechanism as it might be good 13033 * enough before coding your own. e.g. Ruby has 13034 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 13035 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 13036 * Javascript lacks a similar built in function for escaping. Take a look at Google 13037 * Closure library's [goog.string.regExpEscape(s)]( 13038 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 13039 * 13040 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 13041 * 13042 * ## Show me an example using SCE. 13043 * 13044 * @example 13045<example module="mySceApp" deps="angular-sanitize.js"> 13046<file name="index.html"> 13047 <div ng-controller="myAppController as myCtrl"> 13048 <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 13049 <b>User comments</b><br> 13050 By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 13051 $sanitize is available. If $sanitize isn't available, this results in an error instead of an 13052 exploit. 13053 <div class="well"> 13054 <div ng-repeat="userComment in myCtrl.userComments"> 13055 <b>{{userComment.name}}</b>: 13056 <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 13057 <br> 13058 </div> 13059 </div> 13060 </div> 13061</file> 13062 13063<file name="script.js"> 13064 var mySceApp = angular.module('mySceApp', ['ngSanitize']); 13065 13066 mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { 13067 var self = this; 13068 $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 13069 self.userComments = userComments; 13070 }); 13071 self.explicitlyTrustedHtml = $sce.trustAsHtml( 13072 '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 13073 'sanitization."">Hover over this text.</span>'); 13074 }); 13075</file> 13076 13077<file name="test_data.json"> 13078[ 13079 { "name": "Alice", 13080 "htmlComment": 13081 "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 13082 }, 13083 { "name": "Bob",
13084 "htmlComment": "<i>Yes!</i> Am I the only other one?" 13085 } 13086] 13087</file> 13088 13089<file name="protractorTest.js"> 13090 describe('SCE doc demo', function() { 13091 it('should sanitize untrusted values', function() { 13092 expect(element(by.css('.htmlComment')).getInnerHtml()) 13093 .toBe('<span>Is <i>anyone</i> reading this?</span>'); 13094 }); 13095 13096 it('should NOT sanitize explicitly trusted values', function() { 13097 expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 13098 '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 13099 'sanitization."">Hover over this text.</span>'); 13100 }); 13101 }); 13102</file> 13103</example> 13104 * 13105 * 13106 * 13107 * ## Can I disable SCE completely? 13108 * 13109 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 13110 * for little coding overhead. It will be much harder to take an SCE disabled application and 13111 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 13112 * for cases where you have a lot of existing code that was written before SCE was introduced and 13113 * you're migrating them a module at a time. 13114 * 13115 * That said, here's how you can completely disable SCE: 13116 * 13117 * <pre class="prettyprint"> 13118 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 13119 * // Completely disable SCE. For demonstration purposes only! 13120 * // Do not use in new projects. 13121 * $sceProvider.enabled(false); 13122 * }); 13123 * </pre> 13124 * 13125 */ 13126/* jshint maxlen: 100 */ 13127 13128function $SceProvider() { 13129 var enabled = true; 13130 13131 /** 13132 * @ngdoc function 13133 * @name ng.sceProvider#enabled 13134 * @methodOf ng.$sceProvider 13135 * @function 13136 * 13137 * @param {boolean=} value If provided, then enables/disables SCE. 13138 * @return {boolean} true if SCE is enabled, false otherwise. 13139 * 13140 * @description 13141 * Enables/disables SCE and returns the current value. 13142 */ 13143 this.enabled = function (value) { 13144 if (arguments.length) { 13145 enabled = !!value; 13146 } 13147 return enabled; 13148 }; 13149 13150 13151 /* Design notes on the default implementation for SCE. 13152 * 13153 * The API contract for the SCE delegate 13154 * ------------------------------------- 13155 * The SCE delegate object must provide the following 3 methods: 13156 * 13157 * - trustAs(contextEnum, value) 13158 * This method is used to tell the SCE service that the provided value is OK to use in the 13159 * contexts specified by contextEnum. It must return an object that will be accepted by 13160 * getTrusted() for a compatible contextEnum and return this value. 13161 * 13162 * - valueOf(value) 13163 * For values that were not produced by trustAs(), return them as is. For values that were 13164 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 13165 * trustAs is wrapping the given values into some type, this operation unwraps it when given 13166 * such a value. 13167 * 13168 * - getTrusted(contextEnum, value) 13169 * This function should return the a value that is safe to use in the context specified by 13170 * contextEnum or throw and exception otherwise. 13171 * 13172 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 13173 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 13174 * instance, an implementation could maintain a registry of all trusted objects by context. In 13175 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 13176 * return the same object passed in if it was found in the registry under a compatible context or 13177 * throw an exception otherwise. An implementation might only wrap values some of the time based 13178 * on some criteria. getTrusted() might return a value and not throw an exception for special 13179 * constants or objects even if not wrapped. All such implementations fulfill this contract. 13180 * 13181 * 13182 * A note on the inheritance model for SCE contexts 13183 * ------------------------------------------------ 13184 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 13185 * is purely an implementation details. 13186 * 13187 * The contract is simply this: 13188 * 13189 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 13190 * will also succeed. 13191 * 13192 * Inheritance happens to capture this in a natural way. In some future, we
13193 * may not use inheritance anymore. That is OK because no code outside of 13194 * sce.js and sceSpecs.js would need to be aware of this detail. 13195 */ 13196 13197 this.$get = ['$parse', '$sniffer', '$sceDelegate', function( 13198 $parse, $sniffer, $sceDelegate) { 13199 // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows 13200 // the "expression(javascript expression)" syntax which is insecure. 13201 if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { 13202 throw $sceMinErr('iequirks', 13203 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + 13204 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 13205 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 13206 } 13207 13208 var sce = copy(SCE_CONTEXTS); 13209 13210 /** 13211 * @ngdoc function 13212 * @name ng.sce#isEnabled 13213 * @methodOf ng.$sce 13214 * @function 13215 * 13216 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 13217 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 13218 * 13219 * @description 13220 * Returns a boolean indicating if SCE is enabled. 13221 */ 13222 sce.isEnabled = function () { 13223 return enabled; 13224 }; 13225 sce.trustAs = $sceDelegate.trustAs; 13226 sce.getTrusted = $sceDelegate.getTrusted; 13227 sce.valueOf = $sceDelegate.valueOf; 13228 13229 if (!enabled) { 13230 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 13231 sce.valueOf = identity; 13232 } 13233 13234 /** 13235 * @ngdoc method 13236 * @name ng.$sce#parse 13237 * @methodOf ng.$sce 13238 * 13239 * @description 13240 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 13241 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 13242 * wraps the expression in a call to {@link ng.$sce#methods_getTrusted $sce.getTrusted(*type*, 13243 * *result*)} 13244 * 13245 * @param {string} type The kind of SCE context in which this result will be used. 13246 * @param {string} expression String expression to compile. 13247 * @returns {function(context, locals)} a function which represents the compiled expression: 13248 * 13249 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13250 * are evaluated against (typically a scope object). 13251 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13252 * `context`. 13253 */ 13254 sce.parseAs = function sceParseAs(type, expr) { 13255 var parsed = $parse(expr); 13256 if (parsed.literal && parsed.constant) { 13257 return parsed; 13258 } else { 13259 return function sceParseAsTrusted(self, locals) { 13260 return sce.getTrusted(type, parsed(self, locals)); 13261 }; 13262 } 13263 }; 13264 13265 /** 13266 * @ngdoc method 13267 * @name ng.$sce#trustAs 13268 * @methodOf ng.$sce 13269 * 13270 * @description 13271 * Delegates to {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. As such, 13272 * returns an object that is trusted by angular for use in specified strict contextual 13273 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 13274 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 13275 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 13276 * escaping. 13277 * 13278 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 13279 * resource_url, html, js and css. 13280 * @param {*} value The value that that should be considered trusted/safe. 13281 * @returns {*} A value that can be used to stand in for the provided `value` in places 13282 * where Angular expects a $sce.trustAs() return value. 13283 */ 13284 13285 /** 13286 * @ngdoc method 13287 * @name ng.$sce#trustAsHtml 13288 * @methodOf ng.$sce 13289 * 13290 * @description 13291 * Shorthand method. `$sce.trustAsHtml(value)` â 13292 * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 13293 * 13294 * @param {*} value The value to trustAs. 13295 * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedHtml 13296 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 13297 * only accept expressions that are either literal constants or are the 13298 * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) 13299 */ 13300 13301 /** 13302 * @ngdoc method 13303 * @name ng.$sce#trustAsUrl 13304 * @methodOf ng.$sce 13305 *
13306 * @description 13307 * Shorthand method. `$sce.trustAsUrl(value)` â 13308 * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.URL, value)`} 13309 * 13310 * @param {*} value The value to trustAs. 13311 * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedUrl 13312 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 13313 * only accept expressions that are either literal constants or are the 13314 * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) 13315 */ 13316 13317 /** 13318 * @ngdoc method 13319 * @name ng.$sce#trustAsResourceUrl 13320 * @methodOf ng.$sce 13321 * 13322 * @description 13323 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 13324 * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 13325 * 13326 * @param {*} value The value to trustAs. 13327 * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedResourceUrl 13328 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 13329 * only accept expressions that are either literal constants or are the return 13330 * value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) 13331 */ 13332 13333 /** 13334 * @ngdoc method 13335 * @name ng.$sce#trustAsJs 13336 * @methodOf ng.$sce 13337 * 13338 * @description 13339 * Shorthand method. `$sce.trustAsJs(value)` â 13340 * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.JS, value)`} 13341 * 13342 * @param {*} value The value to trustAs. 13343 * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedJs 13344 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 13345 * only accept expressions that are either literal constants or are the 13346 * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) 13347 */ 13348 13349 /** 13350 * @ngdoc method 13351 * @name ng.$sce#getTrusted 13352 * @methodOf ng.$sce 13353 * 13354 * @description 13355 * Delegates to {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted`}. As such, 13356 * takes the result of a {@link ng.$sce#methods_trustAs `$sce.trustAs`}() call and returns the 13357 * originally supplied value if the queried context type is a supertype of the created type. 13358 * If this condition isn't satisfied, throws an exception. 13359 * 13360 * @param {string} type The kind of context in which this value is to be used. 13361 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#methods_trustAs `$sce.trustAs`} 13362 * call. 13363 * @returns {*} The value the was originally provided to 13364 * {@link ng.$sce#methods_trustAs `$sce.trustAs`} if valid in this context. 13365 * Otherwise, throws an exception. 13366 */ 13367 13368 /** 13369 * @ngdoc method 13370 * @name ng.$sce#getTrustedHtml 13371 * @methodOf ng.$sce 13372 * 13373 * @description 13374 * Shorthand method. `$sce.getTrustedHtml(value)` â 13375 * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 13376 * 13377 * @param {*} value The value to pass to `$sce.getTrusted`. 13378 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 13379 */ 13380 13381 /** 13382 * @ngdoc method 13383 * @name ng.$sce#getTrustedCss 13384 * @methodOf ng.$sce 13385 * 13386 * @description 13387 * Shorthand method. `$sce.getTrustedCss(value)` â 13388 * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 13389 * 13390 * @param {*} value The value to pass to `$sce.getTrusted`. 13391 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 13392 */ 13393 13394 /** 13395 * @ngdoc method 13396 * @name ng.$sce#getTrustedUrl 13397 * @methodOf ng.$sce 13398 * 13399 * @description 13400 * Shorthand method. `$sce.getTrustedUrl(value)` â 13401 * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 13402 * 13403 * @param {*} value The value to pass to `$sce.getTrusted`. 13404 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 13405 */ 13406 13407 /** 13408 * @ngdoc method 13409 * @name ng.$sce#getTrustedResourceUrl 13410 * @methodOf ng.$sce 13411 *
13412 * @description 13413 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 13414 * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 13415 * 13416 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 13417 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 13418 */ 13419 13420 /** 13421 * @ngdoc method 13422 * @name ng.$sce#getTrustedJs 13423 * @methodOf ng.$sce 13424 * 13425 * @description 13426 * Shorthand method. `$sce.getTrustedJs(value)` â 13427 * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 13428 * 13429 * @param {*} value The value to pass to `$sce.getTrusted`. 13430 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 13431 */ 13432 13433 /** 13434 * @ngdoc method 13435 * @name ng.$sce#parseAsHtml 13436 * @methodOf ng.$sce 13437 * 13438 * @description 13439 * Shorthand method. `$sce.parseAsHtml(expression string)` â 13440 * {@link ng.$sce#methods_parse `$sce.parseAs($sce.HTML, value)`} 13441 * 13442 * @param {string} expression String expression to compile. 13443 * @returns {function(context, locals)} a function which represents the compiled expression: 13444 * 13445 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13446 * are evaluated against (typically a scope object). 13447 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13448 * `context`. 13449 */ 13450 13451 /** 13452 * @ngdoc method 13453 * @name ng.$sce#parseAsCss 13454 * @methodOf ng.$sce 13455 * 13456 * @description 13457 * Shorthand method. `$sce.parseAsCss(value)` â 13458 * {@link ng.$sce#methods_parse `$sce.parseAs($sce.CSS, value)`} 13459 * 13460 * @param {string} expression String expression to compile. 13461 * @returns {function(context, locals)} a function which represents the compiled expression: 13462 * 13463 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13464 * are evaluated against (typically a scope object). 13465 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13466 * `context`. 13467 */ 13468 13469 /** 13470 * @ngdoc method 13471 * @name ng.$sce#parseAsUrl 13472 * @methodOf ng.$sce 13473 * 13474 * @description 13475 * Shorthand method. `$sce.parseAsUrl(value)` â 13476 * {@link ng.$sce#methods_parse `$sce.parseAs($sce.URL, value)`} 13477 * 13478 * @param {string} expression String expression to compile. 13479 * @returns {function(context, locals)} a function which represents the compiled expression: 13480 * 13481 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13482 * are evaluated against (typically a scope object). 13483 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13484 * `context`. 13485 */ 13486 13487 /** 13488 * @ngdoc method 13489 * @name ng.$sce#parseAsResourceUrl 13490 * @methodOf ng.$sce 13491 * 13492 * @description 13493 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 13494 * {@link ng.$sce#methods_parse `$sce.parseAs($sce.RESOURCE_URL, value)`} 13495 * 13496 * @param {string} expression String expression to compile. 13497 * @returns {function(context, locals)} a function which represents the compiled expression: 13498 * 13499 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13500 * are evaluated against (typically a scope object). 13501 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13502 * `context`. 13503 */ 13504 13505 /** 13506 * @ngdoc method 13507 * @name ng.$sce#parseAsJs 13508 * @methodOf ng.$sce 13509 * 13510 * @description 13511 * Shorthand method. `$sce.parseAsJs(value)` â 13512 * {@link ng.$sce#methods_parse `$sce.parseAs($sce.JS, value)`} 13513 * 13514 * @param {string} expression String expression to compile. 13515 * @returns {function(context, locals)} a function which represents the compiled expression: 13516 * 13517 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13518 * are evaluated against (typically a scope object). 13519 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13520 * `context`. 13521 */ 13522 13523 // Shorthand delegations. 13524 var parse = sce.parseAs, 13525 getTrusted = sce.getTrusted, 13526 trustAs = sce.trustAs; 13527
13528 forEach(SCE_CONTEXTS, function (enumValue, name) { 13529 var lName = lowercase(name); 13530 sce[camelCase("parse_as_" + lName)] = function (expr) { 13531 return parse(enumValue, expr); 13532 }; 13533 sce[camelCase("get_trusted_" + lName)] = function (value) { 13534 return getTrusted(enumValue, value); 13535 }; 13536 sce[camelCase("trust_as_" + lName)] = function (value) { 13537 return trustAs(enumValue, value); 13538 }; 13539 }); 13540 13541 return sce; 13542 }]; 13543} 13544 13545/** 13546 * !!! This is an undocumented "private" service !!! 13547 * 13548 * @name ng.$sniffer 13549 * @requires $window 13550 * @requires $document 13551 * 13552 * @property {boolean} history Does the browser support html5 history api ? 13553 * @property {boolean} hashchange Does the browser support hashchange event ? 13554 * @property {boolean} transitions Does the browser support CSS transition events ? 13555 * @property {boolean} animations Does the browser support CSS animation events ? 13556 * 13557 * @description 13558 * This is very simple implementation of testing browser's features. 13559 */ 13560function $SnifferProvider() { 13561 this.$get = ['$window', '$document', function($window, $document) { 13562 var eventSupport = {}, 13563 android = 13564 int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 13565 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 13566 document = $document[0] || {}, 13567 documentMode = document.documentMode, 13568 vendorPrefix, 13569 vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, 13570 bodyStyle = document.body && document.body.style, 13571 transitions = false, 13572 animations = false, 13573 match; 13574 13575 if (bodyStyle) { 13576 for(var prop in bodyStyle) { 13577 if(match = vendorRegex.exec(prop)) { 13578 vendorPrefix = match[0]; 13579 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 13580 break; 13581 } 13582 } 13583 13584 if(!vendorPrefix) { 13585 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 13586 } 13587 13588 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 13589 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 13590 13591 if (android && (!transitions||!animations)) { 13592 transitions = isString(document.body.style.webkitTransition); 13593 animations = isString(document.body.style.webkitAnimation); 13594 } 13595 } 13596 13597 13598 return { 13599 // Android has history.pushState, but it does not update location correctly 13600 // so let's not use the history API at all. 13601 // http://code.google.com/p/android/issues/detail?id=17471 13602 // https://github.com/angular/angular.js/issues/904 13603 13604 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 13605 // so let's not use the history API also 13606 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 13607 // jshint -W018 13608 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 13609 // jshint +W018 13610 hashchange: 'onhashchange' in $window && 13611 // IE8 compatible mode lies 13612 (!documentMode || documentMode > 7), 13613 hasEvent: function(event) { 13614 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 13615 // it. In particular the event is not fired when backspace or delete key are pressed or 13616 // when cut operation is performed. 13617 if (event == 'input' && msie == 9) return false; 13618 13619 if (isUndefined(eventSupport[event])) { 13620 var divElm = document.createElement('div'); 13621 eventSupport[event] = 'on' + event in divElm; 13622 } 13623 13624 return eventSupport[event]; 13625 }, 13626 csp: csp(), 13627 vendorPrefix: vendorPrefix, 13628 transitions : transitions, 13629 animations : animations, 13630 android: android, 13631 msie : msie, 13632 msieDocumentMode: documentMode 13633 }; 13634 }]; 13635} 13636 13637function $TimeoutProvider() { 13638 this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', 13639 function($rootScope, $browser, $q, $exceptionHandler) { 13640 var deferreds = {}; 13641 13642 13643 /** 13644 * @ngdoc function 13645 * @name ng.$timeout 13646 * @requires $browser 13647 *
13648 * @description 13649 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 13650 * block and delegates any exceptions to 13651 * {@link ng.$exceptionHandler $exceptionHandler} service. 13652 * 13653 * The return value of registering a timeout function is a promise, which will be resolved when 13654 * the timeout is reached and the timeout function is executed. 13655 * 13656 * To cancel a timeout request, call `$timeout.cancel(promise)`. 13657 * 13658 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 13659 * synchronously flush the queue of deferred functions. 13660 * 13661 * @param {function()} fn A function, whose execution should be delayed. 13662 * @param {number=} [delay=0] Delay in milliseconds. 13663 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 13664 * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block. 13665 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 13666 * promise will be resolved with is the return value of the `fn` function. 13667 * 13668 */ 13669 function timeout(fn, delay, invokeApply) { 13670 var deferred = $q.defer(), 13671 promise = deferred.promise, 13672 skipApply = (isDefined(invokeApply) && !invokeApply), 13673 timeoutId; 13674 13675 timeoutId = $browser.defer(function() { 13676 try { 13677 deferred.resolve(fn()); 13678 } catch(e) { 13679 deferred.reject(e); 13680 $exceptionHandler(e); 13681 } 13682 finally { 13683 delete deferreds[promise.$$timeoutId]; 13684 } 13685 13686 if (!skipApply) $rootScope.$apply(); 13687 }, delay); 13688 13689 promise.$$timeoutId = timeoutId; 13690 deferreds[timeoutId] = deferred; 13691 13692 return promise; 13693 } 13694 13695 13696 /** 13697 * @ngdoc function 13698 * @name ng.$timeout#cancel 13699 * @methodOf ng.$timeout 13700 * 13701 * @description 13702 * Cancels a task associated with the `promise`. As a result of this, the promise will be 13703 * resolved with a rejection. 13704 * 13705 * @param {Promise=} promise Promise returned by the `$timeout` function. 13706 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 13707 * canceled. 13708 */ 13709 timeout.cancel = function(promise) { 13710 if (promise && promise.$$timeoutId in deferreds) { 13711 deferreds[promise.$$timeoutId].reject('canceled'); 13712 delete deferreds[promise.$$timeoutId]; 13713 return $browser.defer.cancel(promise.$$timeoutId); 13714 } 13715 return false; 13716 }; 13717 13718 return timeout; 13719 }]; 13720} 13721 13722// NOTE: The usage of window and document instead of $window and $document here is 13723// deliberate. This service depends on the specific behavior of anchor nodes created by the 13724// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 13725// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 13726// doesn't know about mocked locations and resolves URLs to the real document - which is 13727// exactly the behavior needed here. There is little value is mocking these out for this 13728// service. 13729var urlParsingNode = document.createElement("a"); 13730var originUrl = urlResolve(window.location.href, true); 13731 13732 13733/** 13734 * 13735 * Implementation Notes for non-IE browsers 13736 * ---------------------------------------- 13737 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 13738 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 13739 * URL will be resolved into an absolute URL in the context of the application document. 13740 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 13741 * properties are all populated to reflect the normalized URL. This approach has wide 13742 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 13743 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 13744 * 13745 * Implementation Notes for IE 13746 * --------------------------- 13747 * IE >
13747= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other 13748 * browsers. However, the parsed components will not be set if the URL assigned did not specify 13749 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 13750 * work around that by performing the parsing in a 2nd step by taking a previously normalized 13751 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 13752 * properties such as protocol, hostname, port, etc. 13753 * 13754 * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one 13755 * uses the inner HTML approach to assign the URL as part of an HTML snippet - 13756 * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. 13757 * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. 13758 * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that 13759 * method and IE < 8 is unsupported. 13760 * 13761 * References: 13762 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 13763 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 13764 * http://url.spec.whatwg.org/#urlutils 13765 * https://github.com/angular/angular.js/pull/2902 13766 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 13767 * 13768 * @function 13769 * @param {string} url The URL to be parsed. 13770 * @description Normalizes and parses a URL. 13771 * @returns {object} Returns the normalized URL as a dictionary. 13772 * 13773 * | member name | Description | 13774 * |---------------|----------------| 13775 * | href | A normalized version of the provided URL if it was not an absolute URL | 13776 * | protocol | The protocol including the trailing colon | 13777 * | host | The host and port (if the port is non-default) of the normalizedUrl | 13778 * | search | The search params, minus the question mark | 13779 * | hash | The hash string, minus the hash symbol 13780 * | hostname | The hostname 13781 * | port | The port, without ":" 13782 * | pathname | The pathname, beginning with "/" 13783 * 13784 */ 13785function urlResolve(url, base) { 13786 var href = url; 13787 13788 if (msie) { 13789 // Normalize before parse. Refer Implementation Notes on why this is 13790 // done in two steps on IE. 13791 urlParsingNode.setAttribute("href", href); 13792 href = urlParsingNode.href; 13793 } 13794 13795 urlParsingNode.setAttribute('href', href); 13796 13797 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 13798 return { 13799 href: urlParsingNode.href, 13800 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 13801 host: urlParsingNode.host, 13802 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 13803 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 13804 hostname: urlParsingNode.hostname, 13805 port: urlParsingNode.port, 13806 pathname: (urlParsingNode.pathname.charAt(0) === '/') 13807 ? urlParsingNode.pathname 13808 : '/' + urlParsingNode.pathname 13809 }; 13810} 13811 13812/** 13813 * Parse a request URL and determine whether this is a same-origin request as the application document. 13814 * 13815 * @param {string|object} requestUrl The url of the request as a string that will be resolved 13816 * or a parsed URL object. 13817 * @returns {boolean} Whether the request is for the same origin as the application document. 13818 */ 13819function urlIsSameOrigin(requestUrl) { 13820 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 13821 return (parsed.protocol === originUrl.protocol && 13822 parsed.host === originUrl.host); 13823} 13824 13825/** 13826 * @ngdoc object 13827 * @name ng.$window 13828 * 13829 * @description 13830 * A reference to the browser's `window` object. While `window` 13831 * is globally available in JavaScript, it causes testability problems, because 13832 * it is a global variable. In angular we always refer to it through the 13833 * `$window` service, so it may be overridden, removed or mocked for testing. 13834 * 13835 * Expressions, like the one defined for the `ngClick` directive in the example 13836 * below, are evaluated with respect to the current scope. Therefore, there is 13837 * no risk of inadvertently coding in a dependency on a global value in such an 13838 * expression. 13839 * 13840 * @example 13841 <doc:example> 13842 <doc:source> 13843 <script> 13844 function Ctrl($scope, $window) { 13845 $scope.greeting = 'Hello, World!'; 13846 $scope.doGreeting = function(greeting) { 13847 $window.alert(greeting); 13848 }; 13849 } 13850 </script> 13851 <div ng-controller="Ctrl"> 13852 <input type="text" ng-model="greeting" /> 13853 <button ng-click="doGreeting(greeting)">ALERT</button> 13854 </div> 13855 </doc:source> 13856 <doc:protractor> 13857 it('should display the greeting in the input box', function() {
13858 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 13859 // If we click the button it will block the test runner 13860 // element(':button').click(); 13861 }); 13862 </doc:protractor> 13863 </doc:example> 13864 */ 13865function $WindowProvider(){ 13866 this.$get = valueFn(window); 13867} 13868 13869/** 13870 * @ngdoc object 13871 * @name ng.$filterProvider 13872 * @description 13873 * 13874 * Filters are just functions which transform input to an output. However filters need to be 13875 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 13876 * annotated with dependencies and is responsible for creating a filter function. 13877 * 13878 * <pre> 13879 * // Filter registration 13880 * function MyModule($provide, $filterProvider) { 13881 * // create a service to demonstrate injection (not always needed) 13882 * $provide.value('greet', function(name){ 13883 * return 'Hello ' + name + '!'; 13884 * }); 13885 * 13886 * // register a filter factory which uses the 13887 * // greet service to demonstrate DI. 13888 * $filterProvider.register('greet', function(greet){ 13889 * // return the filter function which uses the greet service 13890 * // to generate salutation 13891 * return function(text) { 13892 * // filters need to be forgiving so check input validity 13893 * return text && greet(text) || text; 13894 * }; 13895 * }); 13896 * } 13897 * </pre> 13898 * 13899 * The filter function is registered with the `$injector` under the filter name suffix with 13900 * `Filter`. 13901 * 13902 * <pre> 13903 * it('should be the same instance', inject( 13904 * function($filterProvider) { 13905 * $filterProvider.register('reverse', function(){ 13906 * return ...; 13907 * }); 13908 * }, 13909 * function($filter, reverseFilter) { 13910 * expect($filter('reverse')).toBe(reverseFilter); 13911 * }); 13912 * </pre> 13913 * 13914 * 13915 * For more information about how angular filters work, and how to create your own filters, see 13916 * {@link guide/filter Filters} in the Angular Developer Guide. 13917 */ 13918/** 13919 * @ngdoc method 13920 * @name ng.$filterProvider#register 13921 * @methodOf ng.$filterProvider 13922 * @description 13923 * Register filter factory function. 13924 * 13925 * @param {String} name Name of the filter. 13926 * @param {function} fn The filter factory function which is injectable. 13927 */ 13928 13929 13930/** 13931 * @ngdoc function 13932 * @name ng.$filter 13933 * @function 13934 * @description 13935 * Filters are used for formatting data displayed to the user. 13936 * 13937 * The general syntax in templates is as follows: 13938 * 13939 * {{ expression [| filter_name[:parameter_value] ... ] }} 13940 * 13941 * @param {String} name Name of the filter function to retrieve 13942 * @return {Function} the filter function 13943 */ 13944$FilterProvider.$inject = ['$provide']; 13945function $FilterProvider($provide) { 13946 var suffix = 'Filter'; 13947 13948 /** 13949 * @ngdoc function 13950 * @name ng.$controllerProvider#register 13951 * @methodOf ng.$controllerProvider 13952 * @param {string|Object} name Name of the filter function, or an object map of filters where 13953 * the keys are the filter names and the values are the filter factories. 13954 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 13955 * of the registered filter instances. 13956 */ 13957 function register(name, factory) { 13958 if(isObject(name)) { 13959 var filters = {}; 13960 forEach(name, function(filter, key) { 13961 filters[key] = register(key, filter); 13962 }); 13963 return filters; 13964 } else { 13965 return $provide.factory(name + suffix, factory); 13966 } 13967 } 13968 this.register = register; 13969 13970 this.$get = ['$injector', function($injector) { 13971 return function(name) { 13972 return $injector.get(name + suffix); 13973 }; 13974 }]; 13975 13976 //////////////////////////////////////// 13977 13978 /* global 13979 currencyFilter: false, 13980 dateFilter: false, 13981 filterFilter: false, 13982 jsonFilter: false, 13983 limitToFilter: false, 13984 lowercaseFilter: false, 13985 numberFilter: false, 13986 orderByFilter: false, 13987 uppercaseFilter: false, 13988 */ 13989 13990 register('currency', currencyFilter); 13991 register('date', dateFilter); 13992 register('filter', filterFilter); 13993 register('json', jsonFilter); 13994 register('limitTo', limitToFilter); 13995 register('lowercase', lowercaseFilter); 13996 register('number', numberFilter); 13997 register('orderBy', orderByFilter); 13998 register('uppercase', uppercaseFilter); 13999} 14000 14001/** 14002 * @ngdoc filter 14003 * @name ng.filter:filter 14004 * @function 14005 * 14006 * @description 14007 * Selects a subset of items from `array` and returns it as a new array. 14008 * 14009 * @param {Array} array The source array. 14010 * @param {string|Object|function()} expression The predicate to be used for selecting items from 14011 * `array`. 14012 * 14013 * Can be one of: 14014 * 14015 * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against
14016 * the contents of the `array`. All strings or objects with string properties in `array` that contain this string 14017 * will be returned. The predicate can be negated by prefixing the string with `!`. 14018 * 14019 * - `Object`: A pattern object can be used to filter specific properties on objects contained 14020 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 14021 * which have property `name` containing "M" and property `phone` containing "1". A special 14022 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 14023 * property of the object. That's equivalent to the simple substring match with a `string` 14024 * as described above. 14025 * 14026 * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is 14027 * called for each element of `array`. The final result is an array of those elements that 14028 * the predicate returned true for. 14029 * 14030 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 14031 * determining if the expected value (from the filter expression) and actual value (from 14032 * the object in the array) should be considered a match. 14033 * 14034 * Can be one of: 14035 * 14036 * - `function(actual, expected)`: 14037 * The function will be given the object value and the predicate value to compare and 14038 * should return true if the item should be included in filtered result. 14039 * 14040 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. 14041 * this is essentially strict comparison of expected and actual. 14042 * 14043 * - `false|undefined`: A short hand for a function which will look for a substring match in case 14044 * insensitive way. 14045 * 14046 * @example 14047 <doc:example> 14048 <doc:source> 14049 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 14050 {name:'Mary', phone:'800-BIG-MARY'}, 14051 {name:'Mike', phone:'555-4321'}, 14052 {name:'Adam', phone:'555-5678'}, 14053 {name:'Julie', phone:'555-8765'}, 14054 {name:'Juliette', phone:'555-5678'}]"></div> 14055 14056 Search: <input ng-model="searchText"> 14057 <table id="searchTextResults"> 14058 <tr><th>Name</th><th>Phone</th></tr> 14059 <tr ng-repeat="friend in friends | filter:searchText"> 14060 <td>{{friend.name}}</td> 14061 <td>{{friend.phone}}</td> 14062 </tr> 14063 </table> 14064 <hr> 14065 Any: <input ng-model="search.$"> <br> 14066 Name only <input ng-model="search.name"><br> 14067 Phone only <input ng-model="search.phone"><br> 14068 Equality <input type="checkbox" ng-model="strict"><br> 14069 <table id="searchObjResults"> 14070 <tr><th>Name</th><th>Phone</th></tr> 14071 <tr ng-repeat="friendObj in friends | filter:search:strict"> 14072 <td>{{friendObj.name}}</td> 14073 <td>{{friendObj.phone}}</td> 14074 </tr> 14075 </table> 14076 </doc:source> 14077 <doc:protractor> 14078 var expectFriendNames = function(expectedNames, key) { 14079 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 14080 arr.forEach(function(wd, i) { 14081 expect(wd.getText()).toMatch(expectedNames[i]); 14082 }); 14083 }); 14084 }; 14085 14086 it('should search across all fields when filtering with a string', function() { 14087 var searchText = element(by.model('searchText')); 14088 searchText.clear(); 14089 searchText.sendKeys('m'); 14090 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 14091 14092 searchText.clear(); 14093 searchText.sendKeys('76'); 14094 expectFriendNames(['John', 'Julie'], 'friend'); 14095 }); 14096 14097 it('should search in specific fields when filtering with a predicate object', function() { 14098 var searchAny = element(by.model('search.$')); 14099 searchAny.clear(); 14100 searchAny.sendKeys('i'); 14101 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 14102 }); 14103 it('should use a equal comparison when comparator is true', function() { 14104 var searchName = element(by.model('search.name')); 14105 var strict = element(by.model('strict')); 14106 searchName.clear(); 14107 searchName.sendKeys('Julie');
14108 strict.click(); 14109 expectFriendNames(['Julie'], 'friendObj'); 14110 }); 14111 </doc:protractor> 14112 </doc:example> 14113 */ 14114function filterFilter() { 14115 return function(array, expression, comparator) { 14116 if (!isArray(array)) return array; 14117 14118 var comparatorType = typeof(comparator), 14119 predicates = []; 14120 14121 predicates.check = function(value) { 14122 for (var j = 0; j < predicates.length; j++) { 14123 if(!predicates[j](value)) { 14124 return false; 14125 } 14126 } 14127 return true; 14128 }; 14129 14130 if (comparatorType !== 'function') { 14131 if (comparatorType === 'boolean' && comparator) { 14132 comparator = function(obj, text) { 14133 return angular.equals(obj, text); 14134 }; 14135 } else { 14136 comparator = function(obj, text) { 14137 if (obj && text && typeof obj === 'object' && typeof text === 'object') { 14138 for (var objKey in obj) { 14139 if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) && 14140 comparator(obj[objKey], text[objKey])) { 14141 return true; 14142 } 14143 } 14144 return false; 14145 } 14146 text = (''+text).toLowerCase(); 14147 return (''+obj).toLowerCase().indexOf(text) > -1; 14148 }; 14149 } 14150 } 14151 14152 var search = function(obj, text){ 14153 if (typeof text == 'string' && text.charAt(0) === '!') { 14154 return !search(obj, text.substr(1)); 14155 } 14156 switch (typeof obj) { 14157 case "boolean": 14158 case "number": 14159 case "string": 14160 return comparator(obj, text); 14161 case "object": 14162 switch (typeof text) { 14163 case "object": 14164 return comparator(obj, text); 14165 default: 14166 for ( var objKey in obj) { 14167 if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { 14168 return true; 14169 } 14170 } 14171 break; 14172 } 14173 return false; 14174 case "array": 14175 for ( var i = 0; i < obj.length; i++) { 14176 if (search(obj[i], text)) { 14177 return true; 14178 } 14179 } 14180 return false; 14181 default: 14182 return false; 14183 } 14184 }; 14185 switch (typeof expression) { 14186 case "boolean": 14187 case "number": 14188 case "string": 14189 // Set up expression object and fall through 14190 expression = {$:expression}; 14191 // jshint -W086 14192 case "object": 14193 // jshint +W086 14194 for (var key in expression) { 14195 (function(path) { 14196 if (typeof expression[path] == 'undefined') return; 14197 predicates.push(function(value) { 14198 return search(path == '$' ? value : (value && value[path]), expression[path]); 14199 }); 14200 })(key); 14201 } 14202 break; 14203 case 'function': 14204 predicates.push(expression); 14205 break; 14206 default: 14207 return array; 14208 } 14209 var filtered = []; 14210 for ( var j = 0; j < array.length; j++) { 14211 var value = array[j]; 14212 if (predicates.check(value)) { 14213 filtered.push(value); 14214 } 14215 } 14216 return filtered; 14217 }; 14218} 14219 14220/** 14221 * @ngdoc filter 14222 * @name ng.filter:currency 14223 * @function 14224 * 14225 * @description 14226 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 14227 * symbol for current locale is used. 14228 * 14229 * @param {number} amount Input to filter. 14230 * @param {string=} symbol Currency symbol or identifier to be displayed. 14231 * @returns {string} Formatted number. 14232 * 14233 * 14234 * @example 14235 <doc:example> 14236 <doc:source> 14237 <script> 14238 function Ctrl($scope) { 14239 $scope.amount = 1234.56; 14240 } 14241 </script> 14242 <div ng-controller="Ctrl"> 14243 <input type="number" ng-model="amount"> <br> 14244 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 14245 custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span> 14246 </div> 14247 </doc:source> 14248 <doc:protractor> 14249 it('should init with 1234.56', function() { 14250 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 14251 expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56'); 14252 }); 14253 it('should update', function() { 14254 if (browser.params.browser == 'safari') { 14255 // Safari does not understand the minus key. See 14256 // https://github.com/angular/protractor/issues/481 14257 return; 14258 }
14259 element(by.model('amount')).clear(); 14260 element(by.model('amount')).sendKeys('-1234'); 14261 expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); 14262 expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)'); 14263 }); 14264 </doc:protractor> 14265 </doc:example> 14266 */ 14267currencyFilter.$inject = ['$locale']; 14268function currencyFilter($locale) { 14269 var formats = $locale.NUMBER_FORMATS; 14270 return function(amount, currencySymbol){ 14271 if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; 14272 return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). 14273 replace(/\u00A4/g, currencySymbol); 14274 }; 14275} 14276 14277/** 14278 * @ngdoc filter 14279 * @name ng.filter:number 14280 * @function 14281 * 14282 * @description 14283 * Formats a number as text. 14284 * 14285 * If the input is not a number an empty string is returned. 14286 * 14287 * @param {number|string} number Number to format. 14288 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 14289 * If this is not provided then the fraction size is computed from the current locale's number 14290 * formatting pattern. In the case of the default locale, it will be 3. 14291 * @returns {string} Number rounded to decimalPlaces and places a â,â after each third digit. 14292 * 14293 * @example 14294 <doc:example> 14295 <doc:source> 14296 <script> 14297 function Ctrl($scope) { 14298 $scope.val = 1234.56789; 14299 } 14300 </script> 14301 <div ng-controller="Ctrl"> 14302 Enter number: <input ng-model='val'><br> 14303 Default formatting: <span id='number-default'>{{val | number}}</span><br> 14304 No fractions: <span>{{val | number:0}}</span><br> 14305 Negative number: <span>{{-val | number:4}}</span> 14306 </div> 14307 </doc:source> 14308 <doc:protractor> 14309 it('should format numbers', function() { 14310 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 14311 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 14312 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 14313 }); 14314 14315 it('should update', function() { 14316 element(by.model('val')).clear(); 14317 element(by.model('val')).sendKeys('3374.333'); 14318 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 14319 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 14320 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 14321 }); 14322 </doc:protractor> 14323 </doc:example> 14324 */ 14325 14326 14327numberFilter.$inject = ['$locale']; 14328function numberFilter($locale) { 14329 var formats = $locale.NUMBER_FORMATS; 14330 return function(number, fractionSize) { 14331 return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 14332 fractionSize); 14333 }; 14334} 14335 14336var DECIMAL_SEP = '.'; 14337function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 14338 if (isNaN(number) || !isFinite(number)) return ''; 14339 14340 var isNegative = number < 0; 14341 number = Math.abs(number); 14342 var numStr = number + '', 14343 formatedText = '', 14344 parts = []; 14345 14346 var hasExponent = false; 14347 if (numStr.indexOf('e') !== -1) { 14348 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 14349 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 14350 numStr = '0'; 14351 } else { 14352 formatedText = numStr; 14353 hasExponent = true; 14354 } 14355 } 14356 14357 if (!hasExponent) { 14358 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 14359 14360 // determine fractionSize if it is not specified 14361 if (isUndefined(fractionSize)) { 14362 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 14363 } 14364 14365 var pow = Math.pow(10, fractionSize); 14366 number = Math.round(number * pow) / pow; 14367 var fraction = ('' + number).split(DECIMAL_SEP); 14368 var whole = fraction[0]; 14369 fraction = fraction[1] || ''; 14370 14371 var i, pos = 0, 14372 lgroup = pattern.lgSize, 14373 group = pattern.gSize; 14374 14375 if (whole.length >= (lgroup + group)) { 14376 pos = whole.length - lgroup; 14377 for (i = 0; i < pos; i++) { 14378 if ((pos - i)%group === 0 && i !== 0) { 14379 formatedText += groupSep; 14380 } 14381 formatedText += whole.charAt(i); 14382 } 14383 } 14384 14385 for (i = pos; i < whole.length; i++) { 14386 if ((whole.length - i)%lgroup === 0 && i !== 0) { 14387 formatedText += groupSep; 14388 } 14389 formatedText += whole.charAt(i); 14390 } 14391 14392 // format fraction part. 14393 while(fraction.length < fractionSize) { 14394 fraction += '0'; 14395 } 14396 14397 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 14398 } else { 14399 14400 if (fractionSize > 0 && number > -1 && number < 1) { 14401 formatedText = number.toFixed(fractionSize); 14402 } 14403 } 14404 14405 parts.push(isNegative ? pattern.negPre : pattern.posPre); 14406 parts.push(formatedText); 14407 parts.push(isNegative ? pattern.negSuf : pattern.posSuf); 14408 return parts.join(''); 14409} 14410 14411function padNumber(num, digits, trim) { 14412 var neg = ''; 14413 if (num < 0) { 14414 neg = '-'; 14415 num = -num; 14416 } 14417 num = '' + num; 14418 while(num.length < digits) num = '0' + num; 14419 if (trim) 14420 num = num.substr(num.length - digits); 14421 return neg + num; 14422} 14423 14424 14425function dateGetter(name, size, offset, trim) { 14426 offset = offset || 0; 14427 return function(date) { 14428 var value = date['get' + name](); 14429 if (offset > 0 || value > -offset) 14430 value += offset; 14431 if (value === 0 && offset == -12 ) value = 12; 14432 return padNumber(value, size, trim); 14433 }; 14434} 14435 14436function dateStrGetter(name, shortForm) { 14437 return function(date, formats) { 14438 var value = date['get' + name](); 14439 var get = uppercase(shortForm ? ('SHORT' + name) : name); 14440 14441 return formats[get][value]; 14442 }; 14443} 14444 14445function timeZoneGetter(date) { 14446 var zone = -1 * date.getTimezoneOffset(); 14447 var paddedZone = (zone >= 0) ? "+" : ""; 14448 14449 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 14450 padNumber(Math.abs(zone % 60), 2); 14451 14452 return paddedZone; 14453} 14454 14455function ampmGetter(date, formats) { 14456 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 14457} 14458 14459var DATE_FORMATS = { 14460 yyyy: dateGetter('FullYear', 4), 14461 yy: dateGetter('FullYear', 2, 0, true), 14462 y: dateGetter('FullYear', 1), 14463 MMMM: dateStrGetter('Month'), 14464 MMM: dateStrGetter('Month', true), 14465 MM: dateGetter('Month', 2, 1), 14466 M: dateGetter('Month', 1, 1), 14467 dd: dateGetter('Date', 2), 14468 d: dateGetter('Date', 1), 14469 HH: dateGetter('Hours', 2), 14470 H: dateGetter('Hours', 1), 14471 hh: dateGetter('Hours', 2, -12), 14472 h: dateGetter('Hours', 1, -12), 14473 mm: dateGetter('Minutes', 2), 14474 m: dateGetter('Minutes', 1), 14475 ss: dateGetter('Seconds', 2), 14476 s: dateGetter('Seconds', 1), 14477 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 14478 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 14479 sss: dateGetter('Milliseconds', 3), 14480 EEEE: dateStrGetter('Day'), 14481 EEE: dateStrGetter('Day', true), 14482 a: ampmGetter, 14483 Z: timeZoneGetter 14484}; 14485 14486var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, 14487 NUMBER_STRING = /^\-?\d+$/; 14488 14489/** 14490 * @ngdoc filter 14491 * @name ng.filter:date 14492 * @function 14493 * 14494 * @description 14495 * Formats `date` to a string based on the requested `format`. 14496 * 14497 * `format` string can be composed of the following elements: 14498 * 14499 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 14500 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 14501 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 14502 * * `'MMMM'`: Month in year (January-December) 14503 * * `'MMM'`: Month in year (Jan-Dec) 14504 * * `'MM'`: Month in year, padded (01-12) 14505 * * `'M'`: Month in year (1-12) 14506 * * `'dd'`: Day in month, padded (01-31) 14507 * * `'d'`: Day in month (1-31) 14508 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 14509 * * `'EEE'`: Day in Week, (Sun-Sat) 14510 * * `'HH'`: Hour in day, padded (00-23) 14511 * * `'H'`: Hour in day (0-23) 14512 * * `'hh'`: Hour in am/pm, padded (01-12) 14513 * * `'h'`: Hour in am/pm, (1-12) 14514 * * `'mm'`: Minute in hour, padded (00-59) 14515 * * `'m'`: Minute in hour (0-59) 14516 * * `'ss'`: Second in minute, padded (00-59) 14517 * * `'s'`: Second in minute (0-59) 14518 * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) 14519 * * `'a'`: am/pm marker 14520 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 14521 * 14522 * `format` string can also be one of the following predefined 14523 * {@link guide/i18n localizable formats}: 14524 * 14525 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 14526 * (e.g. Sep 3, 2010 12:05:08 pm) 14527 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) 14528 * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale 14529 * (e.g. Friday, September 3, 2010) 14530 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 14531 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 14532 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 14533 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) 14534 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) 14535 * 14536 * `format` string can contain literal values. These need to be quoted with single quotes (e.g. 14537 * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence 14538 * (e.g. `"h 'o''clock'"`). 14539 * 14540 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 14541 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its 14542 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 14543 * specified in the string input, the time is considered to be in the local timezone. 14544 * @param {string=} format Formatting rules (see Description). If not specified, 14545 * `mediumDate` is used. 14546 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 14547 * 14548 * @example 14549 <doc:example> 14550 <doc:source>
14551 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 14552 <span>{{1288323623006 | date:'medium'}}</span><br> 14553 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 14554 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 14555 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 14556 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 14557 </doc:source> 14558 <doc:protractor> 14559 it('should format date', function() { 14560 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 14561 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 14562 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 14563 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 14564 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 14565 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 14566 }); 14567 </doc:protractor> 14568 </doc:example> 14569 */ 14570dateFilter.$inject = ['$locale']; 14571function dateFilter($locale) { 14572 14573 14574 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 14575 // 1 2 3 4 5 6 7 8 9 10 11 14576 function jsonStringToDate(string) { 14577 var match; 14578 if (match = string.match(R_ISO8601_STR)) { 14579 var date = new Date(0), 14580 tzHour = 0, 14581 tzMin = 0, 14582 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 14583 timeSetter = match[8] ? date.setUTCHours : date.setHours; 14584 14585 if (match[9]) { 14586 tzHour = int(match[9] + match[10]); 14587 tzMin = int(match[9] + match[11]); 14588 } 14589 dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); 14590 var h = int(match[4]||0) - tzHour; 14591 var m = int(match[5]||0) - tzMin; 14592 var s = int(match[6]||0); 14593 var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); 14594 timeSetter.call(date, h, m, s, ms); 14595 return date; 14596 } 14597 return string; 14598 } 14599 14600 14601 return function(date, format) { 14602 var text = '', 14603 parts = [], 14604 fn, match; 14605 14606 format = format || 'mediumDate'; 14607 format = $locale.DATETIME_FORMATS[format] || format; 14608 if (isString(date)) { 14609 if (NUMBER_STRING.test(date)) { 14610 date = int(date); 14611 } else { 14612 date = jsonStringToDate(date); 14613 } 14614 } 14615 14616 if (isNumber(date)) { 14617 date = new Date(date); 14618 } 14619 14620 if (!isDate(date)) { 14621 return date; 14622 } 14623 14624 while(format) { 14625 match = DATE_FORMATS_SPLIT.exec(format); 14626 if (match) { 14627 parts = concat(parts, match, 1); 14628 format = parts.pop(); 14629 } else { 14630 parts.push(format); 14631 format = null; 14632 } 14633 } 14634 14635 forEach(parts, function(value){ 14636 fn = DATE_FORMATS[value]; 14637 text += fn ? fn(date, $locale.DATETIME_FORMATS) 14638 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 14639 }); 14640 14641 return text; 14642 }; 14643} 14644 14645 14646/** 14647 * @ngdoc filter 14648 * @name ng.filter:json 14649 * @function 14650 * 14651 * @description 14652 * Allows you to convert a JavaScript object into JSON string. 14653 * 14654 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 14655 * the binding is automatically converted to JSON. 14656 * 14657 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 14658 * @returns {string} JSON string. 14659 * 14660 * 14661 * @example: 14662 <doc:example> 14663 <doc:source> 14664 <pre>{{ {'name':'value'} | json }}</pre> 14665 </doc:source> 14666 <doc:protractor> 14667 it('should jsonify filtered objects', function() { 14668 expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/); 14669 }); 14670 </doc:protractor> 14671 </doc:example> 14672 * 14673 */ 14674function jsonFilter() { 14675 return function(object) { 14676 return toJson(object, true); 14677 }; 14678} 14679 14680 14681/** 14682 * @ngdoc filter 14683 * @name ng.filter:lowercase 14684 * @function 14685 * @description 14686 * Converts string to lowercase. 14687 * @see angular.lowercase 14688 */ 14689var lowercaseFilter = valueFn(lowercase); 14690 14691 14692/** 14693 * @ngdoc filter 14694 * @name ng.filter:uppercase 14695 * @function 14696 * @description 14697 * Converts string to uppercase. 14698 * @see angular.uppercase 14699 */ 14700var uppercaseFilter = valueFn(uppercase); 14701 14702/** 14703 * @ngdoc function 14704 * @name ng.filter:limitTo 14705 * @function 14706 * 14707 * @description 14708 * Creates a new array or string containing only a specified number of elements. The elements 14709 * are taken from either the beginning or the end of the source array or string, as specified by 14710 * the value and sign (positive or negative) of `limit`. 14711 * 14712 * @param {Array|string} input Source array or string to be limited. 14713 * @param {string|number} limit The length of the returned array or string. If the `limit` number 14714 * is positive, `limit` number of items from the beginning of the source array/string are copied. 14715 * If the number is negative, `limit` number of items from the end of the source array/string 14716 * are copied. The `limit` will be trimmed if it exceeds `array.length` 14717 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 14718 * had less than `limit` elements. 14719 * 14720 * @example 14721 <doc:example> 14722 <doc:source> 14723 <script> 14724 function Ctrl($scope) { 14725 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 14726 $scope.letters = "abcdefghi"; 14727 $scope.numLimit = 3; 14728 $scope.letterLimit = 3; 14729 } 14730 </script> 14731 <div ng-controller="Ctrl"> 14732 Limit {{numbers}} to: <input type="integer" ng-model="numLimit"> 14733 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 14734 Limit {{letters}} to: <input type="integer" ng-model="letterLimit"> 14735 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 14736 </div> 14737 </doc:source> 14738 <doc:protractor> 14739 var numLimitInput = element(by.model('numLimit')); 14740 var letterLimitInput = element(by.model('letterLimit')); 14741 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 14742 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 14743 14744 it('should limit the number array to first three items', function() { 14745 expect(numLimitInput.getAttribute('value')).toBe('3'); 14746 expect(letterLimitInput.getAttribute('value')).toBe('3'); 14747 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 14748 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 14749 }); 14750 14751 it('should update the output when -3 is entered', function() { 14752 numLimitInput.clear(); 14753 numLimitInput.sendKeys('-3'); 14754 letterLimitInput.clear(); 14755 letterLimitInput.sendKeys('-3'); 14756 expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 14757 expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 14758 }); 14759 14760 it('should not exceed the maximum size of input array', function() { 14761 numLimitInput.clear(); 14762 numLimitInput.sendKeys('100'); 14763 letterLimitInput.clear(); 14764 letterLimitInput.sendKeys('100'); 14765 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 14766 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 14767 }); 14768 </doc:protractor> 14769 </doc:example> 14770 */ 14771function limitToFilter(){ 14772 return function(input, limit) { 14773 if (!isArray(input) && !isString(input)) return input; 14774 14775 limit = int(limit); 14776 14777 if (isString(input)) { 14778 //NaN check on limit 14779 if (limit) { 14780 return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); 14781 } else { 14782 return ""; 14783 } 14784 } 14785 14786 var out = [], 14787 i, n; 14788 14789 // if abs(limit) exceeds maximum length, trim it 14790 if (limit > input.length) 14791 limit = input.length; 14792 else if (limit < -input.length) 14793 limit = -input.length; 14794 14795 if (limit > 0) { 14796 i = 0; 14797 n = limit; 14798 } else { 14799 i = input.length + limit; 14800 n = input.length; 14801 } 14802 14803 for (; i<n; i++) { 14804 out.push(input[i]); 14805 } 14806 14807 return out; 14808 }; 14809} 14810 14811/** 14812 * @ngdoc function 14813 * @name ng.filter:orderBy 14814 * @function 14815 * 14816 * @description 14817 * Orders a specified `array` by the `expression` predicate. 14818 * 14819 * @param {Array} array The array to sort. 14820 * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be 14821 * used by the comparator to determine the order of elements. 14822 * 14823 * Can be one of: 14824 * 14825 * - `function`: Getter function. The result of this function will be sorted using the 14826 * `<`, `=`, `>` operator. 14827 * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' 14828 * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control 14829 * ascending or descending sort order (for example, +name or -name). 14830 * - `Array`: An array of function or string predicates. The first predicate in the array 14831 * is used for sorting, but when two items are equivalent, the next predicate is used. 14832 * 14833 * @param {boolean=} reverse Reverse the order the array. 14834 * @returns {Array} Sorted copy of the source array. 14835 * 14836 * @example 14837 <doc:example> 14838 <doc:source> 14839 <script> 14840 function Ctrl($scope) { 14841 $scope.friends = 14842 [{name:'John', phone:'555-1212', age:10}, 14843 {name:'Mary', phone:'555-9876', age:19}, 14844 {name:'Mike', phone:'555-4321', age:21}, 14845 {name:'Adam', phone:'555-5678', age:35}, 14846 {name:'Julie', phone:'555-8765', age:29}] 14847 $scope.predicate = '-age'; 14848 } 14849 </script> 14850 <div ng-controller="Ctrl"> 14851 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 14852 <hr/> 14853 [ <a href="" ng-click="predicate=''">unsorted</a> ] 14854 <table class="friend"> 14855 <tr> 14856 <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a> 14857 (<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th> 14858 <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> 14859 <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th> 14860 </tr> 14861 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 14862 <td>{{friend.name}}</td> 14863 <td>{{friend.phone}}</td> 14864 <td>{{friend.age}}</td> 14865 </tr> 14866 </table> 14867 </div> 14868 </doc:source> 14869 </doc:example> 14870 */ 14871orderByFilter.$inject = ['$parse']; 14872function orderByFilter($parse){ 14873 return function(array, sortPredicate, reverseOrder) { 14874 if (!isArray(array)) return array; 14875 if (!sortPredicate) return array; 14876 sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; 14877 sortPredicate = map(sortPredicate, function(predicate){ 14878 var descending = false, get = predicate || identity; 14879 if (isString(predicate)) { 14880 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 14881 descending = predicate.charAt(0) == '-'; 14882 predicate = predicate.substring(1); 14883 } 14884 get = $parse(predicate); 14885 } 14886 return reverseComparator(function(a,b){ 14887 return compare(get(a),get(b)); 14888 }, descending); 14889 });
14890 var arrayCopy = []; 14891 for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } 14892 return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); 14893 14894 function comparator(o1, o2){ 14895 for ( var i = 0; i < sortPredicate.length; i++) { 14896 var comp = sortPredicate[i](o1, o2); 14897 if (comp !== 0) return comp; 14898 } 14899 return 0; 14900 } 14901 function reverseComparator(comp, descending) { 14902 return toBoolean(descending) 14903 ? function(a,b){return comp(b,a);} 14904 : comp; 14905 } 14906 function compare(v1, v2){ 14907 var t1 = typeof v1; 14908 var t2 = typeof v2; 14909 if (t1 == t2) { 14910 if (t1 == "string") { 14911 v1 = v1.toLowerCase(); 14912 v2 = v2.toLowerCase(); 14913 } 14914 if (v1 === v2) return 0; 14915 return v1 < v2 ? -1 : 1; 14916 } else { 14917 return t1 < t2 ? -1 : 1; 14918 } 14919 } 14920 }; 14921} 14922 14923function ngDirective(directive) { 14924 if (isFunction(directive)) { 14925 directive = { 14926 link: directive 14927 }; 14928 } 14929 directive.restrict = directive.restrict || 'AC'; 14930 return valueFn(directive); 14931} 14932 14933/** 14934 * @ngdoc directive 14935 * @name ng.directive:a 14936 * @restrict E 14937 * 14938 * @description 14939 * Modifies the default behavior of the html A tag so that the default action is prevented when 14940 * the href attribute is empty. 14941 * 14942 * This change permits the easy creation of action links with the `ngClick` directive 14943 * without changing the location or causing page reloads, e.g.: 14944 * `<a href="" ng-click="list.addItem()">Add Item</a>` 14945 */ 14946var htmlAnchorDirective = valueFn({ 14947 restrict: 'E', 14948 compile: function(element, attr) { 14949 14950 if (msie <= 8) { 14951 14952 // turn <a href ng-click="..">link</a> into a stylable link in IE 14953 // but only if it doesn't have name attribute, in which case it's an anchor 14954 if (!attr.href && !attr.name) { 14955 attr.$set('href', ''); 14956 } 14957 14958 // add a comment node to anchors to workaround IE bug that causes element content to be reset 14959 // to new attribute content if attribute is updated with value containing @ and element also 14960 // contains value with @ 14961 // see issue #1949 14962 element.append(document.createComment('IE fix')); 14963 } 14964 14965 if (!attr.href && !attr.xlinkHref && !attr.name) { 14966 return function(scope, element) { 14967 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 14968 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 14969 'xlink:href' : 'href'; 14970 element.on('click', function(event){ 14971 // if we have no href url, then don't navigate anywhere. 14972 if (!element.attr(href)) { 14973 event.preventDefault(); 14974 } 14975 }); 14976 }; 14977 } 14978 } 14979}); 14980 14981/** 14982 * @ngdoc directive 14983 * @name ng.directive:ngHref 14984 * @restrict A 14985 * @priority 99 14986 * 14987 * @description 14988 * Using Angular markup like `{{hash}}` in an href attribute will 14989 * make the link go to the wrong URL if the user clicks it before 14990 * Angular has a chance to replace the `{{hash}}` markup with its 14991 * value. Until Angular replaces the markup the link will be broken 14992 * and will most likely return a 404 error. 14993 * 14994 * The `ngHref` directive solves this problem. 14995 * 14996 * The wrong way to write it: 14997 * <pre> 14998 * <a href="http://www.gravatar.com/avatar/{{hash}}"/> 14999 * </pre> 15000 * 15001 * The correct way to write it: 15002 * <pre> 15003 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/> 15004 * </pre> 15005 * 15006 * @element A 15007 * @param {template} ngHref any string which can contain `{{}}` markup. 15008 * 15009 * @example 15010 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 15011 * in links and their different behaviors: 15012 <doc:example> 15013 <doc:source> 15014 <input ng-model="value" /><br /> 15015 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 15016 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 15017 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 15018 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 15019 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 15020 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 15021 </doc:source> 15022 <doc:protractor> 15023 it('should execute ng-click but not reload when href without value', function() {
15024 element(by.id('link-1')).click(); 15025 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 15026 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 15027 }); 15028 15029 it('should execute ng-click but not reload when href empty string', function() { 15030 element(by.id('link-2')).click(); 15031 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 15032 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 15033 }); 15034 15035 it('should execute ng-click and change url when ng-href specified', function() { 15036 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 15037 15038 element(by.id('link-3')).click(); 15039 15040 // At this point, we navigate away from an Angular page, so we need 15041 // to use browser.driver to get the base webdriver. 15042 15043 browser.wait(function() { 15044 return browser.driver.getCurrentUrl().then(function(url) { 15045 return url.match(/\/123$/); 15046 }); 15047 }, 1000, 'page should navigate to /123'); 15048 }); 15049 15050 it('should execute ng-click but not reload when href empty string and name specified', function() { 15051 element(by.id('link-4')).click(); 15052 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 15053 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 15054 }); 15055 15056 it('should execute ng-click but not reload when no href but name specified', function() { 15057 element(by.id('link-5')).click(); 15058 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 15059 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 15060 }); 15061 15062 it('should only change url when only ng-href', function() { 15063 element(by.model('value')).clear(); 15064 element(by.model('value')).sendKeys('6'); 15065 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 15066 15067 element(by.id('link-6')).click(); 15068 expect(browser.getCurrentUrl()).toMatch(/\/6$/); 15069 }); 15070 </doc:protractor> 15071 </doc:example> 15072 */ 15073 15074/** 15075 * @ngdoc directive 15076 * @name ng.directive:ngSrc 15077 * @restrict A 15078 * @priority 99 15079 * 15080 * @description 15081 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 15082 * work right: The browser will fetch from the URL with the literal 15083 * text `{{hash}}` until Angular replaces the expression inside 15084 * `{{hash}}`. The `ngSrc` directive solves this problem. 15085 * 15086 * The buggy way to write it: 15087 * <pre> 15088 * <img src="http://www.gravatar.com/avatar/{{hash}}"/> 15089 * </pre> 15090 * 15091 * The correct way to write it: 15092 * <pre> 15093 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/> 15094 * </pre> 15095 * 15096 * @element IMG 15097 * @param {template} ngSrc any string which can contain `{{}}` markup. 15098 */ 15099 15100/** 15101 * @ngdoc directive 15102 * @name ng.directive:ngSrcset 15103 * @restrict A 15104 * @priority 99 15105 * 15106 * @description 15107 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 15108 * work right: The browser will fetch from the URL with the literal 15109 * text `{{hash}}` until Angular replaces the expression inside 15110 * `{{hash}}`. The `ngSrcset` directive solves this problem. 15111 * 15112 * The buggy way to write it: 15113 * <pre> 15114 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 15115 * </pre> 15116 * 15117 * The correct way to write it: 15118 * <pre> 15119 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 15120 * </pre> 15121 * 15122 * @element IMG 15123 * @param {template} ngSrcset any string which can contain `{{}}` markup. 15124 */ 15125 15126/** 15127 * @ngdoc directive 15128 * @name ng.directive:ngDisabled 15129 * @restrict A 15130 * @priority 100 15131 * 15132 * @description 15133 * 15134 * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: 15135 * <pre> 15136 * <div ng-init="scope = { isDisabled: false }"> 15137 * <button disabled="{{scope.isDisabled}}">Disabled</button> 15138 * </div> 15139 * </pre> 15140 * 15141 * The HTML specification does not require browsers to preserve the values of boolean attributes 15142 * such as disabled. (Their presence means true and their absence means false.) 15143 * If we put an Angular interpolation expression into such an attribute then the 15144 * binding information would be lost when the browser removes the attribute. 15145 * The `ngDisabled` directive solves this problem for the `disabled` attribute. 15146 * This complementary directive is not removed by the browser and so provides 15147 * a permanent reliable place to store the binding information. 15148 * 15149 * @example 15150 <doc:example> 15151 <doc:source> 15152 Click me to toggle: <input type="checkbox" ng-model="checked"><br/> 15153 <button ng-model="button" ng-disabled="checked">Button</button> 15154 </doc:source> 15155 <doc:protractor> 15156 it('should toggle button', function() { 15157 expect(element(by.css('.doc-example-live button')).getAttribute('disabled')).toBeFalsy();
15158 element(by.model('checked')).click(); 15159 expect(element(by.css('.doc-example-live button')).getAttribute('disabled')).toBeTruthy(); 15160 }); 15161 </doc:protractor> 15162 </doc:example> 15163 * 15164 * @element INPUT 15165 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 15166 * then special attribute "disabled" will be set on the element 15167 */ 15168 15169 15170/** 15171 * @ngdoc directive 15172 * @name ng.directive:ngChecked 15173 * @restrict A 15174 * @priority 100 15175 * 15176 * @description 15177 * The HTML specification does not require browsers to preserve the values of boolean attributes 15178 * such as checked. (Their presence means true and their absence means false.) 15179 * If we put an Angular interpolation expression into such an attribute then the 15180 * binding information would be lost when the browser removes the attribute. 15181 * The `ngChecked` directive solves this problem for the `checked` attribute. 15182 * This complementary directive is not removed by the browser and so provides 15183 * a permanent reliable place to store the binding information. 15184 * @example 15185 <doc:example> 15186 <doc:source> 15187 Check me to check both: <input type="checkbox" ng-model="master"><br/> 15188 <input id="checkSlave" type="checkbox" ng-checked="master"> 15189 </doc:source> 15190 <doc:protractor> 15191 it('should check both checkBoxes', function() { 15192 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 15193 element(by.model('master')).click(); 15194 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 15195 }); 15196 </doc:protractor> 15197 </doc:example> 15198 * 15199 * @element INPUT 15200 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 15201 * then special attribute "checked" will be set on the element 15202 */ 15203 15204 15205/** 15206 * @ngdoc directive 15207 * @name ng.directive:ngReadonly 15208 * @restrict A 15209 * @priority 100 15210 * 15211 * @description 15212 * The HTML specification does not require browsers to preserve the values of boolean attributes 15213 * such as readonly. (Their presence means true and their absence means false.) 15214 * If we put an Angular interpolation expression into such an attribute then the 15215 * binding information would be lost when the browser removes the attribute. 15216 * The `ngReadonly` directive solves this problem for the `readonly` attribute. 15217 * This complementary directive is not removed by the browser and so provides 15218 * a permanent reliable place to store the binding information. 15219 * @example 15220 <doc:example> 15221 <doc:source> 15222 Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/> 15223 <input type="text" ng-readonly="checked" value="I'm Angular"/> 15224 </doc:source> 15225 <doc:protractor> 15226 it('should toggle readonly attr', function() { 15227 expect(element(by.css('.doc-example-live [type="text"]')).getAttribute('readonly')).toBeFalsy(); 15228 element(by.model('checked')).click(); 15229 expect(element(by.css('.doc-example-live [type="text"]')).getAttribute('readonly')).toBeTruthy(); 15230 }); 15231 </doc:protractor> 15232 </doc:example> 15233 * 15234 * @element INPUT 15235 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 15236 * then special attribute "readonly" will be set on the element 15237 */ 15238 15239 15240/** 15241 * @ngdoc directive 15242 * @name ng.directive:ngSelected 15243 * @restrict A 15244 * @priority 100 15245 * 15246 * @description 15247 * The HTML specification does not require browsers to preserve the values of boolean attributes 15248 * such as selected. (Their presence means true and their absence means false.) 15249 * If we put an Angular interpolation expression into such an attribute then the 15250 * binding information would be lost when the browser removes the attribute. 15251 * The `ngSelected` directive solves this problem for the `selected` atttribute. 15252 * This complementary directive is not removed by the browser and so provides 15253 * a permanent reliable place to store the binding information. 15254 * 15255 * @example 15256 <doc:example> 15257 <doc:source> 15258 Check me to select: <input type="checkbox" ng-model="selected"><br/> 15259 <select> 15260 <option>Hello!</option> 15261 <option id="greet" ng-selected="selected">Greetings!</option> 15262 </select> 15263 </doc:source> 15264 <doc:protractor> 15265 it('should select Greetings!', function() { 15266 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
15267 element(by.model('selected')).click(); 15268 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 15269 }); 15270 </doc:protractor> 15271 </doc:example> 15272 * 15273 * @element OPTION 15274 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 15275 * then special attribute "selected" will be set on the element 15276 */ 15277 15278/** 15279 * @ngdoc directive 15280 * @name ng.directive:ngOpen 15281 * @restrict A 15282 * @priority 100 15283 * 15284 * @description 15285 * The HTML specification does not require browsers to preserve the values of boolean attributes 15286 * such as open. (Their presence means true and their absence means false.) 15287 * If we put an Angular interpolation expression into such an attribute then the 15288 * binding information would be lost when the browser removes the attribute. 15289 * The `ngOpen` directive solves this problem for the `open` attribute. 15290 * This complementary directive is not removed by the browser and so provides 15291 * a permanent reliable place to store the binding information. 15292 * @example 15293 <doc:example> 15294 <doc:source> 15295 Check me check multiple: <input type="checkbox" ng-model="open"><br/> 15296 <details id="details" ng-open="open"> 15297 <summary>Show/Hide me</summary> 15298 </details> 15299 </doc:source> 15300 <doc:protractor> 15301 it('should toggle open', function() { 15302 expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); 15303 element(by.model('open')).click(); 15304 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 15305 }); 15306 </doc:protractor> 15307 </doc:example> 15308 * 15309 * @element DETAILS 15310 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 15311 * then special attribute "open" will be set on the element 15312 */ 15313 15314var ngAttributeAliasDirectives = {}; 15315 15316 15317// boolean attrs are evaluated 15318forEach(BOOLEAN_ATTR, function(propName, attrName) { 15319 // binding to multiple is not supported 15320 if (propName == "multiple") return; 15321 15322 var normalized = directiveNormalize('ng-' + attrName); 15323 ngAttributeAliasDirectives[normalized] = function() { 15324 return { 15325 priority: 100, 15326 link: function(scope, element, attr) { 15327 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 15328 attr.$set(attrName, !!value); 15329 }); 15330 } 15331 }; 15332 }; 15333}); 15334 15335 15336// ng-src, ng-srcset, ng-href are interpolated 15337forEach(['src', 'srcset', 'href'], function(attrName) { 15338 var normalized = directiveNormalize('ng-' + attrName); 15339 ngAttributeAliasDirectives[normalized] = function() { 15340 return { 15341 priority: 99, // it needs to run after the attributes are interpolated 15342 link: function(scope, element, attr) { 15343 attr.$observe(normalized, function(value) { 15344 if (!value) 15345 return; 15346 15347 attr.$set(attrName, value); 15348 15349 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 15350 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 15351 // to set the property as well to achieve the desired effect. 15352 // we use attr[attrName] value since $set can sanitize the url. 15353 if (msie) element.prop(attrName, attr[attrName]); 15354 }); 15355 } 15356 }; 15357 }; 15358}); 15359 15360/* global -nullFormCtrl */ 15361var nullFormCtrl = { 15362 $addControl: noop, 15363 $removeControl: noop, 15364 $setValidity: noop, 15365 $setDirty: noop, 15366 $setPristine: noop 15367}; 15368 15369/** 15370 * @ngdoc object 15371 * @name ng.directive:form.FormController 15372 * 15373 * @property {boolean} $pristine True if user has not interacted with the form yet. 15374 * @property {boolean} $dirty True if user has already interacted with the form. 15375 * @property {boolean} $valid True if all of the containing forms and controls are valid. 15376 * @property {boolean} $invalid True if at least one containing control or form is invalid. 15377 * 15378 * @property {Object} $error Is an object hash, containing references to all invalid controls or 15379 * forms, where: 15380 * 15381 * - keys are validation tokens (error names), 15382 * - values are arrays of controls or forms that are invalid for given error name. 15383 * 15384 * 15385 * Built-in validation tokens: 15386 * 15387 * - `email` 15388 * - `max` 15389 * - `maxlength` 15390 * - `min` 15391 * - `minlength` 15392 * - `number` 15393 * - `pattern` 15394 * - `required` 15395 * - `url` 15396 * 15397 * @description 15398 * `FormController` keeps track of all its controls and nested forms as well as state of them, 15399 * such as being valid/invalid or dirty/pristine. 15400 * 15401 * Each {@link ng.directive:form form} directive creates an instance 15402 * of `FormController`. 15403 * 15404 */ 15405//asks for $scope to fool the BC controller module
15406FormController.$inject = ['$element', '$attrs', '$scope']; 15407function FormController(element, attrs) { 15408 var form = this, 15409 parentForm = element.parent().controller('form') || nullFormCtrl, 15410 invalidCount = 0, // used to easily determine if we are valid 15411 errors = form.$error = {}, 15412 controls = []; 15413 15414 // init state 15415 form.$name = attrs.name || attrs.ngForm; 15416 form.$dirty = false; 15417 form.$pristine = true; 15418 form.$valid = true; 15419 form.$invalid = false; 15420 15421 parentForm.$addControl(form); 15422 15423 // Setup initial state of the control 15424 element.addClass(PRISTINE_CLASS); 15425 toggleValidCss(true); 15426 15427 // convenience method for easy toggling of classes 15428 function toggleValidCss(isValid, validationErrorKey) { 15429 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 15430 element. 15431 removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). 15432 addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 15433 } 15434 15435 /** 15436 * @ngdoc function 15437 * @name ng.directive:form.FormController#$addControl 15438 * @methodOf ng.directive:form.FormController 15439 * 15440 * @description 15441 * Register a control with the form. 15442 * 15443 * Input elements using ngModelController do this automatically when they are linked. 15444 */ 15445 form.$addControl = function(control) { 15446 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 15447 // and not added to the scope. Now we throw an error. 15448 assertNotHasOwnProperty(control.$name, 'input'); 15449 controls.push(control); 15450 15451 if (control.$name) { 15452 form[control.$name] = control; 15453 } 15454 }; 15455 15456 /** 15457 * @ngdoc function 15458 * @name ng.directive:form.FormController#$removeControl 15459 * @methodOf ng.directive:form.FormController 15460 * 15461 * @description 15462 * Deregister a control from the form. 15463 * 15464 * Input elements using ngModelController do this automatically when they are destroyed. 15465 */ 15466 form.$removeControl = function(control) { 15467 if (control.$name && form[control.$name] === control) { 15468 delete form[control.$name]; 15469 } 15470 forEach(errors, function(queue, validationToken) { 15471 form.$setValidity(validationToken, true, control); 15472 }); 15473 15474 arrayRemove(controls, control); 15475 }; 15476 15477 /** 15478 * @ngdoc function 15479 * @name ng.directive:form.FormController#$setValidity 15480 * @methodOf ng.directive:form.FormController 15481 * 15482 * @description 15483 * Sets the validity of a form control. 15484 * 15485 * This method will also propagate to parent forms. 15486 */ 15487 form.$setValidity = function(validationToken, isValid, control) { 15488 var queue = errors[validationToken]; 15489 15490 if (isValid) { 15491 if (queue) { 15492 arrayRemove(queue, control); 15493 if (!queue.length) { 15494 invalidCount--; 15495 if (!invalidCount) { 15496 toggleValidCss(isValid); 15497 form.$valid = true; 15498 form.$invalid = false; 15499 } 15500 errors[validationToken] = false; 15501 toggleValidCss(true, validationToken); 15502 parentForm.$setValidity(validationToken, true, form); 15503 } 15504 } 15505 15506 } else { 15507 if (!invalidCount) { 15508 toggleValidCss(isValid); 15509 } 15510 if (queue) { 15511 if (includes(queue, control)) return; 15512 } else { 15513 errors[validationToken] = queue = []; 15514 invalidCount++; 15515 toggleValidCss(false, validationToken); 15516 parentForm.$setValidity(validationToken, false, form); 15517 } 15518 queue.push(control); 15519 15520 form.$valid = false; 15521 form.$invalid = true; 15522 } 15523 }; 15524 15525 /** 15526 * @ngdoc function 15527 * @name ng.directive:form.FormController#$setDirty 15528 * @methodOf ng.directive:form.FormController 15529 * 15530 * @description 15531 * Sets the form to a dirty state. 15532 * 15533 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 15534 * state (ng-dirty class). This method will also propagate to parent forms. 15535 */ 15536 form.$setDirty = function() { 15537 element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); 15538 form.$dirty = true; 15539 form.$pristine = false; 15540 parentForm.$setDirty(); 15541 }; 15542 15543 /** 15544 * @ngdoc function
15545 * @name ng.directive:form.FormController#$setPristine 15546 * @methodOf ng.directive:form.FormController 15547 * 15548 * @description 15549 * Sets the form to its pristine state. 15550 * 15551 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 15552 * state (ng-pristine class). This method will also propagate to all the controls contained 15553 * in this form. 15554 * 15555 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 15556 * saving or resetting it. 15557 */ 15558 form.$setPristine = function () { 15559 element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); 15560 form.$dirty = false; 15561 form.$pristine = true; 15562 forEach(controls, function(control) { 15563 control.$setPristine(); 15564 }); 15565 }; 15566} 15567 15568 15569/** 15570 * @ngdoc directive 15571 * @name ng.directive:ngForm 15572 * @restrict EAC 15573 * 15574 * @description 15575 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 15576 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 15577 * sub-group of controls needs to be determined. 15578 * 15579 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 15580 * related scope, under this name. 15581 * 15582 */ 15583 15584 /** 15585 * @ngdoc directive 15586 * @name ng.directive:form 15587 * @restrict E 15588 * 15589 * @description 15590 * Directive that instantiates 15591 * {@link ng.directive:form.FormController FormController}. 15592 * 15593 * If the `name` attribute is specified, the form controller is published onto the current scope under 15594 * this name. 15595 * 15596 * # Alias: {@link ng.directive:ngForm `ngForm`} 15597 * 15598 * In Angular forms can be nested. This means that the outer form is valid when all of the child 15599 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 15600 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to 15601 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when 15602 * using Angular validation directives in forms that are dynamically generated using the 15603 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` 15604 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an 15605 * `ngForm` directive and nest these in an outer `form` element. 15606 * 15607 * 15608 * # CSS classes 15609 * - `ng-valid` is set if the form is valid. 15610 * - `ng-invalid` is set if the form is invalid. 15611 * - `ng-pristine` is set if the form is pristine. 15612 * - `ng-dirty` is set if the form is dirty. 15613 * 15614 * 15615 * # Submitting a form and preventing the default action 15616 * 15617 * Since the role of forms in client-side Angular applications is different than in classical 15618 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 15619 * page reload that sends the data to the server. Instead some javascript logic should be triggered 15620 * to handle the form submission in an application-specific way. 15621 * 15622 * For this reason, Angular prevents the default action (form submission to the server) unless the 15623 * `<form>` element has an `action` attribute specified. 15624 * 15625 * You can use one of the following two ways to specify what javascript method should be called when 15626 * a form is submitted: 15627 * 15628 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 15629 * - {@link ng.directive:ngClick ngClick} directive on the first 15630 * button or input field of type submit (input[type=submit]) 15631 * 15632 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 15633 * or {@link ng.directive:ngClick ngClick} directives. 15634 * This is because of the following form submission rules in the HTML specification: 15635 * 15636 * - If a form has only one input field then hitting enter in this field triggers form submit 15637 * (`ngSubmit`) 15638 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 15639 * doesn't trigger submit 15640 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 15641 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 15642 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 15643 * 15644 * @param {string=} name Name of the form. If specified, the form controller will be published into 15645 * related scope, under this name. 15646 * 15647 * @example 15648 <doc:example> 15649 <doc:source> 15650 <script> 15651 function Ctrl($scope) { 15652 $scope.userType = 'guest'; 15653 } 15654 </script> 15655 <form name="myForm" ng-controller="Ctrl"> 15656 userType: <input name="input" ng-model="userType" required>
15657 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 15658 <tt>userType = {{userType}}</tt><br> 15659 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br> 15660 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br> 15661 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 15662 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 15663 </form> 15664 </doc:source> 15665 <doc:protractor> 15666 it('should initialize to model', function() { 15667 var userType = element(by.binding('userType')); 15668 var valid = element(by.binding('myForm.input.$valid')); 15669 15670 expect(userType.getText()).toContain('guest'); 15671 expect(valid.getText()).toContain('true'); 15672 }); 15673 15674 it('should be invalid if empty', function() { 15675 var userType = element(by.binding('userType')); 15676 var valid = element(by.binding('myForm.input.$valid')); 15677 var userInput = element(by.model('userType')); 15678 15679 userInput.clear(); 15680 userInput.sendKeys(''); 15681 15682 expect(userType.getText()).toEqual('userType ='); 15683 expect(valid.getText()).toContain('false'); 15684 }); 15685 </doc:protractor> 15686 </doc:example> 15687 */ 15688var formDirectiveFactory = function(isNgForm) { 15689 return ['$timeout', function($timeout) { 15690 var formDirective = { 15691 name: 'form', 15692 restrict: isNgForm ? 'EAC' : 'E', 15693 controller: FormController, 15694 compile: function() { 15695 return { 15696 pre: function(scope, formElement, attr, controller) { 15697 if (!attr.action) { 15698 // we can't use jq events because if a form is destroyed during submission the default 15699 // action is not prevented. see #1238 15700 // 15701 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 15702 // page reload if the form was destroyed by submission of the form via a click handler 15703 // on a button in the form. Looks like an IE9 specific bug. 15704 var preventDefaultListener = function(event) { 15705 event.preventDefault 15706 ? event.preventDefault() 15707 : event.returnValue = false; // IE 15708 }; 15709 15710 addEventListenerFn(formElement[0], 'submit', preventDefaultListener); 15711 15712 // unregister the preventDefault listener so that we don't not leak memory but in a 15713 // way that will achieve the prevention of the default action. 15714 formElement.on('$destroy', function() { 15715 $timeout(function() { 15716 removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); 15717 }, 0, false); 15718 }); 15719 } 15720 15721 var parentFormCtrl = formElement.parent().controller('form'), 15722 alias = attr.name || attr.ngForm; 15723 15724 if (alias) { 15725 setter(scope, alias, controller, alias); 15726 } 15727 if (parentFormCtrl) { 15728 formElement.on('$destroy', function() { 15729 parentFormCtrl.$removeControl(controller); 15730 if (alias) { 15731 setter(scope, alias, undefined, alias); 15732 } 15733 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 15734 }); 15735 } 15736 } 15737 }; 15738 } 15739 }; 15740 15741 return formDirective; 15742 }]; 15743}; 15744 15745var formDirective = formDirectiveFactory(); 15746var ngFormDirective = formDirectiveFactory(true); 15747 15748/* global 15749 15750 -VALID_CLASS, 15751 -INVALID_CLASS, 15752 -PRISTINE_CLASS, 15753 -DIRTY_CLASS 15754*/ 15755 15756var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 15757var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i; 15758var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; 15759 15760var inputType = { 15761 15762 /** 15763 * @ngdoc inputType 15764 * @name ng.directive:input.text 15765 * 15766 * @description 15767 * Standard HTML text input with angular data binding. 15768 * 15769 * @param {string} ngModel Assignable angular expression to data-bind to. 15770 * @param {string=} name Property name of the form under which the control is published. 15771 * @param {string=} required Adds `required` validation error key if the value is not entered. 15772 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 15773 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 15774 * `required` when you want to data-bind to the `required` attribute. 15775 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 15776 * minlength. 15777 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 15778 * maxlength. 15779 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 15780 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 15781 * patterns defined as scope expressions. 15782 * @param {string=} ngChange Angular expression to be executed when input changes due to user 15783 * interaction with the input element. 15784 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 15785 * 15786 * @example 15787 <doc:example> 15788 <doc:source> 15789 <script> 15790 function Ctrl($scope) { 15791 $scope.text = 'guest'; 15792 $scope.word = /^\s*\w*\s*$/; 15793 } 15794 </script> 15795 <form name="myForm" ng-controller="Ctrl"> 15796 Single word: <input type="text" name="input" ng-model="text" 15797 ng-pattern="word" required ng-trim="false">
15798 <span class="error" ng-show="myForm.input.$error.required"> 15799 Required!</span> 15800 <span class="error" ng-show="myForm.input.$error.pattern"> 15801 Single word only!</span> 15802 15803 <tt>text = {{text}}</tt><br/> 15804 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 15805 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 15806 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 15807 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 15808 </form> 15809 </doc:source> 15810 <doc:protractor> 15811 var text = element(by.binding('text')); 15812 var valid = element(by.binding('myForm.input.$valid')); 15813 var input = element(by.model('text')); 15814 15815 it('should initialize to model', function() { 15816 expect(text.getText()).toContain('guest'); 15817 expect(valid.getText()).toContain('true'); 15818 }); 15819 15820 it('should be invalid if empty', function() { 15821 input.clear(); 15822 input.sendKeys(''); 15823 15824 expect(text.getText()).toEqual('text ='); 15825 expect(valid.getText()).toContain('false'); 15826 }); 15827 15828 it('should be invalid if multi word', function() { 15829 input.clear(); 15830 input.sendKeys('hello world'); 15831 15832 expect(valid.getText()).toContain('false'); 15833 }); 15834 </doc:protractor> 15835 </doc:example> 15836 */ 15837 'text': textInputType, 15838 15839 15840 /** 15841 * @ngdoc inputType 15842 * @name ng.directive:input.number 15843 * 15844 * @description 15845 * Text input with number validation and transformation. Sets the `number` validation 15846 * error if not a valid number. 15847 * 15848 * @param {string} ngModel Assignable angular expression to data-bind to. 15849 * @param {string=} name Property name of the form under which the control is published. 15850 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 15851 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 15852 * @param {string=} required Sets `required` validation error key if the value is not entered. 15853 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 15854 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 15855 * `required` when you want to data-bind to the `required` attribute. 15856 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 15857 * minlength. 15858 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 15859 * maxlength. 15860 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 15861 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 15862 * patterns defined as scope expressions. 15863 * @param {string=} ngChange Angular expression to be executed when input changes due to user 15864 * interaction with the input element. 15865 * 15866 * @example 15867 <doc:example> 15868 <doc:source> 15869 <script> 15870 function Ctrl($scope) { 15871 $scope.value = 12; 15872 } 15873 </script> 15874 <form name="myForm" ng-controller="Ctrl"> 15875 Number: <input type="number" name="input" ng-model="value" 15876 min="0" max="99" required> 15877 <span class="error" ng-show="myForm.input.$error.required"> 15878 Required!</span> 15879 <span class="error" ng-show="myForm.input.$error.number"> 15880 Not valid number!</span> 15881 <tt>value = {{value}}</tt><br/> 15882 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 15883 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 15884 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 15885 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 15886 </form> 15887 </doc:source> 15888 <doc:protractor> 15889 var value = element(by.binding('value'));
15890 var valid = element(by.binding('myForm.input.$valid')); 15891 var input = element(by.model('value')); 15892 15893 it('should initialize to model', function() { 15894 expect(value.getText()).toContain('12'); 15895 expect(valid.getText()).toContain('true'); 15896 }); 15897 15898 it('should be invalid if empty', function() { 15899 input.clear(); 15900 input.sendKeys(''); 15901 expect(value.getText()).toEqual('value ='); 15902 expect(valid.getText()).toContain('false'); 15903 }); 15904 15905 it('should be invalid if over max', function() { 15906 input.clear(); 15907 input.sendKeys('123'); 15908 expect(value.getText()).toEqual('value ='); 15909 expect(valid.getText()).toContain('false'); 15910 }); 15911 </doc:protractor> 15912 </doc:example> 15913 */ 15914 'number': numberInputType, 15915 15916 15917 /** 15918 * @ngdoc inputType 15919 * @name ng.directive:input.url 15920 * 15921 * @description 15922 * Text input with URL validation. Sets the `url` validation error key if the content is not a 15923 * valid URL. 15924 * 15925 * @param {string} ngModel Assignable angular expression to data-bind to. 15926 * @param {string=} name Property name of the form under which the control is published. 15927 * @param {string=} required Sets `required` validation error key if the value is not entered. 15928 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 15929 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 15930 * `required` when you want to data-bind to the `required` attribute. 15931 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 15932 * minlength. 15933 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 15934 * maxlength. 15935 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 15936 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 15937 * patterns defined as scope expressions. 15938 * @param {string=} ngChange Angular expression to be executed when input changes due to user 15939 * interaction with the input element. 15940 * 15941 * @example 15942 <doc:example> 15943 <doc:source> 15944 <script> 15945 function Ctrl($scope) { 15946 $scope.text = 'http://google.com'; 15947 } 15948 </script> 15949 <form name="myForm" ng-controller="Ctrl"> 15950 URL: <input type="url" name="input" ng-model="text" required> 15951 <span class="error" ng-show="myForm.input.$error.required"> 15952 Required!</span> 15953 <span class="error" ng-show="myForm.input.$error.url"> 15954 Not valid url!</span> 15955 <tt>text = {{text}}</tt><br/> 15956 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 15957 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 15958 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 15959 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 15960 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 15961 </form> 15962 </doc:source> 15963 <doc:protractor> 15964 var text = element(by.binding('text')); 15965 var valid = element(by.binding('myForm.input.$valid')); 15966 var input = element(by.model('text')); 15967 15968 it('should initialize to model', function() { 15969 expect(text.getText()).toContain('http://google.com'); 15970 expect(valid.getText()).toContain('true'); 15971 }); 15972 15973 it('should be invalid if empty', function() { 15974 input.clear(); 15975 input.sendKeys(''); 15976 15977 expect(text.getText()).toEqual('text ='); 15978 expect(valid.getText()).toContain('false'); 15979 }); 15980 15981 it('should be invalid if not url', function() { 15982 input.clear(); 15983 input.sendKeys('box'); 15984 15985 expect(valid.getText()).toContain('false'); 15986 }); 15987 </doc:protractor> 15988 </doc:example> 15989 */ 15990 'url': urlInputType, 15991 15992 15993 /** 15994 * @ngdoc inputType 15995 * @name ng.directive:input.email 15996 * 15997 * @description 15998 * Text input with email validation. Sets the `email` validation error key if not a valid email 15999 * address. 16000 * 16001 * @param {string} ngModel Assignable angular expression to data-bind to. 16002 * @param {string=} name Property name of the form under which the control is published. 16003 * @param {string=} required Sets `required` validation error key if the value is not entered. 16004 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16005 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16006 * `required` when you want to data-bind to the `required` attribute. 16007 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16008 * minlength. 16009 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16010 * maxlength. 16011 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16012 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16013 * patterns defined as scope expressions. 16014 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16015 * interaction with the input element. 16016 * 16017 * @example 16018 <doc:example> 16019 <doc:source> 16020 <script> 16021 function Ctrl($scope) { 16022 $scope.text = '[email protected]'; 16023 } 16024 </script> 16025 <form name="myForm" ng-controller="Ctrl"> 16026 Email: <input type="email" name="input" ng-model="text" required>
16027 <span class="error" ng-show="myForm.input.$error.required"> 16028 Required!</span> 16029 <span class="error" ng-show="myForm.input.$error.email"> 16030 Not valid email!</span> 16031 <tt>text = {{text}}</tt><br/> 16032 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16033 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16034 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16035 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16036 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 16037 </form> 16038 </doc:source> 16039 <doc:protractor> 16040 var text = element(by.binding('text')); 16041 var valid = element(by.binding('myForm.input.$valid')); 16042 var input = element(by.model('text')); 16043 16044 it('should initialize to model', function() { 16045 expect(text.getText()).toContain('[email protected]'); 16046 expect(valid.getText()).toContain('true'); 16047 }); 16048 16049 it('should be invalid if empty', function() { 16050 input.clear(); 16051 input.sendKeys(''); 16052 expect(text.getText()).toEqual('text ='); 16053 expect(valid.getText()).toContain('false'); 16054 }); 16055 16056 it('should be invalid if not email', function() { 16057 input.clear(); 16058 input.sendKeys('xxx'); 16059 16060 expect(valid.getText()).toContain('false'); 16061 }); 16062 </doc:protractor> 16063 </doc:example> 16064 */ 16065 'email': emailInputType, 16066 16067 16068 /** 16069 * @ngdoc inputType 16070 * @name ng.directive:input.radio 16071 * 16072 * @description 16073 * HTML radio button. 16074 * 16075 * @param {string} ngModel Assignable angular expression to data-bind to. 16076 * @param {string} value The value to which the expression should be set when selected. 16077 * @param {string=} name Property name of the form under which the control is published. 16078 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16079 * interaction with the input element. 16080 * @param {string} ngValue Angular expression which sets the value to which the expression should 16081 * be set when selected. 16082 * 16083 * @example 16084 <doc:example> 16085 <doc:source> 16086 <script> 16087 function Ctrl($scope) { 16088 $scope.color = 'blue'; 16089 $scope.specialValue = { 16090 "id": "12345", 16091 "value": "green" 16092 }; 16093 } 16094 </script> 16095 <form name="myForm" ng-controller="Ctrl"> 16096 <input type="radio" ng-model="color" value="red"> Red <br/> 16097 <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/> 16098 <input type="radio" ng-model="color" value="blue"> Blue <br/> 16099 <tt>color = {{color | json}}</tt><br/> 16100 </form> 16101 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 16102 </doc:source> 16103 <doc:protractor> 16104 it('should change state', function() { 16105 var color = element(by.binding('color')); 16106 16107 expect(color.getText()).toContain('blue'); 16108 16109 element.all(by.model('color')).get(0).click(); 16110 16111 expect(color.getText()).toContain('red'); 16112 }); 16113 </doc:protractor> 16114 </doc:example> 16115 */ 16116 'radio': radioInputType, 16117 16118 16119 /** 16120 * @ngdoc inputType 16121 * @name ng.directive:input.checkbox 16122 * 16123 * @description 16124 * HTML checkbox. 16125 * 16126 * @param {string} ngModel Assignable angular expression to data-bind to. 16127 * @param {string=} name Property name of the form under which the control is published. 16128 * @param {string=} ngTrueValue The value to which the expression should be set when selected. 16129 * @param {string=} ngFalseValue The value to which the expression should be set when not selected. 16130 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16131 * interaction with the input element. 16132 * 16133 * @example 16134 <doc:example> 16135 <doc:source> 16136 <script> 16137 function Ctrl($scope) { 16138 $scope.value1 = true; 16139 $scope.value2 = 'YES' 16140 } 16141 </script> 16142 <form name="myForm" ng-controller="Ctrl"> 16143 Value1: <input type="checkbox" ng-model="value1"> <br/> 16144 Value2: <input type="checkbox" ng-model="value2" 16145 ng-true-value="YES" ng-false-value="NO"> <br/> 16146 <tt>value1 = {{value1}}</tt><br/> 16147 <tt>value2 = {{value2}}</tt><br/> 16148 </form> 16149 </doc:source> 16150 <doc:protractor> 16151 it('should change state', function() { 16152 var value1 = element(by.binding('value1')); 16153 var value2 = element(by.binding('value2')); 16154 16155 expect(value1.getText()).toContain('true'); 16156 expect(value2.getText()).toContain('YES'); 16157
16158 element(by.model('value1')).click(); 16159 element(by.model('value2')).click(); 16160 16161 expect(value1.getText()).toContain('false'); 16162 expect(value2.getText()).toContain('NO'); 16163 }); 16164 </doc:protractor> 16165 </doc:example> 16166 */ 16167 'checkbox': checkboxInputType, 16168 16169 'hidden': noop, 16170 'button': noop, 16171 'submit': noop, 16172 'reset': noop, 16173 'file': noop 16174}; 16175 16176// A helper function to call $setValidity and return the value / undefined, 16177// a pattern that is repeated a lot in the input validation logic. 16178function validate(ctrl, validatorName, validity, value){ 16179 ctrl.$setValidity(validatorName, validity); 16180 return validity ? value : undefined; 16181} 16182 16183function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16184 // In composition mode, users are still inputing intermediate text buffer, 16185 // hold the listener until composition is done. 16186 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 16187 if (!$sniffer.android) { 16188 var composing = false; 16189 16190 element.on('compositionstart', function(data) { 16191 composing = true; 16192 }); 16193 16194 element.on('compositionend', function() { 16195 composing = false; 16196 listener(); 16197 }); 16198 } 16199 16200 var listener = function() { 16201 if (composing) return; 16202 var value = element.val(); 16203 16204 // By default we will trim the value 16205 // If the attribute ng-trim exists we will avoid trimming 16206 // e.g. <input ng-model="foo" ng-trim="false"> 16207 if (toBoolean(attr.ngTrim || 'T')) { 16208 value = trim(value); 16209 } 16210 16211 if (ctrl.$viewValue !== value) { 16212 if (scope.$$phase) { 16213 ctrl.$setViewValue(value); 16214 } else { 16215 scope.$apply(function() { 16216 ctrl.$setViewValue(value); 16217 }); 16218 } 16219 } 16220 }; 16221 16222 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 16223 // input event on backspace, delete or cut 16224 if ($sniffer.hasEvent('input')) { 16225 element.on('input', listener); 16226 } else { 16227 var timeout; 16228 16229 var deferListener = function() { 16230 if (!timeout) { 16231 timeout = $browser.defer(function() { 16232 listener(); 16233 timeout = null; 16234 }); 16235 } 16236 }; 16237 16238 element.on('keydown', function(event) { 16239 var key = event.keyCode; 16240 16241 // ignore 16242 // command modifiers arrows 16243 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 16244 16245 deferListener(); 16246 }); 16247 16248 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 16249 if ($sniffer.hasEvent('paste')) { 16250 element.on('paste cut', deferListener); 16251 } 16252 } 16253 16254 // if user paste into input using mouse on older browser 16255 // or form autocomplete on newer browser, we need "change" event to catch it 16256 element.on('change', listener); 16257 16258 ctrl.$render = function() { 16259 element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); 16260 }; 16261 16262 // pattern validator 16263 var pattern = attr.ngPattern, 16264 patternValidator, 16265 match; 16266 16267 if (pattern) { 16268 var validateRegex = function(regexp, value) { 16269 return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value); 16270 }; 16271 match = pattern.match(/^\/(.*)\/([gim]*)$/); 16272 if (match) { 16273 pattern = new RegExp(match[1], match[2]); 16274 patternValidator = function(value) { 16275 return validateRegex(pattern, value); 16276 }; 16277 } else { 16278 patternValidator = function(value) { 16279 var patternObj = scope.$eval(pattern); 16280 16281 if (!patternObj || !patternObj.test) { 16282 throw minErr('ngPattern')('noregexp', 16283 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, 16284 patternObj, startingTag(element)); 16285 } 16286 return validateRegex(patternObj, value); 16287 }; 16288 } 16289 16290 ctrl.$formatters.push(patternValidator); 16291 ctrl.$parsers.push(patternValidator); 16292 } 16293 16294 // min length validator 16295 if (attr.ngMinlength) { 16296 var minlength = int(attr.ngMinlength); 16297 var minLengthValidator = function(value) { 16298 return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value); 16299 }; 16300 16301 ctrl.$parsers.push(minLengthValidator); 16302 ctrl.$formatters.push(minLengthValidator); 16303 } 16304 16305 // max length validator 16306 if (attr.ngMaxlength) { 16307 var maxlength = int(attr.ngMaxlength); 16308 var maxLengthValidator = function(value) { 16309 return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value); 16310 }; 16311 16312 ctrl.$parsers.push(maxLengthValidator); 16313 ctrl.$formatters.push(maxLengthValidator); 16314 } 16315} 16316 16317function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16318 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 16319 16320 ctrl.$parsers.push(function(value) { 16321 var empty = ctrl.$isEmpty(value); 16322 if (empty || NUMBER_REGEXP.test(value)) { 16323 ctrl.$setValidity('number', true); 16324 return value === '' ? null : (empty ? value : parseFloat(value)); 16325 } else { 16326 ctrl.$setValidity('number', false); 16327 return undefined; 16328 } 16329 }); 16330 16331 ctrl.$formatters.push(function(value) { 16332 return ctrl.$isEmpty(value) ? '' : '' + value; 16333 }); 16334 16335 if (attr.min) { 16336 var minValidator = function(value) { 16337 var min = parseFloat(attr.min); 16338 return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); 16339 }; 16340 16341 ctrl.$parsers.push(minValidator); 16342 ctrl.$formatters.push(minValidator); 16343 } 16344 16345 if (attr.max) { 16346 var maxValidator = function(value) { 16347 var max = parseFloat(attr.max); 16348 return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); 16349 }; 16350 16351 ctrl.$parsers.push(maxValidator); 16352 ctrl.$formatters.push(maxValidator); 16353 } 16354 16355 ctrl.$formatters.push(function(value) { 16356 return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); 16357 }); 16358} 16359 16360function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16361 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 16362 16363 var urlValidator = function(value) { 16364 return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); 16365 }; 16366 16367 ctrl.$formatters.push(urlValidator);
16368 ctrl.$parsers.push(urlValidator); 16369} 16370 16371function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16372 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 16373 16374 var emailValidator = function(value) { 16375 return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); 16376 }; 16377 16378 ctrl.$formatters.push(emailValidator); 16379 ctrl.$parsers.push(emailValidator); 16380} 16381 16382function radioInputType(scope, element, attr, ctrl) { 16383 // make the name unique, if not defined 16384 if (isUndefined(attr.name)) { 16385 element.attr('name', nextUid()); 16386 } 16387 16388 element.on('click', function() { 16389 if (element[0].checked) { 16390 scope.$apply(function() { 16391 ctrl.$setViewValue(attr.value); 16392 }); 16393 } 16394 }); 16395 16396 ctrl.$render = function() { 16397 var value = attr.value; 16398 element[0].checked = (value == ctrl.$viewValue); 16399 }; 16400 16401 attr.$observe('value', ctrl.$render); 16402} 16403 16404function checkboxInputType(scope, element, attr, ctrl) { 16405 var trueValue = attr.ngTrueValue, 16406 falseValue = attr.ngFalseValue; 16407 16408 if (!isString(trueValue)) trueValue = true; 16409 if (!isString(falseValue)) falseValue = false; 16410 16411 element.on('click', function() { 16412 scope.$apply(function() { 16413 ctrl.$setViewValue(element[0].checked); 16414 }); 16415 }); 16416 16417 ctrl.$render = function() { 16418 element[0].checked = ctrl.$viewValue; 16419 }; 16420 16421 // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. 16422 ctrl.$isEmpty = function(value) { 16423 return value !== trueValue; 16424 }; 16425 16426 ctrl.$formatters.push(function(value) { 16427 return value === trueValue; 16428 }); 16429 16430 ctrl.$parsers.push(function(value) { 16431 return value ? trueValue : falseValue; 16432 }); 16433} 16434 16435 16436/** 16437 * @ngdoc directive 16438 * @name ng.directive:textarea 16439 * @restrict E 16440 * 16441 * @description 16442 * HTML textarea element control with angular data-binding. The data-binding and validation 16443 * properties of this element are exactly the same as those of the 16444 * {@link ng.directive:input input element}. 16445 * 16446 * @param {string} ngModel Assignable angular expression to data-bind to. 16447 * @param {string=} name Property name of the form under which the control is published. 16448 * @param {string=} required Sets `required` validation error key if the value is not entered. 16449 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16450 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16451 * `required` when you want to data-bind to the `required` attribute. 16452 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16453 * minlength. 16454 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16455 * maxlength. 16456 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16457 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16458 * patterns defined as scope expressions. 16459 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16460 * interaction with the input element. 16461 */ 16462 16463 16464/** 16465 * @ngdoc directive 16466 * @name ng.directive:input 16467 * @restrict E 16468 * 16469 * @description 16470 * HTML input element control with angular data-binding. Input control follows HTML5 input types 16471 * and polyfills the HTML5 validation behavior for older browsers. 16472 * 16473 * @param {string} ngModel Assignable angular expression to data-bind to. 16474 * @param {string=} name Property name of the form under which the control is published. 16475 * @param {string=} required Sets `required` validation error key if the value is not entered. 16476 * @param {boolean=} ngRequired Sets `required` attribute if set to true 16477 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16478 * minlength. 16479 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16480 * maxlength. 16481 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16482 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16483 * patterns defined as scope expressions. 16484 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16485 * interaction with the input element. 16486 * 16487 * @example 16488 <doc:example> 16489 <doc:source> 16490 <script> 16491 function Ctrl($scope) { 16492 $scope.user = {name: 'guest', last: 'visitor'}; 16493 } 16494 </script> 16495 <div ng-controller="Ctrl"> 16496 <form name="myForm"> 16497 User name: <input type="text" name="userName" ng-model="user.n
16497ame" required> 16498 <span class="error" ng-show="myForm.userName.$error.required"> 16499 Required!</span><br> 16500 Last name: <input type="text" name="lastName" ng-model="user.last" 16501 ng-minlength="3" ng-maxlength="10"> 16502 <span class="error" ng-show="myForm.lastName.$error.minlength"> 16503 Too short!</span> 16504 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 16505 Too long!</span><br> 16506 </form> 16507 <hr> 16508 <tt>user = {{user}}</tt><br/> 16509 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br> 16510 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br> 16511 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br> 16512 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br> 16513 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 16514 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 16515 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br> 16516 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br> 16517 </div> 16518 </doc:source> 16519 <doc:protractor> 16520 var user = element(by.binding('{{user}}')); 16521 var userNameValid = element(by.binding('myForm.userName.$valid')); 16522 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 16523 var lastNameError = element(by.binding('myForm.lastName.$error')); 16524 var formValid = element(by.binding('myForm.$valid')); 16525 var userNameInput = element(by.model('user.name')); 16526 var userLastInput = element(by.model('user.last')); 16527 16528 it('should initialize to model', function() {
16529 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 16530 expect(userNameValid.getText()).toContain('true'); 16531 expect(formValid.getText()).toContain('true'); 16532 }); 16533 16534 it('should be invalid if empty when required', function() { 16535 userNameInput.clear(); 16536 userNameInput.sendKeys(''); 16537 16538 expect(user.getText()).toContain('{"last":"visitor"}'); 16539 expect(userNameValid.getText()).toContain('false'); 16540 expect(formValid.getText()).toContain('false'); 16541 }); 16542 16543 it('should be valid if empty when min length is set', function() { 16544 userLastInput.clear(); 16545 userLastInput.sendKeys(''); 16546 16547 expect(user.getText()).toContain('{"name":"guest","last":""}'); 16548 expect(lastNameValid.getText()).toContain('true'); 16549 expect(formValid.getText()).toContain('true'); 16550 }); 16551 16552 it('should be invalid if less than required min length', function() { 16553 userLastInput.clear(); 16554 userLastInput.sendKeys('xx'); 16555
16556 expect(user.getText()).toContain('{"name":"guest"}'); 16557 expect(lastNameValid.getText()).toContain('false'); 16558 expect(lastNameError.getText()).toContain('minlength'); 16559 expect(formValid.getText()).toContain('false'); 16560 }); 16561 16562 it('should be invalid if longer than max length', function() { 16563 userLastInput.clear(); 16564 userLastInput.sendKeys('some ridiculously long name'); 16565 16566 expect(user.getText()).toContain('{"name":"guest"}'); 16567 expect(lastNameValid.getText()).toContain('false'); 16568 expect(lastNameError.getText()).toContain('maxlength'); 16569 expect(formValid.getText()).toContain('false'); 16570 }); 16571 </doc:protractor> 16572 </doc:example> 16573 */ 16574var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { 16575 return { 16576 restrict: 'E', 16577 require: '?ngModel', 16578 link: function(scope, element, attr, ctrl) { 16579 if (ctrl) { 16580 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, 16581 $browser); 16582 } 16583 } 16584 }; 16585}]; 16586 16587var VALID_CLASS = 'ng-valid', 16588 INVALID_CLASS = 'ng-invalid', 16589 PRISTINE_CLASS = 'ng-pristine', 16590 DIRTY_CLASS = 'ng-dirty'; 16591 16592/** 16593 * @ngdoc object 16594 * @name ng.directive:ngModel.NgModelController 16595 * 16596 * @property {string} $viewValue Actual string value in the view. 16597 * @property {*} $modelValue The value in the model, that the control is bound to. 16598 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 16599 the control reads value from the DOM. Each function is called, in turn, passing the value 16600 through to the next. Used to sanitize / convert the value as well as validation. 16601 For validation, the parsers should update the validity state using 16602 {@link ng.directive:ngModel.NgModelController#methods_$setValidity $setValidity()}, 16603 and return `undefined` for invalid values. 16604 16605 * 16606 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 16607 the model value changes. Each function is called, in turn, passing the value through to the 16608 next. Used to format / convert values for display in the control and validation. 16609 * <pre> 16610 * function formatter(value) { 16611 * if (value) { 16612 * return value.toUpperCase(); 16613 * } 16614 * } 16615 * ngModel.$formatters.push(formatter); 16616 * </pre> 16617 * 16618 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 16619 * view value has changed. It is called with no arguments, and its return value is ignored. 16620 * This can be used in place of additional $watches against the model value. 16621 * 16622 * @property {Object} $error An object hash with all errors as keys. 16623 * 16624 * @property {boolean} $pristine True if user has not interacted with the control yet. 16625 * @property {boolean} $dirty True if user has already interacted with the control. 16626 * @property {boolean} $valid True if there is no error. 16627 * @property {boolean} $invalid True if at least one error on the control. 16628 * 16629 * @description 16630 * 16631 * `NgModelController` provides API for the `ng-model` directive. The controller contains 16632 * services for data-binding, validation, CSS updates, and value formatting and parsing. It 16633 * purposefully does not contain any logic which deals with DOM rendering or listening to 16634 * DOM events. Such DOM related logic should be provided by other directives which make use of 16635 * `NgModelController` for data-binding. 16636 * 16637 * ## Custom Control Example 16638 * This example shows how to use `NgModelController` with a custom control to achieve 16639 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 16640 * collaborate together to achieve the desired result. 16641 * 16642 * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element 16643 * contents be edited in place by the user. This will not work on older browsers. 16644 * 16645 * <example module="customControl"> 16646 <file name="style.css"> 16647 [contenteditable] { 16648 border: 1px solid black; 16649 background-color: white; 16650 min-height: 20px; 16651 } 16652 16653 .ng-invalid { 16654 border: 1px solid red; 16655 } 16656 16657 </file> 16658 <file name="script.js">
16659 angular.module('customControl', []). 16660 directive('contenteditable', function() { 16661 return { 16662 restrict: 'A', // only activate on element attribute 16663 require: '?ngModel', // get a hold of NgModelController 16664 link: function(scope, element, attrs, ngModel) { 16665 if(!ngModel) return; // do nothing if no ng-model 16666 16667 // Specify how UI should be updated 16668 ngModel.$render = function() { 16669 element.html(ngModel.$viewValue || ''); 16670 }; 16671 16672 // Listen for change events to enable binding 16673 element.on('blur keyup change', function() { 16674 scope.$apply(read); 16675 }); 16676 read(); // initialize 16677 16678 // Write data to the model 16679 function read() { 16680 var html = element.html(); 16681 // When we clear the content editable the browser leaves a <br> behind 16682 // If strip-br attribute is provided then we strip this out 16683 if( attrs.stripBr && html == '<br>' ) { 16684 html = ''; 16685 } 16686 ngModel.$setViewValue(html); 16687 } 16688 } 16689 }; 16690 }); 16691 </file> 16692 <file name="index.html"> 16693 <form name="myForm"> 16694 <div contenteditable 16695 name="myWidget" ng-model="userContent" 16696 strip-br="true" 16697 required>Change me!</div> 16698 <span ng-show="myForm.myWidget.$error.required">Required!</span> 16699 <hr> 16700 <textarea ng-model="userContent"></textarea> 16701 </form> 16702 </file> 16703 <file name="protractorTest.js"> 16704 it('should data-bind and become invalid', function() { 16705 if (browser.params.browser = 'safari') { 16706 // SafariDriver can't handle contenteditable. 16707 return; 16708 }; 16709 var contentEditable = element(by.css('.doc-example-live [contenteditable]')); 16710 16711 expect(contentEditable.getText()).toEqual('Change me!'); 16712 16713 // Firefox driver doesn't trigger the proper events on 'clear', so do this hack 16714 contentEditable.click(); 16715 contentEditable.sendKeys(protractor.Key.chord(protractor.Key.COMMAND, "a")); 16716 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 16717 16718 expect(contentEditable.getText()).toEqual(''); 16719 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 16720 }); 16721 </file> 16722 * </example> 16723 * 16724 * 16725 */ 16726var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', 16727 function($scope, $exceptionHandler, $attr, $element, $parse) { 16728 this.$viewValue = Number.NaN; 16729 this.$modelValue = Number.NaN; 16730 this.$parsers = []; 16731 this.$formatters = []; 16732 this.$viewChangeListeners = []; 16733 this.$pristine = true; 16734 this.$dirty = false; 16735 this.$valid = true; 16736 this.$invalid = false; 16737 this.$name = $attr.name; 16738 16739 var ngModelGet = $parse($attr.ngModel), 16740 ngModelSet = ngModelGet.assign; 16741 16742 if (!ngModelSet) { 16743 throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 16744 $attr.ngModel, startingTag($element)); 16745 } 16746 16747 /** 16748 * @ngdoc function 16749 * @name ng.directive:ngModel.NgModelController#$render 16750 * @methodOf ng.directive:ngModel.NgModelController 16751 * 16752 * @description 16753 * Called when the view needs to be updated. It is expected that the user of the ng-model 16754 * directive will implement this method. 16755 */ 16756 this.$render = noop; 16757 16758 /** 16759 * @ngdoc function 16760 * @name { ng.directive:ngModel.NgModelController#$isEmpty 16761 * @methodOf ng.directive:ngModel.NgModelController 16762 * 16763 * @description 16764 * This is called when we need to determine if the value of the input is empty. 16765 * 16766 * For instance, the required directive does this to work out if the input has data or not. 16767 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 16768 * 16769 * You can override this for input directives whose concept of being empty is different to the 16770 * default. The `checkboxInputType` directive does this because in
16770its case a value of `false` 16771 * implies empty. 16772 * 16773 * @param {*} value Reference to check. 16774 * @returns {boolean} True if `value` is empty. 16775 */ 16776 this.$isEmpty = function(value) { 16777 return isUndefined(value) || value === '' || value === null || value !== value; 16778 }; 16779 16780 var parentForm = $element.inheritedData('$formController') || nullFormCtrl, 16781 invalidCount = 0, // used to easily determine if we are valid 16782 $error = this.$error = {}; // keep invalid keys here 16783 16784 16785 // Setup initial state of the control 16786 $element.addClass(PRISTINE_CLASS); 16787 toggleValidCss(true); 16788 16789 // convenience method for easy toggling of classes 16790 function toggleValidCss(isValid, validationErrorKey) { 16791 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 16792 $element. 16793 removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). 16794 addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 16795 } 16796 16797 /** 16798 * @ngdoc function 16799 * @name ng.directive:ngModel.NgModelController#$setValidity 16800 * @methodOf ng.directive:ngModel.NgModelController 16801 * 16802 * @description 16803 * Change the validity state, and notifies the form when the control changes validity. (i.e. it 16804 * does not notify form if given validator is already marked as invalid). 16805 * 16806 * This method should be called by validators - i.e. the parser or formatter functions. 16807 * 16808 * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign 16809 * to `$error[validationErrorKey]=isValid` so that it is available for data-binding. 16810 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 16811 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 16812 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 16813 * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). 16814 */ 16815 this.$setValidity = function(validationErrorKey, isValid) { 16816 // Purposeful use of ! here to cast isValid to boolean in case it is undefined 16817 // jshint -W018 16818 if ($error[validationErrorKey] === !isValid) return; 16819 // jshint +W018 16820 16821 if (isValid) { 16822 if ($error[validationErrorKey]) invalidCount--; 16823 if (!invalidCount) { 16824 toggleValidCss(true); 16825 this.$valid = true; 16826 this.$invalid = false; 16827 } 16828 } else { 16829 toggleValidCss(false); 16830 this.$invalid = true; 16831 this.$valid = false; 16832 invalidCount++; 16833 } 16834 16835 $error[validationErrorKey] = !isValid; 16836 toggleValidCss(isValid, validationErrorKey); 16837 16838 parentForm.$setValidity(validationErrorKey, isValid, this); 16839 }; 16840 16841 /** 16842 * @ngdoc function 16843 * @name ng.directive:ngModel.NgModelController#$setPristine 16844 * @methodOf ng.directive:ngModel.NgModelController 16845 * 16846 * @description 16847 * Sets the control to its pristine state. 16848 * 16849 * This method can be called to remove the 'ng-dirty' class and set the control to its pristine 16850 * state (ng-pristine class). 16851 */ 16852 this.$setPristine = function () { 16853 this.$dirty = false; 16854 this.$pristine = true; 16855 $element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); 16856 }; 16857 16858 /** 16859 * @ngdoc function 16860 * @name ng.directive:ngModel.NgModelController#$setViewValue 16861 * @methodOf ng.directive:ngModel.NgModelController 16862 * 16863 * @description 16864 * Update the view value. 16865 * 16866 * This method should be called when the view value changes, typically from within a DOM event handler. 16867 * For example {@link ng.directive:input input} and 16868 * {@link ng.directive:select select} directives call it. 16869 * 16870 * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, 16871 * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to 16872 * `$modelValue` and the **expression** specified in the `ng-model` attribute. 16873 * 16874 * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. 16875 * 16876 * Note that calling this function does not trigger a `$digest`. 16877 * 16878 * @param {string} value Value from the view. 16879 */ 16880 this.$setViewValue = function(value) { 16881 this.$viewValue = value; 16882 16883 // change to dirty 16884 if (this.$pristine) { 16885 this.$dirty = true; 16886 this.$pristine = false; 16887 $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); 16888 parentForm.$setDirty(); 16889 } 16890
16891 forEach(this.$parsers, function(fn) { 16892 value = fn(value); 16893 }); 16894 16895 if (this.$modelValue !== value) { 16896 this.$modelValue = value; 16897 ngModelSet($scope, value); 16898 forEach(this.$viewChangeListeners, function(listener) { 16899 try { 16900 listener(); 16901 } catch(e) { 16902 $exceptionHandler(e); 16903 } 16904 }); 16905 } 16906 }; 16907 16908 // model -> value 16909 var ctrl = this; 16910 16911 $scope.$watch(function ngModelWatch() { 16912 var value = ngModelGet($scope); 16913 16914 // if scope model value and ngModel value are out of sync 16915 if (ctrl.$modelValue !== value) { 16916 16917 var formatters = ctrl.$formatters, 16918 idx = formatters.length; 16919 16920 ctrl.$modelValue = value; 16921 while(idx--) { 16922 value = formatters[idx](value); 16923 } 16924 16925 if (ctrl.$viewValue !== value) { 16926 ctrl.$viewValue = value; 16927 ctrl.$render(); 16928 } 16929 } 16930 16931 return value; 16932 }); 16933}]; 16934 16935 16936/** 16937 * @ngdoc directive 16938 * @name ng.directive:ngModel 16939 * 16940 * @element input 16941 * 16942 * @description 16943 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 16944 * property on the scope using {@link ng.directive:ngModel.NgModelController NgModelController}, 16945 * which is created and exposed by this directive. 16946 * 16947 * `ngModel` is responsible for: 16948 * 16949 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 16950 * require. 16951 * - Providing validation behavior (i.e. required, number, email, url). 16952 * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). 16953 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`). 16954 * - Registering the control with its parent {@link ng.directive:form form}. 16955 * 16956 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 16957 * current scope. If the property doesn't already exist on this scope, it will be created 16958 * implicitly and added to the scope. 16959 * 16960 * For best practices on using `ngModel`, see: 16961 * 16962 * - {@link https://github.com/angular/angular.js/wiki/Understanding-Scopes} 16963 * 16964 * For basic examples, how to use `ngModel`, see: 16965 * 16966 * - {@link ng.directive:input input} 16967 * - {@link ng.directive:input.text text} 16968 * - {@link ng.directive:input.checkbox checkbox} 16969 * - {@link ng.directive:input.radio radio} 16970 * - {@link ng.directive:input.number number} 16971 * - {@link ng.directive:input.email email} 16972 * - {@link ng.directive:input.url url} 16973 * - {@link ng.directive:select select} 16974 * - {@link ng.directive:textarea textarea} 16975 * 16976 */ 16977var ngModelDirective = function() { 16978 return { 16979 require: ['ngModel', '^?form'], 16980 controller: NgModelController, 16981 link: function(scope, element, attr, ctrls) { 16982 // notify others, especially parent forms 16983 16984 var modelCtrl = ctrls[0], 16985 formCtrl = ctrls[1] || nullFormCtrl; 16986 16987 formCtrl.$addControl(modelCtrl); 16988 16989 scope.$on('$destroy', function() { 16990 formCtrl.$removeControl(modelCtrl); 16991 }); 16992 } 16993 }; 16994}; 16995 16996 16997/** 16998 * @ngdoc directive 16999 * @name ng.directive:ngChange 17000 * 17001 * @description 17002 * Evaluate the given expression when the user changes the input. 17003 * The expression is evaluated immediately, unlike the JavaScript onchange event 17004 * which only triggers at the end of a change (usually, when the user leaves the 17005 * form element or presses the return key). 17006 * The expression is not evaluated when the value change is coming from the model. 17007 * 17008 * Note, this directive requires `ngModel` to be present. 17009 * 17010 * @element input 17011 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 17012 * in input value. 17013 * 17014 * @example 17015 * <doc:example> 17016 * <doc:source> 17017 * <script> 17018 * function Controller($scope) { 17019 * $scope.counter = 0; 17020 * $scope.change = function() { 17021 * $scope.counter++; 17022 * }; 17023 * } 17024 * </script> 17025 * <div ng-controller="Controller"> 17026 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 17027 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 17028 * <label for="ng-change-example2">Confirmed</label><br /> 17029 * <tt>debug = {{confirmed}}</tt><br/> 17030 * <tt>counter = {{counter}}</tt><br/>
17031 * </div> 17032 * </doc:source> 17033 * <doc:protractor> 17034 * var counter = element(by.binding('counter')); 17035 * var debug = element(by.binding('confirmed')); 17036 * 17037 * it('should evaluate the expression if changing from view', function() { 17038 * expect(counter.getText()).toContain('0'); 17039 * 17040 * element(by.id('ng-change-example1')).click(); 17041 * 17042 * expect(counter.getText()).toContain('1'); 17043 * expect(debug.getText()).toContain('true'); 17044 * }); 17045 * 17046 * it('should not evaluate the expression if changing from model', function() { 17047 * element(by.id('ng-change-example2')).click(); 17048 17049 * expect(counter.getText()).toContain('0'); 17050 * expect(debug.getText()).toContain('true'); 17051 * }); 17052 * </doc:protractor> 17053 * </doc:example> 17054 */ 17055var ngChangeDirective = valueFn({ 17056 require: 'ngModel', 17057 link: function(scope, element, attr, ctrl) { 17058 ctrl.$viewChangeListeners.push(function() { 17059 scope.$eval(attr.ngChange); 17060 }); 17061 } 17062}); 17063 17064 17065var requiredDirective = function() { 17066 return { 17067 require: '?ngModel', 17068 link: function(scope, elm, attr, ctrl) { 17069 if (!ctrl) return; 17070 attr.required = true; // force truthy in case we are on non input element 17071 17072 var validator = function(value) { 17073 if (attr.required && ctrl.$isEmpty(value)) { 17074 ctrl.$setValidity('required', false); 17075 return; 17076 } else { 17077 ctrl.$setValidity('required', true); 17078 return value; 17079 } 17080 }; 17081 17082 ctrl.$formatters.push(validator); 17083 ctrl.$parsers.unshift(validator); 17084 17085 attr.$observe('required', function() { 17086 validator(ctrl.$viewValue); 17087 }); 17088 } 17089 }; 17090}; 17091 17092 17093/** 17094 * @ngdoc directive 17095 * @name ng.directive:ngList 17096 * 17097 * @description 17098 * Text input that converts between a delimited string and an array of strings. The delimiter 17099 * can be a fixed string (by default a comma) or a regular expression. 17100 * 17101 * @element input 17102 * @param {string=} ngList optional delimiter that should be used to split the value. If 17103 * specified in form `/something/` then the value will be converted into a regular expression. 17104 * 17105 * @example 17106 <doc:example> 17107 <doc:source> 17108 <script> 17109 function Ctrl($scope) { 17110 $scope.names = ['igor', 'misko', 'vojta']; 17111 } 17112 </script> 17113 <form name="myForm" ng-controller="Ctrl"> 17114 List: <input name="namesInput" ng-model="names" ng-list required> 17115 <span class="error" ng-show="myForm.namesInput.$error.required"> 17116 Required!</span> 17117 <br> 17118 <tt>names = {{names}}</tt><br/> 17119 <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 17120 <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 17121 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 17122 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 17123 </form> 17124 </doc:source> 17125 <doc:protractor> 17126 var listInput = element(by.model('names')); 17127 var names = element(by.binding('{{names}}')); 17128 var valid = element(by.binding('myForm.namesInput.$valid')); 17129 var error = element(by.css('span.error')); 17130 17131 it('should initialize to model', function() { 17132 expect(names.getText()).toContain('["igor","misko","vojta"]'); 17133 expect(valid.getText()).toContain('true'); 17134 expect(error.getCssValue('display')).toBe('none'); 17135 }); 17136 17137 it('should be invalid if empty', function() { 17138 listInput.clear(); 17139 listInput.sendKeys(''); 17140 17141 expect(names.getText()).toContain(''); 17142 expect(valid.getText()).toContain('false'); 17143 expect(error.getCssValue('display')).not.toBe('none'); }); 17144 </doc:protractor> 17145 </doc:example> 17146 */ 17147var ngListDirective = function() { 17148 return { 17149 require: 'ngModel', 17150 link: function(scope, element, attr, ctrl) { 17151 var match = /\/(.*)\//.exec(attr.ngList), 17152 separator = match && new RegExp(match[1]) || attr.ngList || ','; 17153 17154 var parse = function(viewValue) {
17155 // If the viewValue is invalid (say required but empty) it will be `undefined` 17156 if (isUndefined(viewValue)) return; 17157 17158 var list = []; 17159 17160 if (viewValue) { 17161 forEach(viewValue.split(separator), function(value) { 17162 if (value) list.push(trim(value)); 17163 }); 17164 } 17165 17166 return list; 17167 }; 17168 17169 ctrl.$parsers.push(parse); 17170 ctrl.$formatters.push(function(value) { 17171 if (isArray(value)) { 17172 return value.join(', '); 17173 } 17174 17175 return undefined; 17176 }); 17177 17178 // Override the standard $isEmpty because an empty array means the input is empty. 17179 ctrl.$isEmpty = function(value) { 17180 return !value || !value.length; 17181 }; 17182 } 17183 }; 17184}; 17185 17186 17187var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 17188/** 17189 * @ngdoc directive 17190 * @name ng.directive:ngValue 17191 * 17192 * @description 17193 * Binds the given expression to the value of `input[select]` or `input[radio]`, so 17194 * that when the element is selected, the `ngModel` of that element is set to the 17195 * bound value. 17196 * 17197 * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as 17198 * shown below. 17199 * 17200 * @element input 17201 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 17202 * of the `input` element 17203 * 17204 * @example 17205 <doc:example> 17206 <doc:source> 17207 <script> 17208 function Ctrl($scope) { 17209 $scope.names = ['pizza', 'unicorns', 'robots']; 17210 $scope.my = { favorite: 'unicorns' }; 17211 } 17212 </script> 17213 <form ng-controller="Ctrl"> 17214 <h2>Which is your favorite?</h2> 17215 <label ng-repeat="name in names" for="{{name}}"> 17216 {{name}} 17217 <input type="radio" 17218 ng-model="my.favorite" 17219 ng-value="name" 17220 id="{{name}}" 17221 name="favorite"> 17222 </label> 17223 <div>You chose {{my.favorite}}</div> 17224 </form> 17225 </doc:source> 17226 <doc:protractor> 17227 var favorite = element(by.binding('my.favorite')); 17228 17229 it('should initialize to model', function() { 17230 expect(favorite.getText()).toContain('unicorns'); 17231 }); 17232 it('should bind the values to the inputs', function() { 17233 element.all(by.model('my.favorite')).get(0).click(); 17234 expect(favorite.getText()).toContain('pizza'); 17235 }); 17236 </doc:protractor> 17237 </doc:example> 17238 */ 17239var ngValueDirective = function() { 17240 return { 17241 priority: 100, 17242 compile: function(tpl, tplAttr) { 17243 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 17244 return function ngValueConstantLink(scope, elm, attr) { 17245 attr.$set('value', scope.$eval(attr.ngValue)); 17246 }; 17247 } else { 17248 return function ngValueLink(scope, elm, attr) { 17249 scope.$watch(attr.ngValue, function valueWatchAction(value) { 17250 attr.$set('value', value); 17251 }); 17252 }; 17253 } 17254 } 17255 }; 17256}; 17257 17258/** 17259 * @ngdoc directive 17260 * @name ng.directive:ngBind 17261 * @restrict AC 17262 * 17263 * @description 17264 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 17265 * with the value of a given expression, and to update the text content when the value of that 17266 * expression changes. 17267 * 17268 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 17269 * `{{ expression }}` which is similar but less verbose. 17270 * 17271 * It is preferrable to use `ngBind` instead of `{{ expression }}` when a template is momentarily 17272 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 17273 * element attribute, it makes the bindings invisible to the user while the page is loading. 17274 * 17275 * An alternative solution to this problem would be using the 17276 * {@link ng.directive:ngCloak ngCloak} directive. 17277 * 17278 * 17279 * @element ANY 17280 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 17281 * 17282 * @example 17283 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 17284 <doc:example> 17285 <doc:source> 17286 <script> 17287 function Ctrl($scope) { 17288 $scope.name = 'Whirled'; 17289 } 17290 </script> 17291 <div ng-controller="Ctrl"> 17292 Enter name: <input type="text" ng-model="name"><br> 17293 Hello <span ng-bind="name"></span>! 17294 </div> 17295 </doc:source> 17296 <doc:protractor> 17297 it('should check ng-bind', function() { 17298 var exampleContainer = $('.doc-example-live'); 17299 var nameInput = element(by.model('name')); 17300 17301 expect(exampleContainer.findElement(by.binding('name')).getText()).toBe('Whirled'); 17302 nameInput.clear(); 17303 nameInput.sendKeys('world'); 17304 expect(exampleContainer.findElement(by.binding('name')).getText()).toBe('world'); 17305 }); 17306 </doc:protractor> 17307 </doc:example> 17308 */ 17309var ngBindDirective = ngDirective(function(scope, element, attr) { 17310 element.addClass('ng-binding').data('$binding', attr.ngBind); 17311 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 17312 // We are purposefully using == here rather than === because we want to 17313 // catch when value is "null or undefined" 17314 // jshint -W041 17315 element.text(value == undefined ? '' : value); 17316 }); 17317}); 17318 17319 17320/** 17321 * @ngdoc directive 17322 * @name ng.directive:ngBindTemplate 17323 * 17324 * @description 17325 * The `ngBindTemplate` directive specifies that the element 17326 * text content should be replaced with the interpolation of the template 17327 * in the `ngBindTemplate` attribute.
17328 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 17329 * expressions. This directive is needed since some HTML elements 17330 * (such as TITLE and OPTION) cannot contain SPAN elements. 17331 * 17332 * @element ANY 17333 * @param {string} ngBindTemplate template of form 17334 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 17335 * 17336 * @example 17337 * Try it here: enter text in text box and watch the greeting change. 17338 <doc:example> 17339 <doc:source> 17340 <script> 17341 function Ctrl($scope) { 17342 $scope.salutation = 'Hello'; 17343 $scope.name = 'World'; 17344 } 17345 </script> 17346 <div ng-controller="Ctrl"> 17347 Salutation: <input type="text" ng-model="salutation"><br> 17348 Name: <input type="text" ng-model="name"><br> 17349 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 17350 </div> 17351 </doc:source> 17352 <doc:protractor> 17353 it('should check ng-bind', function() { 17354 var salutationElem = element(by.binding('salutation')); 17355 var salutationInput = element(by.model('salutation')); 17356 var nameInput = element(by.model('name')); 17357 17358 expect(salutationElem.getText()).toBe('Hello World!'); 17359 17360 salutationInput.clear(); 17361 salutationInput.sendKeys('Greetings'); 17362 nameInput.clear(); 17363 nameInput.sendKeys('user'); 17364 17365 expect(salutationElem.getText()).toBe('Greetings user!'); 17366 }); 17367 </doc:protractor> 17368 </doc:example> 17369 */ 17370var ngBindTemplateDirective = ['$interpolate', function($interpolate) { 17371 return function(scope, element, attr) { 17372 // TODO: move this to scenario runner 17373 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 17374 element.addClass('ng-binding').data('$binding', interpolateFn); 17375 attr.$observe('ngBindTemplate', function(value) { 17376 element.text(value); 17377 }); 17378 }; 17379}]; 17380 17381 17382/** 17383 * @ngdoc directive 17384 * @name ng.directive:ngBindHtml 17385 * 17386 * @description 17387 * Creates a binding that will innerHTML the result of evaluating the `expression` into the current 17388 * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link 17389 * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` 17390 * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in 17391 * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to 17392 * an explicitly trusted value via {@link ng.$sce#methods_trustAsHtml $sce.trustAsHtml}. See the example 17393 * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. 17394 * 17395 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 17396 * will have an exception (instead of an exploit.) 17397 * 17398 * @element ANY 17399 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 17400 * 17401 * @example 17402 Try it here: enter text in text box and watch the greeting change. 17403 17404 <example module="ngBindHtmlExample" deps="angular-sanitize.js"> 17405 <file name="index.html"> 17406 <div ng-controller="ngBindHtmlCtrl"> 17407 <p ng-bind-html="myHTML"></p> 17408 </div> 17409 </file> 17410 17411 <file name="script.js"> 17412 angular.module('ngBindHtmlExample', ['ngSanitize']) 17413 17414 .controller('ngBindHtmlCtrl', ['$scope', function ngBindHtmlCtrl($scope) { 17415 $scope.myHTML = 17416 'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>'; 17417 }]); 17418 </file> 17419 17420 <file name="protractorTest.js"> 17421 it('should check ng-bind-html', function() { 17422 expect(element(by.binding('myHTML')).getText()).toBe( 17423 'I am an HTMLstring with links! and other stuff'); 17424 }); 17425 </file> 17426 </example> 17427 */ 17428var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { 17429 return function(scope, element, attr) { 17430 element.addClass('ng-binding').data('$binding', attr.ngBindHtml); 17431 17432 var parsed = $parse(attr.ngBindHtml); 17433 function getStringValue() { return (parsed(scope) || '').toString(); } 17434 17435 scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { 17436 element.html($sce.getTrustedHtml(parsed(scope)) || ''); 17437 });
17438 }; 17439}]; 17440 17441function classDirective(name, selector) { 17442 name = 'ngClass' + name; 17443 return function() { 17444 return { 17445 restrict: 'AC', 17446 link: function(scope, element, attr) { 17447 var oldVal; 17448 17449 scope.$watch(attr[name], ngClassWatchAction, true); 17450 17451 attr.$observe('class', function(value) { 17452 ngClassWatchAction(scope.$eval(attr[name])); 17453 }); 17454 17455 17456 if (name !== 'ngClass') { 17457 scope.$watch('$index', function($index, old$index) { 17458 // jshint bitwise: false 17459 var mod = $index & 1; 17460 if (mod !== old$index & 1) { 17461 var classes = flattenClasses(scope.$eval(attr[name])); 17462 mod === selector ? 17463 attr.$addClass(classes) : 17464 attr.$removeClass(classes); 17465 } 17466 }); 17467 } 17468 17469 17470 function ngClassWatchAction(newVal) { 17471 if (selector === true || scope.$index % 2 === selector) { 17472 var newClasses = flattenClasses(newVal || ''); 17473 if(!oldVal) { 17474 attr.$addClass(newClasses); 17475 } else if(!equals(newVal,oldVal)) { 17476 attr.$updateClass(newClasses, flattenClasses(oldVal)); 17477 } 17478 } 17479 oldVal = copy(newVal); 17480 } 17481 17482 17483 function flattenClasses(classVal) { 17484 if(isArray(classVal)) { 17485 return classVal.join(' '); 17486 } else if (isObject(classVal)) { 17487 var classes = [], i = 0; 17488 forEach(classVal, function(v, k) { 17489 if (v) { 17490 classes.push(k); 17491 } 17492 }); 17493 return classes.join(' '); 17494 } 17495 17496 return classVal; 17497 } 17498 } 17499 }; 17500 }; 17501} 17502 17503/** 17504 * @ngdoc directive 17505 * @name ng.directive:ngClass 17506 * @restrict AC 17507 * 17508 * @description 17509 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 17510 * an expression that represents all classes to be added. 17511 * 17512 * The directive won't add duplicate classes if a particular class was already set. 17513 * 17514 * When the expression changes, the previously added classes are removed and only then the 17515 * new classes are added. 17516 * 17517 * @animations 17518 * add - happens just before the class is applied to the element 17519 * remove - happens just before the class is removed from the element 17520 * 17521 * @element ANY 17522 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 17523 * of the evaluation can be a string representing space delimited class 17524 * names, an array, or a map of class names to boolean values. In the case of a map, the 17525 * names of the properties whose values are truthy will be added as css classes to the 17526 * element. 17527 * 17528 * @example Example that demonstrates basic bindings via ngClass directive. 17529 <example> 17530 <file name="index.html"> 17531 <p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p> 17532 <input type="checkbox" ng-model="deleted"> deleted (apply "strike" class)<br> 17533 <input type="checkbox" ng-model="important"> important (apply "bold" class)<br> 17534 <input type="checkbox" ng-model="error"> error (apply "red" class) 17535 <hr> 17536 <p ng-class="style">Using String Syntax</p> 17537 <input type="text" ng-model="style" placeholder="Type: bold strike red"> 17538 <hr> 17539 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 17540 <input ng-model="style1" placeholder="Type: bold, strike or red"><br> 17541 <input ng-model="style2" placeholder="Type: bold, strike or red"><br> 17542 <input ng-model="style3" placeholder="Type: bold, strike or red"><br> 17543 </file> 17544 <file name="style.css"> 17545 .strike { 17546 text-decoration: line-through; 17547 } 17548 .bold { 17549 font-weight: bold; 17550 } 17551 .red { 17552 color: red; 17553 } 17554 </file> 17555 <file name="protractorTest.js"> 17556 var ps = element.all(by.css('.doc-example-live p')); 17557 17558 it('should let you toggle the class', function() { 17559 17560 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 17561 expect(ps.first().getAttribute('class')).not.toMatch(/red/); 17562
17563 element(by.model('important')).click(); 17564 expect(ps.first().getAttribute('class')).toMatch(/bold/); 17565 17566 element(by.model('error')).click(); 17567 expect(ps.first().getAttribute('class')).toMatch(/red/); 17568 }); 17569 17570 it('should let you toggle string example', function() { 17571 expect(ps.get(1).getAttribute('class')).toBe(''); 17572 element(by.model('style')).clear(); 17573 element(by.model('style')).sendKeys('red'); 17574 expect(ps.get(1).getAttribute('class')).toBe('red'); 17575 }); 17576 17577 it('array example should have 3 classes', function() { 17578 expect(ps.last().getAttribute('class')).toBe(''); 17579 element(by.model('style1')).sendKeys('bold'); 17580 element(by.model('style2')).sendKeys('strike'); 17581 element(by.model('style3')).sendKeys('red'); 17582 expect(ps.last().getAttribute('class')).toBe('bold strike red'); 17583 }); 17584 </file> 17585 </example> 17586 17587 ## Animations 17588 17589 The example below demonstrates how to perform animations using ngClass. 17590 17591 <example animations="true"> 17592 <file name="index.html"> 17593 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 17594 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 17595 <br> 17596 <span class="base-class" ng-class="myVar">Sample Text</span> 17597 </file> 17598 <file name="style.css"> 17599 .base-class { 17600 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 17601 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 17602 } 17603 17604 .base-class.my-class { 17605 color: red; 17606 font-size:3em; 17607 } 17608 </file> 17609 <file name="protractorTest.js"> 17610 it('should check ng-class', function() { 17611 expect(element(by.css('.base-class')).getAttribute('class')).not. 17612 toMatch(/my-class/); 17613 17614 element(by.id('setbtn')).click(); 17615 17616 expect(element(by.css('.base-class')).getAttribute('class')). 17617 toMatch(/my-class/); 17618 17619 element(by.id('clearbtn')).click(); 17620 17621 expect(element(by.css('.base-class')).getAttribute('class')).not. 17622 toMatch(/my-class/); 17623 }); 17624 </file> 17625 </example> 17626 17627 17628 ## ngClass and pre-existing CSS3 Transitions/Animations 17629 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 17630 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 17631 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 17632 to view the step by step details of {@link ngAnimate.$animate#methods_addclass $animate.addClass} and 17633 {@link ngAnimate.$animate#methods_removeclass $animate.removeClass}. 17634 */ 17635var ngClassDirective = classDirective('', true); 17636 17637/** 17638 * @ngdoc directive 17639 * @name ng.directive:ngClassOdd 17640 * @restrict AC 17641 * 17642 * @description 17643 * The `ngClassOdd` and `ngClassEven` directives work exactly as 17644 * {@link ng.directive:ngClass ngClass}, except they work in 17645 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 17646 * 17647 * This directive can be applied only within the scope of an 17648 * {@link ng.directive:ngRepeat ngRepeat}. 17649 * 17650 * @element ANY 17651 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 17652 * of the evaluation can be a string representing space delimited class names or an array. 17653 * 17654 * @example 17655 <example> 17656 <file name="index.html"> 17657 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 17658 <li ng-repeat="name in names"> 17659 <span ng-class-odd="'odd'" ng-class-even="'even'"> 17660 {{name}} 17661 </span> 17662 </li> 17663 </ol> 17664 </file> 17665 <file name="style.css"> 17666 .odd { 17667 color: red; 17668 } 17669 .even { 17670 color: blue; 17671 } 17672 </file> 17673 <file name="protractorTest.js"> 17674 it('should check ng-class-odd and ng-class-even', function() { 17675 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 17676 toMatch(/odd/); 17677 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 17678 toMatch(/even/); 17679 }); 17680 </file> 17681 </example> 17682 */ 17683var ngClassOddDirective = classDirective('Odd', 0); 17684 17685/** 17686 * @ngdoc directive 17687 * @name ng.directive:ngClassEven 17688 * @restrict AC 17689 * 17690 * @description 17691 * The `ngClassOdd` and `ngClassEven` directives work exactly as 17692 * {@link ng.directive:ngClass ngClass}, except they work in 17693 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 17694 * 17695 * This directive can be applied only within the scope of an 17696 * {@link ng.directive:ngRepeat ngRepeat}. 17697 * 17698 * @element ANY 17699 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 17700 * result of the evaluation can be a string representing space delimited class names or an array. 17701 * 17702 * @example 17703 <example> 17704 <file name="index.html"> 17705 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 17706 <li ng-repeat="name in names">
17707 <span ng-class-odd="'odd'" ng-class-even="'even'"> 17708 {{name}} 17709 </span> 17710 </li> 17711 </ol> 17712 </file> 17713 <file name="style.css"> 17714 .odd { 17715 color: red; 17716 } 17717 .even { 17718 color: blue; 17719 } 17720 </file> 17721 <file name="protractorTest.js"> 17722 it('should check ng-class-odd and ng-class-even', function() { 17723 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 17724 toMatch(/odd/); 17725 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 17726 toMatch(/even/); 17727 }); 17728 </file> 17729 </example> 17730 */ 17731var ngClassEvenDirective = classDirective('Even', 1); 17732 17733/** 17734 * @ngdoc directive 17735 * @name ng.directive:ngCloak 17736 * @restrict AC 17737 * 17738 * @description 17739 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 17740 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 17741 * directive to avoid the undesirable flicker effect caused by the html template display. 17742 * 17743 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 17744 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 17745 * of the browser view. 17746 * 17747 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 17748 * `angular.min.js`. 17749 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 17750 * 17751 * <pre> 17752 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 17753 * display: none !important; 17754 * } 17755 * </pre> 17756 * 17757 * When this css rule is loaded by the browser, all html elements (including their children) that 17758 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 17759 * during the compilation of the template it deletes the `ngCloak` element attribute, making 17760 * the compiled element visible. 17761 * 17762 * For the best result, the `angular.js` script must be loaded in the head section of the html 17763 * document; alternatively, the css rule above must be included in the external stylesheet of the 17764 * application. 17765 * 17766 * Legacy browsers, like IE7, do not provide attribute selector support (a
17766dded in CSS 2.1) so they 17767 * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css 17768 * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. 17769 * 17770 * @element ANY 17771 * 17772 * @example 17773 <doc:example> 17774 <doc:source> 17775 <div id="template1" ng-cloak>{{ 'hello' }}</div> 17776 <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div> 17777 </doc:source> 17778 <doc:protractor> 17779 it('should remove the template directive and css class', function() { 17780 expect($('.doc-example-live #template1').getAttribute('ng-cloak')). 17781 toBeNull(); 17782 expect($('.doc-example-live #template2').getAttribute('ng-cloak')). 17783 toBeNull(); 17784 }); 17785 </doc:protractor> 17786 </doc:example> 17787 * 17788 */ 17789var ngCloakDirective = ngDirective({ 17790 compile: function(element, attr) { 17791 attr.$set('ngCloak', undefined); 17792 element.removeClass('ng-cloak'); 17793 } 17794}); 17795 17796/** 17797 * @ngdoc directive 17798 * @name ng.directive:ngController 17799 * 17800 * @description 17801 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 17802 * supports the principles behind the Model-View-Controller design pattern. 17803 * 17804 * MVC components in angular: 17805 * 17806 * * Model â The Model is scope properties; scopes are attached to the DOM where scope properties 17807 * are accessed through bindings. 17808 * * View â The template (HTML with data bindings) that is rendered into the View. 17809 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 17810 * logic behind the application to decorate the scope with functions and values 17811 * 17812 * Note that you can also attach controllers to the DOM by declaring it in a route definition 17813 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 17814 * again using `ng-controller` in the template itself. This will cause the controller to be attached 17815 * and executed twice. 17816 * 17817 * @element ANY 17818 * @scope 17819 * @param {expression} ngController Name of a globally accessible constructor function or an 17820 * {@link guide/expression expression} that on the current scope evaluates to a 17821 * constructor function. The controller instance can be published into a scope property 17822 * by specifying `as propertyName`. 17823 * 17824 * @example 17825 * Here is a simple form for editing user contact information. Adding, removing, clearing, and 17826 * greeting are methods declared on the controller (see source tab). These methods can 17827 * easily be called from the angular markup. Notice that the scope becomes the `this` for the 17828 * controller's instance. This allows for easy access to the view data from the controller. Also 17829 * notice that any changes to the data are automatically reflected in the View without the need 17830 * for a manual update. The example is shown in two different declaration styles you may use 17831 * according to preference. 17832 <doc:example> 17833 <doc:source> 17834 <script> 17835 function SettingsController1() { 17836 this.name = "John Smith"; 17837 this.contacts = [ 17838 {type: 'phone', value: '408 555 1212'}, 17839 {type: 'email', value: '[email protected]'} ]; 17840 }; 17841 17842 SettingsController1.prototype.greet = function() { 17843 alert(this.name); 17844 }; 17845 17846 SettingsController1.prototype.addContact = function() { 17847 this.contacts.push({type: 'email', value: '[email protected]'}); 17848 }; 17849 17850 SettingsController1.prototype.removeContact = function(contactToRemove) { 17851 var index = this.contacts.indexOf(contactToRemove); 17852 this.contacts.splice(index, 1); 17853 }; 17854 17855 SettingsController1.prototype.clearContact = function(contact) { 17856 contact.type = 'phone'; 17857 contact.value = ''; 17858 }; 17859 </script> 17860 <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 17861 Name: <input type="text" ng-model="settings.name"/> 17862 [ <a href="" ng-click="settings.greet()">greet</a> ]<br/> 17863 Contact: 17864 <ul> 17865 <li ng-repeat="contact in settings.contacts"> 17866 <select ng-model="contact.type"> 17867 <option>phone</option> 17868 <option>email</option> 17869 </select> 17870 <input type="text" ng-model="contact.value"/> 17871 [ <a href="" ng-click="settings.clearContact(contact)">clear</a> 17872 | <a href="" ng-click="settings.removeContact(contact)">X</a> ] 17873 </li> 17874 <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li> 17875 </ul> 17876 </div> 17877 </doc:source> 17878 <doc:protractor> 17879 it('should check controller as', function() { 17880 var container = element(by.id('ctrl-as-exmpl')); 17881 17882 expect(container.findElement(by.model('settings.name')) 17883 .getAttribute('value')).toBe('John Smith'); 17884 17885 var firstRepeat = 17886 container.findElement(by.repeater('contact in settings.contacts').row(0)); 17887 var secondRepeat = 17888 container.findElement(by.repeater('contact in settings.contacts').row(1)); 17889 17890 expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 17891 .toBe('408 555 1212'); 17892 expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) 17893 .toBe('[email protected]'); 17894 17895 firstRepeat.findElement(by.linkText('clear')).click() 17896 17897 expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 17898 .toBe(''); 17899 17900 container.findElement(by.linkText('add')).click(); 17901 17902 expect(container.findElement(by.repeater('contact in settings.contacts').row(2)) 17903 .findElement(by.model('contact.value')) 17904 .getAttribute('value')) 17905 .toBe('[email protected]'); 17906 }); 17907 </doc:protractor> 17908 </doc:example> 17909 <doc:example> 17910 <doc:source> 17911 <script> 17912 function SettingsController2($scope) { 17913 $scope.name = "John Smith"; 17914 $scope.contacts = [ 17915 {type:'phone', value:'408 555 1212'}, 17916 {type:'email', value:'[email protected]'} ]; 17917 17918 $scope.greet = function() { 17919 alert(this.name); 17920 }; 17921 17922 $scope.addContact = function() { 17923 this.contacts.push({type:'email', value:'[email protected]'}); 17924 }; 17925 17926 $scope.removeContact = function(contactToRemove) { 17927 var index = this.contacts.indexOf(contactToRemove); 17928 this.contacts.splice(index, 1); 17929 }; 17930 17931 $scope.clearContact = function(contact) { 17932 contact.type = 'phone'; 17933 contact.value = ''; 17934 }; 17935 } 17936 </script> 17937 <div id="ctrl-exmpl" ng-controller="SettingsController2"> 17938 Name: <input type="text" ng-model="name"/> 17939 [ <a href="" ng-click="greet()">greet</a> ]<br/> 17940 Contact: 17941 <ul> 17942 <li ng-repeat="contact in contacts"> 17943 <select ng-model="contact.type"> 17944 <option>phone</option> 17945 <option>email</option> 17946 </select> 17947 <input type="text" ng-model="contact.value"/> 17948 [ <a href="" ng-click="clearContact(contact)">clear</a> 17949 | <a href="" ng-click="removeContact(contact)">X</a> ] 17950 </li> 17951 <li>[ <a href="" ng-click="addContact()">add</a> ]</li> 17952 </ul> 17953 </div> 17954 </doc:source> 17955 <doc:protractor> 17956 it('should check controller', function() { 17957 var container = element(by.id('ctrl-exmpl')); 17958 17959 expect(container.findElement(by.model('name')) 17960 .getAttribute('value')).toBe('John Smith'); 17961 17962 var firstRepeat = 17963 container.findElement(by.repeater('contact in contacts').row(0)); 17964 var secondRepeat = 17965 container.findElement(by.repeater('contact in contacts').row(1)); 17966 17967 expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 17968 .toBe('408 555 1212'); 17969 expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) 17970 .toBe('[email protected]'); 17971 17972 firstRepeat.findElement(by.linkText('clear')).click() 17973 17974 expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 17975 .toBe(''); 17976 17977 container.findElement(by.linkText('add')).click(); 17978 17979 expect(container.findElement(by.repeater('contact in contacts').row(2)) 17980 .findElement(by.model('contact.value')) 17981 .getAttribute('value')) 17982 .toBe('[email protected]'); 17983 }); 17984 </doc:protractor> 17985 </doc:example> 17986 17987 */ 17988var ngControllerDirective = [function() { 17989 return { 17990 scope: true, 17991 controller: '@', 17992 priority: 500 17993 }; 17994}]; 17995 17996/** 17997 * @ngdoc directive 17998 * @name ng.directive:ngCsp 17999 * 18000 * @element html 18001 * @description 18002 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. 18003 * 18004 * This is necessary when developing things like Google Chrome Extensions. 18005 * 18006 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). 18007 * For us to be compatible, we just need to implement the "getterFn" in $parse without violating 18008 * any of these restrictions. 18009 * 18010 * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` 18011 * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will
18012 * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will 18013 * be raised. 18014 * 18015 * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically 18016 * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). 18017 * To make those directives work in CSP mode, include the `angular-csp.css` manually. 18018 * 18019 * In order to use this feature put the `ngCsp` directive on the root element of the application. 18020 * 18021 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 18022 * 18023 * @example 18024 * This example shows how to apply the `ngCsp` directive to the `html` tag. 18025 <pre> 18026 <!doctype html> 18027 <html ng-app ng-csp> 18028 ... 18029 ... 18030 </html> 18031 </pre> 18032 */ 18033 18034// ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap 18035// the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute 18036// anywhere in the current doc 18037 18038/** 18039 * @ngdoc directive 18040 * @name ng.directive:ngClick 18041 * 18042 * @description 18043 * The ngClick directive allows you to specify custom behavior when 18044 * an element is clicked. 18045 * 18046 * @element ANY 18047 * @priority 0 18048 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 18049 * click. (Event object is available as `$event`) 18050 * 18051 * @example 18052 <doc:example> 18053 <doc:source> 18054 <button ng-click="count = count + 1" ng-init="count=0"> 18055 Increment 18056 </button> 18057 count: {{count}} 18058 </doc:source> 18059 <doc:protractor> 18060 it('should check ng-click', function() { 18061 expect(element(by.binding('count')).getText()).toMatch('0'); 18062 element(by.css('.doc-example-live button')).click(); 18063 expect(element(by.binding('count')).getText()).toMatch('1'); 18064 }); 18065 </doc:protractor> 18066 </doc:example> 18067 */ 18068/* 18069 * A directive that allows creation of custom onclick handlers that are defined as angular 18070 * expressions and are compiled and executed within the current scope. 18071 * 18072 * Events that are handled via these handler are always configured not to propagate further. 18073 */ 18074var ngEventDirectives = {}; 18075forEach( 18076 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 18077 function(name) { 18078 var directiveName = directiveNormalize('ng-' + name); 18079 ngEventDirectives[directiveName] = ['$parse', function($parse) { 18080 return { 18081 compile: function($element, attr) { 18082 var fn = $parse(attr[directiveName]); 18083 return function(scope, element, attr) { 18084 element.on(lowercase(name), function(event) { 18085 scope.$apply(function() { 18086 fn(scope, {$event:event}); 18087 }); 18088 }); 18089 }; 18090 } 18091 }; 18092 }]; 18093 } 18094); 18095 18096/** 18097 * @ngdoc directive 18098 * @name ng.directive:ngDblclick 18099 * 18100 * @description 18101 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 18102 * 18103 * @element ANY 18104 * @priority 0 18105 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 18106 * a dblclick. (The Event object is available as `$event`) 18107 * 18108 * @example 18109 <doc:example> 18110 <doc:source> 18111 <button ng-dblclick="count = count + 1" ng-init="count=0"> 18112 Increment (on double click) 18113 </button> 18114 count: {{count}} 18115 </doc:source> 18116 </doc:example> 18117 */ 18118 18119 18120/** 18121 * @ngdoc directive 18122 * @name ng.directive:ngMousedown 18123 * 18124 * @description 18125 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 18126 * 18127 * @element ANY 18128 * @priority 0 18129 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 18130 * mousedown. (Event object is available as `$event`) 18131 * 18132 * @example 18133 <doc:example> 18134 <doc:source> 18135 <button ng-mousedown="count = count + 1" ng-init="count=0"> 18136 Increment (on mouse down) 18137 </button> 18138 count: {{count}} 18139 </doc:source> 18140 </doc:example> 18141 */ 18142 18143 18144/** 18145 * @ngdoc directive 18146 * @name ng.directive:ngMouseup 18147 * 18148 * @description 18149 * Specify custom behavior on mouseup event. 18150 * 18151 * @element ANY 18152 * @priority 0 18153 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 18154 * mouseup. (Event object is available as `$event`) 18155 * 18156 * @example 18157 <doc:example> 18158 <doc:source> 18159 <button ng-mouseup="count = count + 1" ng-init="count=0"> 18160 Increment (on mouse up) 18161 </button> 18162 count: {{count}} 18163 </doc:source> 18164 </doc:example> 18165 */ 18166 18167/** 18168 * @ngdoc directive 18169 * @name ng.directive:ngMouseover 18170 * 18171 * @description 18172 * Specify custom behavior on mouseover event. 18173 * 18174 * @element ANY 18175 * @priority 0 18176 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 18177 * mouseover. (Event object is available as `$event`) 18178 * 18179 * @example 18180 <doc:example> 18181 <doc:source> 18182 <button ng-mouseover="count = count + 1" ng-init="count=0"> 18183 Increment (when mouse is over) 18184 </button> 18185 count: {{count}} 18186 </doc:source> 18187 </doc:example> 18188 */ 18189 18190 18191/** 18192 * @ngdoc directive 18193 * @name ng.directive:ngMouseenter 18194 * 18195 * @description 18196 * Specify custom behavior on mouseenter event. 18197 * 18198 * @element ANY 18199 * @priority 0 18200 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 18201 * mouseenter. (Event object is available as `$event`) 18202 * 18203 * @example 18204 <doc:example> 18205 <doc:source> 18206 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 18207 Increment (when mouse enters) 18208 </button> 18209 count: {{count}} 18210 </doc:source> 18211 </doc:example> 18212 */ 18213 18214 18215/** 18216 * @ngdoc directive 18217 * @name ng.directive:ngMouseleave 18218 * 18219 * @description 18220 * Specify custom behavior on mouseleave event. 18221 * 18222 * @element ANY 18223 * @priority 0 18224 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 18225 * mouseleave. (Event object is available as `$event`) 18226 * 18227 * @example 18228 <doc:example> 18229 <doc:source> 18230 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 18231 Increment (when mouse leaves) 18232 </button> 18233 count: {{count}} 18234 </doc:source> 18235 </doc:example> 18236 */ 18237 18238 18239/** 18240 * @ngdoc directive 18241 * @name ng.directive:ngMousemove 18242 * 18243 * @description 18244 * Specify custom behavior on mousemove event. 18245 * 18246 * @element ANY 18247 * @priority 0 18248 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 18249 * mousemove. (Event object is available as `$event`) 18250 * 18251 * @example 18252 <doc:example> 18253 <doc:source> 18254 <button ng-mousemove="count = count + 1" ng-init="count=0"> 18255 Increment (when mouse moves) 18256 </button> 18257 count: {{count}} 18258 </doc:source> 18259 </doc:example> 18260 */ 18261 18262 18263/** 18264 * @ngdoc directive 18265 * @name ng.directive:ngKeydown 18266 * 18267 * @description 18268 * Specify custom behavior on keydown event. 18269 * 18270 * @element ANY 18271 * @priority 0 18272 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 18273 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 18274 * 18275 * @example 18276 <doc:example> 18277 <doc:source> 18278 <input ng-keydown="count = count + 1" ng-init="count=0"> 18279 key down count: {{count}} 18280 </doc:source> 18281 </doc:example> 18282 */ 18283 18284 18285/** 18286 * @ngdoc directive 18287 * @name ng.directive:ngKeyup 18288 * 18289 * @description 18290 * Specify custom behavior on keyup event. 18291 * 18292 * @element ANY 18293 * @priority 0 18294 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 18295 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 18296 * 18297 * @example 18298 <doc:example> 18299 <doc:source> 18300 <input ng-keyup="count = count + 1" ng-init="count=0"> 18301 key up count: {{count}} 18302 </doc:source> 18303 </doc:example> 18304 */ 18305 18306 18307/** 18308 * @ngdoc directive 18309 * @name ng.directive:ngKeypress 18310 * 18311 * @description 18312 * Specify custom behavior on keypress event. 18313 * 18314 * @element ANY 18315 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 18316 * keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 18317 * 18318 * @example 18319 <doc:example> 18320 <doc:source> 18321 <input ng-keypress="count = count + 1" ng-init="count=0"> 18322 key press count: {{count}} 18323 </doc:source> 18324 </doc:example> 18325 */ 18326 18327 18328/** 18329 * @ngdoc directive 18330 * @name ng.directive:ngSubmit 18331 * 18332 * @description 18333 * Enables binding angular expressions to onsubmit events. 18334 * 18335 * Additionally it prevents the default action (which for form means
18335sending the request to the 18336 * server and reloading the current page), but only if the form does not contain `action`, 18337 * `data-action`, or `x-action` attributes. 18338 * 18339 * @element form 18340 * @priority 0 18341 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. (Event object is available as `$event`) 18342 * 18343 * @example 18344 <doc:example> 18345 <doc:source> 18346 <script> 18347 function Ctrl($scope) { 18348 $scope.list = []; 18349 $scope.text = 'hello'; 18350 $scope.submit = function() { 18351 if (this.text) { 18352 this.list.push(this.text); 18353 this.text = ''; 18354 } 18355 }; 18356 } 18357 </script> 18358 <form ng-submit="submit()" ng-controller="Ctrl"> 18359 Enter text and hit enter: 18360 <input type="text" ng-model="text" name="text" /> 18361 <input type="submit" id="submit" value="Submit" /> 18362 <pre>list={{list}}</pre> 18363 </form> 18364 </doc:source> 18365 <doc:protractor> 18366 it('should check ng-submit', function() { 18367 expect(element(by.binding('list')).getText()).toBe('list=[]'); 18368 element(by.css('.doc-example-live #submit')).click(); 18369 expect(element(by.binding('list')).getText()).toContain('hello'); 18370 expect(element(by.input('text')).getAttribute('value')).toBe(''); 18371 }); 18372 it('should ignore empty strings', function() { 18373 expect(element(by.binding('list')).getText()).toBe('list=[]'); 18374 element(by.css('.doc-example-live #submit')).click(); 18375 element(by.css('.doc-example-live #submit')).click(); 18376 expect(element(by.binding('list')).getText()).toContain('hello'); 18377 }); 18378 </doc:protractor> 18379 </doc:example> 18380 */ 18381 18382/** 18383 * @ngdoc directive 18384 * @name ng.directive:ngFocus 18385 * 18386 * @description 18387 * Specify custom behavior on focus event. 18388 * 18389 * @element window, input, select, textarea, a 18390 * @priority 0 18391 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 18392 * focus. (Event object is available as `$event`) 18393 * 18394 * @example 18395 * See {@link ng.directive:ngClick ngClick} 18396 */ 18397 18398/** 18399 * @ngdoc directive 18400 * @name ng.directive:ngBlur 18401 * 18402 * @description 18403 * Specify custom behavior on blur event. 18404 * 18405 * @element window, input, select, textarea, a 18406 * @priority 0 18407 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 18408 * blur. (Event object is available as `$event`) 18409 * 18410 * @example 18411 * See {@link ng.directive:ngClick ngClick} 18412 */ 18413 18414/** 18415 * @ngdoc directive 18416 * @name ng.directive:ngCopy 18417 * 18418 * @description 18419 * Specify custom behavior on copy event. 18420 * 18421 * @element window, input, select, textarea, a 18422 * @priority 0 18423 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 18424 * copy. (Event object is available as `$event`) 18425 * 18426 * @example 18427 <doc:example> 18428 <doc:source> 18429 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 18430 copied: {{copied}} 18431 </doc:source> 18432 </doc:example> 18433 */ 18434 18435/** 18436 * @ngdoc directive 18437 * @name ng.directive:ngCut 18438 * 18439 * @description 18440 * Specify custom behavior on cut event. 18441 * 18442 * @element window, input, select, textarea, a 18443 * @priority 0 18444 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 18445 * cut. (Event object is available as `$event`) 18446 * 18447 * @example 18448 <doc:example> 18449 <doc:source> 18450 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 18451 cut: {{cut}} 18452 </doc:source> 18453 </doc:example> 18454 */ 18455 18456/** 18457 * @ngdoc directive 18458 * @name ng.directive:ngPaste 18459 * 18460 * @description 18461 * Specify custom behavior on paste event. 18462 * 18463 * @element window, input, select, textarea, a 18464 * @priority 0 18465 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 18466 * paste. (Event object is available as `$event`) 18467 * 18468 * @example 18469 <doc:example> 18470 <doc:source> 18471 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 18472 pasted: {{paste}} 18473 </doc:source> 18474 </doc:example> 18475 */ 18476 18477/** 18478 * @ngdoc directive 18479 * @name ng.directive:ngIf 18480 * @restrict A 18481 * 18482 * @description 18483 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 18484 * {expression}. If the expression assigned to `ngIf` evaluates to a false 18485 * value then the element is removed from the DOM, otherwise a clone of the 18486 * element is reinserted into the DOM. 18487 * 18488 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 18489 * element in the DOM rather than changing its visibility via the `display` css property. A common 18490 * case when this difference is significant is when using css selectors that rely on an element's 18491 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
18491s. 18492 * 18493 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 18494 * is created when the element is restored. The scope created within `ngIf` inherits from 18495 * its parent scope using 18496 * {@link https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance prototypal inheritance}. 18497 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 18498 * a javascript primitive defined in the parent scope. In this case any modifications made to the 18499 * variable within the child scope will override (hide) the value in the parent scope. 18500 * 18501 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 18502 * is if an element's class attribute is directly modified after it's compiled, using something like 18503 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 18504 * the added class will be lost because the original compiled state is used to regenerate the element. 18505 * 18506 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 18507 * and `leave` effects. 18508 * 18509 * @animations 18510 * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container 18511 * leave - happens just before the ngIf contents are removed from the DOM 18512 * 18513 * @element ANY 18514 * @scope 18515 * @priority 600 18516 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 18517 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 18518 * element is added to the DOM tree. 18519 * 18520 * @example 18521 <example animations="true"> 18522 <file name="index.html"> 18523 Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/> 18524 Show when checked: 18525 <span ng-if="checked" class="animate-if"> 18526 I'm removed when the checkbox is unchecked. 18527 </span> 18528 </file> 18529 <file name="animations.css"> 18530 .animate-if { 18531 background:white; 18532 border:1px solid black; 18533 padding:10px; 18534 } 18535 18536 .animate-if.ng-enter, .animate-if.ng-leave { 18537 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18538 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18539 } 18540 18541 .animate-if.ng-enter, 18542 .animate-if.ng-leave.ng-leave-active { 18543 opacity:0; 18544 } 18545 18546 .animate-if.ng-leave, 18547 .animate-if.ng-enter.ng-enter-active { 18548 opacity:1; 18549 } 18550 </file> 18551 </example> 18552 */ 18553var ngIfDirective = ['$animate', function($animate) { 18554 return { 18555 transclude: 'element', 18556 priority: 600, 18557 terminal: true, 18558 restrict: 'A', 18559 $$tlb: true, 18560 link: function ($scope, $element, $attr, ctrl, $transclude) { 18561 var block, childScope; 18562 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 18563 18564 if (toBoolean(value)) { 18565 if (!childScope) { 18566 childScope = $scope.$new(); 18567 $transclude(childScope, function (clone) { 18568 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 18569 // Note: We only need the first/last node of the cloned nodes. 18570 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 18571 // by a directive with templateUrl when it's template arrives. 18572 block = { 18573 clone: clone 18574 }; 18575 $animate.enter(clone, $element.parent(), $element); 18576 }); 18577 } 18578 } else { 18579 18580 if (childScope) { 18581 childScope.$destroy(); 18582 childScope = null; 18583 } 18584 18585 if (block) { 18586 $animate.leave(getBlockElements(block.clone)); 18587 block = null; 18588 } 18589 } 18590 }); 18591 } 18592 }; 18593}]; 18594 18595/** 18596 * @ngdoc directive 18597 * @name ng.directive:ngInclude 18598 * @restrict ECA 18599 * 18600 * @description 18601 * Fetches, compiles and includes an external HTML fragment. 18602 * 18603 * By default, the template URL is restricted to the same domain and protocol as the 18604 * application document. This is done by calling {@link ng.$sce#methods_getTrustedResourceUrl 18605 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 18606 * you may either {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelist them} or 18607 * {@link ng.$sce#methods_trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 18608 * ng.$sce Strict Contextual Escaping}. 18609 * 18610 * In addition, the browser's 18611 * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest 18612 * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing 18613 * (CORS)} policy may further restrict whether the template is successfully loaded. 18614 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 18615 * access on some browsers. 18616 * 18617 * @animations 18618 * enter - animation is used to bring new content into the browser. 18619 * leave - animation is used to animate existing content away. 18620 * 18621 * The enter and leave animation occur concurrently. 18622 * 18623 * @scope 18624 * @priority 400 18625 * 18626 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 18627 * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`. 18628 * @param {string=} onload Expression to evaluate when a new partial is loaded. 18629 * 18630 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 18631 * $anchorScroll} to scroll the viewport after the content is loaded. 18632 * 18633 * - If the attribute is not set, disable scrolling. 18634 * - If the attribute is set without value, enable scrolling. 18635 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 18636 * 18637 * @example 18638 <example animations="true"> 18639 <file name="index.html"> 18640 <div ng-controller="Ctrl"> 18641 <select ng-model="template" ng-options="t.name for t in templates"> 18642 <option value="">(blank)</option> 18643 </select> 18644 url of the template: <tt>{{template.url}}</tt> 18645 <hr/> 18646 <div class="slide-animate-container"> 18647 <div class="slide-animate" ng-include="template.url"></div> 18648 </div> 18649 </div> 18650 </file> 18651 <file name="script.js"> 18652 function Ctrl($scope) { 18653 $scope.templates = 18654 [ { name: 'template1.html', url: 'template1.html'} 18655 , { name: 'template2.html', url: 'template2.html'} ]; 18656 $scope.template = $scope.templates[0]; 18657 } 18658 </file> 18659 <file name="template1.html"> 18660 Content of template1.html 18661 </file> 18662 <file name="template2.html"> 18663 Content of template2.html 18664 </file> 18665 <file name="animations.css"> 18666 .slide-animate-container { 18667 position:relative; 18668 background:white; 18669 border:1px solid black; 18670 height:40px; 18671 overflow:hidden; 18672 } 18673 18674 .slide-animate { 18675 padding:10px; 18676 } 18677 18678 .slide-animate.ng-enter, .slide-animate.ng-leave { 18679 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18680 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18681 18682 position:absolute; 18683 top:0; 18684 left:0; 18685 right:0; 18686 bottom:0; 18687 display:block; 18688 padding:10px; 18689 } 18690 18691 .slide-animate.ng-enter { 18692 top:-50px; 18693 } 18694 .slide-animate.ng-enter.ng-enter-active { 18695 top:0; 18696 } 18697 18698 .slide-animate.ng-leave { 18699 top:0; 18700 } 18701 .slide-animate.ng-leave.ng-leave-active { 18702 top:50px; 18703 } 18704 </file> 18705 <file name="protractorTest.js"> 18706 var templateSelect = element(by.model('template'));
18707 var includeElem = element(by.css('.doc-example-live [ng-include]')); 18708 18709 it('should load template1.html', function() { 18710 expect(includeElem.getText()).toMatch(/Content of template1.html/); 18711 }); 18712 18713 it('should load template2.html', function() { 18714 if (browser.params.browser == 'firefox') { 18715 // Firefox can't handle using selects 18716 // See https://github.com/angular/protractor/issues/480 18717 return; 18718 } 18719 templateSelect.click(); 18720 templateSelect.element.all(by.css('option')).get(2).click(); 18721 expect(includeElem.getText()).toMatch(/Content of template2.html/); 18722 }); 18723 18724 it('should change to blank', function() { 18725 if (browser.params.browser == 'firefox') { 18726 // Firefox can't handle using selects 18727 return; 18728 } 18729 templateSelect.click(); 18730 templateSelect.element.all(by.css('option')).get(0).click(); 18731 expect(includeElem.isPresent()).toBe(false); 18732 }); 18733 </file> 18734 </example> 18735 */ 18736 18737 18738/** 18739 * @ngdoc event 18740 * @name ng.directive:ngInclude#$includeContentRequested 18741 * @eventOf ng.directive:ngInclude 18742 * @eventType emit on the scope ngInclude was declared in 18743 * @description 18744 * Emitted every time the ngInclude content is requested. 18745 */ 18746 18747 18748/** 18749 * @ngdoc event 18750 * @name ng.directive:ngInclude#$includeContentLoaded 18751 * @eventOf ng.directive:ngInclude 18752 * @eventType emit on the current ngInclude scope 18753 * @description 18754 * Emitted every time the ngInclude content is reloaded. 18755 */ 18756var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce', 18757 function($http, $templateCache, $anchorScroll, $animate, $sce) { 18758 return { 18759 restrict: 'ECA', 18760 priority: 400, 18761 terminal: true, 18762 transclude: 'element', 18763 controller: angular.noop, 18764 compile: function(element, attr) { 18765 var srcExp = attr.ngInclude || attr.src, 18766 onloadExp = attr.onload || '', 18767 autoScrollExp = attr.autoscroll; 18768 18769 return function(scope, $element, $attr, ctrl, $transclude) { 18770 var changeCounter = 0, 18771 currentScope, 18772 currentElement; 18773 18774 var cleanupLastIncludeContent = function() { 18775 if (currentScope) { 18776 currentScope.$destroy(); 18777 currentScope = null; 18778 } 18779 if(currentElement) { 18780 $animate.leave(currentElement); 18781 currentElement = null; 18782 } 18783 }; 18784 18785 scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { 18786 var afterAnimation = function() { 18787 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 18788 $anchorScroll(); 18789 } 18790 }; 18791 var thisChangeId = ++changeCounter; 18792 18793 if (src) { 18794 $http.get(src, {cache: $templateCache}).success(function(response) { 18795 if (thisChangeId !== changeCounter) return; 18796 var newScope = scope.$new(); 18797 ctrl.template = response; 18798 18799 // Note: This will also link all children of ng-include that were contained in the original 18800 // html. If that content contains controllers, ... they could pollute/change the scope. 18801 // However, using ng-include on an element with additional content does not make sense... 18802 // Note: We can't remove them in the cloneAttchFn of $transclude as that 18803 // function is called before linking the content, which would apply child 18804 // directives to non existing elements. 18805 var clone = $transclude(newScope, function(clone) { 18806 cleanupLastIncludeContent(); 18807 $animate.enter(clone, null, $element, afterAnimation); 18808 }); 18809 18810 currentScope = newScope; 18811 currentElement = clone; 18812 18813 currentScope.$emit('$includeContentLoaded'); 18814 scope.$eval(onloadExp); 18815 }).error(function() { 18816 if (thisChangeId === changeCounter) cleanupLastIncludeContent(); 18817 }); 18818 scope.$emit('$includeContentRequested'); 18819 } else { 18820 cleanupLastIncludeContent(); 18821 ctrl.template = null; 18822 } 18823 }); 18824 }; 18825 } 18826 }; 18827}]; 18828 18829// This directive is called during the $transclude call of the first `ngInclude` directive. 18830// It will replace and compile the content of the element with the loaded template. 18831// We need this directive so that the element content is already filled when 18832// the link function of another directive on the same element as ngInclude 18833// is called. 18834var ngIncludeFillContentDirective = ['$compile', 18835 function($compile) { 18836 return { 18837 restrict: 'ECA', 18838 priority: -400, 18839 require: 'ngInclude', 18840 link: function(scope, $element, $attr, ctrl) { 18841 $element.html(ctrl.template); 18842 $compile($element.contents())(scope); 18843 } 18844 }; 18845 }]; 18846 18847/** 18848 * @ngdoc directive 18849 * @name ng.directive:ngInit 18850 * @restrict AC 18851 * 18852 * @description 18853 * The `ngInit` directive allows you to evaluate an expression in the 18854 * current scope. 18855 * 18856 * <div class="alert alert-error"> 18857 * The only appropriate use of `ngInit` is for aliasing special properties of 18858 * {@link api/ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you 18859 * should use {@link guide/controller controllers} rather than `ngInit` 18860 * to initialize values on a scope. 18861 * </div> 18862 * <div class="alert alert-warning"> 18863 * **Note**: If you have assignment in `ngInit` along with {@link api/ng.$filter `$filter`}, make
18864 * sure you have parenthesis for correct precedence: 18865 * <pre class="prettyprint"> 18866 * <div ng-init="test1 = (data | orderBy:'name')"></div> 18867 * </pre> 18868 * </div> 18869 * 18870 * @priority 450 18871 * 18872 * @element ANY 18873 * @param {expression} ngInit {@link guide/expression Expression} to eval. 18874 * 18875 * @example 18876 <doc:example> 18877 <doc:source> 18878 <script> 18879 function Ctrl($scope) { 18880 $scope.list = [['a', 'b'], ['c', 'd']]; 18881 } 18882 </script> 18883 <div ng-controller="Ctrl"> 18884 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 18885 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 18886 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 18887 </div> 18888 </div> 18889 </div> 18890 </doc:source> 18891 <doc:protractor> 18892 it('should alias index positions', function() { 18893 var elements = element.all(by.css('.example-init')); 18894 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 18895 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 18896 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 18897 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 18898 }); 18899 </doc:protractor> 18900 </doc:example> 18901 */ 18902var ngInitDirective = ngDirective({ 18903 priority: 450, 18904 compile: function() { 18905 return { 18906 pre: function(scope, element, attrs) { 18907 scope.$eval(attrs.ngInit); 18908 } 18909 }; 18910 } 18911}); 18912 18913/** 18914 * @ngdoc directive 18915 * @name ng.directive:ngNonBindable 18916 * @restrict AC 18917 * @priority 1000 18918 * 18919 * @description 18920 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 18921 * DOM element. This is useful if the element contains what appears to be Angular directives and 18922 * bindings but which should be ignored by Angular. This could be the case if you have a site that 18923 * displays snippets of code, for instance. 18924 * 18925 * @element ANY 18926 * 18927 * @example 18928 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 18929 * but the one wrapped in `ngNonBindable` is left alone. 18930 * 18931 * @example 18932 <doc:example> 18933 <doc:source> 18934 <div>Normal: {{1 + 2}}</div> 18935 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 18936 </doc:source> 18937 <doc:protractor> 18938 it('should check ng-non-bindable', function() { 18939 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 18940 expect(element.all(by.css('.doc-example-live div')).last().getText()).toMatch(/1 \+ 2/); 18941 }); 18942 </doc:protractor> 18943 </doc:example> 18944 */ 18945var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 18946 18947/** 18948 * @ngdoc directive 18949 * @name ng.directive:ngPluralize 18950 * @restrict EA 18951 * 18952 * @description 18953 * # Overview 18954 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 18955 * These rules are bundled with angular.js, but can be overridden 18956 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 18957 * by specifying the mappings between 18958 * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html 18959 * plural categories} and the strings to be displayed. 18960 * 18961 * # Plural categories and explicit number rules 18962 * There are two 18963 * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html 18964 * plural categories} in Angular's default en-US locale: "one" and "other". 18965 * 18966 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 18967 * any number that is not 1), an explicit number rule can only match one number. For example, the 18968 * explicit number rule for "3" matches the number 3. There are examples of plural categories 18969 * and explicit number rules throughout the rest of this documentation. 18970 * 18971 * # Configuring ngPluralize 18972 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 18973 * You can also provide an optional attribute, `offset`. 18974 * 18975 * The value of the `count` attribute can be either a string or an {@link guide/expression 18976 * Angular expression}; these are evaluated on the current scope for its bound value. 18977 * 18978 * The `when` attribute specifies the mappings between plural categories and the actual 18979 * string to be displayed. The value of the attribute should be a JSON object. 18980 * 18981 * The following example shows how to configure ngPluralize: 18982 * 18983 * <pre> 18984 * <ng-pluralize count="personCount"
18985 when="{'0': 'Nobody is viewing.', 18986 * 'one': '1 person is viewing.', 18987 * 'other': '{} people are viewing.'}"> 18988 * </ng-pluralize> 18989 *</pre> 18990 * 18991 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 18992 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 18993 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 18994 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 18995 * show "a dozen people are viewing". 18996 * 18997 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 18998 * into pluralized strings. In the previous example, Angular will replace `{}` with 18999 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 19000 * for <span ng-non-bindable>{{numberExpression}}</span>. 19001 * 19002 * # Configuring ngPluralize with offset 19003 * The `offset` attribute allows further customization of pluralized text, which can result in 19004 * a better user experience. For example, instead of the message "4 people are viewing this document", 19005 * you might display "John, Kate and 2 others are viewing this document". 19006 * The offset attribute allows you to offset a number by any desired value. 19007 * Let's take a look at an example: 19008 * 19009 * <pre> 19010 * <ng-pluralize count="personCount" offset=2 19011 * when="{'0': 'Nobody is viewing.', 19012 * '1': '{{person1}} is viewing.', 19013 * '2': '{{person1}} and {{person2}} are viewing.', 19014 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 19015 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 19016 * </ng-pluralize> 19017 * </pre> 19018 * 19019 * Notice that we are still using two plural categories(one, other), but we added 19020 * three explicit number rules 0, 1 and 2. 19021 * When one person, perhaps John, views the document, "John is viewing" will be shown. 19022 * When three people view the document, no explicit number rule is found, so 19023 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 19024 * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" 19025 * is shown. 19026 * 19027 * Note that when you specify offsets, you must provide explicit number rules for 19028 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 19029 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 19030 * plural categories "one" and "other". 19031 * 19032 * @param {string|expression} count The variable to be bounded to. 19033 * @param {string} when The mapping between plural category to its corresponding strings. 19034 * @param {number=} offset Offset to deduct from the total number. 19035 * 19036 * @example 19037 <doc:example> 19038 <doc:source> 19039 <script> 19040 function Ctrl($scope) { 19041 $scope.person1 = 'Igor'; 19042 $scope.person2 = 'Misko'; 19043 $scope.personCount = 1; 19044 } 19045 </script> 19046 <div ng-controller="Ctrl"> 19047 Person 1:<input type="text" ng-model="person1" value="Igor" /><br/> 19048 Person 2:<input type="text" ng-model="person2" value="Misko" /><br/> 19049 Number of People:<input type="text" ng-model="personCount" value="1" /><br/> 19050 19051 <!--- Example with simple pluralization rules for en locale ---> 19052 Without Offset: 19053 <ng-pluralize count="personCount" 19054 when="{'0': 'Nobody is viewing.', 19055 'one': '1 person is viewing.', 19056 'other': '{} people are viewing.'}"> 19057 </ng-pluralize><br> 19058 19059 <!--- Example with offset ---> 19060 With Offset(2): 19061 <ng-pluralize count="personCount" offset=2 19062 when="{'0': 'Nobody is viewing.', 19063 '1': '{{person1}} is viewing.', 19064 '2': '{{person1}} and {{person2}} are viewing.', 19065 'one': '{{person1}}, {{person2}} and one other person are viewing.',
19066 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 19067 </ng-pluralize> 19068 </div> 19069 </doc:source> 19070 <doc:protractor> 19071 it('should show correct pluralized string', function() { 19072 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 19073 var withOffset = element.all(by.css('ng-pluralize')).get(1); 19074 var countInput = element(by.model('personCount')); 19075 19076 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 19077 expect(withOffset.getText()).toEqual('Igor is viewing.'); 19078 19079 countInput.clear(); 19080 countInput.sendKeys('0'); 19081 19082 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 19083 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 19084 19085 countInput.clear(); 19086 countInput.sendKeys('2'); 19087 19088 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 19089 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 19090 19091 countInput.clear(); 19092 countInput.sendKeys('3'); 19093 19094 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 19095 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 19096 19097 countInput.clear(); 19098 countInput.sendKeys('4'); 19099 19100 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 19101 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 19102 }); 19103 it('should show data-bound names', function() { 19104 var withOffset = element.all(by.css('ng-pluralize')).get(1); 19105 var personCount = element(by.model('personCount')); 19106 var person1 = element(by.model('person1')); 19107 var person2 = element(by.model('person2')); 19108 personCount.clear(); 19109 personCount.sendKeys('4'); 19110 person1.clear(); 19111 person1.sendKeys('Di'); 19112 person2.clear(); 19113 person2.sendKeys('Vojta'); 19114 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 19115 }); 19116 </doc:protractor> 19117 </doc:example> 19118 */ 19119var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { 19120 var BRACE = /{}/g; 19121 return { 19122 restrict: 'EA', 19123 link: function(scope, element, attr) { 19124 var numberExp = attr.count, 19125 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 19126 offset = attr.offset || 0, 19127 whens = scope.$eval(whenExp) || {}, 19128 whensExpFns = {}, 19129 startSymbol = $interpolate.startSymbol(), 19130 endSymbol = $interpolate.endSymbol(), 19131 isWhen = /^when(Minus)?(.+)$/; 19132 19133 forEach(attr, function(expression, attributeName) { 19134 if (isWhen.test(attributeName)) { 19135 whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = 19136 element.attr(attr.$attr[attributeName]); 19137 } 19138 }); 19139 forEach(whens, function(expression, key) { 19140 whensExpFns[key] = 19141 $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + 19142 offset + endSymbol)); 19143 }); 19144 19145 scope.$watch(function ngPluralizeWatch() { 19146 var value = parseFloat(scope.$eval(numberExp)); 19147 19148 if (!isNaN(value)) { 19149 //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, 19150 //check it against pluralization rules in $locale service 19151 if (!(value in whens)) value = $locale.pluralCat(value - offset); 19152 return whensExpFns[value](scope, element, true); 19153 } else { 19154 return ''; 19155 } 19156 }, function ngPluralizeWatchAction(newVal) { 19157 element.text(newVal); 19158 }); 19159 } 19160 }; 19161}]; 19162 19163/** 19164 * @ngdoc directive 19165 * @name ng.directive:ngRepeat 19166 * 19167 * @description 19168 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 19169 * instance gets its own scope, where the given loop variable is set to the current collection item, 19170 * and `$index` is set to the item index or key. 19171 * 19172 * Special properties are exposed on the local scope of each template instance, including: 19173 * 19174 * | Variable | Type | Details | 19175 * |-----------|-----------------|-----------------------------------------------------------------------------| 19176 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 19177 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 19178 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 19179 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 19180 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 19181 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 19182 *
19183 * Creating aliases for these properties is possible with {@link api/ng.directive:ngInit `ngInit`}. 19184 * This may be useful when, for instance, nesting ngRepeats. 19185 * 19186 * # Special repeat start and end points 19187 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 19188 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 19189 * 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) 19190 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 19191 * 19192 * The example below makes use of this feature: 19193 * <pre> 19194 * <header ng-repeat-start="item in items"> 19195 * Header {{ item }} 19196 * </header> 19197 * <div class="body"> 19198 * Body {{ item }} 19199 * </div> 19200 * <footer ng-repeat-end> 19201 * Footer {{ item }} 19202 * </footer> 19203 * </pre> 19204 * 19205 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 19206 * <pre> 19207 * <header> 19208 * Header A 19209 * </header> 19210 * <div class="body"> 19211 * Body A 19212 * </div> 19213 * <footer> 19214 * Footer A 19215 * </footer> 19216 * <header> 19217 * Header B 19218 * </header> 19219 * <div class="body"> 19220 * Body B 19221 * </div> 19222 * <footer> 19223 * Footer B 19224 * </footer> 19225 * </pre> 19226 * 19227 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 19228 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 19229 * 19230 * @animations 19231 * enter - when a new item is added to the list or when an item is revealed after a filter 19232 * leave - when an item is removed from the list or when an item is filtered out 19233 * move - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 19234 * 19235 * @element ANY 19236 * @scope 19237 * @priority 1000 19238 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 19239 * formats are currently supported: 19240 * 19241 * * `variable in expression` â where variable is the user defined loop variable and `expression` 19242 * is a scope expression giving the collection to enumerate. 19243 * 19244 * For example: `album in artist.albums`. 19245 * 19246 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 19247 * and `expression` is the scope expression giving the collection to enumerate. 19248 * 19249 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 19250 * 19251 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking function 19252 * which can be used to associate the objects in the collection with the DOM elements. If no tracking function 19253 * is specified the ng-repeat associates elements by identity in the collection. It is an error to have 19254 * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are 19255 * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, 19256 * before specifying a tracking expression. 19257 * 19258 * For example: `item in items` is equivalent to `item in items track by $id(item)'. This implies that the DOM elements 19259 * will be associated by item identity in the array. 19260 * 19261 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 19262 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 19263 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 19264 * element in the same way in the DOM. 19265 * 19266 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 19267 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 19268 * property is same. 19269 * 19270 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 19271 * to items in conjunction with a tracking expression. 19272 * 19273 * @example 19274 * This example initializes the scope to a list of names and 19275 * then uses `ngRepeat` to display every person: 19276 <example animations="true"> 19277 <file name="index.html"> 19278 <div ng-init="friends = [ 19279 {name:'John', age:25, gender:'boy'}, 19280 {name:'Jessie', age:30, gender:'girl'}, 19281 {name:'Johanna', age:28, gender:'girl'}, 19282 {name:'Joy', age:15, gender:'girl'}, 19283 {name:'Mary', age:28, gender:'girl'}, 19284 {name:'Peter', age:95, gender:'boy'}, 19285 {name:'Sebastian', age:50, gender:'boy'}, 19286 {name:'Erika', age:27, gender:'girl'}, 19287 {name:'Patrick', age:40, gender:'boy'}, 19288 {name:'Samantha', age:60, gender:'girl'} 19289 ]"> 19290 I have {{friends.length}} friends. They are: 19291 <input type="search" ng-model="q" placeholder="filter friends..." /> 19292 <ul class="example-animate-container"> 19293 <li class="animate-repeat" ng-repeat="friend in friends | filter:q"> 19294 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 19295 </li> 19296 </ul> 19297 </div> 19298 </file> 19299 <file name="animations.css"> 19300 .example-animate-container { 19301 background:white; 19302 border:1px solid black; 19303 list-style:none; 19304 margin:0; 19305 padding:0 10px; 19306 } 19307 19308 .animate-repeat { 19309 line-height:40px; 19310 list-style:none; 19311 box-sizing:border-box; 19312 } 19313 19314 .animate-repeat.ng-move, 19315 .animate-repeat.ng-enter, 19316 .animate-repeat.ng-leave { 19317 -webkit-transition:all linear 0.5s; 19318 transition:all linear 0.5s; 19319 } 19320 19321 .animate-repeat.ng-leave.ng-leave-active, 19322 .animate-repeat.ng-move, 19323 .animate-repeat.ng-enter { 19324 opacity:0; 19325 max-height:0; 19326 } 19327 19328 .animate-repeat.ng-leave,
19329 .animate-repeat.ng-move.ng-move-active, 19330 .animate-repeat.ng-enter.ng-enter-active { 19331 opacity:1; 19332 max-height:40px; 19333 } 19334 </file> 19335 <file name="protractorTest.js"> 19336 var friends = element(by.css('.doc-example-live')) 19337 .element.all(by.repeater('friend in friends')); 19338 19339 it('should render initial data set', function() { 19340 expect(friends.count()).toBe(10); 19341 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 19342 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 19343 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 19344 expect(element(by.binding('friends.length')).getText()) 19345 .toMatch("I have 10 friends. They are:"); 19346 }); 19347 19348 it('should update repeater when filter predicate changes', function() { 19349 expect(friends.count()).toBe(10); 19350 19351 element(by.css('.doc-example-live')).element(by.model('q')).sendKeys('ma'); 19352 19353 expect(friends.count()).toBe(2); 19354 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 19355 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 19356 }); 19357 </file> 19358 </example> 19359 */ 19360var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 19361 var NG_REMOVED = '$$NG_REMOVED'; 19362 var ngRepeatMinErr = minErr('ngRepeat'); 19363 return { 19364 transclude: 'element', 19365 priority: 1000, 19366 terminal: true, 19367 $$tlb: true, 19368 link: function($scope, $element, $attr, ctrl, $transclude){ 19369 var expression = $attr.ngRepeat; 19370 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), 19371 trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, 19372 lhs, rhs, valueIdentifier, keyIdentifier, 19373 hashFnLocals = {$id: hashKey}; 19374 19375 if (!match) { 19376 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 19377 expression); 19378 } 19379 19380 lhs = match[1]; 19381 rhs = match[2]; 19382 trackByExp = match[3]; 19383 19384 if (trackByExp) { 19385 trackByExpGetter = $parse(trackByExp); 19386 trackByIdExpFn = function(key, value, index) { 19387 // assign key, value, and $index to the locals so that they can be used in hash functions 19388 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 19389 hashFnLocals[valueIdentifier] = value; 19390 hashFnLocals.$index = index; 19391 return trackByExpGetter($scope, hashFnLocals); 19392 }; 19393 } else { 19394 trackByIdArrayFn = function(key, value) { 19395 return hashKey(value); 19396 }; 19397 trackByIdObjFn = function(key) { 19398 return key; 19399 }; 19400 } 19401 19402 match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); 19403 if (!match) { 19404 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 19405 lhs); 19406 } 19407 valueIdentifier = match[3] || match[1]; 19408 keyIdentifier = match[2]; 19409 19410 // Store a list of elements from previous run. This is a hash where key is the item from the 19411 // iterator, and the value is objects with following properties. 19412 // - scope: bound scope 19413 // - element: previous element. 19414 // - index: position 19415 var lastBlockMap = {}; 19416 19417 //watch props 19418 $scope.$watchCollection(rhs, function ngRepeatAction(collection){ 19419 var index, length, 19420 previousNode = $element[0], // current position of the node 19421 nextNode, 19422 // Same as lastBlockMap but it has the current state. It will become the 19423 // lastBlockMap on the next iteration. 19424 nextBlockMap = {}, 19425 arrayLength, 19426 childScope, 19427 key, value, // key/value of iteration 19428 trackById, 19429 trackByIdFn, 19430 collectionKeys, 19431 block, // last object information {scope, element, id} 19432 nextBlockOrder = [], 19433 elementsToRemove; 19434 19435 19436 if (isArrayLike(collection)) { 19437 collectionKeys = collection; 19438 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 19439 } else { 19440 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 19441 // if object, extract keys, sort them and use to determine order of iteration over obj props 19442 collectionKeys = []; 19443 for (key in collection) { 19444 if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { 19445 collectionKeys.push(key); 19446 } 19447 } 19448 collectionKeys.sort(); 19449 } 19450 19451 arrayLength = collectionKeys.length; 19452 19453 // locate existing items 19454 length = nextBlockOrder.length = collectionKeys.length; 19455 for(index = 0; index < length; index++) { 19456 key = (collection === collectionKeys) ? index : collectionKeys[index]; 19457 value = collection[key]; 19458 trackById = trackByIdFn(key, value, index); 19459 assertNotHasOwnProperty(trackById, '`track by` id'); 19460 if(lastBlockMap.hasOwnProperty(trackById)) { 19461 block = lastBlockMap[trackById]; 19462 delete lastBlockMap[trackById]; 19463 nextBlockMap[trackById] = block; 19464 nextBlockOrder[index] = block; 19465 } else if (nextBlockMap.hasOwnProperty(trackById)) { 19466 // restore lastBlockMap
19467 forEach(nextBlockOrder, function(block) { 19468 if (block && block.scope) lastBlockMap[block.id] = block; 19469 }); 19470 // This is a duplicate and we need to throw an error 19471 throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}", 19472 expression, trackById); 19473 } else { 19474 // new never before seen block 19475 nextBlockOrder[index] = { id: trackById }; 19476 nextBlockMap[trackById] = false; 19477 } 19478 } 19479 19480 // remove existing items 19481 for (key in lastBlockMap) { 19482 // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn 19483 if (lastBlockMap.hasOwnProperty(key)) { 19484 block = lastBlockMap[key]; 19485 elementsToRemove = getBlockElements(block.clone); 19486 $animate.leave(elementsToRemove); 19487 forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); 19488 block.scope.$destroy(); 19489 } 19490 } 19491 19492 // we are not using forEach for perf reasons (trying to avoid #call) 19493 for (index = 0, length = collectionKeys.length; index < length; index++) { 19494 key = (collection === collectionKeys) ? index : collectionKeys[index]; 19495 value = collection[key]; 19496 block = nextBlockOrder[index]; 19497 if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]); 19498 19499 if (block.scope) { 19500 // if we have already seen this object, then we need to reuse the 19501 // associated scope/element 19502 childScope = block.scope; 19503 19504 nextNode = previousNode; 19505 do { 19506 nextNode = nextNode.nextSibling; 19507 } while(nextNode && nextNode[NG_REMOVED]); 19508 19509 if (getBlockStart(block) != nextNode) { 19510 // existing item which got moved 19511 $animate.move(getBlockElements(block.clone), null, jqLite(previousNode)); 19512 } 19513 previousNode = getBlockEnd(block); 19514 } else { 19515 // new item which we don't know about 19516 childScope = $scope.$new(); 19517 } 19518 19519 childScope[valueIdentifier] = value; 19520 if (keyIdentifier) childScope[keyIdentifier] = key; 19521 childScope.$index = index; 19522 childScope.$first = (index === 0); 19523 childScope.$last = (index === (arrayLength - 1)); 19524 childScope.$middle = !(childScope.$first || childScope.$last); 19525 // jshint bitwise: false 19526 childScope.$odd = !(childScope.$even = (index&1) === 0); 19527 // jshint bitwise: true 19528 19529 if (!block.scope) { 19530 $transclude(childScope, function(clone) { 19531 clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); 19532 $animate.enter(clone, null, jqLite(previousNode)); 19533 previousNode = clone; 19534 block.scope = childScope; 19535 // Note: We only need the first/last node of the cloned nodes. 19536 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 19537 // by a directive with templateUrl when it's template arrives. 19538 block.clone = clone; 19539 nextBlockMap[block.id] = block; 19540 }); 19541 } 19542 } 19543 lastBlockMap = nextBlockMap; 19544 }); 19545 } 19546 }; 19547 19548 function getBlockStart(block) { 19549 return block.clone[0]; 19550 } 19551 19552 function getBlockEnd(block) { 19553 return block.clone[block.clone.length - 1]; 19554 } 19555}]; 19556 19557/** 19558 * @ngdoc directive 19559 * @name ng.directive:ngShow 19560 * 19561 * @description 19562 * The `ngShow` directive shows or hides the given HTML element based on the expression 19563 * provided to the ngShow attribute. The element is shown or hidden by removing or adding 19564 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 19565 * in AngularJS and sets the display style to none (using an !important flag). 19566 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 19567 * 19568 * <pre> 19569 * <!-- when $scope.myValue is truthy (element is visible) --> 19570 * <div ng-show="myValue"></div> 19571 * 19572 * <!-- when $scope.myValue is falsy (element is hidden) --> 19573 * <div ng-show="myValue" class="ng-hide"></div> 19574 * </pre> 19575 * 19576 * When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute 19577 * on the element causing it to become hidden. When true, the ng-hide CSS class is removed 19578 * from the element causing the element not to appear hidden. 19579 * 19580 * ## Why is !important used? 19581 * 19582 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 19583 * can be easily overridden by heavier selectors. For example, something as simple 19584 * as changing the display style on a HTML list item would make hidden elements appear visible.
19585 * This also becomes a bigger issue when dealing with CSS frameworks. 19586 * 19587 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 19588 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 19589 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 19590 * 19591 * ### Overriding .ng-hide 19592 * 19593 * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by 19594 * restating the styles for the .ng-hide class in CSS: 19595 * <pre> 19596 * .ng-hide { 19597 * //!annotate CSS Specificity|Not to worry, this will override the AngularJS default... 19598 * display:block!important; 19599 * 19600 * //this is just another form of hiding an element 19601 * position:absolute; 19602 * top:-9999px; 19603 * left:-9999px; 19604 * } 19605 * </pre> 19606 * 19607 * Just remember to include the important flag so the CSS override will function. 19608 * 19609 * <div class="alert alert-warning"> 19610 * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):<br /> 19611 * "f" / "0" / "false" / "no" / "n" / "[]" 19612 * </div> 19613 * 19614 * ## A note about animations with ngShow 19615 * 19616 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 19617 * is true and false. This system works like the animation system present with ngClass except that 19618 * you must also include the !important flag to override the display property 19619 * so that you can perform an animation when the element is hidden during the time of the animation. 19620 * 19621 * <pre> 19622 * // 19623 * //a working example can be found at the bottom of this page 19624 * // 19625 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 19626 * transition:0.5s linear all; 19627 * display:block!important; 19628 * } 19629 * 19630 * .my-element.ng-hide-add { ... } 19631 * .my-element.ng-hide-add.ng-hide-add-active { ... } 19632 * .my-element.ng-hide-remove { ... } 19633 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 19634 * </pre> 19635 * 19636 * @animations 19637 * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible 19638 * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden 19639 * 19640 * @element ANY 19641 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 19642 * then the element is shown or hidden respectively. 19643 * 19644 * @example 19645 <example animations="true"> 19646 <file name="index.html"> 19647 Click me: <input type="checkbox" ng-model="checked"><br/> 19648 <div> 19649 Show: 19650 <div class="check-element animate-show" ng-show="checked"> 19651 <span class="icon-thumbs-up"></span> I show up when your checkbox is checked. 19652 </div> 19653 </div> 19654 <div> 19655 Hide: 19656 <div class="check-element animate-show" ng-hide="checked"> 19657 <span class="icon-thumbs-down"></span> I hide when your checkbox is checked. 19658 </div> 19659 </div> 19660 </file> 19661 <file name="animations.css"> 19662 .animate-show { 19663 -webkit-transition:all linear 0.5s; 19664 transition:all linear 0.5s; 19665 line-height:20px; 19666 opacity:1; 19667 padding:10px; 19668 border:1px solid black; 19669 background:white; 19670 } 19671 19672 .animate-show.ng-hide-add, 19673 .animate-show.ng-hide-remove { 19674 display:block!important; 19675 } 19676 19677 .animate-show.ng-hide { 19678 line-height:0; 19679 opacity:0; 19680 padding:0 10px; 19681 } 19682 19683 .check-element { 19684 padding:10px; 19685 border:1px solid black; 19686 background:white; 19687 } 19688 </file> 19689 <file name="protractorTest.js"> 19690 var thumbsUp = element(by.css('.doc-example-live span.icon-thumbs-up')); 19691 var thumbsDown = element(by.css('.doc-example-live span.icon-thumbs-down')); 19692 19693 it('should check ng-show / ng-hide', function() { 19694 expect(thumbsUp.isDisplayed()).toBeFalsy(); 19695 expect(thumbsDown.isDisplayed()).toBeTruthy(); 19696
19697 element(by.model('checked')).click(); 19698 19699 expect(thumbsUp.isDisplayed()).toBeTruthy(); 19700 expect(thumbsDown.isDisplayed()).toBeFalsy(); 19701 }); 19702 </file> 19703 </example> 19704 */ 19705var ngShowDirective = ['$animate', function($animate) { 19706 return function(scope, element, attr) { 19707 scope.$watch(attr.ngShow, function ngShowWatchAction(value){ 19708 $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); 19709 }); 19710 }; 19711}]; 19712 19713 19714/** 19715 * @ngdoc directive 19716 * @name ng.directive:ngHide 19717 * 19718 * @description 19719 * The `ngHide` directive shows or hides the given HTML element based on the expression 19720 * provided to the ngHide attribute. The element is shown or hidden by removing or adding 19721 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 19722 * in AngularJS and sets the display style to none (using an !important flag). 19723 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 19724 * 19725 * <pre> 19726 * <!-- when $scope.myValue is truthy (element is hidden) --> 19727 * <div ng-hide="myValue"></div> 19728 * 19729 * <!-- when $scope.myValue is falsy (element is visible) --> 19730 * <div ng-hide="myValue" class="ng-hide"></div> 19731 * </pre> 19732 * 19733 * When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute 19734 * on the element causing it to become hidden. When false, the ng-hide CSS class is removed 19735 * from the element causing the element not to appear hidden. 19736 * 19737 * ## Why is !important used? 19738 * 19739 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 19740 * can be easily overridden by heavier selectors. For example, something as simple 19741 * as changing the display style on a HTML list item would make hidden elements appear visible. 19742 * This also becomes a bigger issue when dealing with CSS frameworks. 19743 * 19744 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 19745 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 19746 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 19747 * 19748 * ### Overriding .ng-hide 19749 * 19750 * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by 19751 * restating the styles for the .ng-hide class in CSS: 19752 * <pre> 19753 * .ng-hide { 19754 * //!annotate CSS Specificity|Not to worry, this will override the AngularJS default... 19755 * display:block!important; 19756 * 19757 * //this is just another form of hiding an element 19758 * position:absolute; 19759 * top:-9999px; 19760 * left:-9999px; 19761 * } 19762 * </pre> 19763 * 19764 * Just remember to include the important flag so the CSS override will function. 19765 * 19766 * <div class="alert alert-warning"> 19767 * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):<br /> 19768 * "f" / "0" / "false" / "no" / "n" / "[]" 19769 * </div> 19770 * 19771 * ## A note about animations with ngHide 19772 * 19773 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 19774 * is true and false. This system works like the animation system present with ngClass, except that 19775 * you must also include the !important flag to override the display property so 19776 * that you can perform an animation when the element is hidden during the time of the animation. 19777 * 19778 * <pre> 19779 * // 19780 * //a working example can be found at the bottom of this page 19781 * // 19782 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 19783 * transition:0.5s linear all; 19784 * display:block!important; 19785 * } 19786 * 19787 * .my-element.ng-hide-add { ... } 19788 * .my-element.ng-hide-add.ng-hide-add-active { ... } 19789 * .my-element.ng-hide-remove { ... } 19790 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 19791 * </pre> 19792 * 19793 * @animations 19794 * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden 19795 * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible 19796 * 19797 * @element ANY 19798 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 19799 * the element is shown or hidden respectively. 19800 * 19801 * @example 19802 <example animations="true"> 19803 <file name="index.html"> 19804 Click me: <input type="checkbox" ng-model="checked"><br/> 19805 <div> 19806 Show: 19807 <div class="check-element animate-hide" ng-show="checked">
19808 <span class="icon-thumbs-up"></span> I show up when your checkbox is checked. 19809 </div> 19810 </div> 19811 <div> 19812 Hide: 19813 <div class="check-element animate-hide" ng-hide="checked"> 19814 <span class="icon-thumbs-down"></span> I hide when your checkbox is checked. 19815 </div> 19816 </div> 19817 </file> 19818 <file name="animations.css"> 19819 .animate-hide { 19820 -webkit-transition:all linear 0.5s; 19821 transition:all linear 0.5s; 19822 line-height:20px; 19823 opacity:1; 19824 padding:10px; 19825 border:1px solid black; 19826 background:white; 19827 } 19828 19829 .animate-hide.ng-hide-add, 19830 .animate-hide.ng-hide-remove { 19831 display:block!important; 19832 } 19833 19834 .animate-hide.ng-hide { 19835 line-height:0; 19836 opacity:0; 19837 padding:0 10px; 19838 } 19839 19840 .check-element { 19841 padding:10px; 19842 border:1px solid black; 19843 background:white; 19844 } 19845 </file> 19846 <file name="protractorTest.js"> 19847 var thumbsUp = element(by.css('.doc-example-live span.icon-thumbs-up')); 19848 var thumbsDown = element(by.css('.doc-example-live span.icon-thumbs-down')); 19849 19850 it('should check ng-show / ng-hide', function() { 19851 expect(thumbsUp.isDisplayed()).toBeFalsy(); 19852 expect(thumbsDown.isDisplayed()).toBeTruthy(); 19853 19854 element(by.model('checked')).click(); 19855 19856 expect(thumbsUp.isDisplayed()).toBeTruthy(); 19857 expect(thumbsDown.isDisplayed()).toBeFalsy(); 19858 }); 19859 </file> 19860 </example> 19861 */ 19862var ngHideDirective = ['$animate', function($animate) { 19863 return function(scope, element, attr) { 19864 scope.$watch(attr.ngHide, function ngHideWatchAction(value){ 19865 $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); 19866 }); 19867 }; 19868}]; 19869 19870/** 19871 * @ngdoc directive 19872 * @name ng.directive:ngStyle 19873 * @restrict AC 19874 * 19875 * @description 19876 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 19877 * 19878 * @element ANY 19879 * @param {expression} ngStyle {@link guide/expression Expression} which evals to an 19880 * object whose keys are CSS style names and values are corresponding values for those CSS 19881 * keys. 19882 * 19883 * @example 19884 <example> 19885 <file name="index.html"> 19886 <input type="button" value="set" ng-click="myStyle={color:'red'}"> 19887 <input type="button" value="clear" ng-click="myStyle={}"> 19888 <br/> 19889 <span ng-style="myStyle">Sample Text</span> 19890 <pre>myStyle={{myStyle}}</pre> 19891 </file> 19892 <file name="style.css"> 19893 span { 19894 color: black; 19895 } 19896 </file> 19897 <file name="protractorTest.js"> 19898 var colorSpan = element(by.css('.doc-example-live span')); 19899 19900 it('should check ng-style', function() { 19901 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 19902 element(by.css('.doc-example-live input[value=set]')).click(); 19903 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 19904 element(by.css('.doc-example-live input[value=clear]')).click(); 19905 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 19906 }); 19907 </file> 19908 </example> 19909 */ 19910var ngStyleDirective = ngDirective(function(scope, element, attr) { 19911 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 19912 if (oldStyles && (newStyles !== oldStyles)) { 19913 forEach(oldStyles, function(val, style) { element.css(style, '');}); 19914 } 19915 if (newStyles) element.css(newStyles); 19916 }, true); 19917}); 19918 19919/** 19920 * @ngdoc directive 19921 * @name ng.directive:ngSwitch 19922 * @restrict EA 19923 * 19924 * @description 19925 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 19926 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 19927 * as specified in the template. 19928 * 19929 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 19930 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 19931 * matches the value obtained from the evaluated expression. In other words, you define a container element 19932 * (where you place the directive), place an expression on the **`on="..."` attribute** 19933 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 19934 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 19935 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 19936 * attribute is displayed. 19937 * 19938 * <div class="alert alert-info"> 19939 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 19940 * as literal string values to match against. 19941 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
19941omeVal"` not against the 19942 * value of the expression `$scope.someVal`. 19943 * </div> 19944 19945 * @animations 19946 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 19947 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 19948 * 19949 * @usage 19950 * <ANY ng-switch="expression"> 19951 * <ANY ng-switch-when="matchValue1">...</ANY> 19952 * <ANY ng-switch-when="matchValue2">...</ANY> 19953 * <ANY ng-switch-default>...</ANY> 19954 * </ANY> 19955 * 19956 * 19957 * @scope 19958 * @priority 800 19959 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>. 19960 * @paramDescription 19961 * On child elements add: 19962 * 19963 * * `ngSwitchWhen`: the case statement to match against. If match then this 19964 * case will be displayed. If the same match appears multiple times, all the 19965 * elements will be displayed. 19966 * * `ngSwitchDefault`: the default case when no other case match. If there 19967 * are multiple default cases, all of them will be displayed when no other 19968 * case match. 19969 * 19970 * 19971 * @example 19972 <example animations="true"> 19973 <file name="index.html"> 19974 <div ng-controller="Ctrl"> 19975 <select ng-model="selection" ng-options="item for item in items"> 19976 </select> 19977 <tt>selection={{selection}}</tt> 19978 <hr/> 19979 <div class="animate-switch-container" 19980 ng-switch on="selection"> 19981 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 19982 <div class="animate-switch" ng-switch-when="home">Home Span</div> 19983 <div class="animate-switch" ng-switch-default>default</div> 19984 </div> 19985 </div> 19986 </file> 19987 <file name="script.js"> 19988 function Ctrl($scope) { 19989 $scope.items = ['settings', 'home', 'other']; 19990 $scope.selection = $scope.items[0]; 19991 } 19992 </file> 19993 <file name="animations.css"> 19994 .animate-switch-container { 19995 position:relative; 19996 background:white; 19997 border:1px solid black; 19998 height:40px; 19999 overflow:hidden; 20000 } 20001 20002 .animate-switch { 20003 padding:10px; 20004 } 20005 20006 .animate-switch.ng-animate { 20007 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 20008 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 20009 20010 position:absolute; 20011 top:0; 20012 left:0; 20013 right:0; 20014 bottom:0; 20015 } 20016 20017 .animate-switch.ng-leave.ng-leave-active, 20018 .animate-switch.ng-enter { 20019 top:-50px; 20020 } 20021 .animate-switch.ng-leave, 20022 .animate-switch.ng-enter.ng-enter-active { 20023 top:0; 20024 } 20025 </file> 20026 <file name="protractorTest.js"> 20027 var switchElem = element(by.css('.doc-example-live [ng-switch]')); 20028 var select = element(by.model('selection')); 20029 20030 it('should start in settings', function() { 20031 expect(switchElem.getText()).toMatch(/Settings Div/); 20032 }); 20033 it('should change to home', function() { 20034 select.element.all(by.css('option')).get(1).click(); 20035 expect(switchElem.getText()).toMatch(/Home Span/); 20036 }); 20037 it('should select default', function() { 20038 select.element.all(by.css('option')).get(2).click(); 20039 expect(switchElem.getText()).toMatch(/default/); 20040 }); 20041 </file> 20042 </example> 20043 */ 20044var ngSwitchDirective = ['$animate', function($animate) { 20045 return { 20046 restrict: 'EA', 20047 require: 'ngSwitch', 20048 20049 // asks for $scope to fool the BC controller module 20050 controller: ['$scope', function ngSwitchController() { 20051 this.cases = {}; 20052 }], 20053 link: function(scope, element, attr, ngSwitchController) { 20054 var watchExpr = attr.ngSwitch || attr.on, 20055 selectedTranscludes, 20056 selectedElements, 20057 selectedScopes = []; 20058 20059 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 20060 for (var i= 0, ii=selectedScopes.length; i<ii; i++) { 20061 selectedScopes[i].$destroy(); 20062 $animate.leave(selectedElements[i]); 20063 } 20064 20065 selectedElements = []; 20066 selectedScopes = []; 20067 20068 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 20069 scope.$eval(attr.change);
20070 forEach(selectedTranscludes, function(selectedTransclude) { 20071 var selectedScope = scope.$new(); 20072 selectedScopes.push(selectedScope); 20073 selectedTransclude.transclude(selectedScope, function(caseElement) { 20074 var anchor = selectedTransclude.element; 20075 20076 selectedElements.push(caseElement); 20077 $animate.enter(caseElement, anchor.parent(), anchor); 20078 }); 20079 }); 20080 } 20081 }); 20082 } 20083 }; 20084}]; 20085 20086var ngSwitchWhenDirective = ngDirective({ 20087 transclude: 'element', 20088 priority: 800, 20089 require: '^ngSwitch', 20090 link: function(scope, element, attrs, ctrl, $transclude) { 20091 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 20092 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 20093 } 20094}); 20095 20096var ngSwitchDefaultDirective = ngDirective({ 20097 transclude: 'element', 20098 priority: 800, 20099 require: '^ngSwitch', 20100 link: function(scope, element, attr, ctrl, $transclude) { 20101 ctrl.cases['?'] = (ctrl.cases['?'] || []); 20102 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 20103 } 20104}); 20105 20106/** 20107 * @ngdoc directive 20108 * @name ng.directive:ngTransclude 20109 * @restrict AC 20110 * 20111 * @description 20112 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 20113 * 20114 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 20115 * 20116 * @element ANY 20117 * 20118 * @example 20119 <doc:example module="transclude"> 20120 <doc:source> 20121 <script> 20122 function Ctrl($scope) { 20123 $scope.title = 'Lorem Ipsum'; 20124 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 20125 } 20126 20127 angular.module('transclude', []) 20128 .directive('pane', function(){ 20129 return { 20130 restrict: 'E', 20131 transclude: true, 20132 scope: { title:'@' }, 20133 template: '<div style="border: 1px solid black;">' + 20134 '<div style="background-color: gray">{{title}}</div>' + 20135 '<div ng-transclude></div>' + 20136 '</div>' 20137 }; 20138 }); 20139 </script> 20140 <div ng-controller="Ctrl"> 20141 <input ng-model="title"><br> 20142 <textarea ng-model="text"></textarea> <br/> 20143 <pane title="{{title}}">{{text}}</pane> 20144 </div> 20145 </doc:source> 20146 <doc:protractor> 20147 it('should have transcluded', function() { 20148 var titleElement = element(by.model('title')); 20149 titleElement.clear(); 20150 titleElement.sendKeys('TITLE'); 20151 var textElement = element(by.model('text')); 20152 textElement.clear(); 20153 textElement.sendKeys('TEXT'); 20154 expect(element(by.binding('title')).getText()).toEqual('TITLE'); 20155 expect(element(by.binding('text')).getText()).toEqual('TEXT'); 20156 }); 20157 </doc:protractor> 20158 </doc:example> 20159 * 20160 */ 20161var ngTranscludeDirective = ngDirective({ 20162 link: function($scope, $element, $attrs, controller, $transclude) { 20163 if (!$transclude) { 20164 throw minErr('ngTransclude')('orphan', 20165 'Illegal use of ngTransclude directive in the template! ' + 20166 'No parent directive that requires a transclusion found. ' + 20167 'Element: {0}', 20168 startingTag($element)); 20169 } 20170 20171 $transclude(function(clone) { 20172 $element.empty(); 20173 $element.append(clone); 20174 }); 20175 } 20176}); 20177 20178/** 20179 * @ngdoc directive 20180 * @name ng.directive:script 20181 * @restrict E 20182 * 20183 * @description 20184 * Load the content of a `<script>` element into {@link api/ng.$templateCache `$templateCache`}, so that the 20185 * template can be used by {@link api/ng.directive:ngInclude `ngInclude`}, 20186 * {@link api/ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 20187 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 20188 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 20189 * 20190 * @param {'text/ng-template'} type Must be set to `'text/ng-template'`. 20191 * @param {string} id Cache name of the template. 20192 * 20193 * @example 20194 <doc:example> 20195 <doc:source> 20196 <script type="text/ng-template" id="/tpl.html"> 20197 Content of the template. 20198 </script> 20199 20200 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 20201 <div id="tpl-content" ng-include src="currentTpl"></div> 20202 </doc:source> 20203 <doc:protractor> 20204 it('should load template defined inside script tag', function() {
20205 element(by.css('#tpl-link')).click(); 20206 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 20207 }); 20208 </doc:protractor> 20209 </doc:example> 20210 */ 20211var scriptDirective = ['$templateCache', function($templateCache) { 20212 return { 20213 restrict: 'E', 20214 terminal: true, 20215 compile: function(element, attr) { 20216 if (attr.type == 'text/ng-template') { 20217 var templateUrl = attr.id, 20218 // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent 20219 text = element[0].text; 20220 20221 $templateCache.put(templateUrl, text); 20222 } 20223 } 20224 }; 20225}]; 20226 20227var ngOptionsMinErr = minErr('ngOptions'); 20228/** 20229 * @ngdoc directive 20230 * @name ng.directive:select 20231 * @restrict E 20232 * 20233 * @description 20234 * HTML `SELECT` element with angular data-binding. 20235 * 20236 * # `ngOptions` 20237 * 20238 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 20239 * elements for the `<select>` element using the array or object obtained by evaluating the 20240 * `ngOptions` comprehension_expression. 20241 * 20242 * When an item in the `<select>` menu is selected, the array element or object property 20243 * represented by the selected option will be bound to the model identified by the `ngModel` 20244 * directive. 20245 * 20246 * <div class="alert alert-warning"> 20247 * **Note:** `ngModel` compares by reference, not value. This is important when binding to an 20248 * array of objects. See an example {@link http://jsfiddle.net/qWzTb/ in this jsfiddle}. 20249 * </div> 20250 * 20251 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 20252 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 20253 * option. See example below for demonstration. 20254 * 20255 * <div class="alert alert-warning"> 20256 * **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead 20257 * of {@link ng.directive:ngRepeat ngRepeat} when you want the 20258 * `select` model to be bound to a non-string value. This is because an option element can only 20259 * be bound to string values at present. 20260 * </div> 20261 * 20262 * @param {string} ngModel Assignable angular expression to data-bind to. 20263 * @param {string=} name Property name of the form under which the control is published. 20264 * @param {string=} required The control is considered valid only if value is entered. 20265 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20266 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20267 * `required` when you want to data-bind to the `required` attribute. 20268 * @param {comprehension_expression=} ngOptions in one of the following forms: 20269 * 20270 * * for array data sources: 20271 * * `label` **`for`** `value` **`in`** `array` 20272 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 20273 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 20274 * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 20275 * * for object data sources: 20276 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 20277 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 20278 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 20279 * * `select` **`as`** `label` **`group by`** `group` 20280 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 20281 * 20282 * Where: 20283 * 20284 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 20285 * * `value`: local variable which will refer to each item in the `array` or each property value 20286 * of `object` during iteration. 20287 * * `key`: local variable which will refer to a property name in `object` during iteration. 20288 * * `label`: The result of this expression will be the label for `<option>` element. The 20289 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 20290 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 20291 * element. If not specified, `select` expression will default to `value`. 20292 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 20293 * DOM element. 20294 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 20295 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 20296 * `value` variable (e.g. `value.propertyName`). 20297 * 20298 * @example 20299 <doc:example> 20300 <doc:source> 20301 <script> 20302 function MyCntrl($scope) { 20303 $scope.colors = [ 20304 {name:'black', shade:'dark'}, 20305 {name:'white', shade:'light'}, 20306 {name:'red', shade:'dark'}, 20307 {name:'blue', shade:'dark'}, 20308 {name:'yellow', shade:'light'} 20309 ]; 20310 $scope.color = $scope.colors[2]; // red 20311 } 20312 </script> 20313 <div ng-controller="MyCntrl"> 20314 <ul> 20315 <li ng-repeat="color in colors"> 20316 Name: <input ng-model="color.name"> 20317 [<a href ng-click="colors.splice($index, 1)">X</a>] 20318 </li> 20319 <li> 20320 [<a href ng-click="colors.push({})">add</a>] 20321 </li> 20322 </ul> 20323 <hr/> 20324 Color (null not allowed): 20325 <select ng-model="color" ng-options="c.name for c in colors"></select><br> 20326 20327 Color (null allowed):
20328 <span class="nullable"> 20329 <select ng-model="color" ng-options="c.name for c in colors"> 20330 <option value="">-- choose color --</option> 20331 </select> 20332 </span><br/> 20333 20334 Color grouped by shade: 20335 <select ng-model="color" ng-options="c.name group by c.shade for c in colors"> 20336 </select><br/> 20337 20338 20339 Select <a href ng-click="color={name:'not in list'}">bogus</a>.<br> 20340 <hr/> 20341 Currently selected: {{ {selected_color:color} }} 20342 <div style="border:solid 1px black; height:20px" 20343 ng-style="{'background-color':color.name}"> 20344 </div> 20345 </div> 20346 </doc:source> 20347 <doc:protractor> 20348 it('should check ng-options', function() { 20349 expect(element(by.binding('{selected_color:color}')).getText()).toMatch('red'); 20350 element.all(by.select('color')).first().click(); 20351 element.all(by.css('select[ng-model="color"] option')).first().click(); 20352 expect(element(by.binding('{selected_color:color}')).getText()).toMatch('black'); 20353 element(by.css('.nullable select[ng-model="color"]')).click(); 20354 element.all(by.css('.nullable select[ng-model="color"] option')).first().click(); 20355 expect(element(by.binding('{selected_color:color}')).getText()).toMatch('null'); 20356 }); 20357 </doc:protractor> 20358 </doc:example> 20359 */ 20360 20361var ngOptionsDirective = valueFn({ terminal: true }); 20362// jshint maxlen: false 20363var selectDirective = ['$compile', '$parse', function($compile, $parse) { 20364 //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 20365 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]+?))?$/, 20366 nullModelCtrl = {$setViewValue: noop}; 20367// jshint maxlen: 100 20368 20369 return { 20370 restrict: 'E', 20371 require: ['select', '?ngModel'], 20372 controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 20373 var self = this, 20374 optionsMap = {}, 20375 ngModelCtrl = nullModelCtrl, 20376 nullOption, 20377 unknownOption; 20378 20379 20380 self.databound = $attrs.ngModel; 20381 20382 20383 self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { 20384 ngModelCtrl = ngModelCtrl_; 20385 nullOption = nullOption_; 20386 unknownOption = unknownOption_; 20387 }; 20388 20389 20390 self.addOption = function(value) { 20391 assertNotHasOwnProperty(value, '"option value"'); 20392 optionsMap[value] = true; 20393 20394 if (ngModelCtrl.$viewValue == value) { 20395 $element.val(value); 20396 if (unknownOption.parent()) unknownOption.remove(); 20397 } 20398 }; 20399 20400 20401 self.removeOption = function(value) { 20402 if (this.hasOption(value)) { 20403 delete optionsMap[value]; 20404 if (ngModelCtrl.$viewValue == value) { 20405 this.renderUnknownOption(value); 20406 } 20407 } 20408 }; 20409 20410 20411 self.renderUnknownOption = function(val) { 20412 var unknownVal = '? ' + hashKey(val) + ' ?'; 20413 unknownOption.val(unknownVal); 20414 $element.prepend(unknownOption); 20415 $element.val(unknownVal); 20416 unknownOption.prop('selected', true); // needed for IE 20417 }; 20418 20419 20420 self.hasOption = function(value) { 20421 return optionsMap.hasOwnProperty(value); 20422 }; 20423 20424 $scope.$on('$destroy', function() { 20425 // disable unknown option so that we don't do work when the whole select is being destroyed 20426 self.renderUnknownOption = noop; 20427 }); 20428 }], 20429 20430 link: function(scope, element, attr, ctrls) { 20431 // if ngModel is not defined, we don't need to do anything 20432 if (!ctrls[1]) return; 20433 20434 var selectCtrl = ctrls[0], 20435 ngModelCtrl = ctrls[1], 20436 multiple = attr.multiple, 20437 optionsExp = attr.ngOptions, 20438 nullOption = false, // if false, user will not be able to select it (used by ngOptions) 20439 emptyOption, 20440 // we can't just jqLite('<option>') since jqLite is not smart enough 20441 // to create it in <select> and IE barfs otherwise. 20442 optionTemplate = jqLite(document.createElement('option')), 20443 optGroupTemplate =jqLite(document.createElement('optgroup')), 20444 unknownOption = optionTemplate.clone(); 20445 20446 // find "null" option 20447 for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) { 20448 if (children[i].value === '') { 20449 emptyOption = nullOption = children.eq(i); 20450 break; 20451 } 20452 } 20453 20454 selectCtrl.init(ngModelCtrl, nullOption, unknownOption); 20455 20456 // required validator 20457 if (multiple) { 20458 ngModelCtrl.$isEmpty = function(value) { 20459 return !value || value.length === 0; 20460 }; 20461 } 20462 20463 if (optionsExp) setupAsOptions(scope, element, ngModelCtrl); 20464 else if (multiple) setupAsMultiple(scope, element, ngModelCtrl); 20465 else setupAsSingle(scope, element, ngModelCtrl, selectCtrl); 20466 20467
20468 //////////////////////////// 20469 20470 20471 20472 function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) { 20473 ngModelCtrl.$render = function() { 20474 var viewValue = ngModelCtrl.$viewValue; 20475 20476 if (selectCtrl.hasOption(viewValue)) { 20477 if (unknownOption.parent()) unknownOption.remove(); 20478 selectElement.val(viewValue); 20479 if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy 20480 } else { 20481 if (isUndefined(viewValue) && emptyOption) { 20482 selectElement.val(''); 20483 } else { 20484 selectCtrl.renderUnknownOption(viewValue); 20485 } 20486 } 20487 }; 20488 20489 selectElement.on('change', function() { 20490 scope.$apply(function() { 20491 if (unknownOption.parent()) unknownOption.remove(); 20492 ngModelCtrl.$setViewValue(selectElement.val()); 20493 }); 20494 }); 20495 } 20496 20497 function setupAsMultiple(scope, selectElement, ctrl) { 20498 var lastView; 20499 ctrl.$render = function() { 20500 var items = new HashMap(ctrl.$viewValue); 20501 forEach(selectElement.find('option'), function(option) { 20502 option.selected = isDefined(items.get(option.value)); 20503 }); 20504 }; 20505 20506 // we have to do it on each watch since ngModel watches reference, but 20507 // we need to work of an array, so we need to see if anything was inserted/removed 20508 scope.$watch(function selectMultipleWatch() { 20509 if (!equals(lastView, ctrl.$viewValue)) { 20510 lastView = copy(ctrl.$viewValue); 20511 ctrl.$render(); 20512 } 20513 }); 20514 20515 selectElement.on('change', function() { 20516 scope.$apply(function() { 20517 var array = []; 20518 forEach(selectElement.find('option'), function(option) { 20519 if (option.selected) { 20520 array.push(option.value); 20521 } 20522 }); 20523 ctrl.$setViewValue(array); 20524 }); 20525 }); 20526 } 20527 20528 function setupAsOptions(scope, selectElement, ctrl) { 20529 var match; 20530 20531 if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) { 20532 throw ngOptionsMinErr('iexp', 20533 "Expected expression in form of " + 20534 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 20535 " but got '{0}'. Element: {1}", 20536 optionsExp, startingTag(selectElement)); 20537 } 20538 20539 var displayFn = $parse(match[2] || match[1]), 20540 valueName = match[4] || match[6], 20541 keyName = match[5], 20542 groupByFn = $parse(match[3] || ''), 20543 valueFn = $parse(match[2] ? match[1] : valueName), 20544 valuesFn = $parse(match[7]), 20545 track = match[8], 20546 trackFn = track ? $parse(match[8]) : null, 20547 // This is an array of array of existing option groups in DOM. 20548 // We try to reuse these if possible 20549 // - optionGroupsCache[0] is the options with no option group 20550 // - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element 20551 optionGroupsCache = [[{element: selectElement, label:''}]]; 20552 20553 if (nullOption) { 20554 // compile the element since there might be bindings in it 20555 $compile(nullOption)(scope); 20556 20557 // remove the class, which is added automatically because we recompile the element and it 20558 // becomes the compilation root 20559 nullOption.removeClass('ng-scope'); 20560 20561 // we need to remove it before calling selectElement.empty() because otherwise IE will 20562 // remove the label from the element. wtf? 20563 nullOption.remove(); 20564 } 20565 20566 // clear contents, we'll add what's needed based on the model 20567 selectElement.empty(); 20568 20569 selectElement.on('change', function() { 20570 scope.$apply(function() { 20571 var optionGroup, 20572 collection = valuesFn(scope) || [], 20573 locals = {}, 20574 key, value, optionElement, index, groupIndex, length, groupLength, trackIndex; 20575 20576 if (multiple) { 20577 value = []; 20578 for (groupIndex = 0, groupLength = optionGroupsCache.length; 20579 groupIndex < groupLength; 20580 groupIndex++) { 20581 // list of options for that group. (first item has the parent) 20582 optionGroup = optionGroupsCache[groupIndex]; 20583 20584 for(index = 1, length = optionGroup.length; index < length; index++) { 20585 if ((optionElement = optionGroup[index].element)[0].selected) { 20586 key = optionElement.val(); 20587 if (keyName) locals[keyName] = key; 20588 if (trackFn) { 20589 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 20590 locals[valueName] = collection[trackIndex]; 20591 if (trackFn(scope, locals) == key) break; 20592 } 20593 } else { 20594 locals[valueName] = collection[key]; 20595 } 20596 value.push(valueFn(scope, locals)); 20597 } 20598 } 20599 } 20600 } else { 20601 key = selectElement.val(); 20602 if (key == '?') { 20603 value = undefined; 20604 } else if (key === ''){
20605 value = null; 20606 } else { 20607 if (trackFn) { 20608 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 20609 locals[valueName] = collection[trackIndex]; 20610 if (trackFn(scope, locals) == key) { 20611 value = valueFn(scope, locals); 20612 break; 20613 } 20614 } 20615 } else { 20616 locals[valueName] = collection[key]; 20617 if (keyName) locals[keyName] = key; 20618 value = valueFn(scope, locals); 20619 } 20620 } 20621 } 20622 ctrl.$setViewValue(value); 20623 }); 20624 }); 20625 20626 ctrl.$render = render; 20627 20628 // TODO(vojta): can't we optimize this ? 20629 scope.$watch(render); 20630 20631 function render() { 20632 // Temporary location for the option groups before we render them 20633 var optionGroups = {'':[]}, 20634 optionGroupNames = [''], 20635 optionGroupName, 20636 optionGroup, 20637 option, 20638 existingParent, existingOptions, existingOption, 20639 modelValue = ctrl.$modelValue, 20640 values = valuesFn(scope) || [], 20641 keys = keyName ? sortedKeys(values) : values, 20642 key, 20643 groupLength, length, 20644 groupIndex, index, 20645 locals = {}, 20646 selected, 20647 selectedSet = false, // nothing is selected yet 20648 lastElement, 20649 element, 20650 label; 20651 20652 if (multiple) { 20653 if (trackFn && isArray(modelValue)) { 20654 selectedSet = new HashMap([]); 20655 for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) { 20656 locals[valueName] = modelValue[trackIndex]; 20657 selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]); 20658 } 20659 } else { 20660 selectedSet = new HashMap(modelValue); 20661 } 20662 } 20663 20664 // We now build up the list of options we need (we merge later) 20665 for (index = 0; length = keys.length, index < length; index++) { 20666 20667 key = index; 20668 if (keyName) { 20669 key = keys[index]; 20670 if ( key.charAt(0) === '$' ) continue; 20671 locals[keyName] = key; 20672 } 20673 20674 locals[valueName] = values[key]; 20675 20676 optionGroupName = groupByFn(scope, locals) || ''; 20677 if (!(optionGroup = optionGroups[optionGroupName])) { 20678 optionGroup = optionGroups[optionGroupName] = []; 20679 optionGroupNames.push(optionGroupName); 20680 } 20681 if (multiple) { 20682 selected = isDefined( 20683 selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals)) 20684 ); 20685 } else { 20686 if (trackFn) { 20687 var modelCast = {}; 20688 modelCast[valueName] = modelValue; 20689 selected = trackFn(scope, modelCast) === trackFn(scope, locals); 20690 } else { 20691 selected = modelValue === valueFn(scope, locals); 20692 } 20693 selectedSet = selectedSet || selected; // see if at least one item is selected 20694 } 20695 label = displayFn(scope, locals); // what will be seen by the user 20696 20697 // doing displayFn(scope, locals) || '' overwrites zero values 20698 label = isDefined(label) ? label : ''; 20699 optionGroup.push({ 20700 // either the index into array or key from object 20701 id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index), 20702 label: label, 20703 selected: selected // determine if we should be selected 20704 }); 20705 } 20706 if (!multiple) { 20707 if (nullOption || modelValue === null) { 20708 // insert null option if we have a placeholder, or the model is null 20709 optionGroups[''].unshift({id:'', label:'', selected:!selectedSet}); 20710 } else if (!selectedSet) { 20711 // option could not be found, we have to insert the undefined item 20712 optionGroups[''].unshift({id:'?', label:'', selected:true}); 20713 } 20714 } 20715 20716 // Now we need to update the list of DOM nodes to match the optionGroups we computed above 20717 for (groupIndex = 0, groupLength = optionGroupNames.length; 20718 groupIndex < groupLength; 20719 groupIndex++) { 20720 // current option group name or '' if no group 20721 optionGroupName = optionGroupNames[groupIndex]; 20722 20723 // list of options for that group. (first item has the parent) 20724 optionGroup = optionGroups[optionGroupName]; 20725 20726 if (optionGroupsCache.length <= groupIndex) { 20727 // we need to grow the optionGroups 20728 existingParent = { 20729 element: optGroupTemplate.clone().attr('label', optionGroupName), 20730 label: optionGroup.label 20731 }; 20732 existingOptions = [existingParent]; 20733 optionGroupsCache.push(existingOptions); 20734 selectElement.append(existingParent.element); 20735 } else { 20736 existingOptions = optionGroupsCache[groupIndex]; 20737 existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element 20738 20739 // update the OPTGROUP label if not the same. 20740 if (existingParent.label != optionGroupName) { 20741 existingParent.element.attr('label', existingParent.label = optionGroupName); 20742 } 20743 } 20744 20745 lastElement = null; // start at the beginning 20746 for(index = 0, length = optionGroup.length; index < length; index++) { 20747 option = optionGroup[index]; 20748 if ((existingOption = existingOptions[index+1])) { 20749 // reuse elements 20750 lastElement = existingOption.element; 20751 if (existingOption.label !== option.label) { 20752 lastElement.text(existingOption.label = option.label); 20753 } 20754 if (existingOption.id !== option.id) { 20755 lastElement.val(existingOption.id = option.id); 20756 } 20757 // lastElement.prop('selected') provided by jQuery has side-effects 20758 if (lastElement[0].selected !== option.selected) {
20759 lastElement.prop('selected', (existingOption.selected = option.selected)); 20760 } 20761 } else { 20762 // grow elements 20763 20764 // if it's a null option 20765 if (option.id === '' && nullOption) { 20766 // put back the pre-compiled element 20767 element = nullOption; 20768 } else { 20769 // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but 20770 // in this version of jQuery on some browser the .text() returns a string 20771 // rather then the element. 20772 (element = optionTemplate.clone()) 20773 .val(option.id) 20774 .attr('selected', option.selected) 20775 .text(option.label); 20776 } 20777 20778 existingOptions.push(existingOption = { 20779 element: element, 20780 label: option.label, 20781 id: option.id, 20782 selected: option.selected 20783 }); 20784 if (lastElement) { 20785 lastElement.after(element); 20786 } else { 20787 existingParent.element.append(element); 20788 } 20789 lastElement = element; 20790 } 20791 } 20792 // remove any excessive OPTIONs in a group 20793 index++; // increment since the existingOptions[0] is parent element not OPTION 20794 while(existingOptions.length > index) { 20795 existingOptions.pop().element.remove(); 20796 } 20797 } 20798 // remove any excessive OPTGROUPs from select 20799 while(optionGroupsCache.length > groupIndex) { 20800 optionGroupsCache.pop()[0].element.remove(); 20801 } 20802 } 20803 } 20804 } 20805 }; 20806}]; 20807 20808var optionDirective = ['$interpolate', function($interpolate) { 20809 var nullSelectCtrl = { 20810 addOption: noop, 20811 removeOption: noop 20812 }; 20813 20814 return { 20815 restrict: 'E', 20816 priority: 100, 20817 compile: function(element, attr) { 20818 if (isUndefined(attr.value)) { 20819 var interpolateFn = $interpolate(element.text(), true); 20820 if (!interpolateFn) { 20821 attr.$set('value', element.text()); 20822 } 20823 } 20824 20825 return function (scope, element, attr) { 20826 var selectCtrlName = '$selectController', 20827 parent = element.parent(), 20828 selectCtrl = parent.data(selectCtrlName) || 20829 parent.parent().data(selectCtrlName); // in case we are in optgroup 20830 20831 if (selectCtrl && selectCtrl.databound) { 20832 // For some reason Opera defaults to true and if not overridden this messes up the repeater. 20833 // We don't want the view to drive the initialization of the model anyway. 20834 element.prop('selected', false); 20835 } else { 20836 selectCtrl = nullSelectCtrl; 20837 } 20838 20839 if (interpolateFn) { 20840 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 20841 attr.$set('value', newVal); 20842 if (newVal !== oldVal) selectCtrl.removeOption(oldVal); 20843 selectCtrl.addOption(newVal); 20844 }); 20845 } else { 20846 selectCtrl.addOption(attr.value); 20847 } 20848 20849 element.on('$destroy', function() { 20850 selectCtrl.removeOption(attr.value); 20851 }); 20852 }; 20853 } 20854 }; 20855}]; 20856 20857var styleDirective = valueFn({ 20858 restrict: 'E', 20859 terminal: true 20860}); 20861 20862 //try to bind to jquery now so that one can write angular.element().read() 20863 //but we will rebind on bootstrap again. 20864 bindJQuery(); 20865 20866 publishExternalAPI(angular); 20867 20868 jqLite(document).ready(function() { 20869 angularInit(document, bootstrap); 20870 }); 20871 20872})(window, document); 20873 20874!angular.$$csp() && 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;}</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.