1/** 2 * @license AngularJS v1.2.10 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.10/' + 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 164*/ 165
166//////////////////////////////////// 167 168/** 169 * @ngdoc function 170 * @name angular.lowercase 171 * @function 172 * 173 * @description Converts the specified string to lowercase. 174 * @param {string} string String to be converted to lowercase. 175 * @returns {string} Lowercased string. 176 */ 177var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; 178 179 180/** 181 * @ngdoc function 182 * @name angular.uppercase 183 * @function 184 * 185 * @description Converts the specified string to uppercase. 186 * @param {string} string String to be converted to uppercase. 187 * @returns {string} Uppercased string. 188 */ 189var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; 190 191 192var manualLowercase = function(s) { 193 /* jshint bitwise: false */ 194 return isString(s) 195 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 196 : s; 197}; 198var manualUppercase = function(s) { 199 /* jshint bitwise: false */ 200 return isString(s) 201 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 202 : s; 203}; 204 205 206// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 207// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 208// with correct but slower alternatives. 209if ('i' !== 'I'.toLowerCase()) { 210 lowercase = manualLowercase; 211 uppercase = manualUppercase; 212} 213 214 215var /** holds major version number for IE or NaN for real browsers */ 216 msie, 217 jqLite, // delay binding since jQuery could be loaded after us. 218 jQuery, // delay binding 219 slice = [].slice, 220 push = [].push, 221 toString = Object.prototype.toString, 222 ngMinErr = minErr('ng'), 223 224 225 _angular = window.angular, 226 /** @name angular */ 227 angular = window.angular || (window.angular = {}), 228 angularModule, 229 nodeName_, 230 uid = ['0', '0', '0']; 231 232/** 233 * IE 11 changed the format of the UserAgent string. 234 * See http://msdn.microsoft.com/en-us/library/ms537503.aspx 235 */ 236msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); 237if (isNaN(msie)) { 238 msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); 239} 240 241 242/** 243 * @private 244 * @param {*} obj 245 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 246 * String ...) 247 */ 248function isArrayLike(obj) { 249 if (obj == null || isWindow(obj)) { 250 return false; 251 } 252 253 var length = obj.length; 254 255 if (obj.nodeType === 1 && length) { 256 return true; 257 } 258 259 return isString(obj) || isArray(obj) || length === 0 || 260 typeof length === 'number' && length > 0 && (length - 1) in obj; 261} 262 263/** 264 * @ngdoc function 265 * @name angular.forEach 266 * @function 267 * 268 * @description 269 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 270 * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` 271 * is the value of an object property or an array element and `key` is the object property key or 272 * array element index. Specifying a `context` for the function is optional. 273 * 274 * It is worth nothing that `.forEach` does not iterate over inherited properties because it filters 275 * using the `hasOwnProperty` method. 276 * 277 <pre> 278 var values = {name: 'misko', gender: 'male'}; 279 var log = []; 280 angular.forEach(values, function(value, key){ 281 this.push(key + ': ' + value); 282 }, log); 283 expect(log).toEqual(['name: misko', 'gender:male']); 284 </pre> 285 * 286 * @param {Object|Array} obj Object to iterate over. 287 * @param {Function} iterator Iterator function. 288 * @param {Object=} context Object to become context (`this`) for the iterator function. 289 * @returns {Object|Array} Reference to `obj`. 290 */ 291function forEach(obj, iterator, context) { 292 var key; 293 if (obj) { 294 if (isFunction(obj)){ 295 for (key in obj) { 296 // Need to check if hasOwnProperty exists, 297 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 298 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 299 iterator.call(context, obj[key], key); 300 } 301 } 302 }
302 else if (obj.forEach && obj.forEach !== forEach) { 303 obj.forEach(iterator, context); 304 } else if (isArrayLike(obj)) { 305 for (key = 0; key < obj.length; key++) 306 iterator.call(context, obj[key], key); 307 } else { 308 for (key in obj) { 309 if (obj.hasOwnProperty(key)) { 310 iterator.call(context, obj[key], key); 311 } 312 } 313 } 314 } 315 return obj; 316} 317 318function sortedKeys(obj) { 319 var keys = []; 320 for (var key in obj) { 321 if (obj.hasOwnProperty(key)) { 322 keys.push(key); 323 } 324 } 325 return keys.sort(); 326} 327 328function forEachSorted(obj, iterator, context) { 329 var keys = sortedKeys(obj); 330 for ( var i = 0; i < keys.length; i++) { 331 iterator.call(context, obj[keys[i]], keys[i]); 332 } 333 return keys; 334} 335 336 337/** 338 * when using forEach the params are value, key, but it is often useful to have key, value. 339 * @param {function(string, *)} iteratorFn 340 * @returns {function(*, string)} 341 */ 342function reverseParams(iteratorFn) { 343 return function(value, key) { iteratorFn(key, value); }; 344} 345 346/** 347 * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric 348 * characters such as '012ABC'. The reason why we are not using simply a number counter is that 349 * the number string gets longer over time, and it can also overflow, where as the nextId 350 * will grow much slower, it is a string, and it will never overflow. 351 * 352 * @returns an unique alpha-numeric string 353 */ 354function nextUid() { 355 var index = uid.length; 356 var digit; 357 358 while(index) { 359 index--;
360 digit = uid[index].charCodeAt(0); 361 if (digit == 57 /*'9'*/) { 362 uid[index] = 'A'; 363 return uid.join(''); 364 } 365 if (digit == 90 /*'Z'*/) { 366 uid[index] = '0'; 367 } else { 368 uid[index] = String.fromCharCode(digit + 1); 369 return uid.join(''); 370 } 371 } 372 uid.unshift('0'); 373 return uid.join(''); 374} 375 376 377/** 378 * Set or clear the hashkey for an object. 379 * @param obj object 380 * @param h the hashkey (!truthy to delete the hashkey) 381 */ 382function setHashKey(obj, h) { 383 if (h) { 384 obj.$$hashKey = h; 385 } 386 else { 387 delete obj.$$hashKey; 388 } 389} 390 391/** 392 * @ngdoc function 393 * @name angular.extend 394 * @function 395 * 396 * @description 397 * Extends the destination object `dst` by copying all of the properties from the `src` object(s) 398 * to `dst`. You can specify multiple `src` objects. 399 * 400 * @param {Object} dst Destination object. 401 * @param {...Object} src Source object(s). 402 * @returns {Object} Reference to `dst`. 403 */ 404function extend(dst) { 405 var h = dst.$$hashKey; 406 forEach(arguments, function(obj){ 407 if (obj !== dst) { 408 forEach(obj, function(value, key){ 409 dst[key] = value; 410 }); 411 } 412 }); 413 414 setHashKey(dst,h); 415 return dst; 416} 417 418function int(str) { 419 return parseInt(str, 10); 420} 421 422 423function inherit(parent, extra) { 424 return extend(new (extend(function() {}, {prototype:parent}))(), extra); 425} 426 427/** 428 * @ngdoc function 429 * @name angular.noop 430 * @function 431 * 432 * @description 433 * A function that performs no operations. This function can be useful when writing code in the 434 * functional style. 435 <pre> 436 function foo(callback) { 437 var result = calculateResult(); 438 (callback || angular.noop)(result); 439 } 440 </pre> 441 */ 442function noop() {} 443noop.$inject = []; 444 445 446/** 447 * @ngdoc function 448 * @name angular.identity 449 * @function 450 * 451 * @description 452 * A function that returns its first argument. This function is useful when writing code in the 453 * functional style. 454 * 455 <pre> 456 function transformer(transformationFn, value) { 457 return (transformationFn || angular.identity)(value); 458 }; 459 </pre> 460 */ 461function identity($) {return $;} 462identity.$inject = []; 463 464 465function valueFn(value) {return function() {return value;};} 466 467/** 468 * @ngdoc function 469 * @name angular.isUndefined 470 * @function 471 * 472 * @description 473 * Determines if a reference is undefined. 474 * 475 * @param {*} value Reference to check. 476 * @returns {boolean} True if `value` is undefined. 477 */ 478function isUndefined(value){return typeof value === 'undefined';} 479 480 481/** 482 * @ngdoc function 483 * @name angular.isDefined 484 * @function 485 * 486 * @description 487 * Determines if a reference is defined. 488 * 489 * @param {*} value Reference to check. 490 * @returns {boolean} True if `value` is defined. 491 */ 492function isDefined(value){return typeof value !== 'undefined';} 493 494 495/** 496 * @ngdoc function 497 * @name angular.isObject 498 * @function 499 * 500 * @description 501 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 502 * considered to be objects. 503 * 504 * @param {*} value Reference to check. 505 * @returns {boolean} True if `value` is an `Object` but not `null`. 506 */ 507function isObject(value){return value != null && typeof value === 'object';} 508 509 510/** 511 * @ngdoc function 512 * @name angular.isString 513 * @function 514 * 515 * @description 516 * Determines if a reference is a `String`. 517 * 518 * @param {*} value Reference to check. 519 * @returns {boolean} True if `value` is a `String`. 520 */ 521function isString(value){return typeof value === 'string';} 522 523 524/** 525 * @ngdoc function 526 * @name angular.isNumber 527 * @function 528 * 529 * @description 530 * Determines if a reference is a `Number`. 531 * 532 * @param {*} value Reference to check. 533 * @returns {boolean} True if `value` is a `Number`. 534 */ 535function isNumber(value){return typeof value === 'number';} 536 537 538/** 539 * @ngdoc function 540 * @name angular.isDate 541 * @function 542 * 543 * @description 544 * Determines if a value is a date. 545 * 546 * @param {*} value Reference to check. 547 * @returns {boolean} True if `value` is a `Date`. 548 */ 549function isDate(value){ 550 return toString.call(value) === '[object Date]'; 551} 552 553 554/** 555 * @ngdoc function 556 * @name angular.isArray 557 * @function 558 * 559 * @description 560 * Determines if a reference is an `Array`. 561 * 562 * @param {*} value Reference to check. 563 * @returns {boolean} True if `value` is an `Array`. 564 */ 565function isArray(value) { 566 return toString.call(value) === '[object Array]'; 567} 568 569 570/** 571 * @ngdoc function 572 * @name angular.isFunction 573 * @function 574 * 575 * @description 576 * Determines if a reference is a `Function`. 577 * 578 * @param {*} value Reference to check. 579 * @returns {boolean} True if `value` is a `Function`. 580 */ 581function isFunction(value){return typeof value === 'function';} 582 583 584/** 585 * Determines if a value is a regular expression object. 586 * 587 * @private 588 * @param {*} value Reference to check. 589 * @returns {boolean} True if `value` is a `RegExp`. 590 */ 591function isRegExp(value) { 592 return toString.call(value) === '[object RegExp]'; 593} 594 595 596/** 597 * Checks if `obj` is a window object. 598 * 599 * @private 600 * @param {*} obj Object to check 601 * @returns {boolean} True if `obj` is a window obj. 602 */ 603function isWindow(obj) { 604 return obj && obj.document && obj.location && obj.alert && obj.setInterval; 605} 606 607 608function isScope(obj) { 609 return obj && obj.$evalAsync && obj.$watch; 610} 611 612 613function isFile(obj) { 614 return toString.call(obj) === '[object File]'; 615} 616 617 618function isBoolean(value) { 619 return typeof value === 'boolean'; 620} 621 622 623var trim = (function() { 624 // native trim is way faster: http://jsperf.com/angular-trim-test 625 // but IE doesn't have it... :-( 626 // TODO: we should move this into IE/ES5 polyfill 627 if (!String.prototype.trim) { 628 return function(value) { 629 return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value; 630 }; 631 } 632 return function(value) { 633 return isString(value) ? value.trim() : value; 634 }; 635})(); 636 637 638/** 639 * @ngdoc function 640 * @name angular.isElement 641 * @function 642 * 643 * @description 644 * Determines if a reference is a DOM element (or wrapped jQuery element). 645 * 646 * @param {*} value Reference to check. 647 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 648 */ 649function isElement(node) { 650 return !!(node && 651 (node.nodeName // we are a direct element 652 || (node.on && node.find))); // we have an on and find method part of jQuery API 653} 654 655/** 656 * @param str 'key1,key2,...' 657 * @returns {object} in the form of {key1:true, key2:true, ...} 658 */ 659function makeMap(str){ 660 var obj = {}, items = str.split(","), i; 661 for ( i = 0; i < items.length; i++ ) 662 obj[ items[i] ] = true; 663 return obj; 664} 665 666 667if (msie < 9) { 668 nodeName_ = function(element) { 669 element = element.nodeName ? element : element[0]; 670 return (element.scopeName && element.scopeName != 'HTML') 671 ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; 672 }; 673} else { 674 nodeName_ = function(element) { 675 return element.nodeName ? element.nodeName : element[0].nodeName; 676 }; 677} 678 679 680function map(obj, iterator, context) { 681 var results = []; 682 forEach(obj, function(value, index, list) { 683 results.push(iterator.call(context, value, index, list)); 684 }); 685 return results; 686} 687 688 689/** 690 * @description 691 * Determines the number of elements in an array, the number of properties an object has, or 692 * the length of a string. 693 *
694 * Note: This function is used to augment the Object type in Angular expressions. See 695 * {@link angular.Object} for more information about Angular arrays. 696 * 697 * @param {Object|Array|string} obj Object, array, or string to inspect. 698 * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object 699 * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. 700 */ 701function size(obj, ownPropsOnly) { 702 var count = 0, key; 703 704 if (isArray(obj) || isString(obj)) { 705 return obj.length; 706 } else if (isObject(obj)){ 707 for (key in obj) 708 if (!ownPropsOnly || obj.hasOwnProperty(key)) 709 count++; 710 } 711 712 return count; 713} 714 715 716function includes(array, obj) { 717 return indexOf(array, obj) != -1; 718} 719 720function indexOf(array, obj) { 721 if (array.indexOf) return array.indexOf(obj); 722 723 for (var i = 0; i < array.length; i++) { 724 if (obj === array[i]) return i; 725 } 726 return -1; 727} 728 729function arrayRemove(array, value) { 730 var index = indexOf(array, value); 731 if (index >=0) 732 array.splice(index, 1); 733 return value; 734} 735 736function isLeafNode (node) { 737 if (node) { 738 switch (node.nodeName) { 739 case "OPTION": 740 case "PRE": 741 case "TITLE": 742 return true; 743 } 744 } 745 return false; 746} 747 748/** 749 * @ngdoc function 750 * @name angular.copy 751 * @function 752 * 753 * @description 754 * Creates a deep copy of `source`, which should be an object or an array. 755 * 756 * * If no destination is supplied, a copy of the object or array is created. 757 * * If a destination is provided, all of its elements (for array) or properties (for objects) 758 * are deleted and then all elements/properties from the source are copied to it. 759 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 760 * * If `source` is identical to 'destination' an exception will be thrown. 761 * 762 * @param {*} source The source that will be used to make a copy. 763 * Can be any type, including primitives, `null`, and `undefined`. 764 * @param {(Object|Array)=} destination Destination into which the source is copied. If 765 * provided, must be of the same type as `source`. 766 * @returns {*} The copy or updated `destination`, if `destination` was specified. 767 * 768 * @example 769 <doc:example> 770 <doc:source> 771 <div ng-controller="Controller"> 772 <form novalidate class="simple-form"> 773 Name: <input type="text" ng-model="user.name" /><br /> 774 E-mail: <input type="email" ng-model="user.email" /><br /> 775 Gender: <input type="radio" ng-model="user.gender" value="male" />male 776 <input type="radio" ng-model="user.gender" value="female" />female<br /> 777 <button ng-click="reset()">RESET</button> 778 <button ng-click="update(user)">SAVE</button> 779 </form> 780 <pre>form = {{user | json}}</pre> 781 <pre>master = {{master | json}}</pre> 782 </div> 783 784 <script> 785 function Controller($scope) { 786 $scope.master= {}; 787 788 $scope.update = function(user) { 789 // Example with 1 argument 790 $scope.master= angular.copy(user); 791 }; 792 793 $scope.reset = function() { 794 // Example with 2 arguments 795 angular.copy($scope.master, $scope.user); 796 }; 797 798 $scope.reset(); 799 } 800 </script> 801 </doc:source> 802 </doc:example> 803 */ 804function copy(source, destination){ 805 if (isWindow(source) || isScope(source)) { 806 throw ngMinErr('cpws', 807 "Can't copy! Making copies of Window or Scope instances is not supported."); 808 } 809 810 if (!destination) { 811 destination = source; 812 if (source) { 813 if (isArray(source)) { 814 destination = copy(source, []); 815 } else if (isDate(source)) { 816 destination = new Date(source.getTime()); 817 } else if (isRegExp(source)) { 818 destination = new RegExp(source.source); 819 } else if (isObject(source)) { 820 destination = copy(source, {}); 821 } 822 } 823 } else { 824 if (source === destination) throw ngMinErr('cpi', 825 "Can't copy! Source and destination are identical."); 826 if (isArray(source)) { 827 destination.length = 0; 828 for ( var i = 0; i < source.length; i++) { 829 destination.push(copy(source[i])); 830 } 831 } else { 832 var h = destination.$$hashKey; 833 forEach(destination, function(value, key){ 834 delete destination[key]; 835 }); 836 for ( var key in source) { 837 destination[key] = copy(source[key]); 838 } 839 setHashKey(destination,h); 840 } 841 } 842 return destination; 843} 844 845/** 846 * Create a shallow copy of an object 847 */ 848function shallowCopy(src, dst) { 849 dst = dst || {}; 850 851 for(var key in src) { 852 // shallowCopy is only ever called by $compile nodeLinkFn, which has control over src 853 // so we don't need to worry about using our custom hasOwnProperty here 854 if (src.hasOwnProperty(key) && key.charAt(0) !== '$' && key.charAt(1) !== '$') { 855 dst[key] = src[key]; 856 } 857 } 858 859 return dst; 860} 861 862 863/** 864 * @ngdoc function 865 * @name angular.equals 866 * @function 867 * 868 * @description 869 * Determines if two objects or two values are equivalent. Supports value types, regular 870 * expressions, arrays and objects. 871 * 872 * Two objects or values are considered equivalent if at least one of the following is true: 873 * 874 * * Both objects or values pass `===` comparison. 875 * * Both objects or values are of the same type and all of their properties are equal by 876 * comparing them with `angular.equals`. 877 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 878 * * Both values represent the same regular expression (In JavasScript,
879 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 880 * representation matches). 881 * 882 * During a property comparison, properties of `function` type and properties with names 883 * that begin with `$` are ignored. 884 * 885 * Scope and DOMWindow objects are being compared only by identify (`===`). 886 * 887 * @param {*} o1 Object or value to compare. 888 * @param {*} o2 Object or value to compare. 889 * @returns {boolean} True if arguments are equal. 890 */ 891function equals(o1, o2) { 892 if (o1 === o2) return true; 893 if (o1 === null || o2 === null) return false; 894 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 895 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 896 if (t1 == t2) { 897 if (t1 == 'object') { 898 if (isArray(o1)) { 899 if (!isArray(o2)) return false; 900 if ((length = o1.length) == o2.length) { 901 for(key=0; key<length; key++) { 902 if (!equals(o1[key], o2[key])) return false; 903 } 904 return true; 905 } 906 } else if (isDate(o1)) { 907 return isDate(o2) && o1.getTime() == o2.getTime(); 908 } else if (isRegExp(o1) && isRegExp(o2)) { 909 return o1.toString() == o2.toString(); 910 } else { 911 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false; 912 keySet = {}; 913 for(key in o1) { 914 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 915 if (!equals(o1[key], o2[key])) return false; 916 keySet[key] = true; 917 } 918 for(key in o2) { 919 if (!keySet.hasOwnProperty(key) && 920 key.charAt(0) !== '$' && 921 o2[key] !== undefined && 922 !isFunction(o2[key])) return false; 923 } 924 return true; 925 } 926 } 927 } 928 return false; 929} 930 931 932function csp() { 933 return (document.securityPolicy && document.securityPolicy.isActive) || 934 (document.querySelector && 935 !!(document.querySelector('[ng-csp]') || document.querySelector('[data-ng-csp]'))); 936} 937 938 939function concat(array1, array2, index) { 940 return array1.concat(slice.call(array2, index)); 941} 942 943function sliceArgs(args, startIndex) { 944 return slice.call(args, startIndex || 0); 945} 946 947 948/* jshint -W101 */ 949/** 950 * @ngdoc function 951 * @name angular.bind 952 * @function 953 * 954 * @description 955 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 956 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 957 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 958 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 959 * 960 * @param {Object} self Context which `fn` should be evaluated in. 961 * @param {function()} fn Function to be bound. 962 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 963 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 964 */ 965/* jshint +W101 */ 966function bind(self, fn) { 967 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 968 if (isFunction(fn) && !(fn instanceof RegExp)) { 969 return curryArgs.length 970 ? function() { 971 return arguments.length 972 ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) 973 : fn.apply(self, curryArgs); 974 } 975 : function() { 976 return arguments.length 977 ? fn.apply(self, arguments) 978 : fn.call(self); 979 }; 980 } else { 981 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 982 return fn; 983 } 984} 985 986 987function toJsonReplacer(key, value) { 988 var val = value; 989 990 if (typeof key === 'string' && key.charAt(0) === '$') { 991 val = undefined; 992 } else if (isWindow(value)) { 993 val = '$WINDOW'; 994 } else if (value && document === value) { 995 val = '$DOCUMENT'; 996 } else if (isScope(value)) { 997 val = '$SCOPE'; 998 } 999 1000 return val; 1001} 1002 1003 1004/** 1005 * @ngdoc function 1006 * @name angular.toJson 1007 * @function 1008 * 1009 * @description 1010 * Serializes input into a JSON-formatted string. Properties with leading $ characters will be 1011 * stripped since angular uses this notation internally. 1012 * 1013 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1014 * @param {boolean=}
1014 pretty If set to true, the JSON output will contain newlines and whitespace. 1015 * @returns {string|undefined} JSON-ified string representing `obj`. 1016 */ 1017function toJson(obj, pretty) { 1018 if (typeof obj === 'undefined') return undefined; 1019 return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); 1020} 1021 1022 1023/** 1024 * @ngdoc function 1025 * @name angular.fromJson 1026 * @function 1027 * 1028 * @description 1029 * Deserializes a JSON string. 1030 * 1031 * @param {string} json JSON string to deserialize. 1032 * @returns {Object|Array|Date|string|number} Deserialized thingy. 1033 */ 1034function fromJson(json) { 1035 return isString(json) 1036 ? JSON.parse(json) 1037 : json; 1038} 1039 1040 1041function toBoolean(value) { 1042 if (typeof value === 'function') { 1043 value = true; 1044 } else if (value && value.length !== 0) { 1045 var v = lowercase("" + value); 1046 value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); 1047 } else { 1048 value = false; 1049 } 1050 return value; 1051} 1052 1053/** 1054 * @returns {string} Returns the string representation of the element. 1055 */ 1056function startingTag(element) { 1057 element = jqLite(element).clone(); 1058 try { 1059 // turns out IE does not let you set .html() on elements which 1060 // are not allowed to have children. So we just ignore it. 1061 element.empty(); 1062 } catch(e) {} 1063 // As Per DOM Standards 1064 var TEXT_NODE = 3; 1065 var elemHtml = jqLite('<div>').append(element).html(); 1066 try { 1067 return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : 1068 elemHtml. 1069 match(/^(<[^>]+>)/)[1]. 1070 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1071 } catch(e) { 1072 return lowercase(elemHtml); 1073 } 1074 1075} 1076 1077 1078///////////////////////////////////////////////// 1079 1080/** 1081 * Tries to decode the URI component without throwing an exception. 1082 * 1083 * @private 1084 * @param str value potential URI component to check. 1085 * @returns {boolean} True if `value` can be decoded 1086 * with the decodeURIComponent function. 1087 */ 1088function tryDecodeURIComponent(value) { 1089 try { 1090 return decodeURIComponent(value); 1091 } catch(e) { 1092 // Ignore any invalid uri component 1093 } 1094} 1095 1096 1097/** 1098 * Parses an escaped url query string into key-value pairs. 1099 * @returns Object.<(string|boolean)> 1100 */ 1101function parseKeyValue(/**string*/keyValue) { 1102 var obj = {}, key_value, key; 1103 forEach((keyValue || "").split('&'), function(keyValue){ 1104 if ( keyValue ) { 1105 key_value = keyValue.split('='); 1106 key = tryDecodeURIComponent(key_value[0]); 1107 if ( isDefined(key) ) { 1108 var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; 1109 if (!obj[key]) { 1110 obj[key] = val; 1111 } else if(isArray(obj[key])) { 1112 obj[key].push(val); 1113 } else { 1114 obj[key] = [obj[key],val]; 1115 } 1116 } 1117 } 1118 }); 1119 return obj; 1120} 1121 1122function toKeyValue(obj) { 1123 var parts = []; 1124 forEach(obj, function(value, key) { 1125 if (isArray(value)) { 1126 forEach(value, function(arrayValue) { 1127 parts.push(encodeUriQuery(key, true) + 1128 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1129 }); 1130 } else { 1131 parts.push(encodeUriQuery(key, true) + 1132 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1133 } 1134 }); 1135 return parts.length ? parts.join('&') : ''; 1136} 1137 1138 1139/** 1140 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1141 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1142 * segments: 1143 * segment = *pchar 1144 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1145 * pct-encoded = "%" HEXDIG HEXDIG 1146 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1147 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1148 * / "*" / "+" / "," / ";" / "=" 1149 */ 1150function encodeUriSegment(val) { 1151 return encodeUriQuery(val, true). 1152 replace(/%26/gi, '&'). 1153 replace(/%3D/gi, '='). 1154 replace(/%2B/gi, '+'); 1155} 1156 1157 1158/** 1159 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1160 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1161 * encoded per http://tools.ietf.org/html/rfc3986: 1162 * query = *( pchar / "/" / "?" ) 1163 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1164 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1165 * pct-encoded = "%" HEXDIG HEXDIG 1166 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1167 * / "*" / "+" / "," / ";" / "=" 1168 */ 1169function encodeUriQuery(val, pctEncodeSpaces) { 1170 return encodeURIComponent(val). 1171 replace(/%40/gi, '@'). 1172 replace(/%3A/gi, ':'). 1173 replace(/%24/g, '$'). 1174 replace(/%2C/gi, ','). 1175 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1176} 1177 1178 1179/** 1180 * @ngdoc directive 1181 * @name ng.directive:ngApp 1182 * 1183 * @element ANY 1184 * @param {angular.Module} ngApp an optional application 1185 * {@link angular.module module} name to load. 1186 * 1187 * @description 1188 * 1189 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1190 * designates the **root element** of the application and is typically placed near the root element 1191 * of the page - e.g. on the `<body>` or `<html>` tags. 1192 * 1193 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngAp
1193p` 1194 * found in the document will be used to define the root element to auto-bootstrap as an 1195 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1196 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1197 * 1198 * You can specify an **AngularJS module** to be used as the root module for the application. This 1199 * module will be loaded into the {@link AUTO.$injector} when the application is bootstrapped and 1200 * should contain the application code needed or have dependencies on other modules that will 1201 * contain the code. See {@link angular.module} for more information. 1202 * 1203 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1204 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1205 * would not be resolved to `3`. 1206 * 1207 * `ngApp` is the easiest, and most common, way to bootstrap an application. 1208 * 1209 <example module="ngAppDemo"> 1210 <file name="index.html"> 1211 <div ng-controller="ngAppDemoController"> 1212 I can add: {{a}} + {{b}} = {{ a+b }} 1213 </file> 1214 <file name="script.js"> 1215 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1216 $scope.a = 1; 1217 $scope.b = 2; 1218 }); 1219 </file> 1220 </example> 1221 * 1222 */ 1223function angularInit(element, bootstrap) { 1224 var elements = [element], 1225 appElement, 1226 module, 1227 names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], 1228 NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; 1229 1230 function append(element) { 1231 element && elements.push(element); 1232 } 1233 1234 forEach(names, function(name) { 1235 names[name] = true; 1236 append(document.getElementById(name)); 1237 name = name.replace(':', '\\:'); 1238 if (element.querySelectorAll) { 1239 forEach(element.querySelectorAll('.' + name), append); 1240 forEach(element.querySelectorAll('.' + name + '\\:'), append); 1241 forEach(element.querySelectorAll('[' + name + ']'), append); 1242 } 1243 }); 1244 1245 forEach(elements, function(element) { 1246 if (!appElement) { 1247 var className = ' ' + element.className + ' '; 1248 var match = NG_APP_CLASS_REGEXP.exec(className); 1249 if (match) { 1250 appElement = element; 1251 module = (match[2] || '').replace(/\s+/g, ','); 1252 } else { 1253 forEach(element.attributes, function(attr) { 1254 if (!appElement && names[attr.name]) { 1255 appElement = element; 1256 module = attr.value; 1257 } 1258 }); 1259 } 1260 } 1261 }); 1262 if (appElement) { 1263 bootstrap(appElement, module ? [module] : []); 1264 } 1265} 1266 1267/** 1268 * @ngdoc function 1269 * @name angular.bootstrap 1270 * @description 1271 * Use this function to manually start up angular application. 1272 * 1273 * See: {@link guide/bootstrap Bootstrap} 1274 * 1275 * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. 1276 * They must use {@link api/ng.directive:ngApp ngApp}. 1277 * 1278 * @param {Element} element DOM element which is the root of angular application. 1279 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1280 * Each item in the array should be the name of a predefined module or a (DI annotated) 1281 * function that will be invoked by the injector as a run block. 1282 * See: {@link angular.module modules} 1283 * @returns {AUTO.$injector} Returns the newly created injector for this app. 1284 */ 1285function bootstrap(element, modules) { 1286 var doBootstrap = function() { 1287 element = jqLite(element); 1288 1289 if (element.injector()) { 1290 var tag = (element[0] === document) ? 'document' : startingTag(element); 1291 throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); 1292 } 1293 1294 modules = modules || []; 1295 modules.unshift(['$provide', function($provide) { 1296 $provide.value('$rootElement', element); 1297 }]); 1298 modules.unshift('ng'); 1299 var injector = createInjector(modules); 1300 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', 1301 function(scope, element, compile, injector, animate) { 1302 scope.$apply(function() { 1303 element.data('$injector', injector); 1304 compile(element)(scope); 1305 }); 1306 }] 1307 ); 1308 return injector; 1309 }; 1310 1311 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1312 1313 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1314 return doBootstrap(); 1315 } 1316 1317 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1318 angular.resumeBootstrap = function(extraModules) {
1319 forEach(extraModules, function(module) { 1320 modules.push(module); 1321 }); 1322 doBootstrap(); 1323 }; 1324} 1325 1326var SNAKE_CASE_REGEXP = /[A-Z]/g; 1327function snake_case(name, separator){ 1328 separator = separator || '_'; 1329 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1330 return (pos ? separator : '') + letter.toLowerCase(); 1331 }); 1332} 1333 1334function bindJQuery() { 1335 // bind to jQuery if present; 1336 jQuery = window.jQuery; 1337 // reset to jQuery or default to us. 1338 if (jQuery) { 1339 jqLite = jQuery; 1340 extend(jQuery.fn, { 1341 scope: JQLitePrototype.scope, 1342 isolateScope: JQLitePrototype.isolateScope, 1343 controller: JQLitePrototype.controller, 1344 injector: JQLitePrototype.injector, 1345 inheritedData: JQLitePrototype.inheritedData 1346 }); 1347 // Method signature: 1348 // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) 1349 jqLitePatchJQueryRemove('remove', true, true, false); 1350 jqLitePatchJQueryRemove('empty', false, false, false); 1351 jqLitePatchJQueryRemove('html', false, false, true); 1352 } else { 1353 jqLite = JQLite; 1354 } 1355 angular.element = jqLite; 1356} 1357 1358/** 1359 * throw error if the argument is falsy. 1360 */ 1361function assertArg(arg, name, reason) { 1362 if (!arg) { 1363 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1364 } 1365 return arg; 1366} 1367 1368function assertArgFn(arg, name, acceptArrayAnnotation) { 1369 if (acceptArrayAnnotation && isArray(arg)) { 1370 arg = arg[arg.length - 1]; 1371 } 1372 1373 assertArg(isFunction(arg), name, 'not a function, got ' + 1374 (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1375 return arg; 1376} 1377 1378/** 1379 * throw error if the name given is hasOwnProperty 1380 * @param {String} name the name to test 1381 * @param {String} context the context in which the name is used, such as module or directive 1382 */ 1383function assertNotHasOwnProperty(name, context) { 1384 if (name === 'hasOwnProperty') { 1385 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1386 } 1387} 1388 1389/** 1390 * Return the value accessible from the object by path. Any undefined traversals are ignored 1391 * @param {Object} obj starting object 1392 * @param {string} path path to traverse 1393 * @param {boolean=true} bindFnToScope 1394 * @returns value as accessible by path 1395 */ 1396//TODO(misko): this function needs to be removed 1397function getter(obj, path, bindFnToScope) { 1398 if (!path) return obj; 1399 var keys = path.split('.'); 1400 var key; 1401 var lastInstance = obj; 1402 var len = keys.length; 1403 1404 for (var i = 0; i < len; i++) { 1405 key = keys[i]; 1406 if (obj) { 1407 obj = (lastInstance = obj)[key]; 1408 } 1409 } 1410 if (!bindFnToScope && isFunction(obj)) { 1411 return bind(lastInstance, obj); 1412 } 1413 return obj; 1414} 1415 1416/** 1417 * Return the DOM siblings between the first and last node in the given array. 1418 * @param {Array} array like object 1419 * @returns jQlite object containing the elements 1420 */ 1421function getBlockElements(nodes) { 1422 var startNode = nodes[0], 1423 endNode = nodes[nodes.length - 1]; 1424 if (startNode === endNode) { 1425 return jqLite(startNode); 1426 } 1427 1428 var element = startNode; 1429 var elements = [element]; 1430 1431 do { 1432 element = element.nextSibling; 1433 if (!element) break; 1434 elements.push(element); 1435 } while (element !== endNode); 1436 1437 return jqLite(elements); 1438} 1439 1440/** 1441 * @ngdoc interface 1442 * @name angular.Module 1443 * @description 1444 * 1445 * Interface for configuring angular {@link angular.module modules}. 1446 */ 1447 1448function setupModuleLoader(window) { 1449 1450 var $injectorMinErr = minErr('$injector'); 1451 var ngMinErr = minErr('ng'); 1452 1453 function ensure(obj, name, factory) { 1454 return obj[name] || (obj[name] = factory()); 1455 } 1456 1457 var angular = ensure(window, 'angular', Object); 1458 1459 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1460 angular.$$minErr = angular.$$minErr || minErr; 1461 1462 return ensure(angular, 'module', function() { 1463 /** @type {Object.<string, angular.Module>} */ 1464 var modules = {}; 1465 1466 /** 1467 * @ngdoc function 1468 * @name angular.module 1469 * @description 1470 * 1471 * The `angular.module` is a global place for creating, registering and retrieving Angular 1472 * modules. 1473 * All modules (angular core or 3rd party) that should be available to an application must be 1474 * registered using this mechanism. 1475 * 1476 * When passed two or more arguments, a new module is created. If passed only one argument, an 1477 * existing module (the name passed as the first argument to `module`) is retrieved. 1478 * 1479 * 1480 * # Module 1481 * 1482 * A module is a collection of services, directives, filters, and configuration information. 1483 * `angular.module` is used to configure the {@link AUTO.$injector $injector}. 1484 * 1485 * <pre> 1486 * // Create a new module 1487 * var myModule = angular.module('myModule', []); 1488 * 1489 * // register a new service 1490 * myModule.value('appName', 'MyCoolApp'); 1491 * 1492 * // configure existing services inside initialization blocks. 1493 * myModule.config(function($locationProvider) { 1494 * // Configure existing providers 1495 * $locationProvider.hashPrefix('!'); 1496 * }); 1497 * </pre> 1498 * 1499 * Then you can create an injector and load your modules like this: 1500 * 1501 * <pre> 1502 * var injector = angular.injector(['ng', 'MyModule']) 1503 * </pre> 1504 * 1505 * However it's more likely that you'll just use 1506 * {@link ng.directive:ngApp ngApp} or 1507 * {@link angular.bootstrap} to simplify this process for you. 1508 * 1509 * @param {!string} name The name of the module to create or retrieve. 1510 * @param {Array.<string>=} requires If specified then new module is being created. If 1511 * unspecified then the the module is being retrieved for further configuration. 1512 * @param {Function} configFn Optional configuration function for the module. Same as 1513 * {@link angular.Module#methods_config Module#config()}. 1514 * @returns {module} new module with the {@link angular.Module} api. 1515 */ 1516 return function module(name, requires, configFn) { 1517 var assertNotHasOwnProperty = function(name, context) { 1518 if (name === 'hasOwnProperty') { 1519 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 1520 } 1521 }; 1522 1523 assertNotHasOwnProperty(name, 'module'); 1524 if (requires && modules.hasOwnProperty(name)) { 1525 modules[name] = null; 1526 } 1527 return ensure(modules, name, function() { 1528 if (!requires) { 1529 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 1530 "the module name or forgot to load it. If registering a module ensure that you " + 1531 "specify the dependencies as the second argument.", name); 1532 } 1533
1534 /** @type {!Array.<Array.<*>>} */ 1535 var invokeQueue = []; 1536 1537 /** @type {!Array.<Function>} */ 1538 var runBlocks = []; 1539 1540 var config = invokeLater('$injector', 'invoke'); 1541 1542 /** @type {angular.Module} */ 1543 var moduleInstance = { 1544 // Private state 1545 _invokeQueue: invokeQueue, 1546 _runBlocks: runBlocks, 1547 1548 /** 1549 * @ngdoc property 1550 * @name angular.Module#requires 1551 * @propertyOf angular.Module 1552 * @returns {Array.<string>} List of module names which must be loaded before this module. 1553 * @description 1554 * Holds the list of modules which the injector will load before the current module is 1555 * loaded. 1556 */ 1557 requires: requires, 1558 1559 /** 1560 * @ngdoc property 1561 * @name angular.Module#name 1562 * @propertyOf angular.Module 1563 * @returns {string} Name of the module. 1564 * @description 1565 */ 1566 name: name, 1567 1568 1569 /** 1570 * @ngdoc method 1571 * @name angular.Module#provider 1572 * @methodOf angular.Module 1573 * @param {string} name service name 1574 * @param {Function} providerType Construction function for creating new instance of the 1575 * service. 1576 * @description 1577 * See {@link AUTO.$provide#provider $provide.provider()}. 1578 */ 1579 provider: invokeLater('$provide', 'provider'), 1580 1581 /** 1582 * @ngdoc method 1583 * @name angular.Module#factory 1584 * @methodOf angular.Module 1585 * @param {string} name service name 1586 * @param {Function} providerFunction Function for creating new instance of the service. 1587 * @description 1588 * See {@link AUTO.$provide#factory $provide.factory()}. 1589 */ 1590 factory: invokeLater('$provide', 'factory'), 1591 1592 /** 1593 * @ngdoc method 1594 * @name angular.Module#service 1595 * @methodOf angular.Module 1596 * @param {string} name service name 1597 * @param {Function} constructor A constructor function that will be instantiated. 1598 * @description 1599 * See {@link AUTO.$provide#service $provide.service()}. 1600 */ 1601 service: invokeLater('$provide', 'service'), 1602 1603 /** 1604 * @ngdoc method 1605 * @name angular.Module#value 1606 * @methodOf angular.Module 1607 * @param {string} name service name 1608 * @param {*} object Service instance object. 1609 * @description 1610 * See {@link AUTO.$provide#value $provide.value()}. 1611 */ 1612 value: invokeLater('$provide', 'value'), 1613 1614 /** 1615 * @ngdoc method 1616 * @name angular.Module#constant 1617 * @methodOf angular.Module 1618 * @param {string} name constant name 1619 * @param {*} object Constant value. 1620 * @description 1621 * Because the constant are fixed, they get applied before other provide methods. 1622 * See {@link AUTO.$provide#constant $provide.constant()}. 1623 */ 1624 constant: invokeLater('$provide', 'constant', 'unshift'), 1625 1626 /** 1627 * @ngdoc method 1628 * @name angular.Module#animation 1629 * @methodOf angular.Module 1630 * @param {string} name animation name 1631 * @param {Function} animationFactory Factory function for creating new instance of an 1632 * animation. 1633 * @description 1634 * 1635 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 1636 * 1637 * 1638 * Defines an animation hook that can be later used with 1639 * {@link ngAnimate.$animate $animate} service and directives that use this service. 1640 * 1641 * <pre> 1642 * module.animation('.animation-name', function($inject1, $inject2) { 1643 * return { 1644 * eventName : function(element, done) { 1645 * //code to run the animation 1646 * //once complete, then run done() 1647 * return function cancellationFunction(element) { 1648 * //code to cancel the animation 1649 * } 1650 * } 1651 * } 1652 * }) 1653 * </pre> 1654 * 1655 * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and 1656 * {@link ngAnimate ngAnimate module} for more information. 1657 */ 1658 animation: invokeLater('$animateProvider', 'register'), 1659 1660 /** 1661 * @ngdoc method 1662 * @name angular.Module#filter 1663 * @methodOf angular.Module 1664 * @param {string} name Filter name. 1665 * @param {Function} filterFactory Factory function for creating new instance of filter.
1666 * @description 1667 * See {@link ng.$filterProvider#register $filterProvider.register()}. 1668 */ 1669 filter: invokeLater('$filterProvider', 'register'), 1670 1671 /** 1672 * @ngdoc method 1673 * @name angular.Module#controller 1674 * @methodOf angular.Module 1675 * @param {string|Object} name Controller name, or an object map of controllers where the 1676 * keys are the names and the values are the constructors. 1677 * @param {Function} constructor Controller constructor function. 1678 * @description 1679 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 1680 */ 1681 controller: invokeLater('$controllerProvider', 'register'), 1682 1683 /** 1684 * @ngdoc method 1685 * @name angular.Module#directive 1686 * @methodOf angular.Module 1687 * @param {string|Object} name Directive name, or an object map of directives where the 1688 * keys are the names and the values are the factories. 1689 * @param {Function} directiveFactory Factory function for creating new instance of 1690 * directives. 1691 * @description 1692 * See {@link ng.$compileProvider#methods_directive $compileProvider.directive()}. 1693 */ 1694 directive: invokeLater('$compileProvider', 'directive'), 1695 1696 /** 1697 * @ngdoc method 1698 * @name angular.Module#config 1699 * @methodOf angular.Module 1700 * @param {Function} configFn Execute this function on module load. Useful for service 1701 * configuration. 1702 * @description 1703 * Use this method to register work which needs to be performed on module loading. 1704 */ 1705 config: config, 1706 1707 /** 1708 * @ngdoc method 1709 * @name angular.Module#run 1710 * @methodOf angular.Module 1711 * @param {Function} initializationFn Execute this function after injector creation. 1712 * Useful for application initialization. 1713 * @description 1714 * Use this method to register work which should be performed when the injector is done 1715 * loading all modules. 1716 */ 1717 run: function(block) { 1718 runBlocks.push(block); 1719 return this; 1720 } 1721 }; 1722 1723 if (configFn) { 1724 config(configFn); 1725 } 1726 1727 return moduleInstance; 1728 1729 /** 1730 * @param {string} provider 1731 * @param {string} method 1732 * @param {String=} insertMethod 1733 * @returns {angular.Module} 1734 */ 1735 function invokeLater(provider, method, insertMethod) { 1736 return function() { 1737 invokeQueue[insertMethod || 'push']([provider, method, arguments]); 1738 return moduleInstance; 1739 }; 1740 } 1741 }); 1742 }; 1743 }); 1744 1745} 1746 1747/* global 1748 angularModule: true, 1749 version: true, 1750 1751 $LocaleProvider, 1752 $CompileProvider, 1753 1754 htmlAnchorDirective, 1755 inputDirective, 1756 inputDirective, 1757 formDirective, 1758 scriptDirective, 1759 selectDirective, 1760 styleDirective, 1761 optionDirective, 1762 ngBindDirective, 1763 ngBindHtmlDirective, 1764 ngBindTemplateDirective, 1765 ngClassDirective, 1766 ngClassEvenDirective, 1767 ngClassOddDirective, 1768 ngCspDirective, 1769 ngCloakDirective, 1770 ngControllerDirective, 1771 ngFormDirective, 1772 ngHideDirective, 1773 ngIfDirective, 1774 ngIncludeDirective, 1775 ngIncludeFillContentDirective, 1776 ngInitDirective, 1777 ngNonBindableDirective, 1778 ngPluralizeDirective, 1779 ngRepeatDirective, 1780 ngShowDirective, 1781 ngStyleDirective, 1782 ngSwitchDirective, 1783 ngSwitchWhenDirective, 1784 ngSwitchDefaultDirective, 1785 ngOptionsDirective, 1786 ngTranscludeDirective, 1787 ngModelDirective, 1788 ngListDirective, 1789 ngChangeDirective, 1790 requiredDirective, 1791 requiredDirective, 1792 ngValueDirective, 1793 ngAttributeAliasDirectives, 1794 ngEventDirectives, 1795 1796 $AnchorScrollProvider, 1797 $AnimateProvider, 1798 $BrowserProvider, 1799 $CacheFactoryProvider, 1800 $ControllerProvider, 1801 $DocumentProvider, 1802 $ExceptionHandlerProvider, 1803 $FilterProvider, 1804 $InterpolateProvider, 1805 $IntervalProvider, 1806 $HttpProvider, 1807 $HttpBackendProvider, 1808 $LocationProvider, 1809 $LogProvider, 1810 $ParseProvider, 1811 $RootScopeProvider, 1812 $QProvider,
1813 $$SanitizeUriProvider, 1814 $SceProvider, 1815 $SceDelegateProvider, 1816 $SnifferProvider, 1817 $TemplateCacheProvider, 1818 $TimeoutProvider, 1819 $WindowProvider 1820*/ 1821 1822 1823/** 1824 * @ngdoc property 1825 * @name angular.version 1826 * @description 1827 * An object that contains information about the current AngularJS version. This object has the 1828 * following properties: 1829 * 1830 * - `full` â `{string}` â Full version string, such as "0.9.18". 1831 * - `major` â `{number}` â Major version number, such as "0". 1832 * - `minor` â `{number}` â Minor version number, such as "9". 1833 * - `dot` â `{number}` â Dot version number, such as "18". 1834 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 1835 */ 1836var version = { 1837 full: '1.2.10', // all of these placeholder strings will be replaced by grunt's 1838 major: 1, // package task 1839 minor: 2, 1840 dot: 10, 1841 codeName: 'augmented-serendipity' 1842}; 1843 1844 1845function publishExternalAPI(angular){ 1846 extend(angular, { 1847 'bootstrap': bootstrap, 1848 'copy': copy, 1849 'extend': extend, 1850 'equals': equals, 1851 'element': jqLite, 1852 'forEach': forEach, 1853 'injector': createInjector, 1854 'noop':noop, 1855 'bind':bind, 1856 'toJson': toJson, 1857 'fromJson': fromJson, 1858 'identity':identity, 1859 'isUndefined': isUndefined, 1860 'isDefined': isDefined, 1861 'isString': isString, 1862 'isFunction': isFunction, 1863 'isObject': isObject, 1864 'isNumber': isNumber, 1865 'isElement': isElement, 1866 'isArray': isArray, 1867 'version': version, 1868 'isDate': isDate, 1869 'lowercase': lowercase, 1870 'uppercase': uppercase, 1871 'callbacks': {counter: 0}, 1872 '$$minErr': minErr, 1873 '$$csp': csp 1874 }); 1875 1876 angularModule = setupModuleLoader(window); 1877 try { 1878 angularModule('ngLocale'); 1879 } catch (e) { 1880 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 1881 } 1882 1883 angularModule('ng', ['ngLocale'], ['$provide', 1884 function ngModule($provide) { 1885 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 1886 $provide.provider({ 1887 $$sanitizeUri: $$SanitizeUriProvider 1888 }); 1889 $provide.provider('$compile', $CompileProvider). 1890 directive({ 1891 a: htmlAnchorDirective, 1892 input: inputDirective, 1893 textarea: inputDirective, 1894 form: formDirective, 1895 script: scriptDirective, 1896 select: selectDirective, 1897 style: styleDirective, 1898 option: optionDirective, 1899 ngBind: ngBindDirective, 1900 ngBindHtml: ngBindHtmlDirective, 1901 ngBindTemplate: ngBindTemplateDirective, 1902 ngClass: ngClassDirective, 1903 ngClassEven: ngClassEvenDirective, 1904 ngClassOdd: ngClassOddDirective, 1905 ngCloak: ngCloakDirective, 1906 ngController: ngControllerDirective, 1907 ngForm: ngFormDirective, 1908 ngHide: ngHideDirective, 1909 ngIf: ngIfDirective, 1910 ngInclude: ngIncludeDirective, 1911 ngInit: ngInitDirective, 1912 ngNonBindable: ngNonBindableDirective, 1913 ngPluralize: ngPluralizeDirective, 1914 ngRepeat: ngRepeatDirective, 1915 ngShow: ngShowDirective, 1916 ngStyle: ngStyleDirective, 1917 ngSwitch: ngSwitchDirective, 1918 ngSwitchWhen: ngSwitchWhenDirective, 1919 ngSwitchDefault: ngSwitchDefaultDirective, 1920 ngOptions: ngOptionsDirective, 1921 ngTransclude: ngTranscludeDirective, 1922 ngModel: ngModelDirective, 1923 ngList: ngListDirective, 1924 ngChange: ngChangeDirective, 1925 required: requiredDirective, 1926 ngRequired: requiredDirective, 1927 ngValue: ngValueDirective 1928 }). 1929 directive({ 1930 ngInclude: ngIncludeFillContentDirective 1931 }). 1932 directive(ngAttributeAliasDirectives). 1933 directive(ngEventDirectives); 1934 $provide.provider({ 1935 $anchorScroll: $AnchorScrollProvider, 1936 $animate: $AnimateProvider, 1937 $browser: $BrowserProvider, 1938 $cacheFactory: $CacheFactoryProvider, 1939 $controller: $ControllerProvider, 1940 $document: $DocumentProvider, 1941 $exceptionHandler: $ExceptionHandlerProvider, 1942 $filter: $FilterProvider, 1943 $interpolate: $InterpolateProvider, 1944 $interval: $IntervalProvider, 1945 $http: $HttpProvider, 1946 $httpBackend: $HttpBackendProvider, 1947 $location: $LocationProvider, 1948 $log: $LogProvider, 1949 $parse: $ParseProvider, 1950 $rootScope: $RootScopeProvider, 1951 $q: $QProvider, 1952 $sce: $SceProvider, 1953 $sceDelegate: $SceDelegateProvider, 1954 $sniffer: $SnifferProvider, 1955 $templateCache: $TemplateCacheProvider, 1956 $timeout: $TimeoutProvider, 1957 $window: $WindowProvider 1958 }); 1959 } 1960 ]); 1961} 1962 1963/* global 1964 1965 -JQLitePrototype, 1966 -addEventListenerFn, 1967 -removeEventListenerFn, 1968 -BOOLEAN_ATTR 1969*/ 1970
1971////////////////////////////////// 1972//JQLite 1973////////////////////////////////// 1974 1975/** 1976 * @ngdoc function 1977 * @name angular.element 1978 * @function 1979 * 1980 * @description 1981 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 1982 * 1983 * If jQuery is available, `angular.element` is an alias for the 1984 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 1985 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." 1986 * 1987 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows 1988 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most 1989 * commonly needed functionality with the goal of having a very small footprint.</div> 1990 * 1991 * To use jQuery, simply load it before `DOMContentLoaded` event fired. 1992 * 1993 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or 1994 * jqLite; they are never raw DOM references.</div> 1995 * 1996 * ## Angular's jqLite 1997 * jqLite provides only the following jQuery methods: 1998 * 1999 * - [`addClass()`](http://api.jquery.com/addClass/) 2000 * - [`after()`](http://api.jquery.com/after/) 2001 * - [`append()`](http://api.jquery.com/append/) 2002 * - [`attr()`](http://api.jquery.com/attr/) 2003 * - [`bind()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2004 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2005 * - [`clone()`](http://api.jquery.com/clone/) 2006 * - [`contents()`](http://api.jquery.com/contents/) 2007 * - [`css()`](http://api.jquery.com/css/) 2008 * - [`data()`](http://api.jquery.com/data/) 2009 * - [`empty()`](http://api.jquery.com/empty/) 2010 * - [`eq()`](http://api.jquery.com/eq/) 2011 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2012 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2013 * - [`html()`](http://api.jquery.com/html/) 2014 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2015 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2016 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors 2017 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2018 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2019 * - [`prepend()`](http://api.jquery.com/prepend/) 2020 * - [`prop()`](http://api.jquery.com/prop/) 2021 * - [`ready()`](http://api.jquery.com/ready/) 2022 * - [`remove()`](http://api.jquery.com/remove/) 2023 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2024 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2025 * - [`removeData()`](http://api.jquery.com/removeData/) 2026 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2027 * - [`text()`](http://api.jquery.com/text/) 2028 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2029 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2030 * - [`unbind()`](http://api.jquery.com/off/) - Does not support namespaces 2031 * - [`val()`](http://api.jquery.com/val/) 2032 * - [`wrap()`](http://api.jquery.com/wrap/) 2033 * 2034 * ## jQuery/jqLite Extras 2035 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2036 * 2037 * ### Events 2038 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event 2039 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2040 * element before it is removed. 2041 * 2042 * ### Methods 2043 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2044 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2045 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2046 * `'ngModel'`). 2047 * - `injector()` - retrieves the injector of the current element or its parent. 2048 * - `scope()` - retrieves the {@link api/ng.$rootScope.Scope scope} of the current 2049 * element or its parent. 2050 * - `isolateScope()` - retrieves an isolate {@link api/ng.$rootScope.Scope scope} if one is attached directly to the 2051 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
2052 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2053 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2054 * parent element is reached. 2055 * 2056 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2057 * @returns {Object} jQuery object. 2058 */ 2059 2060var jqCache = JQLite.cache = {}, 2061 jqName = JQLite.expando = 'ng-' + new Date().getTime(), 2062 jqId = 1, 2063 addEventListenerFn = (window.document.addEventListener 2064 ? function(element, type, fn) {element.addEventListener(type, fn, false);} 2065 : function(element, type, fn) {element.attachEvent('on' + type, fn);}), 2066 removeEventListenerFn = (window.document.removeEventListener 2067 ? function(element, type, fn) {element.removeEventListener(type, fn, false); } 2068 : function(element, type, fn) {element.detachEvent('on' + type, fn); }); 2069 2070function jqNextId() { return ++jqId; } 2071 2072 2073var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2074var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2075var jqLiteMinErr = minErr('jqLite'); 2076 2077/** 2078 * Converts snake_case to camelCase. 2079 * Also there is special case for Moz prefix starting with upper case letter. 2080 * @param name Name to normalize 2081 */ 2082function camelCase(name) { 2083 return name. 2084 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2085 return offset ? letter.toUpperCase() : letter; 2086 }). 2087 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2088} 2089 2090///////////////////////////////////////////// 2091// jQuery mutation patch 2092// 2093// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a 2094// $destroy event on all DOM nodes being removed. 2095// 2096///////////////////////////////////////////// 2097 2098function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { 2099 var originalJqFn = jQuery.fn[name]; 2100 originalJqFn = originalJqFn.$original || originalJqFn; 2101 removePatch.$original = originalJqFn; 2102 jQuery.fn[name] = removePatch; 2103 2104 function removePatch(param) { 2105 // jshint -W040 2106 var list = filterElems && param ? [this.filter(param)] : [this], 2107 fireEvent = dispatchThis, 2108 set, setIndex, setLength, 2109 element, childIndex, childLength, children; 2110 2111 if (!getterIfNoArguments || param != null) { 2112 while(list.length) { 2113 set = list.shift(); 2114 for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { 2115 element = jqLite(set[setIndex]); 2116 if (fireEvent) { 2117 element.triggerHandler('$destroy'); 2118 } else { 2119 fireEvent = !fireEvent; 2120 } 2121 for(childIndex = 0, childLength = (children = element.children()).length; 2122 childIndex < childLength; 2123 childIndex++) { 2124 list.push(jQuery(children[childIndex])); 2125 } 2126 } 2127 } 2128 } 2129 return originalJqFn.apply(this, arguments); 2130 } 2131} 2132 2133///////////////////////////////////////////// 2134function JQLite(element) { 2135 if (element instanceof JQLite) { 2136 return element; 2137 } 2138 if (!(this instanceof JQLite)) { 2139 if (isString(element) && element.charAt(0) != '<') { 2140 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2141 } 2142 return new JQLite(element); 2143 } 2144 2145 if (isString(element)) { 2146 var div = document.createElement('div'); 2147 // Read about the NoScope elements here: 2148 // http://msdn.microsoft.com/en-us/library/ms533897(VS.85).aspx 2149 div.innerHTML = '<div> </div>' + element; // IE insanity to make NoScope elements work! 2150 div.removeChild(div.firstChild); // remove the superfluous div 2151 jqLiteAddNodes(this, div.childNodes); 2152 var fragment = jqLite(document.createDocumentFragment()); 2153 fragment.append(this); // detach the elements from the temporary DOM div. 2154 } else { 2155 jqLiteAddNodes(this, element); 2156 } 2157} 2158 2159function jqLiteClone(element) { 2160 return element.cloneNode(true); 2161} 2162 2163function jqLiteDealoc(element){ 2164 jqLiteRemoveData(element); 2165 for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { 2166 jqLiteDealoc(children[i]); 2167 } 2168} 2169 2170function jqLiteOff(element, type, fn, unsupported) {
2171 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2172 2173 var events = jqLiteExpandoStore(element, 'events'), 2174 handle = jqLiteExpandoStore(element, 'handle'); 2175 2176 if (!handle) return; //no listeners registered 2177 2178 if (isUndefined(type)) { 2179 forEach(events, function(eventHandler, type) { 2180 removeEventListenerFn(element, type, eventHandler); 2181 delete events[type]; 2182 }); 2183 } else { 2184 forEach(type.split(' '), function(type) { 2185 if (isUndefined(fn)) { 2186 removeEventListenerFn(element, type, events[type]); 2187 delete events[type]; 2188 } else { 2189 arrayRemove(events[type] || [], fn); 2190 } 2191 }); 2192 } 2193} 2194 2195function jqLiteRemoveData(element, name) { 2196 var expandoId = element[jqName], 2197 expandoStore = jqCache[expandoId]; 2198 2199 if (expandoStore) { 2200 if (name) { 2201 delete jqCache[expandoId].data[name]; 2202 return; 2203 } 2204 2205 if (expandoStore.handle) { 2206 expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); 2207 jqLiteOff(element); 2208 } 2209 delete jqCache[expandoId]; 2210 element[jqName] = undefined; // ie does not allow deletion of attributes on elements. 2211 } 2212} 2213 2214function jqLiteExpandoStore(element, key, value) { 2215 var expandoId = element[jqName], 2216 expandoStore = jqCache[expandoId || -1]; 2217 2218 if (isDefined(value)) { 2219 if (!expandoStore) { 2220 element[jqName] = expandoId = jqNextId(); 2221 expandoStore = jqCache[expandoId] = {}; 2222 } 2223 expandoStore[key] = value; 2224 } else { 2225 return expandoStore && expandoStore[key]; 2226 } 2227} 2228 2229function jqLiteData(element, key, value) { 2230 var data = jqLiteExpandoStore(element, 'data'), 2231 isSetter = isDefined(value), 2232 keyDefined = !isSetter && isDefined(key), 2233 isSimpleGetter = keyDefined && !isObject(key); 2234 2235 if (!data && !isSimpleGetter) { 2236 jqLiteExpandoStore(element, 'data', data = {}); 2237 } 2238 2239 if (isSetter) { 2240 data[key] = value; 2241 } else { 2242 if (keyDefined) { 2243 if (isSimpleGetter) { 2244 // don't create data in this case. 2245 return data && data[key]; 2246 } else { 2247 extend(data, key); 2248 } 2249 } else { 2250 return data; 2251 } 2252 } 2253} 2254 2255function jqLiteHasClass(element, selector) { 2256 if (!element.getAttribute) return false; 2257 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2258 indexOf( " " + selector + " " ) > -1); 2259} 2260 2261function jqLiteRemoveClass(element, cssClasses) { 2262 if (cssClasses && element.setAttribute) { 2263 forEach(cssClasses.split(' '), function(cssClass) { 2264 element.setAttribute('class', trim( 2265 (" " + (element.getAttribute('class') || '') + " ") 2266 .replace(/[\n\t]/g, " ") 2267 .replace(" " + trim(cssClass) + " ", " ")) 2268 ); 2269 }); 2270 } 2271} 2272 2273function jqLiteAddClass(element, cssClasses) { 2274 if (cssClasses && element.setAttribute) { 2275 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 2276 .replace(/[\n\t]/g, " "); 2277 2278 forEach(cssClasses.split(' '), function(cssClass) { 2279 cssClass = trim(cssClass); 2280 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 2281 existingClasses += cssClass + ' '; 2282 } 2283 }); 2284 2285 element.setAttribute('class', trim(existingClasses)); 2286 } 2287} 2288 2289function jqLiteAddNodes(root, elements) { 2290 if (elements) { 2291 elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) 2292 ? elements 2293 : [ elements ]; 2294 for(var i=0; i < elements.length; i++) { 2295 root.push(elements[i]); 2296 } 2297 } 2298} 2299 2300function jqLiteController(element, name) { 2301 return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); 2302} 2303 2304function jqLiteInheritedData(element, name, value) { 2305 element = jqLite(element); 2306 2307 // if element is the document object work with the html element instead 2308 // this makes $(document).scope() possible 2309 if(element[0].nodeType == 9) { 2310 element = element.find('html'); 2311 } 2312 var names = isArray(name) ? name : [name]; 2313 2314 while (element.length) { 2315 2316 for (var i = 0, ii = names.length; i < ii; i++) { 2317 if ((value = element.data(names[i])) !== undefined) return value; 2318 } 2319 element = element.parent(); 2320 } 2321} 2322 2323function jqLiteEmpty(element) { 2324 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2325 jqLiteDealoc(childNodes[i]); 2326 } 2327 while (element.firstChild) { 2328 element.removeChild(element.firstChild); 2329 } 2330} 2331
2332////////////////////////////////////////// 2333// Functions which are declared directly. 2334////////////////////////////////////////// 2335var JQLitePrototype = JQLite.prototype = { 2336 ready: function(fn) { 2337 var fired = false; 2338 2339 function trigger() { 2340 if (fired) return; 2341 fired = true; 2342 fn(); 2343 } 2344 2345 // check if document already is loaded 2346 if (document.readyState === 'complete'){ 2347 setTimeout(trigger); 2348 } else { 2349 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 2350 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 2351 // jshint -W064 2352 JQLite(window).on('load', trigger); // fallback to window.onload for others 2353 // jshint +W064 2354 } 2355 }, 2356 toString: function() { 2357 var value = []; 2358 forEach(this, function(e){ value.push('' + e);}); 2359 return '[' + value.join(', ') + ']'; 2360 }, 2361 2362 eq: function(index) { 2363 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 2364 }, 2365 2366 length: 0, 2367 push: push, 2368 sort: [].sort, 2369 splice: [].splice 2370}; 2371 2372////////////////////////////////////////// 2373// Functions iterating getter/setters. 2374// these functions return self on setter and 2375// value on get. 2376////////////////////////////////////////// 2377var BOOLEAN_ATTR = {}; 2378forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 2379 BOOLEAN_ATTR[lowercase(value)] = value; 2380}); 2381var BOOLEAN_ELEMENTS = {}; 2382forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 2383 BOOLEAN_ELEMENTS[uppercase(value)] = true; 2384}); 2385 2386function getBooleanAttrName(element, name) { 2387 // check dom last since we will most likely fail on name 2388 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 2389 2390 // booleanAttr is here twice to minimize DOM access 2391 return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; 2392} 2393 2394forEach({ 2395 data: jqLiteData, 2396 inheritedData: jqLiteInheritedData, 2397 2398 scope: function(element) { 2399 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2400 return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 2401 }, 2402 2403 isolateScope: function(element) { 2404 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2405 return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); 2406 }, 2407 2408 controller: jqLiteController , 2409 2410 injector: function(element) { 2411 return jqLiteInheritedData(element, '$injector'); 2412 }, 2413 2414 removeAttr: function(element,name) { 2415 element.removeAttribute(name); 2416 }, 2417 2418 hasClass: jqLiteHasClass, 2419 2420 css: function(element, name, value) { 2421 name = camelCase(name); 2422 2423 if (isDefined(value)) { 2424 element.style[name] = value; 2425 } else { 2426 var val; 2427 2428 if (msie <= 8) { 2429 // this is some IE specific weirdness that jQuery 1.6.4 does not sure why 2430 val = element.currentStyle && element.currentStyle[name]; 2431 if (val === '') val = 'auto'; 2432 } 2433 2434 val = val || element.style[name]; 2435 2436 if (msie <= 8) { 2437 // jquery weirdness :-/ 2438 val = (val === '') ? undefined : val; 2439 } 2440 2441 return val; 2442 } 2443 }, 2444 2445 attr: function(element, name, value){ 2446 var lowercasedName = lowercase(name); 2447 if (BOOLEAN_ATTR[lowercasedName]) { 2448 if (isDefined(value)) { 2449 if (!!value) { 2450 element[name] = true; 2451 element.setAttribute(name, lowercasedName); 2452 } else { 2453 element[name] = false; 2454 element.removeAttribute(lowercasedName); 2455 } 2456 } else { 2457 return (element[name] || 2458 (element.attributes.getNamedItem(name)|| noop).specified) 2459 ? lowercasedName 2460 : undefined; 2461 } 2462 } else if (isDefined(value)) { 2463 element.setAttribute(name, value); 2464 } else if (element.getAttribute) { 2465 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 2466 // some elements (e.g. Document) don't have get attribute, so return undefined 2467 var ret = element.getAttribute(name, 2); 2468 // normalize non-existing attributes to undefined (as jQuery) 2469 return ret === null ? undefined : ret; 2470 } 2471 }, 2472 2473 prop: function(element, name, value) { 2474 if (isDefined(value)) { 2475 element[name] = value; 2476 } else { 2477 return element[name]; 2478 } 2479 }, 2480 2481 text: (function() { 2482 var NODE_TYPE_TEXT_PROPERTY = []; 2483 if (msie < 9) { 2484 NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ 2485 NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ 2486 } else { 2487 NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ 2488 NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ 2489 } 2490 getText.$dv = ''; 2491 return getText; 2492 2493 function getText(element, value) { 2494 var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; 2495 if (isUndefined(value)) { 2496 return textProp ? element[textProp] : ''; 2497 } 2498 element[textProp] = value; 2499 } 2500 })(), 2501 2502 val: function(element, value) { 2503 if (isUndefined(value)) { 2504 if (nodeName_(element) === 'SELECT' && element.multiple) { 2505 var result = [];
2506 forEach(element.options, function (option) { 2507 if (option.selected) { 2508 result.push(option.value || option.text); 2509 } 2510 }); 2511 return result.length === 0 ? null : result; 2512 } 2513 return element.value; 2514 } 2515 element.value = value; 2516 }, 2517 2518 html: function(element, value) { 2519 if (isUndefined(value)) { 2520 return element.innerHTML; 2521 } 2522 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2523 jqLiteDealoc(childNodes[i]); 2524 } 2525 element.innerHTML = value; 2526 }, 2527 2528 empty: jqLiteEmpty 2529}, function(fn, name){ 2530 /** 2531 * Properties: writes return selection, reads return first value 2532 */ 2533 JQLite.prototype[name] = function(arg1, arg2) { 2534 var i, key; 2535 2536 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 2537 // in a way that survives minification. 2538 // jqLiteEmpty takes no arguments but is a setter. 2539 if (fn !== jqLiteEmpty && 2540 (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { 2541 if (isObject(arg1)) { 2542 2543 // we are a write, but the object properties are the key/values 2544 for (i = 0; i < this.length; i++) { 2545 if (fn === jqLiteData) { 2546 // data() takes the whole object in jQuery 2547 fn(this[i], arg1); 2548 } else { 2549 for (key in arg1) { 2550 fn(this[i], key, arg1[key]); 2551 } 2552 } 2553 } 2554 // return self for chaining 2555 return this; 2556 } else { 2557 // we are a read, so read the first child. 2558 var value = fn.$dv; 2559 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 2560 var jj = (value === undefined) ? Math.min(this.length, 1) : this.length; 2561 for (var j = 0; j < jj; j++) { 2562 var nodeValue = fn(this[j], arg1, arg2); 2563 value = value ? value + nodeValue : nodeValue; 2564 } 2565 return value; 2566 } 2567 } else { 2568 // we are a write, so apply to all children 2569 for (i = 0; i < this.length; i++) { 2570 fn(this[i], arg1, arg2); 2571 } 2572 // return self for chaining 2573 return this; 2574 } 2575 }; 2576}); 2577 2578function createEventHandler(element, events) { 2579 var eventHandler = function (event, type) { 2580 if (!event.preventDefault) { 2581 event.preventDefault = function() { 2582 event.returnValue = false; //ie 2583 }; 2584 } 2585 2586 if (!event.stopPropagation) { 2587 event.stopPropagation = function() { 2588 event.cancelBubble = true; //ie 2589 }; 2590 } 2591 2592 if (!event.target) { 2593 event.target = event.srcElement || document; 2594 } 2595 2596 if (isUndefined(event.defaultPrevented)) { 2597 var prevent = event.preventDefault; 2598 event.preventDefault = function() { 2599 event.defaultPrevented = true; 2600 prevent.call(event); 2601 }; 2602 event.defaultPrevented = false; 2603 } 2604 2605 event.isDefaultPrevented = function() { 2606 return event.defaultPrevented || event.returnValue === false; 2607 }; 2608 2609 // Copy event handlers in case event handlers array is modified during execution. 2610 var eventHandlersCopy = shallowCopy(events[type || event.type] || []); 2611 2612 forEach(eventHandlersCopy, function(fn) { 2613 fn.call(element, event); 2614 }); 2615 2616 // Remove monkey-patched methods (IE), 2617 // as they would cause memory leaks in IE8. 2618 if (msie <= 8) { 2619 // IE7/8 does not allow to delete property on native object 2620 event.preventDefault = null; 2621 event.stopPropagation = null; 2622 event.isDefaultPrevented = null; 2623 } else { 2624 // It shouldn't affect normal browsers (native methods are defined on prototype). 2625 delete event.preventDefault; 2626 delete event.stopPropagation; 2627 delete event.isDefaultPrevented; 2628 } 2629 }; 2630 eventHandler.elem = element; 2631 return eventHandler; 2632} 2633 2634////////////////////////////////////////// 2635// Functions iterating traversal. 2636// These functions chain results into a single 2637// selector. 2638////////////////////////////////////////// 2639forEach({ 2640 removeData: jqLiteRemoveData, 2641 2642 dealoc: jqLiteDealoc, 2643 2644 on: function onFn(element, type, fn, unsupported){
2645 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 2646 2647 var events = jqLiteExpandoStore(element, 'events'), 2648 handle = jqLiteExpandoStore(element, 'handle'); 2649 2650 if (!events) jqLiteExpandoStore(element, 'events', events = {}); 2651 if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); 2652 2653 forEach(type.split(' '), function(type){ 2654 var eventFns = events[type]; 2655 2656 if (!eventFns) { 2657 if (type == 'mouseenter' || type == 'mouseleave') { 2658 var contains = document.body.contains || document.body.compareDocumentPosition ? 2659 function( a, b ) { 2660 // jshint bitwise: false 2661 var adown = a.nodeType === 9 ? a.documentElement : a, 2662 bup = b && b.parentNode; 2663 return a === bup || !!( bup && bup.nodeType === 1 && ( 2664 adown.contains ? 2665 adown.contains( bup ) : 2666 a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 2667 )); 2668 } : 2669 function( a, b ) { 2670 if ( b ) { 2671 while ( (b = b.parentNode) ) { 2672 if ( b === a ) { 2673 return true; 2674 } 2675 } 2676 } 2677 return false; 2678 }; 2679 2680 events[type] = []; 2681 2682 // Refer to jQuery's implementation of mouseenter & mouseleave 2683 // Read about mouseenter and mouseleave: 2684 // http://www.quirksmode.org/js/events_mouse.html#link8 2685 var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; 2686 2687 onFn(element, eventmap[type], function(event) { 2688 var target = this, related = event.relatedTarget; 2689 // For mousenter/leave call the handler if related is outside the target. 2690 // NB: No relatedTarget if the mouse left/entered the browser window 2691 if ( !related || (related !== target && !contains(target, related)) ){ 2692 handle(event, type); 2693 } 2694 }); 2695 2696 } else { 2697 addEventListenerFn(element, type, handle); 2698 events[type] = []; 2699 } 2700 eventFns = events[type]; 2701 } 2702 eventFns.push(fn); 2703 }); 2704 }, 2705 2706 off: jqLiteOff, 2707 2708 one: function(element, type, fn) { 2709 element = jqLite(element); 2710 2711 //add the listener twice so that when it is called 2712 //you can remove the original function and still be 2713 //able to call element.off(ev, fn) normally 2714 element.on(type, function onFn() { 2715 element.off(type, fn); 2716 element.off(type, onFn); 2717 }); 2718 element.on(type, fn); 2719 }, 2720 2721 replaceWith: function(element, replaceNode) { 2722 var index, parent = element.parentNode; 2723 jqLiteDealoc(element); 2724 forEach(new JQLite(replaceNode), function(node){ 2725 if (index) { 2726 parent.insertBefore(node, index.nextSibling); 2727 } else { 2728 parent.replaceChild(node, element); 2729 } 2730 index = node; 2731 }); 2732 }, 2733 2734 children: function(element) { 2735 var children = []; 2736 forEach(element.childNodes, function(element){ 2737 if (element.nodeType === 1) 2738 children.push(element); 2739 }); 2740 return children; 2741 }, 2742 2743 contents: function(element) { 2744 return element.childNodes || []; 2745 }, 2746 2747 append: function(element, node) { 2748 forEach(new JQLite(node), function(child){ 2749 if (element.nodeType === 1 || element.nodeType === 11) { 2750 element.appendChild(child); 2751 } 2752 }); 2753 }, 2754 2755 prepend: function(element, node) { 2756 if (element.nodeType === 1) { 2757 var index = element.firstChild; 2758 forEach(new JQLite(node), function(child){ 2759 element.insertBefore(child, index); 2760 }); 2761 } 2762 }, 2763 2764 wrap: function(element, wrapNode) { 2765 wrapNode = jqLite(wrapNode)[0]; 2766 var parent = element.parentNode; 2767 if (parent) { 2768 parent.replaceChild(wrapNode, element); 2769 } 2770 wrapNode.appendChild(element); 2771 }, 2772 2773 remove: function(element) { 2774 jqLiteDealoc(element); 2775 var parent = element.parentNode; 2776 if (parent) parent.removeChild(element); 2777 }, 2778 2779 after: function(element, newElement) { 2780 var index = element, parent = element.parentNode;
2781 forEach(new JQLite(newElement), function(node){ 2782 parent.insertBefore(node, index.nextSibling); 2783 index = node; 2784 }); 2785 }, 2786 2787 addClass: jqLiteAddClass, 2788 removeClass: jqLiteRemoveClass, 2789 2790 toggleClass: function(element, selector, condition) { 2791 if (isUndefined(condition)) { 2792 condition = !jqLiteHasClass(element, selector); 2793 } 2794 (condition ? jqLiteAddClass : jqLiteRemoveClass)(element, selector); 2795 }, 2796 2797 parent: function(element) { 2798 var parent = element.parentNode; 2799 return parent && parent.nodeType !== 11 ? parent : null; 2800 }, 2801 2802 next: function(element) { 2803 if (element.nextElementSibling) { 2804 return element.nextElementSibling; 2805 } 2806 2807 // IE8 doesn't have nextElementSibling 2808 var elm = element.nextSibling; 2809 while (elm != null && elm.nodeType !== 1) { 2810 elm = elm.nextSibling; 2811 } 2812 return elm; 2813 }, 2814 2815 find: function(element, selector) { 2816 if (element.getElementsByTagName) { 2817 return element.getElementsByTagName(selector); 2818 } else { 2819 return []; 2820 } 2821 }, 2822 2823 clone: jqLiteClone, 2824 2825 triggerHandler: function(element, eventName, eventData) { 2826 var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; 2827 2828 eventData = eventData || []; 2829 2830 var event = [{ 2831 preventDefault: noop, 2832 stopPropagation: noop 2833 }]; 2834 2835 forEach(eventFns, function(fn) { 2836 fn.apply(element, event.concat(eventData)); 2837 }); 2838 } 2839}, function(fn, name){ 2840 /** 2841 * chaining functions 2842 */ 2843 JQLite.prototype[name] = function(arg1, arg2, arg3) { 2844 var value; 2845 for(var i=0; i < this.length; i++) { 2846 if (isUndefined(value)) { 2847 value = fn(this[i], arg1, arg2, arg3); 2848 if (isDefined(value)) { 2849 // any function which returns a value needs to be wrapped 2850 value = jqLite(value); 2851 } 2852 } else { 2853 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 2854 } 2855 } 2856 return isDefined(value) ? value : this; 2857 }; 2858 2859 // bind legacy bind/unbind to on/off 2860 JQLite.prototype.bind = JQLite.prototype.on; 2861 JQLite.prototype.unbind = JQLite.prototype.off; 2862}); 2863 2864/** 2865 * Computes a hash of an 'obj'. 2866 * Hash of a: 2867 * string is string 2868 * number is number as string 2869 * object is either result of calling $$hashKey function on the object or uniquely generated id, 2870 * that is also assigned to the $$hashKey property of the object. 2871 * 2872 * @param obj 2873 * @returns {string} hash string such that the same input will have the same hash string. 2874 * The resulting string key is in 'type:hashKey' format. 2875 */ 2876function hashKey(obj) { 2877 var objType = typeof obj, 2878 key; 2879 2880 if (objType == 'object' && obj !== null) { 2881 if (typeof (key = obj.$$hashKey) == 'function') { 2882 // must invoke on object to keep the right this 2883 key = obj.$$hashKey(); 2884 } else if (key === undefined) { 2885 key = obj.$$hashKey = nextUid(); 2886 } 2887 } else { 2888 key = obj; 2889 } 2890 2891 return objType + ':' + key; 2892} 2893 2894/** 2895 * HashMap which can use objects as keys 2896 */ 2897function HashMap(array){ 2898 forEach(array, this.put, this); 2899} 2900HashMap.prototype = { 2901 /** 2902 * Store key value pair 2903 * @param key key to store can be any type 2904 * @param value value to store can be any type 2905 */ 2906 put: function(key, value) { 2907 this[hashKey(key)] = value; 2908 }, 2909 2910 /** 2911 * @param key 2912 * @returns the value for the key 2913 */ 2914 get: function(key) { 2915 return this[hashKey(key)]; 2916 }, 2917 2918 /** 2919 * Remove the key/value pair 2920 * @param key 2921 */ 2922 remove: function(key) { 2923 var value = this[key = hashKey(key)]; 2924 delete this[key]; 2925 return value; 2926 } 2927}; 2928 2929/** 2930 * @ngdoc function 2931 * @name angular.injector 2932 * @function 2933 * 2934 * @description 2935 * Creates an injector function that can be used for retrieving services as well as for 2936 * dependency injection (see {@link guide/di dependency injection}). 2937 * 2938 2939 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 2940 * {@link angular.module}. The `ng` module must be explicitly added. 2941 * @returns {function()} Injector function. See {@link AUTO.$injector $injector}. 2942 * 2943 * @example 2944 * Typical usage 2945 * <pre> 2946 * // create an injector 2947 * var $injector = angular.injector(['ng']); 2948 * 2949 * // use the injector to kick off your application 2950 * // use the type inference to auto inject arguments, or use impl
2950icit injection 2951 * $injector.invoke(function($rootScope, $compile, $document){ 2952 * $compile($document)($rootScope); 2953 * $rootScope.$digest(); 2954 * }); 2955 * </pre> 2956 * 2957 * Sometimes you want to get access to the injector of a currently running Angular app 2958 * from outside Angular. Perhaps, you want to inject and compile some markup after the 2959 * application has been bootstrapped. You can do this using extra `injector()` added 2960 * to JQuery/jqLite elements. See {@link angular.element}. 2961 * 2962 * *This is fairly rare but could be the case if a third party library is injecting the 2963 * markup.* 2964 * 2965 * In the following example a new block of HTML containing a `ng-controller` 2966 * directive is added to the end of the document body by JQuery. We then compile and link 2967 * it into the current AngularJS scope. 2968 * 2969 * <pre> 2970 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 2971 * $(document.body).append($div); 2972 * 2973 * angular.element(document).injector().invoke(function($compile) { 2974 * var scope = angular.element($div).scope(); 2975 * $compile($div)(scope); 2976 * }); 2977 * </pre> 2978 */ 2979 2980 2981/** 2982 * @ngdoc overview 2983 * @name AUTO 2984 * @description 2985 * 2986 * Implicit module which gets automatically added to each {@link AUTO.$injector $injector}. 2987 */ 2988 2989var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; 2990var FN_ARG_SPLIT = /,/; 2991var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 2992var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 2993var $injectorMinErr = minErr('$injector'); 2994function annotate(fn) { 2995 var $inject, 2996 fnText, 2997 argDecl, 2998 last; 2999 3000 if (typeof fn == 'function') { 3001 if (!($inject = fn.$inject)) { 3002 $inject = []; 3003 if (fn.length) { 3004 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3005 argDecl = fnText.match(FN_ARGS); 3006 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ 3007 arg.replace(FN_ARG, function(all, underscore, name){ 3008 $inject.push(name); 3009 }); 3010 }); 3011 } 3012 fn.$inject = $inject; 3013 } 3014 } else if (isArray(fn)) { 3015 last = fn.length - 1; 3016 assertArgFn(fn[last], 'fn'); 3017 $inject = fn.slice(0, last); 3018 } else { 3019 assertArgFn(fn, 'fn', true); 3020 } 3021 return $inject; 3022} 3023 3024/////////////////////////////////////// 3025 3026/** 3027 * @ngdoc object 3028 * @name AUTO.$injector 3029 * @function 3030 * 3031 * @description 3032 * 3033 * `$injector` is used to retrieve object instances as defined by 3034 * {@link AUTO.$provide provider}, instantiate types, invoke methods, 3035 * and load modules. 3036 * 3037 * The following always holds true: 3038 * 3039 * <pre> 3040 * var $injector = angular.injector(); 3041 * expect($injector.get('$injector')).toBe($injector); 3042 * expect($injector.invoke(function($injector){ 3043 * return $injector; 3044 * }).toBe($injector); 3045 * </pre> 3046 * 3047 * # Injection Function Annotation 3048 * 3049 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3050 * following are all valid ways of annotating function with injection arguments and are equivalent. 3051 * 3052 * <pre> 3053 * // inferred (only works if code not minified/obfuscated) 3054 * $injector.invoke(function(serviceA){}); 3055 * 3056 * // annotated 3057 * function explicit(serviceA) {}; 3058 * explicit.$inject = ['serviceA']; 3059 * $injector.invoke(explicit); 3060 * 3061 * // inline 3062 * $injector.invoke(['serviceA', function(serviceA){}]); 3063 * </pre> 3064 * 3065 * ## Inference 3066 * 3067 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3068 * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with 3069 * minification, and obfuscation tools since these tools change the argument names. 3070 * 3071 * ## `$inject` Annotation 3072 * By adding a `$inject` property onto a function the injection parameters can be specified. 3073 * 3074 * ## Inline 3075 * As an array of injection names, where the last item in the array is the function to call. 3076 */ 3077 3078/** 3079 * @ngdoc method 3080 * @name AUTO.$injector#get 3081 * @methodOf AUTO.$injector 3082 * 3083 * @description 3084 * Return an instance of the service. 3085 * 3086 * @param {string} name The name of the instance to retrieve. 3087 * @return {*} The instance. 3088 */ 3089 3090/** 3091 * @ngdoc method 3092 * @name AUTO.$injector#invoke 3093 * @methodOf AUTO.$injector 3094 * 3095 * @description 3096 * Invoke the method and supply the method arguments from the `$injector`. 3097 * 3098 * @param {!function} fn The function to invoke. Function parameters are injected according to the 3099 * {@link guide/di $inject Annotation} rules. 3100 * @param {Object=} self The `this` for the invoked method. 3101 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3102 * object first, before the `$injector` is consulted. 3103 * @returns {*} the value returned by the invoked `fn` function. 3104 */ 3105 3106/** 3107 * @ngdoc method 3108 * @name AUTO.$injector#has 3109 * @methodOf AUTO.$injector 3110 * 3111 * @description 3112 * Allows the user to query if the particular service exist. 3113 * 3114 * @param {string} Name of the service to query. 3115 * @returns {boolean} returns true if injector has given service. 3116 */ 3117 3118/** 3119 * @ngdoc method 3120 * @name AUTO.$injector#instantiate 3121 * @methodOf AUTO.$injector 3122 * @description 3123 * Create a new instance of JS type. The method takes a constructor function invokes the new 3124 * operator and supplies all of the arguments to the constructor function as specified by the 3125 * constructor annotation. 3126 * 3127 * @param {function} Type Annotated constructor function. 3128 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3129 * object first, before the `$injector` is consulted. 3130 * @returns {Object} new instance of `Type`. 3131 */ 3132 3133/** 3134 * @ngdoc method 3135 * @name AUTO.$injector#annotate 3136 * @methodOf AUTO.$injector 3137 * 3138 * @description 3139 * Returns an array of service names which the function is requesting for injection. This API is 3140 * used by the injector to determine which services need to be injected into the function when the 3141 * function is invoked. There are three ways in which the function can be annotated with the needed 3142 * dependencies. 3143 * 3144 * # Argument names 3145 * 3146 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3147 * by converting the function into a string using `toString()` method and extracting the argument 3148 * names. 3149 * <pre> 3150 * // Given 3151 * function MyController($scope, $route) { 3152 * // ... 3153 * } 3154 * 3155 * // Then 3156 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3157 * </pre> 3158 * 3159 * This method does not work with code minification / obfuscation. For this reason the following 3160 * annotation strategies are supported. 3161 * 3162 * # The `$inject` property 3163 * 3164 * If a function has an `$inject` property and its value is an array of strings, then the strings 3165 * represent names of services to be injected into the function. 3166 * <pre> 3167 * // Given 3168 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 3169 * // ... 3170 * } 3171 * // Define function dependencies 3172 * MyController['$inject'] = ['$scope', '$route']; 3173 * 3174 * // Then 3175 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3176 * </pre> 3177 * 3178 * # The array notation 3179 * 3180 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 3181 * is very inconvenient. In these situations using the array notation to specify the dependencies in 3182 * a way that survives minification is a better choice: 3183 * 3184 * <pre> 3185 * // We wish to write this (not minification / obfuscation safe) 3186 * injector.invoke(function($compile, $rootScope) { 3187 * // ... 3188 * }); 3189 * 3190 * // We are forced to write break inlining 3191 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 3192 * // ... 3193 * }; 3194 * tmpFn.$inject = ['$compile', '$rootScope']; 3195 * injector.invoke(tmpFn); 3196 * 3197 * // To better support inline function the inline annotation is supported 3198 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 3199 * // ... 3200 * }]); 3201 * 3202 * // Therefore 3203 * expect(injector.annotate( 3204 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 3205 * ).toEqual(['$compile', '$rootScope']); 3206 * </pre> 3207 * 3208 * @param {function|Array.<string|Function>} fn Function for which dependent service names need to 3209 * be retrieved as described above. 3210 * 3211 * @returns {Array.<string>} The names of the services which the function requires. 3212 */ 3213 3214 3215 3216 3217/** 3218 * @ngdoc object 3219 * @name AUTO.$provide 3220 * 3221 * @description 3222 * 3223 * The {@link AUTO.$provide $provide} service has a number of methods for registering components 3224 * with the {@link AUTO.$injector $injector}. Many of these functions are also exposed on 3225 * {@link angular.Module}. 3226 * 3227 * An Angular **service** is a singleton object created by a **service factory**. These **service 3228 * factories** are functions which, in turn, are created by a **service provider**. 3229 * The **service providers** are constructor functions. When instantiated they must contain a 3230 * property called `$get`, which holds the **service factory** function. 3231 * 3232 * When you request a service, the {@link AUTO.$injector $injector} is responsible for finding the 3233 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 3234 * function to get the instance of the **service**. 3235 * 3236 * Often services have no configuration options and there is no need to add methods to the service 3237 * provider. The provider will be no more than a constructor function with a `$get` property. For 3238 * these cases the {@link AUTO.$provide $provide} service has additional helper methods to register 3239 * services without specifying a provider. 3240 * 3241 * * {@link AUTO.$provide#methods_provider provider(provider)} - registers a **service provider** with the 3242 * {@link AUTO.$injector $injector} 3243 * * {@link AUTO.$provide#methods_constant constant(obj)} - registers a value/object that can be accessed by 3244 * providers and services. 3245 * * {@link AUTO.$provide#methods_value value(obj)} - registers a value/object that can only be accessed by 3246 * services, not providers. 3247 * * {@link AUTO.$provide#methods_factory factory(fn)} - registers a service **factory function**, `fn`, 3248 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 3249 * given factory function. 3250 * * {@link AUTO.$provide#methods_service service(class)} - registers a **constructor function**, `class` that 3251 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 3252 * a new object using the given constructor function. 3253 * 3254 * See the individual methods for more information and examples. 3255 */ 3256 3257/** 3258 * @ngdoc method 3259 * @name AUTO.$provide#provider 3260 * @methodOf AUTO.$provide 3261 * @description 3262 * 3263 * Register a **provider function** with the {@link AUTO.$injector $injector}. Provider functions 3264 * are constructor functions, whose instances are responsible for "providing" a factory for a 3265 * service. 3266 * 3267 * Service provider names start with the name of the service they provide followed by `Provider`. 3268 * For example, the {@link ng.$log $log} service has a provider called 3269 * {@link ng.$logProvider $logProvider}. 3270 * 3271 * Service provider objects can have additional methods which allow configuration of the provider 3272 * and its service. Importantly, you can configure what kind of service is created by the `$get` 3273 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 3274 * method {@link ng.$logProvider#debugEnabled debugEnabled} 3275 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 3276 * console or not. 3277 * 3278 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 3279 'Provider'` key. 3280 * @param {(Object|function())} provider If the provider is: 3281 * 3282 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 3283 * {@link AUTO.$injector#invoke $injector.invoke()} when an instance needs to be created. 3284 * - `Constructor`: a new instance of the provider will be created using 3285 * {@link AUTO.$injector#instantiate $injector.instantiate()}, then treated as `object`. 3286 * 3287 * @returns {Object} registered provider instance 3288 3289 * @example 3290 * 3291 * The following example shows how to create a simple event tracking service and register it using 3292 * {@link AUTO.$provide#methods_provider $provide.provider()}. 3293 * 3294 * <pre> 3295 * // Define the eventTracker provider 3296 * function EventTrackerProvider() { 3297 * var trackingUrl = '/track'; 3298 * 3299 * // A provider method for configuring where the tracked events should been saved 3300 * this.setTrackingUrl = function(url) { 3301 * trackingUrl = url; 3302 * }; 3303 * 3304 * // The service factory function 3305 * this.$get = ['$http', function($http) { 3306 * var trackedEvents = {}; 3307 * return { 3308 * // Call this to track an event 3309 * event: function(event) { 3310 * var count = trackedEvents[event] || 0; 3311 * count += 1; 3312 * trackedEvents[event] = count; 3313 * return count; 3314 * }, 3315 * // Call this to save the tracked events to the trackingUrl 3316 * save: function() { 3317 * $http.post(trackingUrl, trackedEvents); 3318 * } 3319 * }; 3320 * }]; 3321 * } 3322 * 3323 * describe('eventTracker', function() { 3324 * var postSpy; 3325 * 3326 * beforeEach(module(function($provide) { 3327 * // Register the eventTracker provider 3328 * $provide.provider('eventTracker', EventTrackerProvider); 3329 * })); 3330 * 3331 * beforeEach(module(function(eventTrackerProvider) { 3332 * // Configure eventTracker provider 3333 * eventTrackerProvider.setTrackingUrl('/custom-track'); 3334 * })); 3335 * 3336 * it('tracks events', inject(function(eventTracker) { 3337 * expect(eventTracker.event('login')).toEqual(1); 3338 * expect(eventTracker.event('login')).toEqual(2); 3339 * })); 3340 * 3341 * it('saves to the tracking url', inject(function(eventTracker, $http) { 3342 * postSpy = spyOn($http, 'post'); 3343 * eventTracker.event('login'); 3344 * eventTracker.save(); 3345 * expect(postSpy).toHaveBeenCalled(); 3346 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 3347 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 3348 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 3349 * })); 3350 * }); 3351 * </pre> 3352 */ 3353 3354/** 3355 * @ngdoc method 3356 * @name AUTO.$provide#factory 3357 * @methodOf AUTO.$provide 3358 * @description 3359 * 3360 * Register a **service factory**, which will be called to return the service instance. 3361 * This is short for registering a service where its provider consists of only a `$get` property, 3362 * which is the given service factory function. 3363 * You should use {@link AUTO.$provide#factory $provide.factory(getFn)} if you do not need to 3364 * configure your service in a provider. 3365 * 3366 * @param {string} name The name of the instance. 3367 * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand 3368 * for `$provide.provider(name, {$get: $getFn})`. 3369 * @returns {Object} registered provider instance 3370 * 3371 * @example 3372 * Here is an example of registering a service 3373 * <pre> 3374 * $provide.factory('ping', ['$http', function($http) { 3375 * return function ping() { 3376 * return $http.send('/ping'); 3377 * }; 3378 * }]); 3379 * </pre> 3380 * You would then inject and use this service like this: 3381 * <pre> 3382 * someModule.controller('Ctrl', ['ping', function(ping) { 3383 * ping(); 3384 * }]); 3385 * </pre> 3386 */ 3387 3388 3389/** 3390 * @ngdoc method 3391 * @name AUTO.$provide#service 3392 * @methodOf AUTO.$provide 3393 * @description 3394 * 3395 * Register a **service constructor**, which will be invoked with `new` to create the service 3396 * instance. 3397 * This is short for registering a service where its provider's `$get` property is the service 3398 * constructor function that will be used to instantiate the service instance. 3399 * 3400 * You should use {@link AUTO.$provide#methods_service $provide.service(class)} if you define your service 3401 * as a type/class. 3402 * 3403 * @param {string} name The name of the instance. 3404 * @param {Function} constructor A class (constructor function) that will be instantiated. 3405 * @returns {Object} registered provider instance 3406 * 3407 * @example 3408 * Here is an example of registering a service using 3409 * {@link AUTO.$provide#methods_service $provide.service(class)}. 3410 * <pre> 3411 * $provide.service('ping', ['$http', function($http) { 3412 * var Ping = function() { 3413 * this.$http = $http; 3414 * }; 3415 * 3416 * Ping.prototype.send = function() { 3417 * return this.$http.get('/ping'); 3418 * }; 3419 * 3420 * return Ping; 3421 * }]); 3422 * </pre> 3423 * You would then inject and use this service like this: 3424 * <pre> 3425 * someModule.controller('Ctrl', ['ping', function(ping) { 3426 * ping.send(); 3427 * }]); 3428 * </pre> 3429 */ 3430 3431 3432/** 3433 * @ngdoc method 3434 * @name AUTO.$provide#value 3435 * @methodOf AUTO.$provide 3436 * @description 3437 * 3438 * Register a **value service** with the {@link AUTO.$injector $injector}, such as a string, a 3439 * number, an array, an object or a function. This is short for registering a service where its 3440 * provider's `$get` property is a factory function that takes no arguments and returns the **value 3441 * service**. 3442 * 3443 * Value services are similar to constant services, except that they cannot be injected into a 3444 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 3445 * an Angular 3446 * {@link AUTO.$provide#decorator decorator}. 3447 * 3448 * @param {string} name The name of the instance. 3449 * @param {*} value The value. 3450 * @returns {Object} registered provider instance 3451 * 3452 * @example 3453 * Here are some examples of creating value services. 3454 * <pre> 3455 * $provide.value('ADMIN_USER', 'admin'); 3456 *
3457 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 3458 * 3459 * $provide.value('halfOf', function(value) { 3460 * return value / 2; 3461 * }); 3462 * </pre> 3463 */ 3464 3465 3466/** 3467 * @ngdoc method 3468 * @name AUTO.$provide#constant 3469 * @methodOf AUTO.$provide 3470 * @description 3471 * 3472 * Register a **constant service**, such as a string, a number, an array, an object or a function, 3473 * with the {@link AUTO.$injector $injector}. Unlike {@link AUTO.$provide#value value} it can be 3474 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 3475 * be overridden by an Angular {@link AUTO.$provide#decorator decorator}. 3476 * 3477 * @param {string} name The name of the constant. 3478 * @param {*} value The constant value. 3479 * @returns {Object} registered instance 3480 * 3481 * @example 3482 * Here a some examples of creating constants: 3483 * <pre> 3484 * $provide.constant('SHARD_HEIGHT', 306); 3485 * 3486 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 3487 * 3488 * $provide.constant('double', function(value) { 3489 * return value * 2; 3490 * }); 3491 * </pre> 3492 */ 3493 3494 3495/** 3496 * @ngdoc method 3497 * @name AUTO.$provide#decorator 3498 * @methodOf AUTO.$provide 3499 * @description 3500 * 3501 * Register a **service decorator** with the {@link AUTO.$injector $injector}. A service decorator 3502 * intercepts the creation of a service, allowing it to override or modify the behaviour of the 3503 * service. The object returned by the decorator may be the original service, or a new service 3504 * object which replaces or wraps and delegates to the original service. 3505 * 3506 * @param {string} name The name of the service to decorate. 3507 * @param {function()} decorator This function will be invoked when the service needs to be 3508 * instantiated and should return the decorated service instance. The function is called using 3509 * the {@link AUTO.$injector#invoke injector.invoke} method and is therefore fully injectable. 3510 * Local injection arguments: 3511 * 3512 * * `$delegate` - The original service instance, which can be monkey patched, configured, 3513 * decorated or delegated to. 3514 * 3515 * @example 3516 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 3517 * calls to {@link ng.$log#error $log.warn()}. 3518 * <pre> 3519 * $provider.decorator('$log', ['$delegate', function($delegate) { 3520 * $delegate.warn = $delegate.error; 3521 * return $delegate; 3522 * }]); 3523 * </pre> 3524 */ 3525 3526 3527function createInjector(modulesToLoad) { 3528 var INSTANTIATING = {}, 3529 providerSuffix = 'Provider', 3530 path = [], 3531 loadedModules = new HashMap(), 3532 providerCache = { 3533 $provide: { 3534 provider: supportObject(provider), 3535 factory: supportObject(factory), 3536 service: supportObject(service), 3537 value: supportObject(value), 3538 constant: supportObject(constant), 3539 decorator: decorator 3540 } 3541 }, 3542 providerInjector = (providerCache.$injector = 3543 createInternalInjector(providerCache, function() { 3544 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 3545 })), 3546 instanceCache = {}, 3547 instanceInjector = (instanceCache.$injector = 3548 createInternalInjector(instanceCache, function(servicename) { 3549 var provider = providerInjector.get(servicename + providerSuffix); 3550 return instanceInjector.invoke(provider.$get, provider); 3551 })); 3552 3553 3554 forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); 3555 3556 return instanceInjector; 3557 3558 //////////////////////////////////// 3559 // $provider 3560 //////////////////////////////////// 3561 3562 function supportObject(delegate) { 3563 return function(key, value) { 3564 if (isObject(key)) { 3565 forEach(key, reverseParams(delegate)); 3566 } else { 3567 return delegate(key, value); 3568 } 3569 }; 3570 } 3571 3572 function provider(name, provider_) { 3573 assertNotHasOwnProperty(name, 'service'); 3574 if (isFunction(provider_) || isArray(provider_)) { 3575 provider_ = providerInjector.instantiate(provider_); 3576 } 3577 if (!provider_.$get) { 3578 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 3579 } 3580 return providerCache[name + providerSuffix] = provider_; 3581 } 3582 3583 function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } 3584 3585 function service(name, constructor) { 3586 return factory(name, ['$injector', function($injector) { 3587 return $injector.instantiate(constructor); 3588 }]); 3589 } 3590 3591 function value(name, val) { return factory(name, valueFn(val)); } 3592 3593 function constant(name, value) { 3594 assertNotHasOwnProperty(name, 'constant'); 3595 providerCache[name] = value; 3596 instanceCache[name] = value; 3597 } 3598 3599 function decorator(serviceName, decorFn) { 3600 var origProvider = providerInjector.get(serviceName + providerSuffix), 3601 orig$get = origProvider.$get; 3602 3603 origProvider.$get = function() { 3604 var origInstance = instanceInjector.invoke(orig$get, origProvider); 3605 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 3606 }; 3607 } 3608 3609 //////////////////////////////////// 3610 // Module Loading 3611 //////////////////////////////////// 3612 function loadModules(modulesToLoad){ 3613 var runBlocks = [], moduleFn, invokeQueue, i, ii;
3614 forEach(modulesToLoad, function(module) { 3615 if (loadedModules.get(module)) return; 3616 loadedModules.put(module, true); 3617 3618 try { 3619 if (isString(module)) { 3620 moduleFn = angularModule(module); 3621 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 3622 3623 for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { 3624 var invokeArgs = invokeQueue[i], 3625 provider = providerInjector.get(invokeArgs[0]); 3626 3627 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 3628 } 3629 } else if (isFunction(module)) { 3630 runBlocks.push(providerInjector.invoke(module)); 3631 } else if (isArray(module)) { 3632 runBlocks.push(providerInjector.invoke(module)); 3633 } else { 3634 assertArgFn(module, 'module'); 3635 } 3636 } catch (e) { 3637 if (isArray(module)) { 3638 module = module[module.length - 1]; 3639 } 3640 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 3641 // Safari & FF's stack traces don't contain error.message content 3642 // unlike those of Chrome and IE 3643 // So if stack doesn't contain message, we create a new string that contains both. 3644 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 3645 /* jshint -W022 */ 3646 e = e.message + '\n' + e.stack; 3647 } 3648 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 3649 module, e.stack || e.message || e); 3650 } 3651 }); 3652 return runBlocks; 3653 } 3654 3655 //////////////////////////////////// 3656 // internal Injector 3657 //////////////////////////////////// 3658 3659 function createInternalInjector(cache, factory) { 3660 3661 function getService(serviceName) { 3662 if (cache.hasOwnProperty(serviceName)) { 3663 if (cache[serviceName] === INSTANTIATING) { 3664 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- ')); 3665 } 3666 return cache[serviceName]; 3667 } else { 3668 try { 3669 path.unshift(serviceName); 3670 cache[serviceName] = INSTANTIATING; 3671 return cache[serviceName] = factory(serviceName); 3672 } catch (err) { 3673 if (cache[serviceName] === INSTANTIATING) { 3674 delete cache[serviceName]; 3675 } 3676 throw err; 3677 } finally { 3678 path.shift(); 3679 } 3680 } 3681 } 3682 3683 function invoke(fn, self, locals){ 3684 var args = [], 3685 $inject = annotate(fn), 3686 length, i, 3687 key; 3688 3689 for(i = 0, length = $inject.length; i < length; i++) { 3690 key = $inject[i]; 3691 if (typeof key !== 'string') { 3692 throw $injectorMinErr('itkn', 3693 'Incorrect injection token! Expected service name as string, got {0}', key); 3694 } 3695 args.push( 3696 locals && locals.hasOwnProperty(key) 3697 ? locals[key] 3698 : getService(key) 3699 ); 3700 } 3701 if (!fn.$inject) { 3702 // this means that we must be an array. 3703 fn = fn[length]; 3704 } 3705 3706 // http://jsperf.com/angularjs-invoke-apply-vs-switch 3707 // #5388 3708 return fn.apply(self, args); 3709 } 3710 3711 function instantiate(Type, locals) { 3712 var Constructor = function() {}, 3713 instance, returnedValue; 3714 3715 // Check if Type is annotated and use just the given function at n-1 as parameter 3716 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 3717 Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; 3718 instance = new Constructor(); 3719 returnedValue = invoke(Type, instance, locals); 3720 3721 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 3722 } 3723 3724 return { 3725 invoke: invoke, 3726 instantiate: instantiate, 3727 get: getService, 3728 annotate: annotate, 3729 has: function(name) { 3730 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 3731 } 3732 }; 3733 } 3734} 3735 3736/** 3737 * @ngdoc function 3738 * @name ng.$anchorScroll 3739 * @requires $window 3740 * @requires $location 3741 * @requires $rootScope 3742 * 3743 * @description 3744 * When called, it checks current value of `$location.hash()` and scroll to related element, 3745 * according to rules specified in 3746 * {@link http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document Html5 spec}. 3747 * 3748 * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. 3749 * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. 3750 * 3751 * @example 3752 <example> 3753 <file name="index.html"> 3754 <div id="scrollArea" ng-controller="ScrollCtrl"> 3755 <a ng-click="gotoBottom()">Go to bottom</a> 3756 <a id="bottom"></a> You're at the bottom! 3757 </div> 3758 </file> 3759 <file name="script.js"> 3760 function ScrollCtrl($scope, $location, $anchorScroll) { 3761 $scope.gotoBottom = function (){ 3762 // set the location.hash to the id of
3763 // the element you wish to scroll to. 3764 $location.hash('bottom'); 3765 3766 // call $anchorScroll() 3767 $anchorScroll(); 3768 } 3769 } 3770 </file> 3771 <file name="style.css"> 3772 #scrollArea { 3773 height: 350px; 3774 overflow: auto; 3775 } 3776 3777 #bottom { 3778 display: block; 3779 margin-top: 2000px; 3780 } 3781 </file> 3782 </example> 3783 */ 3784function $AnchorScrollProvider() { 3785 3786 var autoScrollingEnabled = true; 3787 3788 this.disableAutoScrolling = function() { 3789 autoScrollingEnabled = false; 3790 }; 3791 3792 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 3793 var document = $window.document; 3794 3795 // helper function to get first anchor from a NodeList 3796 // can't use filter.filter, as it accepts only instances of Array 3797 // and IE can't convert NodeList to an array using [].slice 3798 // TODO(vojta): use filter if we change it to accept lists as well 3799 function getFirstAnchor(list) { 3800 var result = null; 3801 forEach(list, function(element) { 3802 if (!result && lowercase(element.nodeName) === 'a') result = element; 3803 }); 3804 return result; 3805 } 3806 3807 function scroll() { 3808 var hash = $location.hash(), elm; 3809 3810 // empty hash, scroll to the top of the page 3811 if (!hash) $window.scrollTo(0, 0); 3812 3813 // element with given id 3814 else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); 3815 3816 // first anchor with given name :-D 3817 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); 3818 3819 // no element and hash == 'top', scroll to the top of the page 3820 else if (hash === 'top') $window.scrollTo(0, 0); 3821 } 3822 3823 // does not scroll when user clicks on anchor link that is currently on 3824 // (no url change, no $location.hash() change), browser native does scroll 3825 if (autoScrollingEnabled) { 3826 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 3827 function autoScrollWatchAction() { 3828 $rootScope.$evalAsync(scroll); 3829 }); 3830 } 3831 3832 return scroll; 3833 }]; 3834} 3835 3836var $animateMinErr = minErr('$animate'); 3837 3838/** 3839 * @ngdoc object 3840 * @name ng.$animateProvider 3841 * 3842 * @description 3843 * Default implementation of $animate that doesn't perform any animations, instead just 3844 * synchronously performs DOM 3845 * updates and calls done() callbacks. 3846 * 3847 * In order to enable animations the ngAnimate module has to be loaded. 3848 * 3849 * To see the functional implementation check out src/ngAnimate/animate.js 3850 */ 3851var $AnimateProvider = ['$provide', function($provide) { 3852 3853 3854 this.$$selectors = {}; 3855 3856 3857 /** 3858 * @ngdoc function 3859 * @name ng.$animateProvider#register 3860 * @methodOf ng.$animateProvider 3861 * 3862 * @description 3863 * Registers a new injectable animation factory function. The factory function produces the 3864 * animation object which contains callback functions for each event that is expected to be 3865 * animated. 3866 * 3867 * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` 3868 * must be called once the element animation is complete. If a function is returned then the 3869 * animation service will use this function to cancel the animation whenever a cancel event is 3870 * triggered. 3871 * 3872 * 3873 *<pre> 3874 * return { 3875 * eventFn : function(element, done) { 3876 * //code to run the animation 3877 * //once complete, then run done() 3878 * return function cancellationFunction() { 3879 * //code to cancel the animation 3880 * } 3881 * } 3882 * } 3883 *</pre> 3884 * 3885 * @param {string} name The name of the animation. 3886 * @param {function} factory The factory function that will be executed to return the animation 3887 * object. 3888 */ 3889 this.register = function(name, factory) { 3890 var key = name + '-animation'; 3891 if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', 3892 "Expecting class selector starting with '.' got '{0}'.", name); 3893 this.$$selectors[name.substr(1)] = key; 3894 $provide.factory(key, factory); 3895 }; 3896 3897 /** 3898 * @ngdoc function 3899 * @name ng.$animateProvider#classNameFilter 3900 * @methodOf ng.$animateProvider 3901 * 3902 * @description 3903 * Sets and/or returns the CSS class regular expression that is checked when performing 3904 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 3905 * therefore enable $animate to attempt to perform an animation on any element. 3906 * When setting the classNameFilter value, animations will only be performed on elements 3907 * that successfully match the filter expression. This in turn can boost performance 3908 * for low-powered devices as well as applications containing a lot of structural operations. 3909 * @param {RegExp=} expression The className expression which will be checked against all animations 3910 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 3911 */ 3912 this.classNameFilter = function(expression) { 3913 if(arguments.length === 1) { 3914 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 3915 } 3916 return this.$$classNameFilter; 3917 }; 3918 3919 this.$get = ['$timeout', function($timeout) { 3920 3921 /** 3922 * 3923 * @ngdoc object 3924 * @name ng.$animate
3925 * @description The $animate service provides rudimentary DOM manipulation functions to 3926 * insert, remove and move elements within the DOM, as well as adding and removing classes. 3927 * This service is the core service used by the ngAnimate $animator service which provides 3928 * high-level animation hooks for CSS and JavaScript. 3929 * 3930 * $animate is available in the AngularJS core, however, the ngAnimate module must be included 3931 * to enable full out animation support. Otherwise, $animate will only perform simple DOM 3932 * manipulation operations. 3933 * 3934 * To learn more about enabling animation support, click here to visit the {@link ngAnimate 3935 * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service 3936 * page}. 3937 */ 3938 return { 3939 3940 /** 3941 * 3942 * @ngdoc function 3943 * @name ng.$animate#enter 3944 * @methodOf ng.$animate 3945 * @function 3946 * @description Inserts the element into the DOM either after the `after` element or within 3947 * the `parent` element. Once complete, the done() callback will be fired (if provided). 3948 * @param {jQuery/jqLite element} element the element which will be inserted into the DOM 3949 * @param {jQuery/jqLite element} parent the parent element which will append the element as 3950 * a child (if the after element is not present) 3951 * @param {jQuery/jqLite element} after the sibling element which will append the element 3952 * after itself 3953 * @param {function=} done callback function that will be called after the element has been 3954 * inserted into the DOM 3955 */ 3956 enter : function(element, parent, after, done) { 3957 if (after) { 3958 after.after(element); 3959 } else { 3960 if (!parent || !parent[0]) { 3961 parent = after.parent(); 3962 } 3963 parent.append(element); 3964 } 3965 done && $timeout(done, 0, false); 3966 }, 3967 3968 /** 3969 * 3970 * @ngdoc function 3971 * @name ng.$animate#leave 3972 * @methodOf ng.$animate 3973 * @function 3974 * @description Removes the element from the DOM. Once complete, the done() callback will be 3975 * fired (if provided). 3976 * @param {jQuery/jqLite element} element the element which will be removed from the DOM 3977 * @param {function=} done callback function that will be called after the element has been 3978 * removed from the DOM 3979 */ 3980 leave : function(element, done) { 3981 element.remove(); 3982 done && $timeout(done, 0, false); 3983 }, 3984 3985 /** 3986 * 3987 * @ngdoc function 3988 * @name ng.$animate#move 3989 * @methodOf ng.$animate 3990 * @function 3991 * @description Moves the position of the provided element within the DOM to be placed 3992 * either after the `after` element or inside of the `parent` element. Once complete, the 3993 * done() callback will be fired (if provided). 3994 * 3995 * @param {jQuery/jqLite element} element the element which will be moved around within the 3996 * DOM 3997 * @param {jQuery/jqLite element} parent the parent element where the element will be 3998 * inserted into (if the after element is not present) 3999 * @param {jQuery/jqLite element} after the sibling element where the element will be 4000 * positioned next to 4001 * @param {function=} done the callback function (if provided) that will be fired after the 4002 * element has been moved to its new position 4003 */ 4004 move : function(element, parent, after, done) { 4005 // Do not remove element before insert. Removing will cause data associated with the 4006 // element to be dropped. Insert will implicitly do the remove. 4007 this.enter(element, parent, after, done); 4008 }, 4009 4010 /** 4011 * 4012 * @ngdoc function 4013 * @name ng.$animate#addClass 4014 * @methodOf ng.$animate 4015 * @function 4016 * @description Adds the provided className CSS class value to the provided element. Once 4017 * complete, the done() callback will be fired (if provided). 4018 * @param {jQuery/jqLite element} element the element which will have the className value 4019 * added to it 4020 * @param {string} className the CSS class which will be added to the element 4021 * @param {function=} done the callback function (if provided) that will be fired after the 4022 * className value has been added to the element 4023 */ 4024 addClass : function(element, className, done) { 4025 className = isString(className) ? 4026 className : 4027 isArray(className) ? className.join(' ') : '';
4028 forEach(element, function (element) { 4029 jqLiteAddClass(element, className); 4030 }); 4031 done && $timeout(done, 0, false); 4032 }, 4033 4034 /** 4035 * 4036 * @ngdoc function 4037 * @name ng.$animate#removeClass 4038 * @methodOf ng.$animate 4039 * @function 4040 * @description Removes the provided className CSS class value from the provided element. 4041 * Once complete, the done() callback will be fired (if provided). 4042 * @param {jQuery/jqLite element} element the element which will have the className value 4043 * removed from it 4044 * @param {string} className the CSS class which will be removed from the element 4045 * @param {function=} done the callback function (if provided) that will be fired after the 4046 * className value has been removed from the element 4047 */ 4048 removeClass : function(element, className, done) { 4049 className = isString(className) ? 4050 className : 4051 isArray(className) ? className.join(' ') : ''; 4052 forEach(element, function (element) { 4053 jqLiteRemoveClass(element, className); 4054 }); 4055 done && $timeout(done, 0, false); 4056 }, 4057 4058 enabled : noop 4059 }; 4060 }]; 4061}]; 4062 4063/** 4064 * ! This is a private undocumented service ! 4065 * 4066 * @name ng.$browser 4067 * @requires $log 4068 * @description 4069 * This object has two goals: 4070 * 4071 * - hide all the global state in the browser caused by the window object 4072 * - abstract away all the browser specific features and inconsistencies 4073 * 4074 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 4075 * service, which can be used for convenient testing of the application without the interaction with 4076 * the real browser apis. 4077 */ 4078/** 4079 * @param {object} window The global window object. 4080 * @param {object} document jQuery wrapped document. 4081 * @param {function()} XHR XMLHttpRequest constructor. 4082 * @param {object} $log console.log or an object with the same interface. 4083 * @param {object} $sniffer $sniffer service 4084 */ 4085function Browser(window, document, $log, $sniffer) { 4086 var self = this, 4087 rawDocument = document[0], 4088 location = window.location, 4089 history = window.history, 4090 setTimeout = window.setTimeout, 4091 clearTimeout = window.clearTimeout, 4092 pendingDeferIds = {}; 4093 4094 self.isMock = false; 4095 4096 var outstandingRequestCount = 0; 4097 var outstandingRequestCallbacks = []; 4098 4099 // TODO(vojta): remove this temporary api 4100 self.$$completeOutstandingRequest = completeOutstandingRequest; 4101 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 4102 4103 /** 4104 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 4105 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 4106 */ 4107 function completeOutstandingRequest(fn) { 4108 try { 4109 fn.apply(null, sliceArgs(arguments, 1)); 4110 } finally { 4111 outstandingRequestCount--; 4112 if (outstandingRequestCount === 0) { 4113 while(outstandingRequestCallbacks.length) { 4114 try { 4115 outstandingRequestCallbacks.pop()(); 4116 } catch (e) { 4117 $log.error(e); 4118 } 4119 } 4120 } 4121 } 4122 } 4123 4124 /** 4125 * @private 4126 * Note: this method is used only by scenario runner 4127 * TODO(vojta): prefix this method with $$ ? 4128 * @param {function()} callback Function that will be called when no outstanding request 4129 */ 4130 self.notifyWhenNoOutstandingRequests = function(callback) { 4131 // force browser to execute all pollFns - this is needed so that cookies and other pollers fire 4132 // at some deterministic time in respect to the test runner's actions. Leaving things up to the 4133 // regular poller would result in flaky tests. 4134 forEach(pollFns, function(pollFn){ pollFn(); }); 4135 4136 if (outstandingRequestCount === 0) { 4137 callback(); 4138 } else { 4139 outstandingRequestCallbacks.push(callback); 4140 } 4141 }; 4142 4143 ////////////////////////////////////////////////////////////// 4144 // Poll Watcher API 4145 ////////////////////////////////////////////////////////////// 4146 var pollFns = [], 4147 pollTimeout; 4148 4149 /** 4150 * @name ng.$browser#addPollFn 4151 * @methodOf ng.$browser 4152 * 4153 * @param {function()} fn Poll function to add 4154 * 4155 * @description 4156 * Adds a function to the list of functions that poller periodically executes, 4157 * and starts polling if not started yet. 4158 * 4159 * @returns {function()} the added function 4160 */ 4161 self.addPollFn = function(fn) {
4162 if (isUndefined(pollTimeout)) startPoller(100, setTimeout); 4163 pollFns.push(fn); 4164 return fn; 4165 }; 4166 4167 /** 4168 * @param {number} interval How often should browser call poll functions (ms) 4169 * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. 4170 * 4171 * @description 4172 * Configures the poller to run in the specified intervals, using the specified 4173 * setTimeout fn and kicks it off. 4174 */ 4175 function startPoller(interval, setTimeout) { 4176 (function check() { 4177 forEach(pollFns, function(pollFn){ pollFn(); }); 4178 pollTimeout = setTimeout(check, interval); 4179 })(); 4180 } 4181 4182 ////////////////////////////////////////////////////////////// 4183 // URL API 4184 ////////////////////////////////////////////////////////////// 4185 4186 var lastBrowserUrl = location.href, 4187 baseElement = document.find('base'), 4188 newLocation = null; 4189 4190 /** 4191 * @name ng.$browser#url 4192 * @methodOf ng.$browser 4193 * 4194 * @description 4195 * GETTER: 4196 * Without any argument, this method just returns current value of location.href. 4197 * 4198 * SETTER: 4199 * With at least one argument, this method sets url to new value. 4200 * If html5 history api supported, pushState/replaceState is used, otherwise 4201 * location.href/location.replace is used. 4202 * Returns its own instance to allow chaining 4203 * 4204 * NOTE: this api is intended for use only by the $location service. Please use the 4205 * {@link ng.$location $location service} to change url. 4206 * 4207 * @param {string} url New url (when used as setter) 4208 * @param {boolean=} replace Should new url replace current history record ? 4209 */ 4210 self.url = function(url, replace) { 4211 // Android Browser BFCache causes location, history reference to become stale. 4212 if (location !== window.location) location = window.location; 4213 if (history !== window.history) history = window.history; 4214 4215 // setter 4216 if (url) { 4217 if (lastBrowserUrl == url) return; 4218 lastBrowserUrl = url; 4219 if ($sniffer.history) { 4220 if (replace) history.replaceState(null, '', url); 4221 else { 4222 history.pushState(null, '', url); 4223 // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 4224 baseElement.attr('href', baseElement.attr('href')); 4225 } 4226 } else { 4227 newLocation = url; 4228 if (replace) { 4229 location.replace(url); 4230 } else { 4231 location.href = url; 4232 } 4233 } 4234 return self; 4235 // getter 4236 } else { 4237 // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href 4238 // methods not updating location.href synchronously. 4239 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 4240 return newLocation || location.href.replace(/%27/g,"'"); 4241 } 4242 }; 4243 4244 var urlChangeListeners = [], 4245 urlChangeInit = false; 4246 4247 function fireUrlChange() { 4248 newLocation = null; 4249 if (lastBrowserUrl == self.url()) return; 4250 4251 lastBrowserUrl = self.url(); 4252 forEach(urlChangeListeners, function(listener) { 4253 listener(self.url()); 4254 }); 4255 } 4256 4257 /** 4258 * @name ng.$browser#onUrlChange 4259 * @methodOf ng.$browser 4260 * @TODO(vojta): refactor to use node's syntax for events 4261 * 4262 * @description 4263 * Register callback function that will be called, when url changes. 4264 * 4265 * It's only called when the url is changed from outside of angular: 4266 * - user types different url into address bar 4267 * - user clicks on history (forward/back) button 4268 * - user clicks on a link 4269 * 4270 * It's not called when url is changed by $browser.url() method 4271 * 4272 * The listener gets called with new url as parameter. 4273 * 4274 * NOTE: this api is intended for use only by the $location service. Please use the 4275 * {@link ng.$location $location service} to monitor url changes in angular apps. 4276 * 4277 * @param {function(string)} listener Listener function to be called when url changes. 4278 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 4279 */ 4280 self.onUrlChange = function(callback) { 4281 if (!urlChangeInit) { 4282 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 4283 // don't fire popstate when user change the address bar and don't fire hashchange when url 4284 // changed by push/replaceState 4285 4286 // html5 history api - popstate event 4287 if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); 4288 // hashchange event 4289 if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); 4290 // polling 4291 else self.addPollFn(fireUrlChange); 4292 4293 urlChangeInit = true; 4294 } 4295 4296 urlChangeListeners.push(callback); 4297 return callback; 4298 }; 4299 4300 ////////////////////////////////////////////////////////////// 4301 // Misc API 4302 ////////////////////////////////////////////////////////////// 4303 4304 /** 4305 * @name ng.$browser#baseHref 4306 * @methodOf ng.$browser 4307 * 4308 * @description 4309 * Returns current <base href> 4310 * (always relative - without domain) 4311 * 4312 * @returns {string=} current <base href> 4313 */ 4314 self.baseHref = function() { 4315 var href = baseElement.attr('href'); 4316 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 4317 }; 4318 4319 ////////////////////////////////////////////////////////////// 4320 // Cookies API 4321 ////////////////////////////////////////////////////////////// 4322 var lastCookies = {}; 4323 var lastCookieString = ''; 4324 var cookiePath = self.baseHref(); 4325 4326 /** 4327 * @name ng.$browser#cookies 4328 * @methodOf ng.$browser 4329 * 4330 * @param {string=} name Cookie name 4331 * @param {string=} value Cookie value 4332 * 4333 * @description 4334 * The cookies method provides a 'private' low level access to browser cookies. 4335 * It is not meant to be used directly, use the $cookie service instead. 4336 * 4337 * The return values vary depending on the arguments that the method was called with as follows: 4338 * 4339 * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify 4340 * it 4341 * - cookies(name, value) -> set name to value, if value is undefined delete the cookie 4342 * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that 4343 * way) 4344 * 4345 * @returns {Object} Hash of all cookies (if called without any parameter) 4346 */ 4347 self.cookies = function(name, value) { 4348 /* global escape: false, unescape: false */ 4349 var cookieLength, cookieArray, cookie, i, index; 4350 4351 if (name) { 4352 if (value === undefined) { 4353 rawDocument.cookie = escape(name) + "=;
4353path=" + cookiePath + 4354 ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; 4355 } else { 4356 if (isString(value)) { 4357 cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + 4358 ';path=' + cookiePath).length + 1; 4359 4360 // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: 4361 // - 300 cookies 4362 // - 20 cookies per unique domain 4363 // - 4096 bytes per cookie 4364 if (cookieLength > 4096) { 4365 $log.warn("Cookie '"+ name + 4366 "' possibly not set or overflowed because it was too large ("+ 4367 cookieLength + " > 4096 bytes)!"); 4368 } 4369 } 4370 } 4371 } else { 4372 if (rawDocument.cookie !== lastCookieString) { 4373 lastCookieString = rawDocument.cookie; 4374 cookieArray = lastCookieString.split("; "); 4375 lastCookies = {}; 4376 4377 for (i = 0; i < cookieArray.length; i++) { 4378 cookie = cookieArray[i]; 4379 index = cookie.indexOf('='); 4380 if (index > 0) { //ignore nameless cookies 4381 name = unescape(cookie.substring(0, index)); 4382 // the first value that is seen for a cookie is the most 4383 // specific one. values for the same cookie name that 4384 // follow are for less specific paths. 4385 if (lastCookies[name] === undefined) { 4386 lastCookies[name] = unescape(cookie.substring(index + 1)); 4387 } 4388 } 4389 } 4390 } 4391 return lastCookies; 4392 } 4393 }; 4394 4395 4396 /** 4397 * @name ng.$browser#defer 4398 * @methodOf ng.$browser 4399 * @param {function()} fn A function, who's execution should be deferred. 4400 * @param {number=} [delay=0] of milliseconds to defer the function execution. 4401 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 4402 * 4403 * @description 4404 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 4405 * 4406 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 4407 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 4408 * via `$browser.defer.flush()`. 4409 * 4410 */ 4411 self.defer = function(fn, delay) { 4412 var timeoutId; 4413 outstandingRequestCount++; 4414 timeoutId = setTimeout(function() { 4415 delete pendingDeferIds[timeoutId]; 4416 completeOutstandingRequest(fn); 4417 }, delay || 0); 4418 pendingDeferIds[timeoutId] = true; 4419 return timeoutId; 4420 }; 4421 4422 4423 /** 4424 * @name ng.$browser#defer.cancel 4425 * @methodOf ng.$browser.defer 4426 * 4427 * @description 4428 * Cancels a deferred task identified with `deferId`. 4429 * 4430 * @param {*} deferId Token returned by the `$browser.defer` function. 4431 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 4432 * canceled. 4433 */ 4434 self.defer.cancel = function(deferId) { 4435 if (pendingDeferIds[deferId]) { 4436 delete pendingDeferIds[deferId]; 4437 clearTimeout(deferId); 4438 completeOutstandingRequest(noop); 4439 return true; 4440 } 4441 return false; 4442 }; 4443 4444} 4445 4446function $BrowserProvider(){ 4447 this.$get = ['$window', '$log', '$sniffer', '$document', 4448 function( $window, $log, $sniffer, $document){ 4449 return new Browser($window, $document, $log, $sniffer); 4450 }]; 4451} 4452 4453/** 4454 * @ngdoc object 4455 * @name ng.$cacheFactory 4456 * 4457 * @description 4458 * Factory that constructs cache objects and gives access to them. 4459 * 4460 * <pre> 4461 * 4462 * var cache = $cacheFactory('cacheId'); 4463 * expect($cacheFactory.get('cacheId')).toBe(cache); 4464 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 4465 * 4466 * cache.put("key", "value"); 4467 * cache.put("another key", "another value"); 4468 * 4469 * // We've specified no options on creation 4470 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 4471 * 4472 * </pre> 4473 * 4474 * 4475 * @param {string} cacheId Name or id of the newly created cache. 4476 * @param {object=} options Options object that specifies the cache behavior. Properties: 4477 * 4478 * - `{number=}` `capacity` â turns the cache into LRU cache. 4479 * 4480 * @returns {object} Newly created cache object with the following set of methods: 4481 * 4482 * - `{object}` `info()` â Returns id, size, and options of cache. 4483 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 4484 * it. 4485 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 4486 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 4487 * - `{void}` `removeAll()` â Removes all cached values. 4488 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 4489 * 4490 */
4491function $CacheFactoryProvider() { 4492 4493 this.$get = function() { 4494 var caches = {}; 4495 4496 function cacheFactory(cacheId, options) { 4497 if (cacheId in caches) { 4498 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 4499 } 4500 4501 var size = 0, 4502 stats = extend({}, options, {id: cacheId}), 4503 data = {}, 4504 capacity = (options && options.capacity) || Number.MAX_VALUE, 4505 lruHash = {}, 4506 freshEnd = null, 4507 staleEnd = null; 4508 4509 return caches[cacheId] = { 4510 4511 put: function(key, value) { 4512 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 4513 4514 refresh(lruEntry); 4515 4516 if (isUndefined(value)) return; 4517 if (!(key in data)) size++; 4518 data[key] = value; 4519 4520 if (size > capacity) { 4521 this.remove(staleEnd.key); 4522 } 4523 4524 return value; 4525 }, 4526 4527 4528 get: function(key) { 4529 var lruEntry = lruHash[key]; 4530 4531 if (!lruEntry) return; 4532 4533 refresh(lruEntry); 4534 4535 return data[key]; 4536 }, 4537 4538 4539 remove: function(key) { 4540 var lruEntry = lruHash[key]; 4541 4542 if (!lruEntry) return; 4543 4544 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 4545 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 4546 link(lruEntry.n,lruEntry.p); 4547 4548 delete lruHash[key]; 4549 delete data[key]; 4550 size--; 4551 }, 4552 4553 4554 removeAll: function() { 4555 data = {}; 4556 size = 0; 4557 lruHash = {}; 4558 freshEnd = staleEnd = null; 4559 }, 4560 4561 4562 destroy: function() { 4563 data = null; 4564 stats = null; 4565 lruHash = null; 4566 delete caches[cacheId]; 4567 }, 4568 4569 4570 info: function() { 4571 return extend({}, stats, {size: size}); 4572 } 4573 }; 4574 4575 4576 /** 4577 * makes the `entry` the freshEnd of the LRU linked list 4578 */ 4579 function refresh(entry) { 4580 if (entry != freshEnd) { 4581 if (!staleEnd) { 4582 staleEnd = entry; 4583 } else if (staleEnd == entry) { 4584 staleEnd = entry.n; 4585 } 4586 4587 link(entry.n, entry.p); 4588 link(entry, freshEnd); 4589 freshEnd = entry; 4590 freshEnd.n = null; 4591 } 4592 } 4593 4594 4595 /** 4596 * bidirectionally links two entries of the LRU linked list 4597 */ 4598 function link(nextEntry, prevEntry) { 4599 if (nextEntry != prevEntry) { 4600 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 4601 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 4602 } 4603 } 4604 } 4605 4606 4607 /** 4608 * @ngdoc method 4609 * @name ng.$cacheFactory#info 4610 * @methodOf ng.$cacheFactory 4611 * 4612 * @description 4613 * Get information about all the of the caches that have been created 4614 * 4615 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 4616 */ 4617 cacheFactory.info = function() { 4618 var info = {}; 4619 forEach(caches, function(cache, cacheId) { 4620 info[cacheId] = cache.info(); 4621 }); 4622 return info; 4623 }; 4624 4625 4626 /** 4627 * @ngdoc method 4628 * @name ng.$cacheFactory#get 4629 * @methodOf ng.$cacheFactory 4630 * 4631 * @description 4632 * Get access to a cache object by the `cacheId` used when it was created. 4633 * 4634 * @param {string} cacheId Name or id of a cache to access. 4635 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 4636 */ 4637 cacheFactory.get = function(cacheId) { 4638 return caches[cacheId]; 4639 }; 4640 4641 4642 return cacheFactory; 4643 }; 4644} 4645 4646/** 4647 * @ngdoc object 4648 * @name ng.$templateCache 4649 * 4650 * @description 4651 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 4652 * can load templates directly into the cache in a `script` tag, or by consuming the 4653 * `$templateCache` service directly. 4654 * 4655 * Adding via the `script` tag: 4656 * <pre> 4657 * <html ng-app> 4658 * <head> 4659 * <script type="text/ng-template" id="templateId.html"> 4660 * This is the content of the template 4661 * </script> 4662 * </head> 4663 * ... 4664 * </html> 4665 * </pre> 4666 * 4667 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 4668 * the document, but it must be below the `ng-app` definition. 4669 * 4670 * Adding via the $templateCache service: 4671 * 4672 * <pre> 4673 * var myApp = angular.module('myApp', []); 4674 * myApp.run(function($templateCache) { 4675 * $templateCache.put('templateId.html', 'This is the content of the template'); 4676 * }); 4677 * </pre> 4678 * 4679 * To retrieve the template later, simply use it in your HTML: 4680 * <pre> 4681 * <div ng-include=" 'templateId.html' "></div> 4682 * </pre> 4683 * 4684 * or get it via Javascript: 4685 * <pre> 4686 * $templateCache.get('templateId.html') 4687 * </pre> 4688 * 4689 * See {@link ng.$cacheFactory $cacheFactory}. 4690 * 4691 */ 4692function $TemplateCacheProvider() { 4693 this.$get = ['$cacheFactory', function($cacheFactory) { 4694 return $cacheFactory('templates'); 4695 }]; 4696} 4697 4698/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 4699 * 4700 * DOM-related variables: 4701 * 4702 * - "node" - DOM Node 4703 * - "element" - DOM Element or Node 4704 * - "$node" or "$element" - jqLite-wrapped node or element 4705 * 4706 * 4707 * Compiler related stuff: 4708 * 4709 * - "linkFn" - linking fn of a single directive 4710 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 4711 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 4712 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 4713 */ 4714 4715 4716/** 4717 * @ngdoc function 4718 * @name ng.$compile 4719 * @function 4720 * 4721 * @description 4722 * Compiles an HTML string or DOM into a template and produces a template function, which 4723 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 4724 * 4725 * The compilation is a process of walking the DOM tree and matching DOM elements to 4726 * {@link ng.$compileProvider#methods_directive directives}. 4727 * 4728 * <div class="alert alert-warning"> 4729 * **Note:** This document is an in-depth reference of all directive options. 4730 * For a gentle introduction to directives with examples of common use cases, 4731 * see the {@link guide/directive directive guide}. 4732 * </div> 4733 * 4734 * ## Comprehensive Directive API 4735 * 4736 * There are many different options for a directive. 4737 * 4738 * The difference resides in the return value of the factory function. 4739 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 4740 * or just the `postLink` function (all other properties will have the default values). 4741 * 4742 * <div class="alert alert-success"> 4743 * **Best Practice:** It's recommended to use the "directive definition object" form. 4744 * </div> 4745 * 4746 * Here's an example directive declared with a Directive Definition Object: 4747 * 4748 * <pre> 4749 * var myModule = angular.module(...); 4750 * 4751 * myModule.directive('directiveName', function factory(injectables) { 4752 * var directiveDefinitionObject = { 4753 * priority: 0, 4754 * template: '<div></div>
4754', // or // function(tElement, tAttrs) { ... }, 4755 * // or 4756 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 4757 * replace: false, 4758 * transclude: false, 4759 * restrict: 'A', 4760 * scope: false, 4761 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 4762 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 4763 * compile: function compile(tElement, tAttrs, transclude) { 4764 * return { 4765 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 4766 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 4767 * } 4768 * // or 4769 * // return function postLink( ... ) { ... } 4770 * }, 4771 * // or 4772 * // link: { 4773 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 4774 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 4775 * // } 4776 * // or 4777 * // link: function postLink( ... ) { ... } 4778 * }; 4779 * return directiveDefinitionObject; 4780 * }); 4781 * </pre> 4782 * 4783 * <div class="alert alert-warning"> 4784 * **Note:** Any unspecified options will use the default value. You can see the default values below. 4785 * </div> 4786 * 4787 * Therefore the above can be simplified as: 4788 * 4789 * <pre> 4790 * var myModule = angular.module(...); 4791 * 4792 * myModule.directive('directiveName', function factory(injectables) { 4793 * var directiveDefinitionObject = { 4794 * link: function postLink(scope, iElement, iAttrs) { ... } 4795 * }; 4796 * return directiveDefinitionObject; 4797 * // or 4798 * // return function postLink(scope, iElement, iAttrs) { ... } 4799 * }); 4800 * </pre> 4801 * 4802 * 4803 * 4804 * ### Directive Definition Object 4805 * 4806 * The directive definition object provides instructions to the {@link api/ng.$compile 4807 * compiler}. The attributes are: 4808 * 4809 * #### `priority` 4810 * When there are multiple directives defined on a single DOM element, sometimes it 4811 * is necessary to specify the order in which the directives are applied. The `priority` is used 4812 * to sort the directives before their `compile` functions get called. Priority is defined as a 4813 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 4814 * are also run in priority order, but post-link functions are run in reverse order. The order 4815 * of directives with the same priority is undefined. The default priority is `0`. 4816 * 4817 * #### `terminal` 4818 * If set to true then the current `priority` will be the last set of directives 4819 * which will execute (any directives at the current priority will still execute 4820 * as the order of execution on same `priority` is undefined). 4821 * 4822 * #### `scope` 4823 * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the 4824 * same element request a new scope, only one new scope is created. The new scope rule does not 4825 * apply for the root of the template since the root of the template always gets a new scope. 4826 * 4827 * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from 4828 * normal scope in that it does not prototypically inherit from the parent scope. This is useful 4829 * when creating reusable components, which should not accidentally read or modify data in the 4830 * parent scope. 4831 * 4832 * The 'isolate' scope takes an object hash which defines a set of local scope properties 4833 * derived from the parent scope. These local properties are useful for aliasing values for 4834 * templates. Locals definition is a hash of local scope property to its source: 4835 * 4836 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is 4837 * always a string since DOM attributes are strings. If no `attr` name is specified then the 4838 * attribute name is assumed to be the same as the local name. 4839 * Given `<widget my-attr="hello {{name}}">` and widget definition 4840 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 4841 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 4842 * `localName` property on the widget scope. The `name` is read from the parent scope (not 4843 * component scope). 4844 * 4845 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 4846 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 4847 * name is specified then the attribute name is assumed to be the same as the local name. 4848 * Given `<widget my-attr="parentModel">` and widget definition of 4849 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 4850 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 4851 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 4852 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 4853 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. 4854 * 4855 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 4856 * If no `attr` name is specified then the attribute name is assumed to be the same as the 4857 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 4858 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 4859 * a function wrapper for the `count = count + value` expression. Often it's desirable to 4860 * pass data from the isolated scope via an expression and to the parent scope, this can be 4861 * done by passing a map of local variable names and values into the expression wrapper fn. 4862 * For example, if the expression is `increment(amount)` then we can specify the amount value 4863 * by calling the `localFn` as `localFn({amount: 22})`. 4864 * 4865 * 4866 * 4867 * #### `controller` 4868 * Controller constructor function. The controller is instantiated before the 4869 * pre-linking phase and it is shared with other directives (see 4870 * `require` attribute). This allows the directives to communicate with each other and augment 4871 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 4872 * 4873 * * `$scope` - Current scope associated with the element 4874 * * `$element` - Current element 4875 * * `$attrs` - Current attributes object for the element 4876 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. 4877 * The scope can be overridden by an optional first argument. 4878 * `function([scope], cloneLinkingFn)`. 4879 * 4880 * 4881 * #### `require` 4882 * Require another directive and inject its controller as the fourth argument to the linking function. The
4883 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 4884 * injected argument will be an array in corresponding order. If no such directive can be 4885 * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: 4886 * 4887 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 4888 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 4889 * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found. 4890 * * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the 4891 * `link` fn if not found. 4892 * 4893 * 4894 * #### `controllerAs` 4895 * Controller alias at the directive scope. An alias for the controller so it 4896 * can be referenced at the directive template. The directive needs to define a scope for this 4897 * configuration to be used. Useful in the case when directive is used as component. 4898 * 4899 * 4900 * #### `restrict` 4901 * String of subset of `EACM` which restricts the directive to a specific directive 4902 * declaration style. If omitted, the default (attributes only) is used. 4903 * 4904 * * `E` - Element name: `<my-directive></my-directive>` 4905 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 4906 * * `C` - Class: `<div class="my-directive: exp;"></div>` 4907 * * `M` - Comment: `<!-- directive: my-directive exp -->` 4908 * 4909 * 4910 * #### `template` 4911 * replace the current element with the contents of the HTML. The replacement process 4912 * migrates all of the attributes / classes from the old element to the new one. See the 4913 * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive 4914 * Directives Guide} for an example. 4915 * 4916 * You can specify `template` as a string representing the template or as a function which takes 4917 * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and 4918 * returns a string value representing the template. 4919 * 4920 * 4921 * #### `templateUrl` 4922 * Same as `template` but the template is loaded from the specified URL. Because 4923 * the template loading is asynchronous the compilation/linking is suspended until the template 4924 * is loaded. 4925 * 4926 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 4927 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 4928 * a string value representing the url. In either case, the template URL is passed through {@link 4929 * api/ng.$sce#methods_getTrustedResourceUrl $sce.getTrustedResourceUrl}. 4930 * 4931 * 4932 * #### `replace` 4933 * specify where the template should be inserted. Defaults to `false`. 4934 * 4935 * * `true` - the template will replace the current element. 4936 * * `false` - the template will replace the contents of the current element. 4937 * 4938 * 4939 * #### `transclude` 4940 * compile the content of the element and make it available to the directive. 4941 * Typically used with {@link api/ng.directive:ngTransclude 4942 * ngTransclude}. The advantage of transclusion is that the linking function receives a 4943 * transclusion function which is pre-bound to the correct scope. In a typical setup the widget 4944 * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` 4945 * scope. This makes it possible for the widget to have private state, and the transclusion to 4946 * be bound to the parent (pre-`isolate`) scope. 4947 * 4948 * * `true` - transclude the content of the directive. 4949 * * `'element'` - transclude the whole element including any directives defined at lower priority. 4950 * 4951 * 4952 * #### `compile` 4953 * 4954 * <pre> 4955 * function compile(tElement, tAttrs, transclude) { ... } 4956 * </pre> 4957 * 4958 * The compile function deals with transforming the template DOM. Since most directives do not do 4959 * template transformation, it is not used often. Examples that require compile functions are 4960 * directives that transform template DOM, such as {@link 4961 * api/ng.directive:ngRepeat ngRepeat}, or load the contents 4962 * asynchronously, such as {@link api/ngRoute.directive:ngView ngView}. The 4963 * compile function takes the following arguments. 4964 * 4965 * * `tElement` - template element - The element where the directive has been declared. It is 4966 * safe to do template transformation on the element and child elements only. 4967 * 4968 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 4969 * between all directive compile functions. 4970 * 4971 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 4972 * 4973 * <div class="alert alert-warning"> 4974 * **Note:** The template instance and the link instance may be different objects if the template has 4975 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 4976 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
4977 * should be done in a linking function rather than in a compile function. 4978 * </div> 4979 * 4980 * <div class="alert alert-error"> 4981 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 4982 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 4983 * to the link function instead. 4984 * </div> 4985 4986 * A compile function can have a return value which can be either a function or an object. 4987 * 4988 * * returning a (post-link) function - is equivalent to registering the linking function via the 4989 * `link` property of the config object when the compile function is empty. 4990 * 4991 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 4992 * control when a linking function should be called during the linking phase. See info about 4993 * pre-linking and post-linking functions below. 4994 * 4995 * 4996 * #### `link` 4997 * This property is used only if the `compile` property is not defined. 4998 * 4999 * <pre> 5000 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 5001 * </pre> 5002 * 5003 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 5004 * executed after the template has been cloned. This is where most of the directive logic will be 5005 * put. 5006 * 5007 * * `scope` - {@link api/ng.$rootScope.Scope Scope} - The scope to be used by the 5008 * directive for registering {@link api/ng.$rootScope.Scope#methods_$watch watches}. 5009 * 5010 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 5011 * manipulate the children of the element only in `postLink` function since the children have 5012 * already been linked. 5013 * 5014 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 5015 * between all directive linking functions. 5016 * 5017 * * `controller` - a controller instance - A controller instance if at least one directive on the 5018 * element defines a controller. The controller is shared among all the directives, which allows 5019 * the directives to use the controllers as a communication channel. 5020 * 5021 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 5022 * The scope can be overridden by an optional first argument. This is the same as the `$transclude` 5023 * parameter of directive controllers. 5024 * `function([scope], cloneLinkingFn)`. 5025 * 5026 * 5027 * #### Pre-linking function 5028 * 5029 * Executed before the child elements are linked. Not safe to do DOM transformation since the 5030 * compiler linking function will fail to locate the correct elements for linking. 5031 * 5032 * #### Post-linking function 5033 * 5034 * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. 5035 * 5036 * <a name="Attributes"></a> 5037 * ### Attributes 5038 * 5039 * The {@link api/ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 5040 * `link()` or `compile()` functions. It has a variety of uses. 5041 * 5042 * accessing *Normalized attribute names:* 5043 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 5044 * the attributes object allows for normalized access to 5045 * the attributes. 5046 * 5047 * * *Directive inter-communication:* All directives share the same instance of the attributes 5048 * object which allows the directives to use the attributes object as inter directive 5049 * communication. 5050 * 5051 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 5052 * allowing other directives to read the interpolated value. 5053 * 5054 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 5055 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 5056 * the only way to easily get the actual value because during the linking phase the interpolation 5057 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 5058 * 5059 * <pre> 5060 * function linkingFn(scope, elm, attrs, ctrl) { 5061 * // get the attribute value 5062 * console.log(attrs.ngModel); 5063 * 5064 * // change the attribute 5065 * attrs.$set('ngModel', 'new value'); 5066 * 5067 * // observe changes to interpolated attribute 5068 * attrs.$observe('ngModel', function(value) { 5069 * console.log('ngModel has changed value to ' + value); 5070 * }); 5071 * } 5072 * </pre> 5073 * 5074 * Below is an example using `$compileProvider`. 5075 * 5076 * <div class="alert alert-warning"> 5077 * **Note**: Typically directives are registered with `module.directive`. The example below is 5078 * to illustrate how `$compile` works. 5079 * </div> 5080 * 5081 <doc:example module="compile"> 5082 <doc:source> 5083 <script>
5084 angular.module('compile', [], function($compileProvider) { 5085 // configure new 'compile' directive by passing a directive 5086 // factory function. The factory function injects the '$compile' 5087 $compileProvider.directive('compile', function($compile) { 5088 // directive factory creates a link function 5089 return function(scope, element, attrs) { 5090 scope.$watch( 5091 function(scope) { 5092 // watch the 'compile' expression for changes 5093 return scope.$eval(attrs.compile); 5094 }, 5095 function(value) { 5096 // when the 'compile' expression changes 5097 // assign it into the current DOM 5098 element.html(value); 5099 5100 // compile the new DOM and link it to the current 5101 // scope. 5102 // NOTE: we only compile .childNodes so that 5103 // we don't get into infinite loop compiling ourselves 5104 $compile(element.contents())(scope); 5105 } 5106 ); 5107 }; 5108 }) 5109 }); 5110 5111 function Ctrl($scope) { 5112 $scope.name = 'Angular'; 5113 $scope.html = 'Hello {{name}}'; 5114 } 5115 </script> 5116 <div ng-controller="Ctrl"> 5117 <input ng-model="name"> <br> 5118 <textarea ng-model="html"></textarea> <br> 5119 <div compile="html"></div> 5120 </div> 5121 </doc:source> 5122 <doc:scenario> 5123 it('should auto compile', function() { 5124 expect(element('div[compile]').text()).toBe('Hello Angular'); 5125 input('html').enter('{{name}}!'); 5126 expect(element('div[compile]').text()).toBe('Angular!'); 5127 }); 5128 </doc:scenario> 5129 </doc:example> 5130 5131 * 5132 * 5133 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 5134 * @param {function(angular.Scope[, cloneAttachFn]} transclude function available to directives. 5135 * @param {number} maxPriority only apply directives lower then given priority (Only effects the 5136 * root element(s), not their children) 5137 * @returns {function(scope[, cloneAttachFn])} a link function which is used to bind template 5138 * (a DOM element/tree) to a scope. Where: 5139 * 5140 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 5141 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 5142 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 5143 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 5144 * called as: <br> `cloneAttachFn(clonedElement, scope)` where: 5145 * 5146 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 5147 * * `scope` - is the current scope with which the linking function is working with. 5148 * 5149 * Calling the linking function returns the element of the template. It is either the original 5150 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 5151 * 5152 * After linking the view is not updated until after a call to $digest which typically is done by 5153 * Angular automatically. 5154 * 5155 * If you need access to the bound view, there are two ways to do it: 5156 * 5157 * - If you are not asking the linking function to clone the template, create the DOM element(s) 5158 * before you send them to the compiler and keep this reference around. 5159 * <pre> 5160 * var element = $compile('<p>{{total}}</p>')(scope); 5161 * </pre> 5162 * 5163 * - if on the other hand, you need the element to be cloned, the view reference from the original 5164 * example would not point to the clone, but rather to the original template that was cloned. In 5165 * this case, you can access the clone via the cloneAttachFn: 5166 * <pre> 5167 * var templateElement = angular.element('<p>{{total}}</p>'), 5168 * scope = ....; 5169 * 5170 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 5171 * //attach the clone to DOM document at the right place 5172 * }); 5173 * 5174 * //now we have reference to the cloned DOM via `clonedElement` 5175 * </pre> 5176 * 5177 * 5178 * For information on how the compiler works, see the 5179 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 5180 */ 5181 5182var $compileMinErr = minErr('$compile'); 5183 5184/** 5185 * @ngdoc service 5186 * @name ng.$compileProvider 5187 * @function 5188 * 5189 * @description 5190 */
vendor: 4,665 bytes, lines 5191-5302
5191$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 5192function $CompileProvider($provide, $$sanitizeUriProvider) { 5193 var hasDirectives = {}, 5194 Suffix = 'Directive', 5195 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, 5196 CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/; 5197 5198 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 5199 // The assumption is that future DOM event attribute names will begin with 5200 // 'on' and be composed of only English letters. 5201 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 5202 5203 /** 5204 * @ngdoc function 5205 * @name ng.$compileProvider#directive 5206 * @methodOf ng.$compileProvider 5207 * @function 5208 * 5209 * @description 5210 * Register a new directive with the compiler. 5211 * 5212 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 5213 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 5214 * names and the values are the factories. 5215 * @param {function|Array} directiveFactory An injectable directive factory function. See 5216 * {@link guide/directive} for more info. 5217 * @returns {ng.$compileProvider} Self for chaining. 5218 */ 5219 this.directive = function registerDirective(name, directiveFactory) { 5220 assertNotHasOwnProperty(name, 'directive'); 5221 if (isString(name)) { 5222 assertArg(directiveFactory, 'directiveFactory'); 5223 if (!hasDirectives.hasOwnProperty(name)) { 5224 hasDirectives[name] = []; 5225 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 5226 function($injector, $exceptionHandler) { 5227 var directives = []; 5228 forEach(hasDirectives[name], function(directiveFactory, index) { 5229 try { 5230 var directive = $injector.invoke(directiveFactory); 5231 if (isFunction(directive)) { 5232 directive = { compile: valueFn(directive) }; 5233 } else if (!directive.compile && directive.link) { 5234 directive.compile = valueFn(directive.link); 5235 } 5236 directive.priority = directive.priority || 0; 5237 directive.index = index; 5238 directive.name = directive.name || name; 5239 directive.require = directive.require || (directive.controller && directive.name); 5240 directive.restrict = directive.restrict || 'A'; 5241 directives.push(directive); 5242 } catch (e) { 5243 $exceptionHandler(e); 5244 } 5245 }); 5246 return directives; 5247 }]); 5248 } 5249 hasDirectives[name].push(directiveFactory); 5250 } else { 5251 forEach(name, reverseParams(registerDirective)); 5252 } 5253 return this; 5254 }; 5255 5256 5257 /** 5258 * @ngdoc function 5259 * @name ng.$compileProvider#aHrefSanitizationWhitelist 5260 * @methodOf ng.$compileProvider 5261 * @function 5262 * 5263 * @description 5264 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5265 * urls during a[href] sanitization. 5266 * 5267 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5268 * 5269 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 5270 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 5271 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 5272 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5273 * 5274 * @param {RegExp=} regexp New regexp to whitelist urls with. 5275 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5276 * chaining otherwise. 5277 */ 5278 this.aHrefSanitizationWhitelist = function(regexp) { 5279 if (isDefined(regexp)) { 5280 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 5281 return this; 5282 } else { 5283 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 5284 } 5285 }; 5286 5287 5288 /** 5289 * @ngdoc function 5290 * @name ng.$compileProvider#imgSrcSanitizationWhitelist 5291 * @methodOf ng.$compileProvider 5292 * @function 5293 * 5294 * @description 5295 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5296 * urls during img[src] sanitization. 5297 * 5298 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5299 * 5300 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 5301 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 5302 * regular expression. If a match is found, the original url is
5302written into the dom. Otherwise, 5303 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5304 * 5305 * @param {RegExp=} regexp New regexp to whitelist urls with. 5306 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5307 * chaining otherwise. 5308 */ 5309 this.imgSrcSanitizationWhitelist = function(regexp) { 5310 if (isDefined(regexp)) { 5311 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 5312 return this; 5313 } else { 5314 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 5315 } 5316 }; 5317 5318 this.$get = [ 5319 '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', 5320 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 5321 function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, 5322 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 5323 5324 var Attributes = function(element, attr) { 5325 this.$$element = element; 5326 this.$attr = attr || {}; 5327 }; 5328 5329 Attributes.prototype = { 5330 $normalize: directiveNormalize, 5331 5332 5333 /** 5334 * @ngdoc function 5335 * @name ng.$compile.directive.Attributes#$addClass 5336 * @methodOf ng.$compile.directive.Attributes 5337 * @function 5338 * 5339 * @description 5340 * Adds the CSS class value specified by the classVal parameter to the element. If animations 5341 * are enabled then an animation will be triggered for the class addition. 5342 * 5343 * @param {string} classVal The className value that will be added to the element 5344 */ 5345 $addClass : function(classVal) { 5346 if(classVal && classVal.length > 0) { 5347 $animate.addClass(this.$$element, classVal); 5348 } 5349 }, 5350 5351 /** 5352 * @ngdoc function 5353 * @name ng.$compile.directive.Attributes#$removeClass 5354 * @methodOf ng.$compile.directive.Attributes 5355 * @function 5356 * 5357 * @description 5358 * Removes the CSS class value specified by the classVal parameter from the element. If 5359 * animations are enabled then an animation will be triggered for the class removal. 5360 * 5361 * @param {string} classVal The className value that will be removed from the element 5362 */ 5363 $removeClass : function(classVal) { 5364 if(classVal && classVal.length > 0) { 5365 $animate.removeClass(this.$$element, classVal); 5366 } 5367 }, 5368 5369 /** 5370 * @ngdoc function 5371 * @name ng.$compile.directive.Attributes#$updateClass 5372 * @methodOf ng.$compile.directive.Attributes 5373 * @function 5374 * 5375 * @description 5376 * Adds and removes the appropriate CSS class values to the element based on the difference 5377 * between the new and old CSS class values (specified as newClasses and oldClasses). 5378 * 5379 * @param {string} newClasses The current CSS className value 5380 * @param {string} oldClasses The former CSS className value 5381 */ 5382 $updateClass : function(newClasses, oldClasses) { 5383 this.$removeClass(tokenDifference(oldClasses, newClasses)); 5384 this.$addClass(tokenDifference(newClasses, oldClasses)); 5385 }, 5386 5387 /** 5388 * Set a normalized attribute on the element in a way such that all directives 5389 * can share the attribute. This function properly handles boolean attributes. 5390 * @param {string} key Normalized key. (ie ngAttribute) 5391 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 5392 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 5393 * Defaults to true. 5394 * @param {string=} attrName Optional none normalized name. Defaults to key. 5395 */ 5396 $set: function(key, value, writeAttr, attrName) { 5397 // TODO: decide whether or not to throw an error if "class" 5398 //is set through this function since it may cause $updateClass to 5399 //become unstable. 5400 5401 var booleanKey = getBooleanAttrName(this.$$element[0], key), 5402 normalizedVal, 5403 nodeName; 5404 5405 if (booleanKey) { 5406 this.$$element.prop(key, value); 5407 attrName = booleanKey; 5408 } 5409 5410 this[key] = value; 5411 5412 // translate normalized key to actual key 5413 if (attrName) { 5414 this.$attr[key] = attrName; 5415 } else { 5416 attrName = this.$attr[key]; 5417 if (!attrName) { 5418 this.$attr[key] = attrName = snake_case(key, '-'); 5419 } 5420 } 5421 5422 nodeName = nodeName_(this.$$element); 5423 5424 // sanitize a[href] and img[src] values 5425 if ((nodeName === 'A' && key === 'href') || 5426 (nodeName === 'IMG' && key === 'src')) { 5427 this[key] = value = $$sanitizeUri(value, key === 'src'); 5428 } 5429 5430 if (writeAttr !== false) { 5431 if (value === null || value === undefined) { 5432 this.$$element.removeAttr(attrName); 5433 } else { 5434 this.$$element.attr(attrName, value); 5435 } 5436 } 5437 5438 // fire observers 5439 var $$observers = this.$$observers;
5440 $$observers && forEach($$observers[key], function(fn) { 5441 try { 5442 fn(value); 5443 } catch (e) { 5444 $exceptionHandler(e); 5445 } 5446 }); 5447 }, 5448 5449 5450 /** 5451 * @ngdoc function 5452 * @name ng.$compile.directive.Attributes#$observe 5453 * @methodOf ng.$compile.directive.Attributes 5454 * @function 5455 * 5456 * @description 5457 * Observes an interpolated attribute. 5458 * 5459 * The observer function will be invoked once during the next `$digest` following 5460 * compilation. The observer is then invoked whenever the interpolated value 5461 * changes. 5462 * 5463 * @param {string} key Normalized key. (ie ngAttribute) . 5464 * @param {function(interpolatedValue)} fn Function that will be called whenever 5465 the interpolated value of the attribute changes. 5466 * See the {@link guide/directive#Attributes Directives} guide for more info. 5467 * @returns {function()} the `fn` parameter. 5468 */ 5469 $observe: function(key, fn) { 5470 var attrs = this, 5471 $$observers = (attrs.$$observers || (attrs.$$observers = {})), 5472 listeners = ($$observers[key] || ($$observers[key] = [])); 5473 5474 listeners.push(fn); 5475 $rootScope.$evalAsync(function() { 5476 if (!listeners.$$inter) { 5477 // no one registered attribute interpolation function, so lets call it manually 5478 fn(attrs[key]); 5479 } 5480 }); 5481 return fn; 5482 } 5483 }; 5484 5485 var startSymbol = $interpolate.startSymbol(), 5486 endSymbol = $interpolate.endSymbol(), 5487 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 5488 ? identity 5489 : function denormalizeTemplate(template) { 5490 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 5491 }, 5492 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 5493 5494 5495 return compile; 5496 5497 //================================ 5498 5499 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 5500 previousCompileContext) { 5501 if (!($compileNodes instanceof jqLite)) { 5502 // jquery always rewraps, whereas we need to preserve the original selector so that we can 5503 // modify it. 5504 $compileNodes = jqLite($compileNodes); 5505 } 5506 // We can not compile top level text elements since text nodes can be merged and we will 5507 // not be able to attach scope data to them, so we will wrap them in <span> 5508 forEach($compileNodes, function(node, index){ 5509 if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { 5510 $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0]; 5511 } 5512 }); 5513 var compositeLinkFn = 5514 compileNodes($compileNodes, transcludeFn, $compileNodes, 5515 maxPriority, ignoreDirective, previousCompileContext); 5516 safeAddClass($compileNodes, 'ng-scope'); 5517 return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){ 5518 assertArg(scope, 'scope'); 5519 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 5520 // and sometimes changes the structure of the DOM. 5521 var $linkNode = cloneConnectFn 5522 ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! 5523 : $compileNodes; 5524 5525 forEach(transcludeControllers, function(instance, name) { 5526 $linkNode.data('$' + name + 'Controller', instance); 5527 }); 5528 5529 // Attach scope only to non-text nodes. 5530 for(var i = 0, ii = $linkNode.length; i<ii; i++) { 5531 var node = $linkNode[i], 5532 nodeType = node.nodeType; 5533 if (nodeType === 1 /* element */ || nodeType === 9 /* document */) { 5534 $linkNode.eq(i).data('$scope', scope); 5535 } 5536 } 5537 5538 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 5539 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode); 5540 return $linkNode; 5541 }; 5542 } 5543 5544 function safeAddClass($element, className) {
5545 try { 5546 $element.addClass(className); 5547 } catch(e) { 5548 // ignore, since it means that we are trying to set class on 5549 // SVG element, where class name is read-only. 5550 } 5551 } 5552 5553 /** 5554 * Compile function matches each node in nodeList against the directives. Once all directives 5555 * for a particular node are collected their compile functions are executed. The compile 5556 * functions return values - the linking functions - are combined into a composite linking 5557 * function, which is the a linking function for the node. 5558 * 5559 * @param {NodeList} nodeList an array of nodes or NodeList to compile 5560 * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the 5561 * scope argument is auto-generated to the new child of the transcluded parent scope. 5562 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 5563 * the rootElement must be set the jqLite collection of the compile root. This is 5564 * needed so that the jqLite collection items can be replaced with widgets. 5565 * @param {number=} maxPriority Max directive priority. 5566 * @returns {?function} A composite linking function of all of the matched directives or null. 5567 */ 5568 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 5569 previousCompileContext) { 5570 var linkFns = [], 5571 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound; 5572 5573 for (var i = 0; i < nodeList.length; i++) { 5574 attrs = new Attributes(); 5575 5576 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 5577 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 5578 ignoreDirective); 5579 5580 nodeLinkFn = (directives.length) 5581 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 5582 null, [], [], previousCompileContext) 5583 : null; 5584 5585 if (nodeLinkFn && nodeLinkFn.scope) { 5586 safeAddClass(jqLite(nodeList[i]), 'ng-scope'); 5587 } 5588 5589 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 5590 !(childNodes = nodeList[i].childNodes) || 5591 !childNodes.length) 5592 ? null 5593 : compileNodes(childNodes, 5594 nodeLinkFn ? nodeLinkFn.transclude : transcludeFn); 5595 5596 linkFns.push(nodeLinkFn, childLinkFn); 5597 linkFnFound = linkFnFound || nodeLinkFn || childLinkFn; 5598 //use the previous context only for the first element in the virtual group 5599 previousCompileContext = null; 5600 } 5601 5602 // return a linking function if we have found anything, null otherwise 5603 return linkFnFound ? compositeLinkFn : null; 5604 5605 function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) { 5606 var nodeLinkFn, childLinkFn, node, $node, childScope, childTranscludeFn, i, ii, n; 5607 5608 // copy nodeList so that linking doesn't break due to live list updates. 5609 var nodeListLength = nodeList.length, 5610 stableNodeList = new Array(nodeListLength); 5611 for (i = 0; i < nodeListLength; i++) { 5612 stableNodeList[i] = nodeList[i]; 5613 } 5614 5615 for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) { 5616 node = stableNodeList[n]; 5617 nodeLinkFn = linkFns[i++]; 5618 childLinkFn = linkFns[i++]; 5619 $node = jqLite(node); 5620 5621 if (nodeLinkFn) { 5622 if (nodeLinkFn.scope) { 5623 childScope = scope.$new(); 5624 $node.data('$scope', childScope); 5625 } else { 5626 childScope = scope; 5627 } 5628 childTranscludeFn = nodeLinkFn.transclude; 5629 if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) { 5630 nodeLinkFn(childLinkFn, childScope, node, $rootElement, 5631 createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn) 5632 ); 5633 } else { 5634 nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn); 5635 } 5636 } else if (childLinkFn) { 5637 childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn); 5638 } 5639 } 5640 } 5641 } 5642 5643 function createBoundTranscludeFn(scope, transcludeFn) { 5644 return function boundTranscludeFn(transcludedScope, cloneFn, controllers) { 5645 var scopeCreated = false;
5646 5647 if (!transcludedScope) { 5648 transcludedScope = scope.$new(); 5649 transcludedScope.$$transcluded = true; 5650 scopeCreated = true; 5651 } 5652 5653 var clone = transcludeFn(transcludedScope, cloneFn, controllers); 5654 if (scopeCreated) { 5655 clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy)); 5656 } 5657 return clone; 5658 }; 5659 } 5660 5661 /** 5662 * Looks for directives on the given node and adds them to the directive collection which is 5663 * sorted. 5664 * 5665 * @param node Node to search. 5666 * @param directives An array to which the directives are added to. This array is sorted before 5667 * the function returns. 5668 * @param attrs The shared attrs object which is used to populate the normalized attributes. 5669 * @param {number=} maxPriority Max directive priority. 5670 */ 5671 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 5672 var nodeType = node.nodeType, 5673 attrsMap = attrs.$attr, 5674 match, 5675 className; 5676 5677 switch(nodeType) { 5678 case 1: /* Element */ 5679 // use the node name: <directive> 5680 addDirective(directives, 5681 directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); 5682 5683 // iterate over the attributes 5684 for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes, 5685 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 5686 var attrStartName = false; 5687 var attrEndName = false; 5688 5689 attr = nAttrs[j]; 5690 if (!msie || msie >= 8 || attr.specified) { 5691 name = attr.name; 5692 // support ngAttr attribute binding 5693 ngAttrName = directiveNormalize(name); 5694 if (NG_ATTR_BINDING.test(ngAttrName)) { 5695 name = snake_case(ngAttrName.substr(6), '-'); 5696 } 5697 5698 var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); 5699 if (ngAttrName === directiveNName + 'Start') { 5700 attrStartName = name; 5701 attrEndName = name.substr(0, name.length - 5) + 'end'; 5702 name = name.substr(0, name.length - 6); 5703 } 5704 5705 nName = directiveNormalize(name.toLowerCase()); 5706 attrsMap[nName] = name; 5707 attrs[nName] = value = trim(attr.value); 5708 if (getBooleanAttrName(node, nName)) { 5709 attrs[nName] = true; // presence means true 5710 } 5711 addAttrInterpolateDirective(node, directives, value, nName); 5712 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 5713 attrEndName); 5714 } 5715 } 5716 5717 // use class as directive 5718 className = node.className; 5719 if (isString(className) && className !== '') { 5720 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 5721 nName = directiveNormalize(match[2]); 5722 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 5723 attrs[nName] = trim(match[3]); 5724 } 5725 className = className.substr(match.index + match[0].length); 5726 } 5727 } 5728 break; 5729 case 3: /* Text Node */ 5730 addTextInterpolateDirective(directives, node.nodeValue); 5731 break; 5732 case 8: /* Comment */ 5733 try { 5734 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 5735 if (match) { 5736 nName = directiveNormalize(match[1]); 5737 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 5738 attrs[nName] = trim(match[2]); 5739 } 5740 } 5741 } catch (e) { 5742 // turns out that under some circumstances IE9 throws errors when one attempts to read 5743 // comment's node value. 5744 // Just ignore it and continue. (Can't seem to reproduce in test case.) 5745 } 5746 break; 5747 } 5748 5749 directives.sort(byPriority); 5750 return directives; 5751 } 5752 5753 /**
5754 * Given a node with an directive-start it collects all of the siblings until it finds 5755 * directive-end. 5756 * @param node 5757 * @param attrStart 5758 * @param attrEnd 5759 * @returns {*} 5760 */ 5761 function groupScan(node, attrStart, attrEnd) { 5762 var nodes = []; 5763 var depth = 0; 5764 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 5765 var startNode = node; 5766 do { 5767 if (!node) { 5768 throw $compileMinErr('uterdir', 5769 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 5770 attrStart, attrEnd); 5771 } 5772 if (node.nodeType == 1 /** Element **/) { 5773 if (node.hasAttribute(attrStart)) depth++; 5774 if (node.hasAttribute(attrEnd)) depth--; 5775 } 5776 nodes.push(node); 5777 node = node.nextSibling; 5778 } while (depth > 0); 5779 } else { 5780 nodes.push(node); 5781 } 5782 5783 return jqLite(nodes); 5784 } 5785 5786 /** 5787 * Wrapper for linking function which converts normal linking function into a grouped 5788 * linking function. 5789 * @param linkFn 5790 * @param attrStart 5791 * @param attrEnd 5792 * @returns {Function} 5793 */ 5794 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 5795 return function(scope, element, attrs, controllers, transcludeFn) { 5796 element = groupScan(element[0], attrStart, attrEnd); 5797 return linkFn(scope, element, attrs, controllers, transcludeFn); 5798 }; 5799 } 5800 5801 /** 5802 * Once the directives have been collected, their compile functions are executed. This method 5803 * is responsible for inlining directive templates as well as terminating the application 5804 * of the directives if the terminal directive has been reached. 5805 * 5806 * @param {Array} directives Array of collected directives to execute their compile function. 5807 * this needs to be pre-sorted by priority order. 5808 * @param {Node} compileNode The raw DOM node to apply the compile functions to 5809 * @param {Object} templateAttrs The shared attribute function 5810 * @param {function(angular.Scope[, cloneAttachFn]} transcludeFn A linking function, where the 5811 * scope argument is auto-generated to the new 5812 * child of the transcluded parent scope. 5813 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 5814 * argument has the root jqLite array so that we can replace nodes 5815 * on it. 5816 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 5817 * compiling the transclusion. 5818 * @param {Array.<Function>} preLinkFns 5819 * @param {Array.<Function>} postLinkFns 5820 * @param {Object} previousCompileContext Context used for previous compilation of the current 5821 * node 5822 * @returns linkFn 5823 */ 5824 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 5825 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 5826 previousCompileContext) { 5827 previousCompileContext = previousCompileContext || {}; 5828 5829 var terminalPriority = -Number.MAX_VALUE, 5830 newScopeDirective, 5831 controllerDirectives = previousCompileContext.controllerDirectives, 5832 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 5833 templateDirective = previousCompileContext.templateDirective, 5834 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 5835 hasTranscludeDirective = false, 5836 hasElementTranscludeDirective = false, 5837 $compileNode = templateAttrs.$$element = jqLite(compileNode), 5838 directive, 5839 directiveName, 5840 $template, 5841 replaceDirective = originalReplaceDirective, 5842 childTranscludeFn = transcludeFn, 5843 linkFn, 5844 directiveValue; 5845 5846 // executes all directives on the current element 5847 for(var i = 0, ii = directives.length; i < ii; i++) { 5848 directive = directives[i]; 5849 var attrStart = directive.$$start; 5850 var attrEnd = directive.$$end; 5851 5852 // collect multiblock sections 5853 if (attrStart) { 5854 $compileNode = groupScan(compileNode, attrStart, attrEnd); 5855 } 5856 $template = undefined; 5857 5858 if (terminalPriority > directive.priority) { 5859 break; // prevent further processing of directives 5860 } 5861 5862 if (directiveValue = directive.scope) { 5863 newScopeDirective = newScopeDirective || directive; 5864 5865 // skip the check for directives with async templates, we'll check the derived sync 5866 // directive when the template arrives 5867 if (!directive.templateUrl) { 5868 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 5869 $compileNode); 5870 if (isObject(directiveValue)) { 5871 newIsolateScopeDirective = directive; 5872 } 5873 } 5874 } 5875 5876 directiveName = directive.name; 5877 5878 if (!directive.templateUrl && directive.controller) { 5879 directiveValue = directive.controller; 5880 controllerDirectives = controllerDirectives || {}; 5881 assertNoDuplicate("'" + directiveName + "' controller", 5882 controllerDirectives[directiveName], directive, $compileNode); 5883 controllerDirectives[directiveName] = directive; 5884 } 5885 5886 if (directiveValue = directive.transclude) { 5887 hasTranscludeDirective = true; 5888 5889 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 5890 // This option should only be used by directives that know how to how to safely handle element transclusion, 5891 // where the transcluded nodes are added or replaced after linking. 5892 if (!directive.$$tlb) { 5893 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 5894 nonTlbTranscludeDirective = directive; 5895 } 5896 5897 if (directiveValue == 'element') { 5898 hasElementTranscludeDirective = true; 5899 terminalPriority = directive.priority; 5900 $template = groupScan(compileNode, attrStart, attrEnd); 5901 $compileNode = templateAttrs.$$element = 5902 jqLite(document.createComment(' ' + directiveName + ': ' + 5903 templateAttrs[directiveName] + ' ')); 5904 compileNode = $compileNode[0]; 5905 replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); 5906 5907 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 5908 replaceDirective && replaceDirective.name, { 5909 // Don't pass in: 5910 // - controllerDirectives - otherwise we'll create duplicates controllers 5911 // - newIsolateScopeDirective or templateDirective - comb
5911ining templates with 5912 // element transclusion doesn't make sense. 5913 // 5914 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 5915 // on the same element more than once. 5916 nonTlbTranscludeDirective: nonTlbTranscludeDirective 5917 }); 5918 } else { 5919 $template = jqLite(jqLiteClone(compileNode)).contents(); 5920 $compileNode.empty(); // clear contents 5921 childTranscludeFn = compile($template, transcludeFn); 5922 } 5923 } 5924 5925 if (directive.template) { 5926 assertNoDuplicate('template', templateDirective, directive, $compileNode); 5927 templateDirective = directive; 5928 5929 directiveValue = (isFunction(directive.template)) 5930 ? directive.template($compileNode, templateAttrs) 5931 : directive.template; 5932 5933 directiveValue = denormalizeTemplate(directiveValue); 5934 5935 if (directive.replace) { 5936 replaceDirective = directive; 5937 $template = jqLite('<div>' + 5938 trim(directiveValue) + 5939 '</div>').contents(); 5940 compileNode = $template[0]; 5941 5942 if ($template.length != 1 || compileNode.nodeType !== 1) { 5943 throw $compileMinErr('tplrt', 5944 "Template for directive '{0}' must have exactly one root element. {1}", 5945 directiveName, ''); 5946 } 5947 5948 replaceWith(jqCollection, $compileNode, compileNode); 5949 5950 var newTemplateAttrs = {$attr: {}}; 5951 5952 // combine directives from the original node and from the template: 5953 // - take the array of directives for this element 5954 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 5955 // - collect directives from the template and sort them by priority 5956 // - combine directives as: processed + template + unprocessed 5957 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 5958 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 5959 5960 if (newIsolateScopeDirective) { 5961 markDirectivesAsIsolate(templateDirectives); 5962 } 5963 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 5964 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 5965 5966 ii = directives.length; 5967 } else { 5968 $compileNode.html(directiveValue); 5969 } 5970 } 5971 5972 if (directive.templateUrl) { 5973 assertNoDuplicate('template', templateDirective, directive, $compileNode); 5974 templateDirective = directive; 5975 5976 if (directive.replace) { 5977 replaceDirective = directive; 5978 } 5979 5980 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 5981 templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, { 5982 controllerDirectives: controllerDirectives, 5983 newIsolateScopeDirective: newIsolateScopeDirective, 5984 templateDirective: templateDirective, 5985 nonTlbTranscludeDirective: nonTlbTranscludeDirective 5986 }); 5987 ii = directives.length; 5988 } else if (directive.compile) { 5989 try { 5990 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 5991 if (isFunction(linkFn)) { 5992 addLinkFns(null, linkFn, attrStart, attrEnd); 5993 } else if (linkFn) { 5994 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 5995 } 5996 } catch (e) { 5997 $exceptionHandler(e, startingTag($compileNode)); 5998 } 5999 } 6000 6001 if (directive.terminal) { 6002 nodeLinkFn.terminal = true; 6003 terminalPriority = Math.max(terminalPriority, directive.priority); 6004 } 6005 6006 } 6007 6008 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 6009 nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn; 6010
6011 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 6012 return nodeLinkFn; 6013 6014 //////////////////// 6015 6016 function addLinkFns(pre, post, attrStart, attrEnd) { 6017 if (pre) { 6018 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 6019 pre.require = directive.require; 6020 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6021 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 6022 } 6023 preLinkFns.push(pre); 6024 } 6025 if (post) { 6026 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 6027 post.require = directive.require; 6028 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6029 post = cloneAndAnnotateFn(post, {isolateScope: true}); 6030 } 6031 postLinkFns.push(post); 6032 } 6033 } 6034 6035 6036 function getControllers(require, $element, elementControllers) { 6037 var value, retrievalMethod = 'data', optional = false;
6038 if (isString(require)) { 6039 while((value = require.charAt(0)) == '^' || value == '?') { 6040 require = require.substr(1); 6041 if (value == '^') { 6042 retrievalMethod = 'inheritedData'; 6043 } 6044 optional = optional || value == '?'; 6045 } 6046 value = null; 6047 6048 if (elementControllers && retrievalMethod === 'data') { 6049 value = elementControllers[require]; 6050 } 6051 value = value || $element[retrievalMethod]('$' + require + 'Controller'); 6052 6053 if (!value && !optional) { 6054 throw $compileMinErr('ctreq', 6055 "Controller '{0}', required by directive '{1}', can't be found!", 6056 require, directiveName); 6057 } 6058 return value; 6059 } else if (isArray(require)) { 6060 value = []; 6061 forEach(require, function(require) { 6062 value.push(getControllers(require, $element, elementControllers)); 6063 }); 6064 } 6065 return value; 6066 } 6067 6068 6069 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 6070 var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; 6071 6072 if (compileNode === linkNode) { 6073 attrs = templateAttrs; 6074 } else { 6075 attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); 6076 } 6077 $element = attrs.$$element; 6078 6079 if (newIsolateScopeDirective) { 6080 var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; 6081 var $linkNode = jqLite(linkNode); 6082 6083 isolateScope = scope.$new(true); 6084 6085 if (templateDirective && (templateDirective === newIsolateScopeDirective.$$originalDirective)) { 6086 $linkNode.data('$isolateScope', isolateScope) ; 6087 } else { 6088 $linkNode.data('$isolateScopeNoTemplate', isolateScope); 6089 } 6090 6091 6092 6093 safeAddClass($linkNode, 'ng-isolate-scope'); 6094 6095 forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { 6096 var match = definition.match(LOCAL_REGEXP) || [], 6097 attrName = match[3] || scopeName, 6098 optional = (match[2] == '?'), 6099 mode = match[1], // @, =, or & 6100 lastValue, 6101 parentGet, parentSet, compare; 6102 6103 isolateScope.$$isolateBindings[scopeName] = mode + attrName; 6104 6105 switch (mode) { 6106 6107 case '@': 6108 attrs.$observe(attrName, function(value) { 6109 isolateScope[scopeName] = value; 6110 }); 6111 attrs.$$observers[attrName].$$scope = scope; 6112 if( attrs[attrName] ) { 6113 // If the attribute has been provided then we trigger an interpolation to ensure 6114 // the value is there for use in the link fn 6115 isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); 6116 } 6117 break; 6118 6119 case '=': 6120 if (optional && !attrs[attrName]) { 6121 return; 6122 } 6123 parentGet = $parse(attrs[attrName]); 6124 if (parentGet.literal) { 6125 compare = equals; 6126 } else { 6127 compare = function(a,b) { return a === b; }; 6128 } 6129 parentSet = parentGet.assign || function() { 6130 // reset the change, or we will throw this exception on every $digest 6131 lastValue = isolateScope[scopeName] = parentGet(scope); 6132 throw $compileMinErr('nonassign', 6133 "Expression '{0}' used with directive '{1}' is non-assignable!", 6134 attrs[attrName], newIsolateScopeDirective.name); 6135 }; 6136 lastValue = isolateScope[scopeName] = parentGet(scope); 6137 isolateScope.$watch(function parentValueWatch() { 6138 var parentValue = parentGet(scope); 6139 if (!compare(parentValue, isolateScope[scopeName])) { 6140 // we are out of sync and need to copy 6141 if (!compare(parentValue, lastValue)) { 6142 // parent changed and it has precedence 6143 isolateScope[scopeName] = parentValue; 6144 } else { 6145 // if the parent can be assigned then do so 6146 parentSet(scope, parentValue = isolateScope[scopeName]); 6147 } 6148 } 6149 return lastValue = parentValue; 6150 }, null, parentGet.literal); 6151 break; 6152 6153 case '&': 6154 parentGet = $parse(attrs[attrName]); 6155 isolateScope[scopeName] = function(locals) { 6156 return parentGet(scope, locals); 6157 }; 6158 break; 6159 6160 default: 6161 throw $compileMinErr('iscp', 6162 "Invalid isolate scope definition for directive '{0}'." + 6163 " Definition: {... {1}: '{2}' ...}", 6164 newIsolateScopeDirective.name, scopeName, definition); 6165 } 6166 }); 6167 } 6168 transcludeFn = boundTranscludeFn && controllersBoundTransclude; 6169 if (controllerDirectives) {
6170 forEach(controllerDirectives, function(directive) { 6171 var locals = { 6172 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 6173 $element: $element, 6174 $attrs: attrs, 6175 $transclude: transcludeFn 6176 }, controllerInstance; 6177 6178 controller = directive.controller; 6179 if (controller == '@') { 6180 controller = attrs[directive.name]; 6181 } 6182 6183 controllerInstance = $controller(controller, locals); 6184 // For directives with element transclusion the element is a comment, 6185 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 6186 // clean up (http://bugs.jquery.com/ticket/8335). 6187 // Instead, we save the controllers for the element in a local hash and attach to .data 6188 // later, once we have the actual element. 6189 elementControllers[directive.name] = controllerInstance; 6190 if (!hasElementTranscludeDirective) { 6191 $element.data('$' + directive.name + 'Controller', controllerInstance); 6192 } 6193 6194 if (directive.controllerAs) { 6195 locals.$scope[directive.controllerAs] = controllerInstance; 6196 } 6197 }); 6198 } 6199 6200 // PRELINKING 6201 for(i = 0, ii = preLinkFns.length; i < ii; i++) { 6202 try { 6203 linkFn = preLinkFns[i]; 6204 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6205 linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); 6206 } catch (e) { 6207 $exceptionHandler(e, startingTag($element)); 6208 } 6209 } 6210 6211 // RECURSION 6212 // We only pass the isolate scope, if the isolate directive has a template, 6213 // otherwise the child elements do not belong to the isolate directive. 6214 var scopeToChild = scope; 6215 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 6216 scopeToChild = isolateScope; 6217 } 6218 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 6219 6220 // POSTLINKING 6221 for(i = postLinkFns.length - 1; i >= 0; i--) { 6222 try { 6223 linkFn = postLinkFns[i]; 6224 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6225 linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); 6226 } catch (e) { 6227 $exceptionHandler(e, startingTag($element)); 6228 } 6229 } 6230 6231 // This is the function that is injected as `$transclude`. 6232 function controllersBoundTransclude(scope, cloneAttachFn) { 6233 var transcludeControllers; 6234 6235 // no scope passed 6236 if (arguments.length < 2) { 6237 cloneAttachFn = scope; 6238 scope = undefined; 6239 } 6240 6241 if (hasElementTranscludeDirective) { 6242 transcludeControllers = elementControllers; 6243 } 6244 6245 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); 6246 } 6247 } 6248 } 6249 6250 function markDirectivesAsIsolate(directives) { 6251 // mark all directives as needing isolate scope. 6252 for (var j = 0, jj = directives.length; j < jj; j++) { 6253 directives[j] = inherit(directives[j], {$$isolateScope: true}); 6254 } 6255 } 6256 6257 /** 6258 * looks up the directive and decorates it with exception handling and proper parameters. We 6259 * call this the boundDirective. 6260 * 6261 * @param {string} name name of the directive to look up. 6262 * @param {string} location The directive must be found in specific format. 6263 * String containing any of theses characters: 6264 * 6265 * * `E`: element name 6266 * * `A': attribute 6267 * * `C`: class 6268 * * `M`: comment 6269 * @returns true if directive was added. 6270 */ 6271 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 6272 endAttrName) { 6273 if (name === ignoreDirective) return null; 6274 var match = null; 6275 if (hasDirectives.hasOwnProperty(name)) { 6276 for(var directive, directives = $injector.get(name + Suffix), 6277 i = 0, ii = directives.length; i<ii; i++) { 6278 try { 6279 directive = directives[i]; 6280 if ( (maxPriority === undefined || maxPriority > directive.priority) && 6281 directive.restrict.indexOf(location) != -1) { 6282 if (startAttrName) { 6283 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 6284 } 6285 tDirectives.push(directive); 6286 match = directive; 6287 } 6288 } catch(e) { $exceptionHandler(e); } 6289 } 6290 } 6291 return match; 6292 } 6293 6294 6295 /** 6296 * When the element is replaced with HTML template then the new attributes 6297 * on the template need to be merged with the existing attributes in the DOM. 6298 * The desired effect is to have both of the attributes present. 6299 * 6300 * @param {object} dst destination attributes (original DOM) 6301 * @param {object} src source attributes (from the directive template) 6302 */ 6303 function mergeTemplateAttributes(dst, src) { 6304 var srcAttr = src.$attr, 6305 dstAttr = dst.$attr, 6306 $element = dst.$$element; 6307 6308 // reapply the old attributes to the new element
6309 forEach(dst, function(value, key) { 6310 if (key.charAt(0) != '$') { 6311 if (src[key]) { 6312 value += (key === 'style' ? ';' : ' ') + src[key]; 6313 } 6314 dst.$set(key, value, true, srcAttr[key]); 6315 } 6316 }); 6317 6318 // copy the new attributes on the old attrs object 6319 forEach(src, function(value, key) { 6320 if (key == 'class') { 6321 safeAddClass($element, value); 6322 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 6323 } else if (key == 'style') { 6324 $element.attr('style', $element.attr('style') + ';' + value); 6325 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 6326 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 6327 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 6328 // have an attribute like "has-own-property" or "data-has-own-property", etc. 6329 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 6330 dst[key] = value; 6331 dstAttr[key] = srcAttr[key]; 6332 } 6333 }); 6334 } 6335 6336 6337 function compileTemplateUrl(directives, $compileNode, tAttrs, 6338 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 6339 var linkQueue = [], 6340 afterTemplateNodeLinkFn, 6341 afterTemplateChildLinkFn, 6342 beforeTemplateCompileNode = $compileNode[0], 6343 origAsyncDirective = directives.shift(), 6344 // The fact that we have to copy and patch the directive seems wrong! 6345 derivedSyncDirective = extend({}, origAsyncDirective, { 6346 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 6347 }), 6348 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 6349 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 6350 : origAsyncDirective.templateUrl; 6351 6352 $compileNode.empty(); 6353 6354 $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). 6355 success(function(content) { 6356 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 6357 6358 content = denormalizeTemplate(content); 6359 6360 if (origAsyncDirective.replace) { 6361 $template = jqLite('<div>' + trim(content) + '</div>').contents(); 6362 compileNode = $template[0]; 6363 6364 if ($template.length != 1 || compileNode.nodeType !== 1) { 6365 throw $compileMinErr('tplrt', 6366 "Template for directive '{0}' must have exactly one root element. {1}", 6367 origAsyncDirective.name, templateUrl); 6368 } 6369 6370 tempTemplateAttrs = {$attr: {}}; 6371 replaceWith($rootElement, $compileNode, compileNode); 6372 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 6373 6374 if (isObject(origAsyncDirective.scope)) { 6375 markDirectivesAsIsolate(templateDirectives); 6376 } 6377 directives = templateDirectives.concat(directives); 6378 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 6379 } else { 6380 compileNode = beforeTemplateCompileNode; 6381 $compileNode.html(content); 6382 } 6383 6384 directives.unshift(derivedSyncDirective); 6385 6386 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 6387 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 6388 previousCompileContext); 6389 forEach($rootElement, function(node, i) { 6390 if (node == compileNode) { 6391 $rootElement[i] = $compileNode[0]; 6392 } 6393 }); 6394 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 6395 6396 6397 while(linkQueue.length) { 6398 var scope = linkQueue.shift(), 6399 beforeTemplateLinkNode = linkQueue.shift(), 6400 linkRootElement = linkQueue.shift(), 6401 boundTranscludeFn = linkQueue.shift(), 6402 linkNode = $compileNode[0]; 6403 6404 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 6405 // it was cloned therefore we have to clone as well. 6406 linkNode = jqLiteClone(compileNode); 6407 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 6408 } 6409 if (afterTemplateNodeLinkFn.transclude) { 6410 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude); 6411 } else { 6412 childBoundTranscludeFn = boundTranscludeFn; 6413 } 6414 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 6415 childBoundTranscludeFn); 6416 } 6417 linkQueue = null; 6418 }). 6419 error(function(response, code, headers, config) { 6420 throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); 6421 }); 6422 6423 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 6424 if (linkQueue) { 6425 linkQueue.push(scope); 6426 linkQueue.push(node); 6427 linkQueue.push(rootElement); 6428 linkQueue.push(boundTranscludeFn); 6429 } else { 6430 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn); 6431 } 6432 }; 6433 } 6434 6435 6436 /** 6437 * Sorting function for bound directives. 6438 */ 6439 function byPriority(a, b) { 6440 var diff = b.priority - a.priority; 6441 if (diff !== 0) return diff; 6442 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 6443 return a.index - b.index; 6444 } 6445 6446 6447 function assertNoDuplicate(what, previousDirective, directive, element) { 6448 if (previousDirective) { 6449 throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', 6450 previousDirective.name, directive.name, what, startingTag(element)); 6451 } 6452 } 6453 6454 6455 function addTextInterpolateDirective(directives, text) { 6456 var interpolateFn = $interpolate(text, true); 6457 if (interpolateFn) { 6458 directives.push({ 6459 priority: 0, 6460 compile: valueFn(function textInterpolateLinkFn(scope, node) { 6461 var parent = node.parent(), 6462 bindings = parent.data('$binding') || []; 6463 bindings.push(interpolateFn); 6464 safeAddClass(parent.data('$binding', bindings), 'ng-binding'); 6465 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 6466 node[0].nodeValue = value; 6467 }); 6468 }) 6469 }); 6470 } 6471 } 6472 6473 6474 function getTrustedContext(node, attrNormalizedName) { 6475 if (attrNormalizedName == "srcdoc") { 6476 return $sce.HTML; 6477 } 6478 var tag = nodeName_(node); 6479 // maction[xlink:href] can source SVG. It's not limited to <maction>. 6480 if (attrNormalizedName == "xlinkHref" || 6481 (tag == "FORM" && attrNormalizedName == "action") || 6482 (tag != "IMG" && (attrNormalizedName == "src" || 6483 attrNormalizedName == "ngSrc"))) { 6484 return $sce.RESOURCE_URL; 6485 } 6486 } 6487 6488 6489 function addAttrInterpolateDirective(node, directives, value, name) { 6490 var interpolateFn = $interpolate(value, true); 6491 6492 // no interpolation found -> ignore 6493 if (!interpolateFn) return; 6494 6495 6496 if (name === "multiple" && nodeName_(node) === "SELECT") { 6497 throw $compileMinErr("selmulti", 6498 "Binding to the 'multiple' attribute is not supported. Element: {0}", 6499 startingTag(node)); 6500 } 6501 6502 directives.push({ 6503 priority: 100, 6504 compile: function() { 6505 return { 6506 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 6507 var $$observers = (attr.$$observers || (attr.$$observers = {})); 6508 6509 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 6510 throw $compileMinErr('nodomevents', 6511 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 6512 "ng- versions (such as ng-click instead of onclick) instead."); 6513 } 6514 6515 // we need to interpolate again, in case the attribute value has been updated 6516 // (e.g. by another directive's compile function) 6517 interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); 6518 6519 // if attribute was updated so that there is no interpolation going on we don't want to 6520 // register any observers 6521 if (!interpolateFn) return; 6522 6523 // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the 6524 // actual attr value 6525 attr[name] = interpolateFn(scope); 6526 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 6527 (attr.$$observers && attr.$$observers[name].$$scope || scope). 6528 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 6529 //special case for class attribute addition + removal 6530 //so that class changes can tap into the animation 6531 //hooks provided by the $animate service. Be sure to 6532 //skip animations when the first digest occurs (when 6533 //both the new and the old values are the same) since 6534 //the CSS classes are the non-interpolated values 6535 if(name === 'class' && newValue != oldValue) { 6536 attr.$updateClass(newValue, oldValue); 6537 } else { 6538 attr.$set(name, newValue); 6539 } 6540 }); 6541 } 6542 }; 6543 } 6544 }); 6545 } 6546 6547 6548 /** 6549 * This is a special jqLite.replaceWith, which can replace items which 6550 * have no parents, provided that the containing jqLite collection is provided. 6551 * 6552 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 6553 * in the root of the tree. 6554 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 6555 * the shell, but replace its DOM node reference. 6556 * @param {Node} newNode The new DOM node. 6557 */ 6558 function replaceWith($rootElement, elementsToRemove, newNode) { 6559 var firstElementToRemove = elementsToRemove[0], 6560 removeCount = elementsToRemove.length, 6561 parent = firstElementToRemove.parentNode, 6562 i, ii; 6563 6564 if ($rootElement) { 6565 for(i = 0, ii = $rootElement.length; i < ii; i++) { 6566 if ($rootElement[i] == firstElementToRemove) { 6567 $rootElement[i++] = newNode; 6568 for (var j = i, j2 = j + removeCount - 1, 6569 jj = $rootElement.length; 6570 j < jj; j++, j2++) { 6571 if (j2 < jj) { 6572 $rootElement[j] = $rootElement[j2]; 6573 } else { 6574 delete $rootElement[j]; 6575 } 6576 } 6577 $rootElement.length -= removeCount - 1; 6578 break; 6579 } 6580 } 6581 } 6582 6583 if (parent) { 6584 parent.replaceChild(newNode, firstElementToRemove); 6585 } 6586 var fragment = document.createDocumentFragment(); 6587 fragment.appendChild(firstElementToRemove); 6588 newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; 6589 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 6590 var element = elementsToRemove[k]; 6591 jqLite(element).remove(); // must do this way to clean up expando 6592 fragment.appendChild(element); 6593 delete elementsToRemove[k]; 6594 } 6595 6596 elementsToRemove[0] = newNode; 6597 elementsToRemove.length = 1; 6598 } 6599 6600 6601 function cloneAndAnnotateFn(fn, annotation) { 6602 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 6603 } 6604 }]; 6605} 6606 6607var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; 6608/** 6609 * Converts all accepted directives format into proper directive name. 6610 * All of these will become 'myDirective': 6611 * my:Directive 6612 * my-directive 6613 * x-my-directive 6614 * data-my:directive 6615 * 6616 * Also there is special case for Moz prefix starting with upper case letter. 6617 * @param name Name to normalize 6618 */ 6619function directiveNormalize(name) { 6620 return camelCase(name.replace(PREFIX_REGEXP, '')); 6621} 6622 6623/** 6624 * @ngdoc object 6625 * @name ng.$compile.directive.Attributes 6626 * 6627 * @description 6628 * A shared object between directive compile / linking functions which contains
6628normalized DOM 6629 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 6630 * needed since all of these are treated as equivalent in Angular: 6631 * 6632 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 6633 */ 6634 6635/** 6636 * @ngdoc property 6637 * @name ng.$compile.directive.Attributes#$attr 6638 * @propertyOf ng.$compile.directive.Attributes 6639 * @returns {object} A map of DOM element attribute names to the normalized name. This is 6640 * needed to do reverse lookup from normalized name back to actual name. 6641 */ 6642 6643 6644/** 6645 * @ngdoc function 6646 * @name ng.$compile.directive.Attributes#$set 6647 * @methodOf ng.$compile.directive.Attributes 6648 * @function 6649 * 6650 * @description 6651 * Set DOM element attribute value. 6652 * 6653 * 6654 * @param {string} name Normalized element attribute name of the property to modify. The name is 6655 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 6656 * property to the original name. 6657 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 6658 */ 6659 6660 6661 6662/** 6663 * Closure compiler type information 6664 */ 6665 6666function nodesetLinkingFn( 6667 /* angular.Scope */ scope, 6668 /* NodeList */ nodeList, 6669 /* Element */ rootElement, 6670 /* function(Function) */ boundTranscludeFn 6671){} 6672 6673function directiveLinkingFn( 6674 /* nodesetLinkingFn */ nodesetLinkingFn, 6675 /* angular.Scope */ scope, 6676 /* Node */ node, 6677 /* Element */ rootElement, 6678 /* function(Function) */ boundTranscludeFn 6679){} 6680 6681function tokenDifference(str1, str2) { 6682 var values = '', 6683 tokens1 = str1.split(/\s+/), 6684 tokens2 = str2.split(/\s+/); 6685 6686 outer: 6687 for(var i = 0; i < tokens1.length; i++) { 6688 var token = tokens1[i]; 6689 for(var j = 0; j < tokens2.length; j++) { 6690 if(token == tokens2[j]) continue outer; 6691 } 6692 values += (values.length > 0 ? ' ' : '') + token; 6693 } 6694 return values; 6695} 6696 6697/** 6698 * @ngdoc object 6699 * @name ng.$controllerProvider 6700 * @description 6701 * The {@link ng.$controller $controller service} is used by Angular to create new 6702 * controllers. 6703 * 6704 * This provider allows controller registration via the 6705 * {@link ng.$controllerProvider#methods_register register} method. 6706 */ 6707function $ControllerProvider() { 6708 var controllers = {}, 6709 CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 6710 6711 6712 /** 6713 * @ngdoc function 6714 * @name ng.$controllerProvider#register 6715 * @methodOf ng.$controllerProvider 6716 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 6717 * the names and the values are the constructors. 6718 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 6719 * annotations in the array notation). 6720 */ 6721 this.register = function(name, constructor) { 6722 assertNotHasOwnProperty(name, 'controller'); 6723 if (isObject(name)) { 6724 extend(controllers, name); 6725 } else { 6726 controllers[name] = constructor; 6727 } 6728 }; 6729 6730 6731 this.$get = ['$injector', '$window', function($injector, $window) { 6732 6733 /** 6734 * @ngdoc function 6735 * @name ng.$controller 6736 * @requires $injector 6737 * 6738 * @param {Function|string} constructor If called with a function then it's considered to be the 6739 * controller constructor function. Otherwise it's considered to be a string which is used 6740 * to retrieve the controller constructor using the following steps: 6741 * 6742 * * check if a controller with given name is registered via `$controllerProvider` 6743 * * check if evaluating the string on the current scope returns a constructor 6744 * * check `window[constructor]` on the global `window` object 6745 * 6746 * @param {Object} locals Injection locals for Controller. 6747 * @return {Object} Instance of given controller. 6748 * 6749 * @description 6750 * `$controller` service is responsible for instantiating controllers. 6751 * 6752 * It's just a simple call to {@link AUTO.$injector $injector}, but extracted into 6753 * a service, so that one can override this service with {@link https://gist.github.com/1649788 6754 * BC version}. 6755 */ 6756 return function(expression, locals) { 6757 var instance, match, constructor, identifier; 6758 6759 if(isString(expression)) { 6760 match = expression.match(CNTRL_REG), 6761 constructor = match[1], 6762 identifier = match[3]; 6763 expression = controllers.hasOwnProperty(constructor) 6764 ? controllers[constructor] 6765 : getter(locals.$scope, constructor, true) || getter($window, constructor, true); 6766 6767 assertArgFn(expression, constructor, true); 6768 } 6769 6770 instance = $injector.instantiate(expression, locals); 6771 6772 if (identifier) { 6773 if (!(locals && typeof locals.$scope == 'object')) { 6774 throw minErr('$controller')('noscp', 6775 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 6776 constructor || expression.name, identifier); 6777 } 6778 6779 locals.$scope[identifier] = instance; 6780 } 6781 6782 return instance; 6783 }; 6784 }]; 6785} 6786 6787/** 6788 * @ngdoc object 6789 * @name ng.$document 6790 * @requires $window 6791 * 6792 * @description 6793 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 6794 */ 6795function $DocumentProvider(){ 6796 this.$get = ['$window', function(window){ 6797 return jqLite(window.document); 6798 }]; 6799} 6800 6801/** 6802 * @ngdoc function 6803 * @name ng.$exceptionHandler 6804 * @requires $log 6805 * 6806 * @description 6807 * Any uncaught exception in angular expressions is delegated to this service. 6808 * The default implementation simply delegates to `$log.error` which logs it into 6809 * the browser console. 6810 * 6811 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 6812 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 6813 * 6814 * ## Example: 6815 * 6816 * <pre> 6817 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { 6818 * return function (exception, cause) { 6819 * exception.message += ' (caused by "' + cause + '")'; 6820 * throw exception; 6821 * }; 6822 * }); 6823 * </pre> 6824 * 6825 * This example will override the normal action of `$exceptionHandler`, to make angular
6826 * exceptions fail hard when they happen, instead of just logging to the console. 6827 * 6828 * @param {Error} exception Exception associated with the error. 6829 * @param {string=} cause optional information about the context in which 6830 * the error was thrown. 6831 * 6832 */ 6833function $ExceptionHandlerProvider() { 6834 this.$get = ['$log', function($log) { 6835 return function(exception, cause) { 6836 $log.error.apply($log, arguments); 6837 }; 6838 }]; 6839} 6840 6841/** 6842 * Parse headers into key value object 6843 * 6844 * @param {string} headers Raw headers as a string 6845 * @returns {Object} Parsed headers as key value object 6846 */ 6847function parseHeaders(headers) { 6848 var parsed = {}, key, val, i; 6849 6850 if (!headers) return parsed; 6851 6852 forEach(headers.split('\n'), function(line) { 6853 i = line.indexOf(':'); 6854 key = lowercase(trim(line.substr(0, i))); 6855 val = trim(line.substr(i + 1)); 6856 6857 if (key) { 6858 if (parsed[key]) { 6859 parsed[key] += ', ' + val; 6860 } else { 6861 parsed[key] = val; 6862 } 6863 } 6864 }); 6865 6866 return parsed; 6867} 6868 6869 6870/** 6871 * Returns a function that provides access to parsed headers. 6872 * 6873 * Headers are lazy parsed when first requested. 6874 * @see parseHeaders 6875 * 6876 * @param {(string|Object)} headers Headers to provide access to. 6877 * @returns {function(string=)} Returns a getter function which if called with: 6878 * 6879 * - if called with single an argument returns a single header value or null
6880 * - if called with no arguments returns an object containing all headers. 6881 */ 6882function headersGetter(headers) { 6883 var headersObj = isObject(headers) ? headers : undefined; 6884 6885 return function(name) { 6886 if (!headersObj) headersObj = parseHeaders(headers); 6887 6888 if (name) { 6889 return headersObj[lowercase(name)] || null; 6890 } 6891 6892 return headersObj; 6893 }; 6894} 6895 6896 6897/** 6898 * Chain all given functions 6899 * 6900 * This function is used for both request and response transforming 6901 * 6902 * @param {*} data Data to transform. 6903 * @param {function(string=)} headers Http headers getter fn. 6904 * @param {(function|Array.<function>)} fns Function or an array of functions. 6905 * @returns {*} Transformed data. 6906 */ 6907function transformData(data, headers, fns) { 6908 if (isFunction(fns)) 6909 return fns(data, headers); 6910 6911 forEach(fns, function(fn) { 6912 data = fn(data, headers); 6913 }); 6914 6915 return data; 6916} 6917 6918 6919function isSuccess(status) { 6920 return 200 <= status && status < 300; 6921} 6922 6923 6924function $HttpProvider() { 6925 var JSON_START = /^\s*(\[|\{[^\{])/, 6926 JSON_END = /[\}\]]\s*$/, 6927 PROTECTION_PREFIX = /^\)\]\}',?\n/, 6928 CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; 6929 6930 var defaults = this.defaults = { 6931 // transform incoming response data 6932 transformResponse: [function(data) { 6933 if (isString(data)) { 6934 // strip json vulnerability protection prefix 6935 data = data.replace(PROTECTION_PREFIX, ''); 6936 if (JSON_START.test(data) && JSON_END.test(data)) 6937 data = fromJson(data); 6938 } 6939 return data; 6940 }], 6941 6942 // transform outgoing request data 6943 transformRequest: [function(d) { 6944 return isObject(d) && !isFile(d) ? toJson(d) : d; 6945 }], 6946 6947 // default headers 6948 headers: { 6949 common: { 6950 'Accept': 'application/json, text/plain, */*' 6951 }, 6952 post: copy(CONTENT_TYPE_APPLICATION_JSON), 6953 put: copy(CONTENT_TYPE_APPLICATION_JSON), 6954 patch: copy(CONTENT_TYPE_APPLICATION_JSON) 6955 }, 6956 6957 xsrfCookieName: 'XSRF-TOKEN', 6958 xsrfHeaderName: 'X-XSRF-TOKEN' 6959 }; 6960 6961 /** 6962 * Are ordered by request, i.e. they are applied in the same order as the 6963 * array, on request, but reverse order, on response. 6964 */ 6965 var interceptorFactories = this.interceptors = []; 6966 6967 /** 6968 * For historical reasons, response interceptors are ordered by the order in which 6969 * they are applied to the response. (This is the opposite of interceptorFactories) 6970 */ 6971 var responseInterceptorFactories = this.responseInterceptors = []; 6972 6973 this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', 6974 function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { 6975 6976 var defaultCache = $cacheFactory('$http'); 6977 6978 /** 6979 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 6980 * The reversal is needed so that we can build up the interception chain around the 6981 * server request. 6982 */ 6983 var reversedInterceptors = []; 6984 6985 forEach(interceptorFactories, function(interceptorFactory) { 6986 reversedInterceptors.unshift(isString(interceptorFactory) 6987 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 6988 }); 6989 6990 forEach(responseInterceptorFactories, function(interceptorFactory, index) { 6991 var responseFn = isString(interceptorFactory) 6992 ? $injector.get(interceptorFactory) 6993 : $injector.invoke(interceptorFactory); 6994 6995 /** 6996 * Response interceptors go before "around" interceptors (no real reason, just 6997 * had to pick one.) But they are already reversed, so we can't use unshift, hence 6998 * the splice. 6999 */ 7000 reversedInterceptors.splice(index, 0, { 7001 response: function(response) { 7002 return responseFn($q.when(response)); 7003 }, 7004 responseError: function(response) { 7005 return responseFn($q.reject(response)); 7006 } 7007 }); 7008 }); 7009 7010 7011 /** 7012 * @ngdoc function 7013 * @name ng.$http 7014 * @requires $httpBackend 7015 * @requires $browser 7016 * @requires $cacheFactory 7017 * @requires $rootScope 7018 * @requires $q 7019 * @requires $injector 7020 *
7021 * @description 7022 * The `$http` service is a core Angular service that facilitates communication with the remote 7023 * HTTP servers via the browser's {@link https://developer.mozilla.org/en/xmlhttprequest 7024 * XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}. 7025 * 7026 * For unit testing applications that use `$http` service, see 7027 * {@link ngMock.$httpBackend $httpBackend mock}. 7028 * 7029 * For a higher level of abstraction, please check out the {@link ngResource.$resource 7030 * $resource} service. 7031 * 7032 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 7033 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 7034 * it is important to familiarize yourself with these APIs and the guarantees they provide. 7035 * 7036 * 7037 * # General usage 7038 * The `$http` service is a function which takes a single argument â a configuration object â 7039 * that is used to generate an HTTP request and returns a {@link ng.$q promise} 7040 * with two $http specific methods: `success` and `error`. 7041 * 7042 * <pre> 7043 * $http({method: 'GET', url: '/someUrl'}). 7044 * success(function(data, status, headers, config) { 7045 * // this callback will be called asynchronously 7046 * // when the response is available 7047 * }). 7048 * error(function(data, status, headers, config) { 7049 * // called asynchronously if an error occurs 7050 * // or server returns response with an error status. 7051 * }); 7052 * </pre> 7053 * 7054 * Since the returned value of calling the $http function is a `promise`, you can also use 7055 * the `then` method to register callbacks, and these callbacks will receive a single argument â 7056 * an object representing the response. See the API signature and type info below for more 7057 * details. 7058 * 7059 * A response status code between 200 and 299 is considered a success status and 7060 * will result in the success callback being called. Note that if the response is a redirect, 7061 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 7062 * called for such responses. 7063 * 7064 * # Writing Unit Tests that use $http 7065 * When unit testing (using {@link api/ngMock ngMock}), it is necessary to call 7066 * {@link api/ngMock.$httpBackend#methods_flush $httpBackend.flush()} to flush each pending 7067 * request using trained responses. 7068 * 7069 * ``` 7070 * $httpBackend.expectGET(...); 7071 * $http.get(...); 7072 * $httpBackend.flush(); 7073 * ``` 7074 * 7075 * # Shortcut methods 7076 * 7077 * Since all invocations of the $http service require passing in an HTTP method and URL, and 7078 * POST/PUT requests require request data to be provided as well, shortcut methods 7079 * were created: 7080 * 7081 * <pre> 7082 * $http.get('/someUrl').success(successCallback); 7083 * $http.post('/someUrl', data).success(successCallback); 7084 * </pre> 7085 * 7086 * Complete list of shortcut methods: 7087 * 7088 * - {@link ng.$http#methods_get $http.get} 7089 * - {@link ng.$http#methods_head $http.head} 7090 * - {@link ng.$http#methods_post $http.post} 7091 * - {@link ng.$http#methods_put $http.put} 7092 * - {@link ng.$http#methods_delete $http.delete} 7093 * - {@link ng.$http#methods_jsonp $http.jsonp} 7094 * 7095 * 7096 * # Setting HTTP Headers 7097 * 7098 * The $http service will automatically add certain HTTP headers to all requests. These defaults 7099 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 7100 * object, which currently contains this default configuration: 7101 * 7102 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 7103 * - `Accept: application/json, text/plain, * / *` 7104 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 7105 * - `Content-Type: application/json` 7106 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 7107 * - `Content-Type: application/json` 7108 *
7109 * To add or overwrite these defaults, simply add or remove a property from these configuration 7110 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 7111 * with the lowercased HTTP method name as the key, e.g. 7112 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. 7113 * 7114 * The defaults can also be set at runtime via the `$http.defaults` object in the same 7115 * fashion. For example: 7116 * 7117 * ``` 7118 * module.run(function($http) { 7119 * $http.defaults.headers.common.Authentication = 'Basic YmVlcDpib29w' 7120 * }); 7121 * ``` 7122 * 7123 * In addition, you can supply a `headers` property in the config object passed when 7124 * calling `$http(config)`, which overrides the defaults without changing them globally. 7125 * 7126 * 7127 * # Transforming Requests and Responses 7128 * 7129 * Both requests and responses can be transformed using transform functions. By default, Angular 7130 * applies these transformations: 7131 * 7132 * Request transformations: 7133 * 7134 * - If the `data` property of the request configuration object contains an object, serialize it 7135 * into JSON format. 7136 * 7137 * Response transformations: 7138 * 7139 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 7140 * - If JSON response is detected, deserialize it using a JSON parser. 7141 * 7142 * To globally augment or override the default transforms, modify the 7143 * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` 7144 * properties. These properties are by default an array of transform functions, which allows you 7145 * to `push` or `unshift` a new transformation function into the transformation chain. You can 7146 * also decide to completely override any default transformations by assigning your 7147 * transformation functions to these properties directly without the array wrapper. These defaults 7148 * are again available on the $http factory at run-time, which may be useful if you have run-time 7149 * services you wish to be involved in your transformations. 7150 * 7151 * Similarly, to locally override the request/response transforms, augment the 7152 * `transformRequest` and/or `transformResponse` properties of the configuration object passed 7153 * into `$http`. 7154 * 7155 * 7156 * # Caching 7157 * 7158 * To enable caching, set the request configuration `cache` property to `true` (to use default 7159 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 7160 * When the cache is enabled, `$http` stores the response from the server in the specified 7161 * cache. The next time the same request is made, the response is served from the cache without 7162 * sending a request to the server. 7163 * 7164 * Note that even if the response is served from cache, delivery of the data is asynchronous in 7165 * the same way that real requests are. 7166 * 7167 * If there are multiple GET requests for the same URL that should be cached using the same 7168 * cache, but the cache is not populated yet, only one request to the server will be made and 7169 * the remaining requests will be fulfilled using the response from the first request. 7170 * 7171 * You can change the default cache to a new object (built with 7172 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 7173 * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set 7174 * their `cache` property to `true` will now use this cache object. 7175 * 7176 * If you set the default cache to `false` then only requests that specify their own custom 7177 * cache object will be cached. 7178 * 7179 * # Interceptors 7180 * 7181 * Before you start creating interceptors, be sure to understand the 7182 * {@link ng.$q $q and deferred/promise APIs}. 7183 * 7184 * For purposes of global error handling, authentication, or any kind of synchronous or 7185 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 7186 * able to intercept requests before they are handed to the server and 7187 * responses before they are handed over to the application code that 7188 * initiated these requests. The interceptors leverage the {@link ng.$q 7189 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 7190 * 7191 * The interceptors are service factories that are registered with the `$httpProvider` by 7192 * adding them to the `$httpProvider.interceptors` array. The factory is called and 7193 * injected with dependencies (if specified) and returns the interceptor. 7194 * 7195 * There are two kinds of interceptors (and two kinds of rejection interceptors): 7196 * 7197 * * `request`: interceptors get called with http `config` object. The function is free to 7198 * modify the `config` or create a new one. The function needs to return the `config` 7199 * directly or as a promise. 7200 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 7201 * resolved with a rejection. 7202 * * `response`: interceptors get called with http `response` object. The function is free to 7203 * modify the `response` or create a new one. The function needs to return the `response` 7204 * directly or as a promise. 7205 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 7206 * resolved with a rejection. 7207 * 7208 * 7209 * <pre> 7210 * // register the interceptor as a service 7211 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7212 * return { 7213 * // optional method 7214 * 'request': function(config) {
7215 * // do something on success 7216 * return config || $q.when(config); 7217 * }, 7218 * 7219 * // optional method 7220 * 'requestError': function(rejection) { 7221 * // do something on error 7222 * if (canRecover(rejection)) { 7223 * return responseOrNewPromise 7224 * } 7225 * return $q.reject(rejection); 7226 * }, 7227 * 7228 * 7229 * 7230 * // optional method 7231 * 'response': function(response) { 7232 * // do something on success 7233 * return response || $q.when(response); 7234 * }, 7235 * 7236 * // optional method 7237 * 'responseError': function(rejection) { 7238 * // do something on error 7239 * if (canRecover(rejection)) { 7240 * return responseOrNewPromise 7241 * } 7242 * return $q.reject(rejection); 7243 * } 7244 * }; 7245 * }); 7246 * 7247 * $httpProvider.interceptors.push('myHttpInterceptor'); 7248 * 7249 * 7250 * // alternatively, register the interceptor via an anonymous factory 7251 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 7252 * return { 7253 * 'request': function(config) { 7254 * // same as above 7255 * }, 7256 * 7257 * 'response': function(response) { 7258 * // same as above 7259 * } 7260 * }; 7261 * }); 7262 * </pre> 7263 * 7264 * # Response interceptors (DEPRECATED) 7265 * 7266 * Before you start creating interceptors, be sure to understand the 7267 * {@link ng.$q $q and deferred/promise APIs}. 7268 * 7269 * For purposes of global error handling, authentication or any kind of synchronous or 7270 * asynchronous preprocessing of received responses, it is desirable to be able to intercept 7271 * responses for http requests before they are handed over to the application code that 7272 * initiated these requests. The response interceptors leverage the {@link ng.$q 7273 * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. 7274 * 7275 * The interceptors are service factories that are registered with the $httpProvider by 7276 * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and 7277 * injected with dependencies (if specified) and returns the interceptor â a function that 7278 * takes a {@link ng.$q promise} and returns the original or a new promise. 7279 * 7280 * <pre> 7281 * // register the interceptor as a service 7282 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7283 * return function(promise) { 7284 * return promise.then(function(response) { 7285 * // do something on success 7286 * return response; 7287 * }, function(response) { 7288 * // do something on error 7289 * if (canRecover(response)) { 7290 * return responseOrNewPromise 7291 * } 7292 * return $q.reject(response); 7293 * }); 7294 * } 7295 * }); 7296 * 7297 * $httpProvider.responseInterceptors.push('myHttpInterceptor'); 7298 * 7299 * 7300 * // register the interceptor via an anonymous factory 7301 * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) { 7302 * return function(promise) { 7303 * // same as above 7304 * } 7305 * }); 7306 * </pre> 7307 * 7308 * 7309 * # Security Considerations 7310 * 7311 * When designing web applications, consider security threats from: 7312 * 7313 * - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx 7314 * JSON vulnerability} 7315 * - {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} 7316 * 7317 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 7318 * pre-configured with strategies that address these issues, but for this to work backend server 7319 * cooperation is required. 7320 * 7321 * ## JSON Vulnerability Protection 7322 * 7323 * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx 7324 * JSON vulnerability} allows third party website to turn your JSON resource URL into 7325 * {@link http://en.wikipedia.org/wiki/JSONP JSONP} request under some conditions. To 7326 * counter this your server can prefix all JSON requests with following str
7326ing `")]}',\n"`. 7327 * Angular will automatically strip the prefix before processing it as JSON. 7328 * 7329 * For example if your server needs to return: 7330 * <pre> 7331 * ['one','two'] 7332 * </pre> 7333 * 7334 * which is vulnerable to attack, your server can return: 7335 * <pre> 7336 * )]}', 7337 * ['one','two'] 7338 * </pre> 7339 * 7340 * Angular will strip the prefix, before processing the JSON. 7341 * 7342 * 7343 * ## Cross Site Request Forgery (XSRF) Protection 7344 * 7345 * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which 7346 * an unauthorized site can gain your user's private data. Angular provides a mechanism 7347 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 7348 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 7349 * JavaScript that runs on your domain could read the cookie, your server can be assured that 7350 * the XHR came from JavaScript running on your domain. The header will not be set for 7351 * cross-domain requests. 7352 * 7353 * To take advantage of this, your server needs to set a token in a JavaScript readable session 7354 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 7355 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 7356 * that only JavaScript running on your domain could have sent the request. The token must be 7357 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 7358 * making up its own tokens). We recommend that the token is a digest of your site's 7359 * authentication cookie with a {@link https://en.wikipedia.org/wiki/Salt_(cryptography) salt} 7360 * for added security. 7361 * 7362 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 7363 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 7364 * or the per-request config object. 7365 * 7366 * 7367 * @param {object} config Object describing the request to be made and how it should be 7368 * processed. The object has following properties: 7369 * 7370 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 7371 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 7372 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be turned 7373 * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be 7374 * JSONified. 7375 * - **data** â `{string|Object}` â Data to be sent as the request message data. 7376 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 7377 * HTTP headers to send to the server. If the return value of a function is null, the 7378 * header will not be sent. 7379 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 7380 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 7381 * - **transformRequest** â 7382 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 7383 * transform function or an array of such functions. The transform function takes the http 7384 * request body and headers and returns its transformed (typically serialized) version. 7385 * - **transformResponse** â 7386 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 7387 * transform function or an array of such functions. The transform function takes the http 7388 * response body and headers and returns its transformed (typically deserialized) version. 7389 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 7390 * GET request, otherwise if a cache instance built with 7391 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 7392 * caching. 7393 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 7394 * that should abort the request when resolved. 7395 * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the 7396 * XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5 7397 * requests with credentials} for more information. 7398 * - **responseType** - `{string}` - see {@link 7399 * https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}. 7400 * 7401 * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the 7402 * standard `then` method and two http specific methods: `success` and `error`. The `then` 7403 * method takes two arguments a success and an error callback which will be called with a 7404 * response object. The `success` and `error` methods take a single argument - a function that 7405 * will be called when the request succeeds or fails respectively. The arguments passed into 7406 * these functions are destructured representation of the response object passed into the 7407 * `then` method. The response object has these properties: 7408 * 7409 * - **data** â `{string|Object}` â The response body transformed with the transform 7410 * functions. 7411 * - **status** â `{number}` â HTTP status code of the response. 7412 * - **headers** â `{function([headerName])}` â Header getter function. 7413 * - **config** â `{Object}` â The configuration object that was used to generate the request. 7414 * 7415 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 7416 * requests. This is primarily meant to be used for debugging purposes. 7417 * 7418 * 7419 * @example 7420<example> 7421<file name="index.html"> 7422 <div ng-controller="FetchCtrl"> 7423 <select ng-model="method"> 7424 <option>GET</option> 7425 <option>JSONP</option> 7426 </select> 7427 <input type="text" ng-model="url" size="80"/> 7428 <button ng-click="fetch()">fetch</button><br> 7429 <button ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 7430 <button 7431 ng-click="updateModel('JSONP', 7432 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 7433 Sample JSONP 7434 </button> 7435 <button 7436 ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 7437 Invalid JSONP 7438 </button> 7439 <pre>http status code: {{status}}</pre> 7440 <pre>http response data: {{data}}</pre> 7441 </div> 7442</file> 7443<file name="script.js"> 7444 function FetchCtrl($scope, $http, $templateCache) { 7445 $scope.method = 'GET'; 7446 $scope.url = 'http-hello.html'; 7447 7448 $scope.fetch = function() { 7449 $scope.code = null; 7450 $scope.response = null; 7451 7452 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 7453 success(function(data, status) { 7454 $scope.status = status; 7455 $scope.data = data; 7456 }). 7457 error(function(data, status) { 7458 $scope.data = data || "Request failed"; 7459 $scope.status = status; 7460 }); 7461 }; 7462 7463 $scope.updateModel = function(method, url) { 7464 $scope.method = method; 7465 $scope.url = url; 7466 }; 7467 } 7468</file> 7469<file name="http-hello.html"> 7470 Hello, $http! 7471</file> 7472<file name="scenario.js"> 7473 it('should make an xhr GET request', function() {
7474 element(':button:contains("Sample GET")').click(); 7475 element(':button:contains("fetch")').click(); 7476 expect(binding('status')).toBe('200'); 7477 expect(binding('data')).toMatch(/Hello, \$http!/); 7478 }); 7479 7480 it('should make a JSONP request to angularjs.org', function() { 7481 element(':button:contains("Sample JSONP")').click(); 7482 element(':button:contains("fetch")').click(); 7483 expect(binding('status')).toBe('200'); 7484 expect(binding('data')).toMatch(/Super Hero!/); 7485 }); 7486 7487 it('should make JSONP request to invalid URL and invoke the error handler', 7488 function() { 7489 element(':button:contains("Invalid JSONP")').click(); 7490 element(':button:contains("fetch")').click(); 7491 expect(binding('status')).toBe('0'); 7492 expect(binding('data')).toBe('Request failed'); 7493 }); 7494</file> 7495</example> 7496 */ 7497 function $http(requestConfig) { 7498 var config = { 7499 transformRequest: defaults.transformRequest, 7500 transformResponse: defaults.transformResponse 7501 }; 7502 var headers = mergeHeaders(requestConfig); 7503 7504 extend(config, requestConfig); 7505 config.headers = headers; 7506 config.method = uppercase(config.method); 7507 7508 var xsrfValue = urlIsSameOrigin(config.url) 7509 ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] 7510 : undefined; 7511 if (xsrfValue) { 7512 headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 7513 } 7514 7515 7516 var serverRequest = function(config) { 7517 headers = config.headers; 7518 var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); 7519 7520 // strip content-type if data is undefined 7521 if (isUndefined(config.data)) { 7522 forEach(headers, function(value, header) { 7523 if (lowercase(header) === 'content-type') { 7524 delete headers[header]; 7525 } 7526 }); 7527 } 7528 7529 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 7530 config.withCredentials = defaults.withCredentials; 7531 } 7532 7533 // send request 7534 return sendReq(config, reqData, headers).then(transformResponse, transformResponse); 7535 }; 7536 7537 var chain = [serverRequest, undefined]; 7538 var promise = $q.when(config); 7539 7540 // apply interceptors 7541 forEach(reversedInterceptors, function(interceptor) { 7542 if (interceptor.request || interceptor.requestError) { 7543 chain.unshift(interceptor.request, interceptor.requestError); 7544 } 7545 if (interceptor.response || interceptor.responseError) { 7546 chain.push(interceptor.response, interceptor.responseError); 7547 } 7548 }); 7549 7550 while(chain.length) { 7551 var thenFn = chain.shift(); 7552 var rejectFn = chain.shift(); 7553 7554 promise = promise.then(thenFn, rejectFn); 7555 } 7556 7557 promise.success = function(fn) { 7558 promise.then(function(response) { 7559 fn(response.data, response.status, response.headers, config); 7560 }); 7561 return promise; 7562 }; 7563 7564 promise.error = function(fn) { 7565 promise.then(null, function(response) { 7566 fn(response.data, response.status, response.headers, config); 7567 }); 7568 return promise; 7569 }; 7570 7571 return promise; 7572 7573 function transformResponse(response) { 7574 // make a copy since the response must be cacheable 7575 var resp = extend({}, response, { 7576 data: transformData(response.data, response.headers, config.transformResponse) 7577 }); 7578 return (isSuccess(response.status)) 7579 ? resp 7580 : $q.reject(resp); 7581 } 7582 7583 function mergeHeaders(config) { 7584 var defHeaders = defaults.headers, 7585 reqHeaders = extend({}, config.headers), 7586 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 7587 7588 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 7589 7590 // execute if header value is function 7591 execHeaders(defHeaders); 7592 execHeaders(reqHeaders); 7593 7594 // using for-in instead of forEach to avoid unecessary iteration after header has been found 7595 defaultHeadersIteration: 7596 for (defHeaderName in defHeaders) { 7597 lowercaseDefHeaderName = lowercase(defHeaderName); 7598 7599 for (reqHeaderName in reqHeaders) { 7600 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 7601 continue defaultHeadersIteration; 7602 } 7603 } 7604 7605 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 7606 } 7607 7608 return reqHeaders; 7609 7610 function execHeaders(headers) { 7611 var headerContent; 7612
7613 forEach(headers, function(headerFn, header) { 7614 if (isFunction(headerFn)) { 7615 headerContent = headerFn(); 7616 if (headerContent != null) { 7617 headers[header] = headerContent; 7618 } else { 7619 delete headers[header]; 7620 } 7621 } 7622 }); 7623 } 7624 } 7625 } 7626 7627 $http.pendingRequests = []; 7628 7629 /** 7630 * @ngdoc method 7631 * @name ng.$http#get 7632 * @methodOf ng.$http 7633 * 7634 * @description 7635 * Shortcut method to perform `GET` request. 7636 * 7637 * @param {string} url Relative or absolute URL specifying the destination of the request 7638 * @param {Object=} config Optional configuration object 7639 * @returns {HttpPromise} Future object 7640 */ 7641 7642 /** 7643 * @ngdoc method 7644 * @name ng.$http#delete 7645 * @methodOf ng.$http 7646 * 7647 * @description 7648 * Shortcut method to perform `DELETE` request. 7649 * 7650 * @param {string} url Relative or absolute URL specifying the destination of the request 7651 * @param {Object=} config Optional configuration object 7652 * @returns {HttpPromise} Future object 7653 */ 7654 7655 /** 7656 * @ngdoc method 7657 * @name ng.$http#head 7658 * @methodOf ng.$http 7659 * 7660 * @description 7661 * Shortcut method to perform `HEAD` request. 7662 * 7663 * @param {string} url Relative or absolute URL specifying the destination of the request 7664 * @param {Object=} config Optional configuration object 7665 * @returns {HttpPromise} Future object 7666 */ 7667 7668 /** 7669 * @ngdoc method 7670 * @name ng.$http#jsonp 7671 * @methodOf ng.$http 7672 * 7673 * @description 7674 * Shortcut method to perform `JSONP` request. 7675 * 7676 * @param {string} url Relative or absolute URL specifying the destination of the request. 7677 * Should contain `JSON_CALLBACK` string. 7678 * @param {Object=} config Optional configuration object 7679 * @returns {HttpPromise} Future object 7680 */ 7681 createShortMethods('get', 'delete', 'head', 'jsonp'); 7682 7683 /** 7684 * @ngdoc method 7685 * @name ng.$http#post 7686 * @methodOf ng.$http 7687 * 7688 * @description 7689 * Shortcut method to perform `POST` request. 7690 * 7691 * @param {string} url Relative or absolute URL specifying the destination of the request 7692 * @param {*} data Request content 7693 * @param {Object=} config Optional configuration object 7694 * @returns {HttpPromise} Future object 7695 */ 7696 7697 /** 7698 * @ngdoc method 7699 * @name ng.$http#put 7700 * @methodOf ng.$http 7701 * 7702 * @description 7703 * Shortcut method to perform `PUT` request. 7704 * 7705 * @param {string} url Relative or absolute URL specifying the destination of the request 7706 * @param {*} data Request content 7707 * @param {Object=} config Optional configuration object 7708 * @returns {HttpPromise} Future object 7709 */ 7710 createShortMethodsWithData('post', 'put'); 7711 7712 /** 7713 * @ngdoc property 7714 * @name ng.$http#defaults 7715 * @propertyOf ng.$http 7716 * 7717 * @description 7718 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 7719 * default headers, withCredentials as well as request and response transformations. 7720 * 7721 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 7722 */ 7723 $http.defaults = defaults; 7724 7725 7726 return $http; 7727 7728 7729 function createShortMethods(names) { 7730 forEach(arguments, function(name) { 7731 $http[name] = function(url, config) { 7732 return $http(extend(config || {}, { 7733 method: name, 7734 url: url 7735 })); 7736 }; 7737 }); 7738 } 7739 7740 7741 function createShortMethodsWithData(name) { 7742 forEach(arguments, function(name) { 7743 $http[name] = function(url, data, config) { 7744 return $http(extend(config || {}, { 7745 method: name, 7746 url: url, 7747 data: data 7748 })); 7749 }; 7750 }); 7751 } 7752 7753 7754 /** 7755 * Makes the request. 7756 * 7757 * !!! ACCESSES CLOSURE VARS: 7758 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 7759 */ 7760 function sendReq(config, reqData, reqHeaders) { 7761 var deferred = $q.defer(), 7762 promise = deferred.promise, 7763 cache, 7764 cachedResp, 7765 url = buildUrl(config.url, config.params); 7766 7767 $http.pendingRequests.push(config); 7768 promise.then(removePendingReq, removePendingReq); 7769 7770 7771 if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') { 7772 cache = isObject(config.cache) ? config.cache 7773 : isObject(defaults.cache) ? defaults.cache 7774 : defaultCache;
7775 } 7776 7777 if (cache) { 7778 cachedResp = cache.get(url); 7779 if (isDefined(cachedResp)) { 7780 if (cachedResp.then) { 7781 // cached request has already been sent, but there is no response yet 7782 cachedResp.then(removePendingReq, removePendingReq); 7783 return cachedResp; 7784 } else { 7785 // serving from cache 7786 if (isArray(cachedResp)) { 7787 resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2])); 7788 } else { 7789 resolvePromise(cachedResp, 200, {}); 7790 } 7791 } 7792 } else { 7793 // put the promise for the non-transformed response into cache as a placeholder 7794 cache.put(url, promise); 7795 } 7796 } 7797 7798 // if we won't have the response in cache, send the request to the backend 7799 if (isUndefined(cachedResp)) { 7800 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 7801 config.withCredentials, config.responseType); 7802 } 7803 7804 return promise; 7805 7806 7807 /** 7808 * Callback registered to $httpBackend(): 7809 * - caches the response if desired 7810 * - resolves the raw $http promise 7811 * - calls $apply 7812 */ 7813 function done(status, response, headersString) { 7814 if (cache) { 7815 if (isSuccess(status)) { 7816 cache.put(url, [status, response, parseHeaders(headersString)]); 7817 } else { 7818 // remove promise from the cache 7819 cache.remove(url); 7820 } 7821 } 7822 7823 resolvePromise(response, status, headersString); 7824 if (!$rootScope.$$phase) $rootScope.$apply(); 7825 } 7826 7827 7828 /** 7829 * Resolves the raw $http promise. 7830 */ 7831 function resolvePromise(response, status, headers) { 7832 // normalize internal statuses to 0 7833 status = Math.max(status, 0); 7834 7835 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 7836 data: response, 7837 status: status, 7838 headers: headersGetter(headers), 7839 config: config 7840 }); 7841 } 7842 7843 7844 function removePendingReq() { 7845 var idx = indexOf($http.pendingRequests, config); 7846 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 7847 } 7848 } 7849 7850 7851 function buildUrl(url, params) { 7852 if (!params) return url; 7853 var parts = []; 7854 forEachSorted(params, function(value, key) { 7855 if (value === null || isUndefined(value)) return; 7856 if (!isArray(value)) value = [value]; 7857 7858 forEach(value, function(v) { 7859 if (isObject(v)) { 7860 v = toJson(v); 7861 } 7862 parts.push(encodeUriQuery(key) + '=' + 7863 encodeUriQuery(v)); 7864 }); 7865 }); 7866 return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); 7867 } 7868 7869 7870 }]; 7871} 7872 7873function createXhr(method) { 7874 // IE8 doesn't support PATCH method, but the ActiveX object does 7875 /* global ActiveXObject */ 7876 return (msie <= 8 && lowercase(method) === 'patch') 7877 ? new ActiveXObject('Microsoft.XMLHTTP') 7878 : new window.XMLHttpRequest(); 7879} 7880 7881 7882/** 7883 * @ngdoc object 7884 * @name ng.$httpBackend 7885 * @requires $browser 7886 * @requires $window 7887 * @requires $document 7888 * 7889 * @description 7890 * HTTP backend used by the {@link ng.$http service} that delegates to 7891 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 7892 * 7893 * You should never need to use this service directly, instead use the higher-level abstractions: 7894 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 7895 * 7896 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 7897 * $httpBackend} which can be trained with responses. 7898 */ 7899function $HttpBackendProvider() { 7900 this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { 7901 return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); 7902 }]; 7903} 7904 7905function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 7906 var ABORTED = -1; 7907 7908 // TODO(vojta): fix the signature 7909 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 7910 var status; 7911 $browser.$$incOutstandingRequestCount(); 7912 url = url || $browser.url(); 7913 7914 if (lowercase(method) == 'jsonp') { 7915 var callbackId = '_' + (callbacks.counter++).toString(36); 7916 callbacks[callbackId] = function(data) { 7917 callbacks[callbackId].data = data; 7918 }; 7919 7920 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 7921 function() { 7922 if (callbacks[callbackId].data) { 7923 completeRequest(callback, 200, callbacks[callbackId].data); 7924 } else { 7925 completeRequest(callback, status || -2); 7926 } 7927 callbacks[callbackId] = angular.noop; 7928 }); 7929 } else { 7930 7931 var xhr = createXhr(method); 7932 7933 xhr.open(method, url, true);
7934 forEach(headers, function(value, key) { 7935 if (isDefined(value)) { 7936 xhr.setRequestHeader(key, value); 7937 } 7938 }); 7939 7940 // In IE6 and 7, this might be called synchronously when xhr.send below is called and the 7941 // response is in the cache. the promise api will ensure that to the app code the api is 7942 // always async 7943 xhr.onreadystatechange = function() { 7944 // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by 7945 // xhrs that are resolved while the app is in the background (see #5426). 7946 // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before 7947 // continuing 7948 // 7949 // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and 7950 // Safari respectively. 7951 if (xhr && xhr.readyState == 4) { 7952 var responseHeaders = null, 7953 response = null; 7954 7955 if(status !== ABORTED) { 7956 responseHeaders = xhr.getAllResponseHeaders(); 7957 7958 // responseText is the old-school way of retrieving response (supported by IE8 & 9) 7959 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 7960 response = ('response' in xhr) ? xhr.response : xhr.responseText; 7961 } 7962 7963 completeRequest(callback, 7964 status || xhr.status, 7965 response, 7966 responseHeaders); 7967 } 7968 }; 7969 7970 if (withCredentials) { 7971 xhr.withCredentials = true; 7972 } 7973 7974 if (responseType) { 7975 xhr.responseType = responseType; 7976 } 7977 7978 xhr.send(post || null); 7979 } 7980 7981 if (timeout > 0) { 7982 var timeoutId = $browserDefer(timeoutRequest, timeout); 7983 } else if (timeout && timeout.then) { 7984 timeout.then(timeoutRequest); 7985 } 7986 7987 7988 function timeoutRequest() { 7989 status = ABORTED; 7990 jsonpDone && jsonpDone(); 7991 xhr && xhr.abort(); 7992 } 7993 7994 function completeRequest(callback, status, response, headersString) { 7995 // cancel timeout and subsequent timeout promise resolution 7996 timeoutId && $browserDefer.cancel(timeoutId); 7997 jsonpDone = xhr = null; 7998 7999 // fix status code when it is 0 (0 status is undocumented). 8000 // Occurs when accessing file resources. 8001 // On Android 4.1 stock browser it occurs while retrieving files from application cache. 8002 status = (status === 0) ? (response ? 200 : 404) : status; 8003 8004 // normalize IE bug (http://bugs.jquery.com/ticket/1450) 8005 status = status == 1223 ? 204 : status; 8006 8007 callback(status, response, headersString); 8008 $browser.$$completeOutstandingRequest(noop); 8009 } 8010 }; 8011 8012 function jsonpReq(url, done) { 8013 // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: 8014 // - fetches local scripts via XHR and evals them 8015 // - adds and immediately removes script elements from the document 8016 var script = rawDocument.createElement('script'), 8017 doneWrapper = function() { 8018 script.onreadystatechange = script.onload = script.onerror = null; 8019 rawDocument.body.removeChild(script); 8020 if (done) done(); 8021 }; 8022 8023 script.type = 'text/javascript'; 8024 script.src = url; 8025 8026 if (msie && msie <= 8) { 8027 script.onreadystatechange = function() { 8028 if (/loaded|complete/.test(script.readyState)) { 8029 doneWrapper(); 8030 } 8031 }; 8032 } else { 8033 script.onload = script.onerror = function() { 8034 doneWrapper(); 8035 }; 8036 } 8037 8038 rawDocument.body.appendChild(script); 8039 return doneWrapper; 8040 } 8041} 8042 8043var $interpolateMinErr = minErr('$interpolate'); 8044 8045/** 8046 * @ngdoc object 8047 * @name ng.$interpolateProvider 8048 * @function 8049 * 8050 * @description 8051 *
8052 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 8053 * 8054 * @example 8055<doc:example module="customInterpolationApp"> 8056<doc:source> 8057<script> 8058 var customInterpolationApp = angular.module('customInterpolationApp', []); 8059 8060 customInterpolationApp.config(function($interpolateProvider) { 8061 $interpolateProvider.startSymbol('//'); 8062 $interpolateProvider.endSymbol('//'); 8063 }); 8064 8065 8066 customInterpolationApp.controller('DemoController', function DemoController() { 8067 this.label = "This binding is brought you by // interpolation symbols."; 8068 }); 8069</script> 8070<div ng-app="App" ng-controller="DemoController as demo"> 8071 //demo.label// 8072</div> 8073</doc:source> 8074<doc:scenario> 8075 it('should interpolate binding with custom symbols', function() { 8076 expect(binding('demo.label')).toBe('This binding is brought you by // interpolation symbols.'); 8077 }); 8078</doc:scenario> 8079</doc:example> 8080 */ 8081function $InterpolateProvider() { 8082 var startSymbol = '{{'; 8083 var endSymbol = '}}'; 8084 8085 /** 8086 * @ngdoc method 8087 * @name ng.$interpolateProvider#startSymbol 8088 * @methodOf ng.$interpolateProvider 8089 * @description 8090 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 8091 * 8092 * @param {string=} value new value to set the starting symbol to. 8093 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8094 */ 8095 this.startSymbol = function(value){ 8096 if (value) { 8097 startSymbol = value; 8098 return this; 8099 } else { 8100 return startSymbol; 8101 } 8102 }; 8103 8104 /** 8105 * @ngdoc method 8106 * @name ng.$interpolateProvider#endSymbol 8107 * @methodOf ng.$interpolateProvider 8108 * @description 8109 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8110 * 8111 * @param {string=} value new value to set the ending symbol to. 8112 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8113 */ 8114 this.endSymbol = function(value){ 8115 if (value) { 8116 endSymbol = value; 8117 return this; 8118 } else { 8119 return endSymbol; 8120 } 8121 }; 8122 8123 8124 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 8125 var startSymbolLength = startSymbol.length, 8126 endSymbolLength = endSymbol.length; 8127 8128 /** 8129 * @ngdoc function 8130 * @name ng.$interpolate 8131 * @function 8132 * 8133 * @requires $parse 8134 * @requires $sce 8135 * 8136 * @description 8137 * 8138 * Compiles a string with markup into an interpolation function. This service is used by the 8139 * HTML {@link ng.$compile $compile} service for data binding. See 8140 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 8141 * interpolation markup. 8142 * 8143 * 8144 <pre> 8145 var $interpolate = ...; // injected 8146 var exp = $interpolate('Hello {{name | uppercase}}!'); 8147 expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); 8148 </pre> 8149 * 8150 * 8151 * @param {string} text The text with markup to interpolate. 8152 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 8153 * embedded expression in order to return an interpolation function. Strings with no 8154 * embedded expression will return null for the interpolation function. 8155 * @param {string=} trustedContext when provided, the returned function passes the interpolated 8156 * result through {@link ng.$sce#methods_getTrusted $sce.getTrusted(interpolatedResult, 8157 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 8158 * provides Strict Contextual Escaping for details. 8159 * @returns {function(context)} an interpolation function which is used to compute the 8160 * interpolated string. The function has these parameters: 8161 * 8162 * * `context`: an object against which any expressions embedded in the strings are evaluated 8163 * against. 8164 * 8165 */ 8166 function $interpolate(text, mustHaveExpression, trustedContext) { 8167 var startIndex, 8168 endIndex, 8169 index = 0, 8170 parts = [], 8171 length = text.length, 8172 hasInterpolation = false, 8173 fn, 8174 exp, 8175 concat = []; 8176 8177 while(index < length) { 8178 if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && 8179 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { 8180 (index != startIndex) && parts.push(text.substring(index, startIndex)); 8181 parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); 8182 fn.exp = exp;
8183 index = endIndex + endSymbolLength; 8184 hasInterpolation = true; 8185 } else { 8186 // we did not find anything, so we have to add the remainder to the parts array 8187 (index != length) && parts.push(text.substring(index)); 8188 index = length; 8189 } 8190 } 8191 8192 if (!(length = parts.length)) { 8193 // we added, nothing, must have been an empty string. 8194 parts.push(''); 8195 length = 1; 8196 } 8197 8198 // Concatenating expressions makes it hard to reason about whether some combination of 8199 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 8200 // single expression be used for iframe[src], object[src], etc., we ensure that the value 8201 // that's used is assigned or constructed by some JS code somewhere that is more testable or 8202 // make it obvious that you bound the value to some user controlled value. This helps reduce 8203 // the load when auditing for XSS issues. 8204 if (trustedContext && parts.length > 1) { 8205 throw $interpolateMinErr('noconcat', 8206 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 8207 "interpolations that concatenate multiple expressions when a trusted value is " + 8208 "required. See http://docs.angularjs.org/api/ng.$sce", text); 8209 } 8210 8211 if (!mustHaveExpression || hasInterpolation) { 8212 concat.length = length; 8213 fn = function(context) { 8214 try { 8215 for(var i = 0, ii = length, part; i<ii; i++) { 8216 if (typeof (part = parts[i]) == 'function') { 8217 part = part(context); 8218 if (trustedContext) { 8219 part = $sce.getTrusted(trustedContext, part); 8220 } else { 8221 part = $sce.valueOf(part); 8222 } 8223 if (part === null || isUndefined(part)) { 8224 part = ''; 8225 } else if (typeof part != 'string') { 8226 part = toJson(part); 8227 } 8228 } 8229 concat[i] = part; 8230 } 8231 return concat.join(''); 8232 } 8233 catch(err) { 8234 var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, 8235 err.toString()); 8236 $exceptionHandler(newErr); 8237 } 8238 }; 8239 fn.exp = text; 8240 fn.parts = parts; 8241 return fn; 8242 } 8243 } 8244 8245 8246 /** 8247 * @ngdoc method 8248 * @name ng.$interpolate#startSymbol 8249 * @methodOf ng.$interpolate 8250 * @description 8251 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 8252 * 8253 * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change 8254 * the symbol. 8255 * 8256 * @returns {string} start symbol. 8257 */ 8258 $interpolate.startSymbol = function() { 8259 return startSymbol; 8260 }; 8261 8262 8263 /** 8264 * @ngdoc method 8265 * @name ng.$interpolate#endSymbol 8266 * @methodOf ng.$interpolate 8267 * @description 8268 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8269 * 8270 * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change 8271 * the symbol. 8272 * 8273 * @returns {string} start symbol. 8274 */ 8275 $interpolate.endSymbol = function() { 8276 return endSymbol; 8277 }; 8278 8279 return $interpolate; 8280 }]; 8281} 8282 8283function $IntervalProvider() { 8284 this.$get = ['$rootScope', '$window', '$q', 8285 function($rootScope, $window, $q) { 8286 var intervals = {}; 8287 8288 8289 /** 8290 * @ngdoc function 8291 * @name ng.$interval 8292 * 8293 * @description 8294 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 8295 * milliseconds. 8296 * 8297 * The return value of registering an interval function is a promise. This promise will be 8298 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 8299 * run indefinitely if `count` is not defined. The value of the notification will be the 8300 * number of iterations that have run. 8301 * To cancel an interval, call `$interval.cancel(promise)`. 8302 * 8303 * In tests you can use {@link ngMock.$interval#methods_flush `$interval.flush(millis)`} to 8304 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 8305 * time. 8306 * 8307 * <div class="alert alert-warning">
8308 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 8309 * with them. In particular they are not automatically destroyed when a controller's scope or a 8310 * directive's element are destroyed. 8311 * You should take this into consideration and make sure to always cancel the interval at the 8312 * appropriate moment. See the example below for more details on how and when to do this. 8313 * </div> 8314 * 8315 * @param {function()} fn A function that should be called repeatedly. 8316 * @param {number} delay Number of milliseconds between each function call. 8317 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 8318 * indefinitely. 8319 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 8320 * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block. 8321 * @returns {promise} A promise which will be notified on each iteration. 8322 * 8323 * @example 8324 <doc:example module="time"> 8325 <doc:source> 8326 <script> 8327 function Ctrl2($scope,$interval) { 8328 $scope.format = 'M/d/yy h:mm:ss a'; 8329 $scope.blood_1 = 100; 8330 $scope.blood_2 = 120; 8331 8332 var stop; 8333 $scope.fight = function() { 8334 // Don't start a new fight if we are already fighting 8335 if ( angular.isDefined(stop) ) return; 8336 8337 stop = $interval(function() { 8338 if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 8339 $scope.blood_1 = $scope.blood_1 - 3; 8340 $scope.blood_2 = $scope.blood_2 - 4; 8341 } else { 8342 $scope.stopFight(); 8343 } 8344 }, 100); 8345 }; 8346 8347 $scope.stopFight = function() { 8348 if (angular.isDefined(stop)) { 8349 $interval.cancel(stop); 8350 stop = undefined; 8351 } 8352 }; 8353 8354 $scope.resetFight = function() { 8355 $scope.blood_1 = 100; 8356 $scope.blood_2 = 120; 8357 } 8358 8359 $scope.$on('$destroy', function() { 8360 // Make sure that the interval is destroyed too 8361 $scope.stopFight(); 8362 }); 8363 } 8364 8365 angular.module('time', []) 8366 // Register the 'myCurrentTime' directive factory method. 8367 // We inject $interval and dateFilter service since the factory method is DI. 8368 .directive('myCurrentTime', function($interval, dateFilter) { 8369 // return the directive link function. (compile function not needed) 8370 return function(scope, element, attrs) { 8371 var format, // date format 8372 stopTime; // so that we can cancel the time updates 8373 8374 // used to update the UI 8375 function updateTime() { 8376 element.text(dateFilter(new Date(), format)); 8377 } 8378 8379 // watch the expression, and update the UI on change. 8380 scope.$watch(attrs.myCurrentTime, function(value) { 8381 format = value; 8382 updateTime(); 8383 }); 8384 8385 stopTime = $interval(updateTime, 1000); 8386 8387 // listen on DOM destroy (removal) event, and cancel the next UI update 8388 // to prevent updating time ofter the DOM element was removed. 8389 element.bind('$destroy', function() { 8390 $interval.cancel(stopTime); 8391 }); 8392 } 8393 }); 8394 </script> 8395 8396 <div> 8397 <div ng-controller="Ctrl2"> 8398 Date format: <input ng-model="format"> <hr/> 8399 Current time is: <span my-current-time="format"></span> 8400 <hr/> 8401 Blood 1 : <font color='red'>{{blood_1}}</font> 8402 Blood 2 : <font color='red'>{{blood_2}}</font> 8403 <button type="button" data-ng-click="fight()">Fight</button> 8404 <button type="button" data-ng-click="stopFight()">StopFight</button> 8405 <button type="button" data-ng-click="resetFight()">resetFight</button> 8406 </div> 8407 </div> 8408 8409 </doc:source> 8410 </doc:example> 8411 */
8412 function interval(fn, delay, count, invokeApply) { 8413 var setInterval = $window.setInterval, 8414 clearInterval = $window.clearInterval, 8415 deferred = $q.defer(), 8416 promise = deferred.promise, 8417 iteration = 0, 8418 skipApply = (isDefined(invokeApply) && !invokeApply); 8419 8420 count = isDefined(count) ? count : 0; 8421 8422 promise.then(null, null, fn); 8423 8424 promise.$$intervalId = setInterval(function tick() { 8425 deferred.notify(iteration++); 8426 8427 if (count > 0 && iteration >= count) { 8428 deferred.resolve(iteration); 8429 clearInterval(promise.$$intervalId); 8430 delete intervals[promise.$$intervalId]; 8431 } 8432 8433 if (!skipApply) $rootScope.$apply(); 8434 8435 }, delay); 8436 8437 intervals[promise.$$intervalId] = deferred; 8438 8439 return promise; 8440 } 8441 8442 8443 /** 8444 * @ngdoc function 8445 * @name ng.$interval#cancel 8446 * @methodOf ng.$interval 8447 * 8448 * @description 8449 * Cancels a task associated with the `promise`. 8450 * 8451 * @param {number} promise Promise returned by the `$interval` function. 8452 * @returns {boolean} Returns `true` if the task was successfully canceled. 8453 */ 8454 interval.cancel = function(promise) { 8455 if (promise && promise.$$intervalId in intervals) { 8456 intervals[promise.$$intervalId].reject('canceled'); 8457 clearInterval(promise.$$intervalId); 8458 delete intervals[promise.$$intervalId]; 8459 return true; 8460 } 8461 return false; 8462 }; 8463 8464 return interval; 8465 }]; 8466} 8467 8468/** 8469 * @ngdoc object 8470 * @name ng.$locale 8471 * 8472 * @description 8473 * $locale service provides localization rules for various Angular components. As of right now the 8474 * only public api is: 8475 * 8476 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 8477 */ 8478function $LocaleProvider(){ 8479 this.$get = function() { 8480 return { 8481 id: 'en-us', 8482 8483 NUMBER_FORMATS: { 8484 DECIMAL_SEP: '.', 8485 GROUP_SEP: ',', 8486 PATTERNS: [ 8487 { // Decimal Pattern 8488 minInt: 1, 8489 minFrac: 0, 8490 maxFrac: 3, 8491 posPre: '', 8492 posSuf: '', 8493 negPre: '-', 8494 negSuf: '', 8495 gSize: 3, 8496 lgSize: 3 8497 },{ //Currency Pattern 8498 minInt: 1, 8499 minFrac: 2, 8500 maxFrac: 2, 8501 posPre: '\u00A4', 8502 posSuf: '', 8503 negPre: '(\u00A4', 8504 negSuf: ')', 8505 gSize: 3, 8506 lgSize: 3 8507 } 8508 ], 8509 CURRENCY_SYM: '$' 8510 }, 8511 8512 DATETIME_FORMATS: { 8513 MONTH: 8514 'January,February,March,April,May,June,July,August,September,October,November,December' 8515 .split(','), 8516 SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), 8517 DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), 8518 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), 8519 AMPMS: ['AM','PM'], 8520 medium: 'MMM d, y h:mm:ss a', 8521 short: 'M/d/yy h:mm a', 8522 fullDate: 'EEEE, MMMM d, y', 8523 longDate: 'MMMM d, y', 8524 mediumDate: 'MMM d, y', 8525 shortDate: 'M/d/yy', 8526 mediumTime: 'h:mm:ss a', 8527 shortTime: 'h:mm a' 8528 }, 8529 8530 pluralCat: function(num) { 8531 if (num === 1) { 8532 return 'one'; 8533 } 8534 return 'other'; 8535 } 8536 }; 8537 }; 8538} 8539 8540var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 8541 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 8542var $locationMinErr = minErr('$location'); 8543 8544 8545/** 8546 * Encode path using encodeUriSegment, ignoring forward slashes 8547 * 8548 * @param {string} path Path to encode 8549 * @returns {string} 8550 */ 8551function encodePath(path) { 8552 var segments = path.split('/'), 8553 i = segments.length; 8554 8555 while (i--) { 8556 segments[i] = encodeUriSegment(segments[i]); 8557 } 8558 8559 return segments.join('/'); 8560} 8561 8562function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) { 8563 var parsedUrl = urlResolve(absoluteUrl, appBase); 8564 8565 locationObj.$$protocol = parsedUrl.protocol; 8566 locationObj.$$host = parsedUrl.hostname; 8567 locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 8568} 8569 8570 8571function parseAppUrl(relativeUrl, locationObj, appBase) { 8572 var prefixed = (relativeUrl.charAt(0) !== '/'); 8573 if (prefixed) { 8574 relativeUrl = '/' + relativeUrl; 8575 } 8576 var match = urlResolve(relativeUrl, appBase); 8577 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 8578 match.pathname.substring(1) : match.pathname); 8579 locationObj.$$search = parseKeyValue(match.search); 8580 locationObj.$$hash = decodeURIComponent(match.hash); 8581 8582 // make sure path starts with '/'; 8583 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 8584 locationObj.$$path = '/' + locationObj.$$path; 8585 } 8586} 8587 8588 8589/** 8590 * 8591 * @param {string} begin 8592 * @param {string} whole 8593 * @returns {string} returns text from whole after begin or undefined if it does not begin with 8594 * expected string. 8595 */ 8596function beginsWith(begin, whole) { 8597 if (whole.indexOf(begin) === 0) { 8598 return whole.substr(begin.length); 8599 } 8600} 8601 8602 8603function stripHash(url) { 8604 var index = url.indexOf('#'); 8605 return index == -1 ? url : url.substr(0, index); 8606} 8607 8608 8609function stripFile(url) { 8610 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 8611} 8612 8613/* return the server only (scheme://host:port) */ 8614function serverBase(url) { 8615 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 8616} 8617 8618 8619/** 8620 * LocationHtml5Url represents an url 8621 * This object is exposed as $location service when HTML5 mode is enabled and supported 8622 * 8623 * @constructor 8624 * @param {string} appBase application base URL 8625 * @param {string} basePrefix url path prefix 8626 */ 8627function LocationHtml5Url(appBase, basePrefix) { 8628 this.$$html5 = true; 8629 basePrefix = basePrefix || ''; 8630 var appBaseNoFile = stripFile(appBase); 8631 parseAbsoluteUrl(appBase, this, appBase); 8632 8633 8634 /** 8635 * Parse given html5 (regular) url string into properties 8636 * @param {string} newAbsoluteUrl HTML5 url 8637 * @private 8638 */ 8639 this.$$parse = function(url) { 8640 var pathUrl = beginsWith(appBaseNoFile, url); 8641 if (!isString(pathUrl)) { 8642 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 8643 appBaseNoFile); 8644 } 8645 8646 parseAppUrl(pathUrl, this, appBase); 8647 8648 if (!this.$$path) { 8649 this.$$path = '/'; 8650 } 8651 8652 this.$$compose(); 8653 }; 8654 8655 /** 8656 * Compose url and update `absUrl` property 8657 * @private 8658 */ 8659 this.$$compose = function() {
8660 var search = toKeyValue(this.$$search), 8661 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 8662 8663 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 8664 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 8665 }; 8666 8667 this.$$rewrite = function(url) { 8668 var appUrl, prevAppUrl; 8669 8670 if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { 8671 prevAppUrl = appUrl; 8672 if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { 8673 return appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 8674 } else { 8675 return appBase + prevAppUrl; 8676 } 8677 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { 8678 return appBaseNoFile + appUrl; 8679 } else if (appBaseNoFile == url + '/') { 8680 return appBaseNoFile; 8681 } 8682 }; 8683} 8684 8685 8686/** 8687 * LocationHashbangUrl represents url 8688 * This object is exposed as $location service when developer doesn't opt into html5 mode. 8689 * It also serves as the base class for html5 mode fallback on legacy browsers. 8690 * 8691 * @constructor 8692 * @param {string} appBase application base URL 8693 * @param {string} hashPrefix hashbang prefix 8694 */ 8695function LocationHashbangUrl(appBase, hashPrefix) { 8696 var appBaseNoFile = stripFile(appBase); 8697 8698 parseAbsoluteUrl(appBase, this, appBase); 8699 8700 8701 /** 8702 * Parse given hashbang url into properties 8703 * @param {string} url Hashbang url 8704 * @private 8705 */ 8706 this.$$parse = function(url) { 8707 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 8708 var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' 8709 ? beginsWith(hashPrefix, withoutBaseUrl) 8710 : (this.$$html5) 8711 ? withoutBaseUrl 8712 : ''; 8713 8714 if (!isString(withoutHashUrl)) { 8715 throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, 8716 hashPrefix); 8717 } 8718 parseAppUrl(withoutHashUrl, this, appBase); 8719 8720 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 8721 8722 this.$$compose(); 8723 8724 /* 8725 * In Windows, on an anchor node on documents loaded from 8726 * the filesystem, the browser will return a pathname 8727 * prefixed with the drive name ('/C:/path') when a 8728 * pathname without a drive is set: 8729 * * a.setAttribute('href', '/foo') 8730 * * a.pathname === '/C:/foo' //true 8731 * 8732 * Inside of Angular, we're always using pathnames that 8733 * do not include drive names for routing. 8734 */ 8735 function removeWindowsDriveName (path, url, base) { 8736 /* 8737 Matches paths for file protocol on windows, 8738 such as /C:/foo/bar, and captures only /foo/bar. 8739 */ 8740 var windowsFilePathExp = /^\/?.*?:(\/.*)/; 8741 8742 var firstPathSegmentMatch; 8743 8744 //Get the relative path from the input URL. 8745 if (url.indexOf(base) === 0) { 8746 url = url.replace(base, ''); 8747 } 8748 8749 /* 8750 * The input URL intentionally contains a 8751 * first path segment that ends with a colon. 8752 */ 8753 if (windowsFilePathExp.exec(url)) { 8754 return path; 8755 } 8756 8757 firstPathSegmentMatch = windowsFilePathExp.exec(path); 8758 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 8759 } 8760 }; 8761 8762 /** 8763 * Compose hashbang url and update `absUrl` property 8764 * @private 8765 */ 8766 this.$$compose = function() { 8767 var search = toKeyValue(this.$$search), 8768 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 8769 8770 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 8771 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 8772 }; 8773 8774 this.$$rewrite = function(url) { 8775 if(stripHash(appBase) == stripHash(url)) { 8776 return url; 8777 } 8778 }; 8779} 8780 8781 8782/** 8783 * LocationHashbangUrl represents url 8784 * This object is exposed as $location service when html5 history api is enabled but the browser 8785 * does not support it. 8786 * 8787 * @constructor 8788 * @param {string} appBase application base URL 8789 * @param {string} hashPrefix hashbang prefix 8790 */ 8791function LocationHashbangInHtml5Url(appBase, hashPrefix) { 8792 this.$$html5 = true; 8793 LocationHashbangUrl.apply(this, arguments); 8794 8795 var appBaseNoFile = stripFile(appBase); 8796 8797 this.$$rewrite = function(url) { 8798 var appUrl; 8799 8800 if ( appBase == stripHash(url) ) { 8801 return url; 8802 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { 8803 return appBase + hashPrefix + appUrl; 8804 } else if ( appBaseNoFile === url + '/') { 8805 return appBaseNoFile; 8806 } 8807 }; 8808} 8809 8810 8811LocationHashbangInHtml5Url.prototype = 8812 LocationHashbangUrl.prototype = 8813 LocationHtml5Url.prototype = { 8814 8815 /** 8816 * Are we in html5 mode? 8817 * @private 8818 */ 8819 $$html5: false, 8820 8821 /** 8822 * Has any change been replacing ? 8823 * @private 8824 */ 8825 $$replace: false, 8826 8827 /** 8828 * @ngdoc method 8829 * @name ng.$location#absUrl 8830 * @methodOf ng.$location 8831 * 8832 * @description 8833 * This method is getter only. 8834 * 8835 * Return full url representation with all segments encoded according to rules specified in 8836 * {@link http://www.ietf.org/rfc/rfc3986.txt RFC 3986}. 8837 * 8838 * @return {string} full url 8839 */ 8840 absUrl: locationGetter('$$absUrl'), 8841 8842 /** 8843 * @ngdoc method 8844 * @name ng.$location#url 8845 * @methodOf ng.$location 8846 * 8847 * @description 8848 * This method is getter / setter. 8849 * 8850 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 8851 * 8852 * Change path, search and hash, when called with parameter and return `$location`. 8853 * 8854 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 8855 * @param {string=} replace The path that will be changed 8856 * @return {string} url 8857 */ 8858 url: function(url, replace) { 8859 if (isUndefined(url)) 8860 return this.$$url; 8861 8862 var match = PATH_MATCH.exec(url); 8863 if (match[1]) this.path(decodeURIComponent(match[1])); 8864 if (match[2] || match[1]) this.search(match[3] || ''); 8865 this.hash(match[5] || '', replace); 8866 8867 return this; 8868 }, 8869 8870 /** 8871 * @ngdoc method 8872 * @name ng.$location#protocol 8873 * @methodOf ng.$location 8874 * 8875 * @description 8876 * This method is getter only. 8877 * 8878 * Return protocol of current url. 8879 * 8880 * @return {string} protocol of current url 8881 */ 8882 protocol: locationGetter('$$protocol'), 8883 8884 /** 8885 * @ngdoc method 8886 * @name ng.$location#host 8887 * @methodOf ng.$location 8888 * 8889 * @description 8890 * This method is getter only. 8891 * 8892 * Return host of current url. 8893 * 8894 * @return {string} host of current url. 8895 */ 8896 host: locationGetter('$$host'), 8897 8898 /** 8899 * @ngdoc method 8900 * @name ng.$location#port 8901 * @methodOf ng.$location 8902 * 8903 * @description 8904 * This method is getter only. 8905 * 8906 * Return port of current url. 8907 * 8908 * @return {Number} port 8909 */ 8910 port: locationGetter('$$port'), 8911 8912 /** 8913 * @ngdoc method 8914 * @name ng.$location#path 8915 * @methodOf ng.$location 8916 * 8917 * @description 8918 * This method is getter / setter. 8919 * 8920 * Return path of current url when called without any parameter. 8921 * 8922 * Change path when called with parameter and return `$location`. 8923 * 8924 * Note: Path should always begin with forward slash (/), this method will add the forward slash 8925 * if it is missing. 8926 * 8927 * @param {string=} path New path 8928 * @return {string} path 8929 */ 8930 path: locationGetterSetter('$$path', function(path) { 8931 return path.charAt(0) == '/' ? path : '/' + path; 8932 }), 8933 8934 /** 8935 * @ngdoc method 8936 * @name ng.$location#search 8937 * @methodOf ng.$location 8938 * 8939 * @description 8940 * This method is getter / setter. 8941 * 8942 * Return search part (as object) of current url when called without any parameter. 8943 * 8944 * Change search part when called with parameter and return `$location`. 8945 * 8946 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 8947 * hash object. Hash object may contain an array of values, which will be decoded as duplicates in 8948 * the url. 8949 * 8950 * @param {(string|Array<string>)=} paramValue If `search` is a string, then `paramValue` will override only a 8951 * single search parameter. If `paramValue` is an array, it will set the parameter as a 8952 * comma-separated value. If `paramValue` is `null`, the parameter will be deleted. 8953 * 8954 * @return {string} search 8955 */ 8956 search: function(search, paramValue) { 8957 switch (arguments.length) { 8958 case 0: 8959 return this.$$search; 8960 case 1: 8961 if (isString(search)) { 8962 this.$$search = parseKeyValue(search); 8963 } else if (isObject(search)) { 8964 this.$$search = search; 8965 } else { 8966 throw $locationMinErr('isrcharg', 8967 'The first argument of the `$location#search()` call must be a string or an object.'); 8968 } 8969 break; 8970 default: 8971 if (isUndefined(paramValue) || paramValue === null) { 8972 delete this.$$search[search]; 8973 } else { 8974 this.$$search[search] = paramValue; 8975 } 8976 } 8977 8978 this.$$compose(); 8979 return this; 8980 }, 8981 8982 /** 8983 * @ngdoc method 8984 * @name ng.$location#hash 8985 * @methodOf ng.$location 8986 * 8987 * @description 8988 * This method is getter / setter. 8989 * 8990 * Return hash fragment when called without any parameter. 8991 * 8992 * Change hash fragment when called with parameter and return `$location`. 8993 * 8994 * @param {string=} hash New hash fragment 8995 * @return {string} hash 8996 */ 8997 hash: locationGetterSetter('$$hash', identity), 8998 8999 /** 9000 * @ngdoc method 9001 * @name ng.$location#replace 9002 * @methodOf ng.$location 9003 * 9004 * @description 9005 * If called, all changes to $location during current `$digest` will be replacing current history 9006 * record, instead of adding new one. 9007 */ 9008 replace: function() { 9009 this.$$replace = true; 9010 return this; 9011 } 9012}; 9013 9014function locationGetter(property) { 9015 return function() { 9016 return this[property]; 9017 }; 9018} 9019 9020 9021function locationGetterSetter(property, preprocess) { 9022 return function(value) { 9023 if (isUndefined(value)) 9024 return this[property]; 9025 9026 this[property] = preprocess(value); 9027 this.$$compose(); 9028 9029 return this; 9030 }; 9031} 9032 9033 9034/** 9035 * @ngdoc object 9036 * @name ng.$location 9037 * 9038 * @requires $browser 9039 * @requires $sniffer 9040 * @requires $rootElement 9041 * 9042 * @description 9043 * The $location service parses the URL in the browser address bar (based on the 9044 * {@link https://developer.mozilla.org/en/window.location window.location}) and makes the URL 9045 * available to your application. Changes to the URL in the address bar are reflected into 9046 * $location service and changes to $location are reflected into the browser address bar. 9047 * 9048 * **The $location service:** 9049 * 9050 * - Exposes the current URL in the browser address bar, so you can 9051 * - Watch and observe the URL. 9052 * - Change the URL. 9053 * - Synchronizes the URL with the browser when the user 9054 * - Changes the address bar. 9055 * - Clicks the back or forward button (or clicks a History link). 9056 * - Clicks on a link. 9057 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 9058 * 9059 * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular 9060 * Services: Using $location} 9061 */ 9062
9063/** 9064 * @ngdoc object 9065 * @name ng.$locationProvider 9066 * @description 9067 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 9068 */ 9069function $LocationProvider(){ 9070 var hashPrefix = '', 9071 html5Mode = false; 9072 9073 /** 9074 * @ngdoc property 9075 * @name ng.$locationProvider#hashPrefix 9076 * @methodOf ng.$locationProvider 9077 * @description 9078 * @param {string=} prefix Prefix for hash part (containing path and search) 9079 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9080 */ 9081 this.hashPrefix = function(prefix) { 9082 if (isDefined(prefix)) { 9083 hashPrefix = prefix; 9084 return this; 9085 } else { 9086 return hashPrefix; 9087 } 9088 }; 9089 9090 /** 9091 * @ngdoc property 9092 * @name ng.$locationProvider#html5Mode 9093 * @methodOf ng.$locationProvider 9094 * @description 9095 * @param {boolean=} mode Use HTML5 strategy if available. 9096 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9097 */ 9098 this.html5Mode = function(mode) { 9099 if (isDefined(mode)) { 9100 html5Mode = mode; 9101 return this; 9102 } else { 9103 return html5Mode; 9104 } 9105 }; 9106 9107 /** 9108 * @ngdoc event 9109 * @name ng.$location#$locationChangeStart 9110 * @eventOf ng.$location 9111 * @eventType broadcast on root scope 9112 * @description 9113 * Broadcasted before a URL will change. This change can be prevented by calling 9114 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 9115 * details about event object. Upon successful change 9116 * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. 9117 * 9118 * @param {Object} angularEvent Synthetic event object. 9119 * @param {string} newUrl New URL 9120 * @param {string=} oldUrl URL that was before it was changed. 9121 */ 9122 9123 /** 9124 * @ngdoc event 9125 * @name ng.$location#$locationChangeSuccess 9126 * @eventOf ng.$location 9127 * @eventType broadcast on root scope 9128 * @description 9129 * Broadcasted after a URL was changed. 9130 * 9131 * @param {Object} angularEvent Synthetic event object. 9132 * @param {string} newUrl New URL 9133 * @param {string=} oldUrl URL that was before it was changed. 9134 */ 9135 9136 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', 9137 function( $rootScope, $browser, $sniffer, $rootElement) { 9138 var $location, 9139 LocationMode, 9140 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 9141 initialUrl = $browser.url(), 9142 appBase; 9143 9144 if (html5Mode) { 9145 appBase = serverBase(initialUrl) + (baseHref || '/'); 9146 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 9147 } else { 9148 appBase = stripHash(initialUrl); 9149 LocationMode = LocationHashbangUrl; 9150 } 9151 $location = new LocationMode(appBase, '#' + hashPrefix); 9152 $location.$$parse($location.$$rewrite(initialUrl)); 9153 9154 $rootElement.on('click', function(event) { 9155 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 9156 // currently we open nice url link and redirect then 9157 9158 if (event.ctrlKey || event.metaKey || event.which == 2) return; 9159 9160 var elm = jqLite(event.target); 9161 9162 // traverse the DOM up to find first A tag 9163 while (lowercase(elm[0].nodeName) !== 'a') { 9164 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 9165 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 9166 } 9167 9168 var absHref = elm.prop('href'); 9169 9170 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 9171 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 9172 // an animation. 9173 absHref = urlResolve(absHref.animVal).href; 9174 } 9175 9176 var rewrittenUrl = $location.$$rewrite(absHref); 9177 9178 if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) { 9179 event.preventDefault(); 9180 if (rewrittenUrl != $browser.url()) { 9181 // update location manually 9182 $location.$$parse(rewrittenUrl); 9183 $rootScope.$apply(); 9184 // hack to work around FF6 bug 684208 when scenario runner clicks on links 9185 window.angular['ff-684208-preventDefault'] = true; 9186 } 9187 } 9188 }); 9189 9190 9191 // rewrite hashbang url <> html5 url 9192 if ($location.absUrl() != initialUrl) { 9193 $browser.url($location.absUrl(), true); 9194 } 9195 9196 // update $location when $browser url changes 9197 $browser.onUrlChange(function(newUrl) { 9198 if ($location.absUrl() != newUrl) { 9199 $rootScope.$evalAsync(function() { 9200 var oldUrl = $location.absUrl(); 9201 9202 $location.$$parse(newUrl); 9203 if ($rootScope.$broadcast('$locationChangeStart', newUrl, 9204 oldUrl).defaultPrevented) { 9205 $location.$$parse(oldUrl); 9206 $browser.url(oldUrl); 9207 } else { 9208 afterLocationChange(oldUrl); 9209 } 9210 }); 9211 if (!$rootScope.$$phase) $rootScope.$digest(); 9212 } 9213 }); 9214 9215 // update browser 9216 var changeCounter = 0; 9217 $rootScope.$watch(function $locationWatch() { 9218 var oldUrl = $browser.url(); 9219 var currentReplace = $location.$$replace; 9220 9221 if (!changeCounter || oldUrl != $location.absUrl()) { 9222 changeCounter++; 9223 $rootScope.$evalAsync(function() { 9224 if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). 9225 defaultPrevented) { 9226 $location.$$parse(oldUrl); 9227 } else { 9228 $browser.url($location.absUrl(), currentReplace); 9229 afterLocationChange(oldUrl); 9230 } 9231 }); 9232 } 9233 $location.$$replace = false;
9234 9235 return changeCounter; 9236 }); 9237 9238 return $location; 9239 9240 function afterLocationChange(oldUrl) { 9241 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); 9242 } 9243}]; 9244} 9245 9246/** 9247 * @ngdoc object 9248 * @name ng.$log 9249 * @requires $window 9250 * 9251 * @description 9252 * Simple service for logging. Default implementation safely writes the message 9253 * into the browser's console (if present). 9254 * 9255 * The main purpose of this service is to simplify debugging and troubleshooting. 9256 * 9257 * The default is to log `debug` messages. You can use 9258 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 9259 * 9260 * @example 9261 <example> 9262 <file name="script.js"> 9263 function LogCtrl($scope, $log) { 9264 $scope.$log = $log; 9265 $scope.message = 'Hello World!'; 9266 } 9267 </file> 9268 <file name="index.html"> 9269 <div ng-controller="LogCtrl"> 9270 <p>Reload this page with open console, enter text and hit the log button...</p> 9271 Message: 9272 <input type="text" ng-model="message"/> 9273 <button ng-click="$log.log(message)">log</button> 9274 <button ng-click="$log.warn(message)">warn</button> 9275 <button ng-click="$log.info(message)">info</button> 9276 <button ng-click="$log.error(message)">error</button> 9277 </div> 9278 </file> 9279 </example> 9280 */ 9281 9282/** 9283 * @ngdoc object 9284 * @name ng.$logProvider 9285 * @description 9286 * Use the `$logProvider` to configure how the application logs messages 9287 */ 9288function $LogProvider(){ 9289 var debug = true, 9290 self = this; 9291 9292 /** 9293 * @ngdoc property 9294 * @name ng.$logProvider#debugEnabled 9295 * @methodOf ng.$logProvider 9296 * @description 9297 * @param {string=} flag enable or disable debug level messages 9298 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9299 */ 9300 this.debugEnabled = function(flag) { 9301 if (isDefined(flag)) { 9302 debug = flag; 9303 return this; 9304 } else { 9305 return debug; 9306 } 9307 }; 9308 9309 this.$get = ['$window', function($window){ 9310 return { 9311 /** 9312 * @ngdoc method 9313 * @name ng.$log#log 9314 * @methodOf ng.$log 9315 * 9316 * @description 9317 * Write a log message 9318 */ 9319 log: consoleLog('log'), 9320 9321 /** 9322 * @ngdoc method 9323 * @name ng.$log#info 9324 * @methodOf ng.$log 9325 * 9326 * @description 9327 * Write an information message 9328 */ 9329 info: consoleLog('info'), 9330 9331 /** 9332 * @ngdoc method 9333 * @name ng.$log#warn 9334 * @methodOf ng.$log 9335 * 9336 * @description 9337 * Write a warning message 9338 */ 9339 warn: consoleLog('warn'), 9340 9341 /** 9342 * @ngdoc method 9343 * @name ng.$log#error 9344 * @methodOf ng.$log 9345 * 9346 * @description 9347 * Write an error message 9348 */ 9349 error: consoleLog('error'), 9350 9351 /** 9352 * @ngdoc method 9353 * @name ng.$log#debug 9354 * @methodOf ng.$log 9355 * 9356 * @description 9357 * Write a debug message 9358 */ 9359 debug: (function () { 9360 var fn = consoleLog('debug'); 9361 9362 return function() { 9363 if (debug) { 9364 fn.apply(self, arguments); 9365 } 9366 }; 9367 }()) 9368 }; 9369 9370 function formatError(arg) { 9371 if (arg instanceof Error) { 9372 if (arg.stack) { 9373 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 9374 ? 'Error: ' + arg.message + '\n' + arg.stack 9375 : arg.stack; 9376 } else if (arg.sourceURL) { 9377 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 9378 } 9379 } 9380 return arg; 9381 } 9382 9383 function consoleLog(type) { 9384 var console = $window.console || {}, 9385 logFn = console[type] || console.log || noop, 9386 hasApply = false; 9387 9388 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 9389 // The reason behind this is that console.log has type "object" in IE8... 9390 try { 9391 hasApply = !! logFn.apply; 9392 } catch (e) {} 9393 9394 if (hasApply) { 9395 return function() { 9396 var args = []; 9397 forEach(arguments, function(arg) { 9398 args.push(formatError(arg)); 9399 }); 9400 return logFn.apply(console, args); 9401 }; 9402 } 9403 9404 // we are IE which either doesn't have window.console => this is noop and we do nothing, 9405 // or we are IE where console.log doesn't have apply so we log at least first 2 args 9406 return function(arg1, arg2) { 9407 logFn(arg1, arg2 == null ? '' : arg2); 9408 }; 9409 } 9410 }]; 9411} 9412 9413var $parseMinErr = minErr('$parse'); 9414var promiseWarningCache = {}; 9415var promiseWarning; 9416 9417// Sandboxing Angular Expressions 9418// ------------------------------ 9419// Angular expressions are generally considered safe because these expressions only have direct 9420// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by 9421// obtaining a reference to native JS functions such as the Function constructor. 9422// 9423// As an example, consider the following Angular expression: 9424// 9425// {}.toString.constructor(alert("evil JS code")) 9426// 9427// We want to prevent this type of access. For the sake of performance, during the lexing phase we 9428// disallow any "dotted" access to any member named "constructor". 9429// 9430// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor 9431// while evaluating the expression, which is a stronger but more expensive test. Since reflective 9432// calls are expensive anyway, this is not such a big deal compared to static dereferencing. 9433// 9434// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 9435// against the expression language, but not to prevent exploits that were enabled by exposing 9436// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good 9437// practice and therefore we are not even trying to protect against interaction with an object 9438// explicitly exposed in this way. 9439// 9440// A developer could foil the name check by aliasing the Function constructor under a different 9441// name on the scope. 9442// 9443// In general, it is not possible to access a Window object from an angular expression unless a 9444// window or some DOM object that has a reference to window is published onto a Scope. 9445 9446function ensureSafeMemberName(name, fullExpression) { 9447 if (name === "constructor") { 9448 throw $parseMinErr('isecfld', 9449 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', 9450 fullExpression); 9451 } 9452 return name; 9453} 9454 9455function ensureSafeObject(obj, fullExpression) { 9456 // nifty check if obj is Function that is fast and works across iframes and other contexts 9457 if (obj) { 9458 if (obj.constructor === obj) { 9459 throw $parseMinErr('isecfn', 9460 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 9461 fullExpression); 9462 } else if (// isWindow(obj) 9463 obj.document && obj.location && obj.alert && obj.setInterval) { 9464 throw $parseMinErr('isecwindow', 9465 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 9466 fullExpression); 9467 } else if (// isElement(obj) 9468 obj.children && (obj.nodeName || (obj.on && obj.find))) { 9469 throw $parseMinErr('isecdom', 9470 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 9471 fullExpression); 9472 } 9473 } 9474 return obj; 9475} 9476 9477var OPERATORS = { 9478 /* jshint bitwise : false */ 9479 'null':function(){return null;}, 9480 'true':function(){return true;}, 9481 'false':function(){return false;}, 9482 undefined:noop, 9483 '+':function(self, locals, a,b){ 9484 a=a(self, locals); b=b(self, locals); 9485 if (isDefined(a)) { 9486 if (isDefined(b)) { 9487 return a + b; 9488 } 9489 return a; 9490 } 9491 return isDefined(b)?b:undefined;}, 9492 '-':function(self, locals, a,b){ 9493 a=a(self, locals); b=b(self, locals); 9494 return (isDefined(a)?a:0)-(isDefined(b)?b:0); 9495 }, 9496 '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, 9497 '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, 9498 '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, 9499 '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, 9500 '=':noop, 9501 '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, 9502 '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, 9503 '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, 9504 '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, 9505 '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);}, 9506 '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, 9507 '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, 9508 '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, 9509 '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, 9510 '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, 9511 '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, 9512// '|':function(self, locals, a,b){return a|b;}, 9513 '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, 9514 '!':function(self, locals, a){return !a(self, locals);} 9515}; 9516/* jshint bitwise: true */ 9517var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 9518 9519
9520///////////////////////////////////////// 9521 9522 9523/** 9524 * @constructor 9525 */ 9526var Lexer = function (options) { 9527 this.options = options; 9528}; 9529 9530Lexer.prototype = { 9531 constructor: Lexer, 9532 9533 lex: function (text) { 9534 this.text = text; 9535 9536 this.index = 0; 9537 this.ch = undefined; 9538 this.lastCh = ':'; // can start regexp 9539 9540 this.tokens = []; 9541 9542 var token; 9543 var json = []; 9544 9545 while (this.index < this.text.length) { 9546 this.ch = this.text.charAt(this.index); 9547 if (this.is('"\'')) { 9548 this.readString(this.ch); 9549 } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { 9550 this.readNumber(); 9551 } else if (this.isIdent(this.ch)) { 9552 this.readIdent(); 9553 // identifiers can only be if the preceding char was a { or , 9554 if (this.was('{,') && json[0] === '{' && 9555 (token = this.tokens[this.tokens.length - 1])) { 9556 token.json = token.text.indexOf('.') === -1; 9557 } 9558 } else if (this.is('(){}[].,;:?')) { 9559 this.tokens.push({ 9560 index: this.index, 9561 text: this.ch, 9562 json: (this.was(':[,') && this.is('{[')) || this.is('}]:,') 9563 }); 9564 if (this.is('{[')) json.unshift(this.ch); 9565 if (this.is('}]')) json.shift(); 9566 this.index++; 9567 } else if (this.isWhitespace(this.ch)) { 9568 this.index++; 9569 continue; 9570 } else { 9571 var ch2 = this.ch + this.peek(); 9572 var ch3 = ch2 + this.peek(2); 9573 var fn = OPERATORS[this.ch]; 9574 var fn2 = OPERATORS[ch2]; 9575 var fn3 = OPERATORS[ch3]; 9576 if (fn3) { 9577 this.tokens.push({index: this.index, text: ch3, fn: fn3}); 9578 this.index += 3; 9579 } else if (fn2) { 9580 this.tokens.push({index: this.index, text: ch2, fn: fn2}); 9581 this.index += 2; 9582 } else if (fn) { 9583 this.tokens.push({ 9584 index: this.index, 9585 text: this.ch, 9586 fn: fn, 9587 json: (this.was('[,:') && this.is('+-')) 9588 }); 9589 this.index += 1; 9590 } else { 9591 this.throwError('Unexpected next character ', this.index, this.index + 1); 9592 } 9593 } 9594 this.lastCh = this.ch; 9595 } 9596 return this.tokens; 9597 }, 9598 9599 is: function(chars) { 9600 return chars.indexOf(this.ch) !== -1; 9601 }, 9602 9603 was: function(chars) { 9604 return chars.indexOf(this.lastCh) !== -1; 9605 }, 9606 9607 peek: function(i) { 9608 var num = i || 1; 9609 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 9610 }, 9611 9612 isNumber: function(ch) { 9613 return ('0' <= ch && ch <= '9'); 9614 }, 9615 9616 isWhitespace: function(ch) { 9617 // IE treats non-breaking space as \u00A0 9618 return (ch === ' ' || ch === '\r' || ch === '\t' || 9619 ch === '\n' || ch === '\v' || ch === '\u00A0'); 9620 }, 9621 9622 isIdent: function(ch) { 9623 return ('a' <= ch && ch <= 'z' || 9624 'A' <= ch && ch <= 'Z' || 9625 '_' === ch || ch === '$'); 9626 }, 9627 9628 isExpOperator: function(ch) { 9629 return (ch === '-' || ch === '+' || this.isNumber(ch)); 9630 }, 9631 9632 throwError: function(error, start, end) { 9633 end = end || this.index; 9634 var colStr = (isDefined(start) 9635 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 9636 : ' ' + end); 9637 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 9638 error, colStr, this.text); 9639 }, 9640 9641 readNumber: function() { 9642 var number = ''; 9643 var start = this.index; 9644 while (this.index < this.text.length) { 9645 var ch = lowercase(this.text.charAt(this.index)); 9646 if (ch == '.' || this.isNumber(ch)) { 9647 number += ch; 9648 } else { 9649 var peekCh = this.peek(); 9650 if (ch == 'e' && this.isExpOperator(peekCh)) { 9651 number += ch; 9652 } else if (this.isExpOperator(ch) && 9653 peekCh && this.isNumber(peekCh) && 9654 number.charAt(number.length - 1) == 'e') { 9655 number += ch; 9656 } else if (this.isExpOperator(ch) && 9657 (!peekCh || !this.isNumber(peekCh)) && 9658 number.charAt(number.length - 1) == 'e') { 9659 this.throwError('Invalid exponent'); 9660 } else { 9661 break; 9662 } 9663 } 9664 this.index++; 9665 } 9666 number = 1 * number; 9667 this.tokens.push({ 9668 index: start, 9669 text: number, 9670 json: true, 9671 fn: function() { return number; } 9672 }); 9673 }, 9674 9675 readIdent: function() { 9676 var parser = this; 9677 9678 var ident = ''; 9679 var start = this.index; 9680 9681 var lastDot, peekIndex, methodName, ch; 9682 9683 while (this.index < this.text.length) { 9684 ch = this.text.charAt(this.index); 9685 if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { 9686 if (ch === '.') lastDot = this.index; 9687 ident += ch; 9688 } else { 9689 break; 9690 } 9691 this.index++; 9692 } 9693 9694 //check if this is not a method invocation and if it is back out to last dot 9695 if (lastDot) { 9696 peekIndex = this.index; 9697 while (peekIndex < this.text.length) { 9698 ch = this.text.charAt(peekIndex); 9699 if (ch === '(') { 9700 methodName = ident.substr(lastDot - start + 1); 9701 ident = ident.substr(0, lastDot - start); 9702 this.index = peekIndex; 9703 break; 9704 } 9705 if (this.isWhitespace(ch)) { 9706 peekIndex++; 9707 } else { 9708 break; 9709 } 9710 } 9711 } 9712 9713 9714 var token = { 9715 index: start, 9716 text: ident 9717 }; 9718 9719 // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn 9720 if (OPERATORS.hasOwnProperty(ident)) { 9721 token.fn = OPERATORS[ident]; 9722 token.json = OPERATORS[ident]; 9723 } else { 9724 var getter = getterFn(ident, this.options, this.text); 9725 token.fn = extend(function(self, locals) { 9726 return (getter(self, locals)); 9727 }, { 9728 assign: function(self, value) { 9729 return setter(self, ident, value, parser.text, parser.options); 9730 } 9731 }); 9732 } 9733 9734 this.tokens.push(token); 9735 9736 if (methodName) { 9737 this.tokens.push({ 9738 index:lastDot, 9739 text: '.', 9740 json: false 9741 }); 9742 this.tokens.push({ 9743 index: lastDot + 1, 9744 text: methodName, 9745 json: false 9746 }); 9747 } 9748 }, 9749 9750 readString: function(quote) { 9751 var start = this.index; 9752 this.index++; 9753 var string = ''; 9754 var rawString = quote; 9755 var escape = false;
9756 while (this.index < this.text.length) { 9757 var ch = this.text.charAt(this.index); 9758 rawString += ch; 9759 if (escape) { 9760 if (ch === 'u') { 9761 var hex = this.text.substring(this.index + 1, this.index + 5); 9762 if (!hex.match(/[\da-f]{4}/i)) 9763 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 9764 this.index += 4; 9765 string += String.fromCharCode(parseInt(hex, 16)); 9766 } else { 9767 var rep = ESCAPE[ch]; 9768 if (rep) { 9769 string += rep; 9770 } else { 9771 string += ch; 9772 } 9773 } 9774 escape = false; 9775 } else if (ch === '\\') { 9776 escape = true; 9777 } else if (ch === quote) { 9778 this.index++; 9779 this.tokens.push({ 9780 index: start, 9781 text: rawString, 9782 string: string, 9783 json: true, 9784 fn: function() { return string; } 9785 }); 9786 return; 9787 } else { 9788 string += ch; 9789 } 9790 this.index++; 9791 } 9792 this.throwError('Unterminated quote', start); 9793 } 9794}; 9795 9796 9797/** 9798 * @constructor 9799 */ 9800var Parser = function (lexer, $filter, options) { 9801 this.lexer = lexer; 9802 this.$filter = $filter; 9803 this.options = options; 9804}; 9805 9806Parser.ZERO = function () { return 0; }; 9807 9808Parser.prototype = { 9809 constructor: Parser, 9810 9811 parse: function (text, json) { 9812 this.text = text; 9813 9814 //TODO(i): strip all the obsolte json stuff from this file 9815 this.json = json; 9816 9817 this.tokens = this.lexer.lex(text); 9818 9819 if (json) { 9820 // The extra level of aliasing is here, just in case the lexer misses something, so that 9821 // we prevent any accidental execution in JSON. 9822 this.assignment = this.logicalOR; 9823 9824 this.functionCall = 9825 this.fieldAccess = 9826 this.objectIndex = 9827 this.filterChain = function() { 9828 this.throwError('is not valid json', {text: text, index: 0}); 9829 }; 9830 } 9831 9832 var value = json ? this.primary() : this.statements(); 9833 9834 if (this.tokens.length !== 0) { 9835 this.throwError('is an unexpected token', this.tokens[0]); 9836 } 9837 9838 value.literal = !!value.literal; 9839 value.constant = !!value.constant; 9840 9841 return value; 9842 }, 9843 9844 primary: function () { 9845 var primary; 9846 if (this.expect('(')) { 9847 primary = this.filterChain(); 9848 this.consume(')'); 9849 } else if (this.expect('[')) { 9850 primary = this.arrayDeclaration(); 9851 } else if (this.expect('{')) { 9852 primary = this.object(); 9853 } else { 9854 var token = this.expect(); 9855 primary = token.fn; 9856 if (!primary) { 9857 this.throwError('not a primary expression', token); 9858 } 9859 if (token.json) { 9860 primary.constant = true; 9861 primary.literal = true; 9862 } 9863 } 9864 9865 var next, context; 9866 while ((next = this.expect('(', '[', '.'))) { 9867 if (next.text === '(') { 9868 primary = this.functionCall(primary, context); 9869 context = null; 9870 } else if (next.text === '[') { 9871 context = primary; 9872 primary = this.objectIndex(primary); 9873 } else if (next.text === '.') { 9874 context = primary; 9875 primary = this.fieldAccess(primary); 9876 } else { 9877 this.throwError('IMPOSSIBLE'); 9878 } 9879 } 9880 return primary; 9881 }, 9882 9883 throwError: function(msg, token) { 9884 throw $parseMinErr('syntax', 9885 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 9886 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 9887 }, 9888 9889 peekToken: function() { 9890 if (this.tokens.length === 0) 9891 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 9892 return this.tokens[0]; 9893 }, 9894 9895 peek: function(e1, e2, e3, e4) { 9896 if (this.tokens.length > 0) { 9897 var token = this.tokens[0]; 9898 var t = token.text; 9899 if (t === e1 || t === e2 || t === e3 || t === e4 || 9900 (!e1 && !e2 && !e3 && !e4)) { 9901 return token; 9902 } 9903 } 9904 return false; 9905 }, 9906 9907 expect: function(e1, e2, e3, e4){ 9908 var token = this.peek(e1, e2, e3, e4); 9909 if (token) { 9910 if (this.json && !token.json) { 9911 this.throwError('is not valid json', token); 9912 } 9913 this.tokens.shift(); 9914 return token; 9915 } 9916 return false;
9917 }, 9918 9919 consume: function(e1){ 9920 if (!this.expect(e1)) { 9921 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 9922 } 9923 }, 9924 9925 unaryFn: function(fn, right) { 9926 return extend(function(self, locals) { 9927 return fn(self, locals, right); 9928 }, { 9929 constant:right.constant 9930 }); 9931 }, 9932 9933 ternaryFn: function(left, middle, right){ 9934 return extend(function(self, locals){ 9935 return left(self, locals) ? middle(self, locals) : right(self, locals); 9936 }, { 9937 constant: left.constant && middle.constant && right.constant 9938 }); 9939 }, 9940 9941 binaryFn: function(left, fn, right) { 9942 return extend(function(self, locals) { 9943 return fn(self, locals, left, right); 9944 }, { 9945 constant:left.constant && right.constant 9946 }); 9947 }, 9948 9949 statements: function() { 9950 var statements = []; 9951 while (true) { 9952 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 9953 statements.push(this.filterChain()); 9954 if (!this.expect(';')) { 9955 // optimize for the common case where there is only one statement. 9956 // TODO(size): maybe we should not support multiple statements? 9957 return (statements.length === 1) 9958 ? statements[0] 9959 : function(self, locals) { 9960 var value; 9961 for (var i = 0; i < statements.length; i++) { 9962 var statement = statements[i]; 9963 if (statement) { 9964 value = statement(self, locals); 9965 } 9966 } 9967 return value; 9968 }; 9969 } 9970 } 9971 }, 9972 9973 filterChain: function() { 9974 var left = this.expression(); 9975 var token; 9976 while (true) { 9977 if ((token = this.expect('|'))) { 9978 left = this.binaryFn(left, token.fn, this.filter()); 9979 } else { 9980 return left; 9981 } 9982 } 9983 }, 9984 9985 filter: function() { 9986 var token = this.expect(); 9987 var fn = this.$filter(token.text); 9988 var argsFn = []; 9989 while (true) { 9990 if ((token = this.expect(':'))) { 9991 argsFn.push(this.expression()); 9992 } else { 9993 var fnInvoke = function(self, locals, input) { 9994 var args = [input]; 9995 for (var i = 0; i < argsFn.length; i++) { 9996 args.push(argsFn[i](self, locals)); 9997 } 9998 return fn.apply(self, args); 9999 }; 10000 return function() { 10001 return fnInvoke; 10002 }; 10003 } 10004 } 10005 }, 10006 10007 expression: function() { 10008 return this.assignment(); 10009 }, 10010 10011 assignment: function() { 10012 var left = this.ternary(); 10013 var right; 10014 var token; 10015 if ((token = this.expect('='))) { 10016 if (!left.assign) { 10017 this.throwError('implies assignment but [' + 10018 this.text.substring(0, token.index) + '] can not be assigned to', token); 10019 } 10020 right = this.ternary(); 10021 return function(scope, locals) { 10022 return left.assign(scope, right(scope, locals), locals); 10023 }; 10024 } 10025 return left; 10026 }, 10027 10028 ternary: function() { 10029 var left = this.logicalOR(); 10030 var middle; 10031 var token; 10032 if ((token = this.expect('?'))) { 10033 middle = this.ternary(); 10034 if ((token = this.expect(':'))) { 10035 return this.ternaryFn(left, middle, this.ternary()); 10036 } else { 10037 this.throwError('expected :', token); 10038 } 10039 } else { 10040 return left; 10041 } 10042 }, 10043 10044 logicalOR: function() { 10045 var left = this.logicalAND(); 10046 var token; 10047 while (true) { 10048 if ((token = this.expect('||'))) { 10049 left = this.binaryFn(left, token.fn, this.logicalAND()); 10050 } else { 10051 return left; 10052 } 10053 } 10054 }, 10055 10056 logicalAND: function() { 10057 var left = this.equality(); 10058 var token; 10059 if ((token = this.expect('&&'))) { 10060 left = this.binaryFn(left, token.fn, this.logicalAND()); 10061 } 10062 return left; 10063 }, 10064 10065 equality: function() { 10066 var left = this.relational(); 10067 var token; 10068 if ((token = this.expect('==','!=','===','!=='))) { 10069 left = this.binaryFn(left, token.fn, this.equality()); 10070 } 10071 return left; 10072 }, 10073 10074 relational: function() { 10075 var left = this.additive(); 10076 var token; 10077 if ((token = this.expect('<', '>', '<=', '>='))) { 10078 left = this.binaryFn(left, token.fn, this.relational()); 10079 } 10080 return left; 10081 }, 10082 10083 additive: function() { 10084 var left = this.multiplicative(); 10085 var token; 10086 while ((token = this.expect('+','-'))) { 10087 left = this.binaryFn(left, token.fn, this.multiplicative()); 10088 } 10089 return left; 10090 }, 10091 10092 multiplicative: function() { 10093 var left = this.unary(); 10094 var token; 10095 while ((token = this.expect('*','/','%'))) { 10096 left = this.binaryFn(left, token.fn, this.unary()); 10097 } 10098 return left; 10099 }, 10100 10101 unary: function() { 10102 var token; 10103 if (this.expect('+')) { 10104 return this.primary(); 10105 } else if ((token = this.expect('-'))) { 10106 return this.binaryFn(Parser.ZERO, token.fn, this.unary()); 10107 } else if ((token = this.expect('!'))) { 10108 return this.unaryFn(token.fn, this.unary()); 10109 } else { 10110 return this.primary(); 10111 } 10112 }, 10113 10114 fieldAccess: function(object) { 10115 var parser = this; 10116 var field = this.expect().text; 10117 var getter = getterFn(field, this.options, this.text); 10118 10119 return extend(function(scope, locals, self) { 10120 return getter(self || object(scope, locals)); 10121 }, {
10122 assign: function(scope, value, locals) { 10123 return setter(object(scope, locals), field, value, parser.text, parser.options); 10124 } 10125 }); 10126 }, 10127 10128 objectIndex: function(obj) { 10129 var parser = this; 10130 10131 var indexFn = this.expression(); 10132 this.consume(']'); 10133 10134 return extend(function(self, locals) { 10135 var o = obj(self, locals), 10136 i = indexFn(self, locals), 10137 v, p; 10138 10139 if (!o) return undefined; 10140 v = ensureSafeObject(o[i], parser.text); 10141 if (v && v.then && parser.options.unwrapPromises) { 10142 p = v; 10143 if (!('$$v' in v)) { 10144 p.$$v = undefined; 10145 p.then(function(val) { p.$$v = val; }); 10146 } 10147 v = v.$$v; 10148 } 10149 return v; 10150 }, { 10151 assign: function(self, value, locals) { 10152 var key = indexFn(self, locals); 10153 // prevent overwriting of Function.constructor which would break ensureSafeObject check 10154 var safe = ensureSafeObject(obj(self, locals), parser.text); 10155 return safe[key] = value; 10156 } 10157 }); 10158 }, 10159 10160 functionCall: function(fn, contextGetter) { 10161 var argsFn = []; 10162 if (this.peekToken().text !== ')') { 10163 do { 10164 argsFn.push(this.expression()); 10165 } while (this.expect(',')); 10166 } 10167 this.consume(')'); 10168 10169 var parser = this; 10170 10171 return function(scope, locals) { 10172 var args = []; 10173 var context = contextGetter ? contextGetter(scope, locals) : scope; 10174 10175 for (var i = 0; i < argsFn.length; i++) { 10176 args.push(argsFn[i](scope, locals)); 10177 } 10178 var fnPtr = fn(scope, locals, context) || noop; 10179 10180 ensureSafeObject(context, parser.text); 10181 ensureSafeObject(fnPtr, parser.text); 10182 10183 // IE stupidity! (IE doesn't have apply for some native functions) 10184 var v = fnPtr.apply 10185 ? fnPtr.apply(context, args) 10186 : fnPtr(args[0], args[1], args[2], args[3], args[4]); 10187 10188 return ensureSafeObject(v, parser.text); 10189 }; 10190 }, 10191 10192 // This is used with json array declaration 10193 arrayDeclaration: function () { 10194 var elementFns = []; 10195 var allConstant = true; 10196 if (this.peekToken().text !== ']') { 10197 do { 10198 var elementFn = this.expression(); 10199 elementFns.push(elementFn); 10200 if (!elementFn.constant) { 10201 allConstant = false; 10202 } 10203 } while (this.expect(',')); 10204 } 10205 this.consume(']'); 10206 10207 return extend(function(self, locals) { 10208 var array = []; 10209 for (var i = 0; i < elementFns.length; i++) { 10210 array.push(elementFns[i](self, locals)); 10211 } 10212 return array; 10213 }, { 10214 literal: true, 10215 constant: allConstant 10216 }); 10217 }, 10218 10219 object: function () { 10220 var keyValues = []; 10221 var allConstant = true; 10222 if (this.peekToken().text !== '}') { 10223 do { 10224 var token = this.expect(), 10225 key = token.string || token.text; 10226 this.consume(':'); 10227 var value = this.expression(); 10228 keyValues.push({key: key, value: value}); 10229 if (!value.constant) { 10230 allConstant = false; 10231 } 10232 } while (this.expect(',')); 10233 } 10234 this.consume('}'); 10235 10236 return extend(function(self, locals) { 10237 var object = {}; 10238 for (var i = 0; i < keyValues.length; i++) { 10239 var keyValue = keyValues[i]; 10240 object[keyValue.key] = keyValue.value(self, locals); 10241 } 10242 return object; 10243 }, { 10244 literal: true, 10245 constant: allConstant 10246 }); 10247 } 10248}; 10249 10250 10251////////////////////////////////////////////////// 10252// Parser helper functions 10253////////////////////////////////////////////////// 10254 10255function setter(obj, path, setValue, fullExp, options) { 10256 //needed? 10257 options = options || {}; 10258 10259 var element = path.split('.'), key; 10260 for (var i = 0; element.length > 1; i++) { 10261 key = ensureSafeMemberName(element.shift(), fullExp); 10262 var propertyObj = obj[key]; 10263 if (!propertyObj) { 10264 propertyObj = {}; 10265 obj[key] = propertyObj; 10266 } 10267 obj = propertyObj; 10268 if (obj.then && options.unwrapPromises) { 10269 promiseWarning(fullExp); 10270 if (!("$$v" in obj)) { 10271 (function(promise) { 10272 promise.then(function(val) { promise.$$v = val; }); } 10273 )(obj); 10274 } 10275 if (obj.$$v === undefined) { 10276 obj.$$v = {}; 10277 } 10278 obj = obj.$$v; 10279 } 10280 } 10281 key = ensureSafeMemberName(element.shift(), fullExp); 10282 obj[key] = setValue; 10283 return setValue; 10284} 10285 10286var getterFnCache = {}; 10287 10288/** 10289 * Implementation of the "Black Hole" variant from: 10290 * - http://jsperf.com/angularjs-parse-getter/4 10291 * - http://jsperf.com/path-evaluation-simplified/7 10292 */ 10293function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { 10294 ensureSafeMemberName(key0, fullExp); 10295 ensureSafeMemberName(key1, fullExp); 10296 ensureSafeMemberName(key2, fullExp); 10297 ensureSafeMemberName(key3, fullExp); 10298 ensureSafeMemberName(key4, fullExp); 10299 10300 return !options.unwrapPromises 10301 ? function cspSafeGetter(scope, locals) { 10302 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; 10303 10304 if (pathVal == null) return pathVal; 10305 pathVal = pathVal[key0]; 10306 10307 if (!key1) return pathVal; 10308 if (pathVal == null) return undefined; 10309 pathVal = pathVal[key1]; 10310 10311 if (!key2) return pathVal; 10312 if (pathVal == null) return undefined; 10313 pathVal = pathVal[key2]; 10314 10315 if (!key3) return pathVal; 10316 if (pathVal == null) return undefined; 10317 pathVal = pathVal[key3]; 10318 10319 if (!key4) return pathVal; 10320 if (pathVal == null) return undefined; 10321 pathVal = pathVal[key4]; 10322 10323 return pathVal; 10324 } 10325 : function cspSafePromiseEnabledGetter(scope, locals) { 10326 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, 10327 promise; 10328 10329 if (pathVal == null) return pathVal; 10330 10331 pathVal = pathVal[key0]; 10332 if (pathVal && pathVal.then) { 10333 promiseWarning(fullExp); 10334 if (!("$$v" in pathVal)) { 10335 promise = pathVal; 10336 promise.$$v = undefined; 10337 promise.then(function(val) { promise.$$v = val; }); 10338 } 10339 pathVal = pathVal.$$v; 10340 } 10341 10342 if (!key1) return pathVal; 10343 if (pathVal == null) return undefined; 10344 pathVal = pathVal[key1]; 10345 if (pathVal && pathVal.then) { 10346 promiseWarning(fullExp); 10347 if (!("$$v" in pathVal)) { 10348 promise = pathVal; 10349 promise.$$v = undefined; 10350 promise.then(function(val) { promise.$$v = val; }); 10351 } 10352 pathVal = pathVal.$$v; 10353 } 10354 10355 if (!key2) return pathVal; 10356 if (pathVal == null) return undefined; 10357 pathVal = pathVal[key2]; 10358 if (pathVal && pathVal.then) { 10359 promiseWarning(fullExp); 10360 if (!("$$v" in pathVal)) { 10361 promise = pathVal; 10362 promise.$$v = undefined; 10363 promise.then(function(val) { promise.$$v = val; }); 10364 } 10365 pathVal = pathVal.$$v; 10366 } 10367 10368 if (!key3) return pathVal; 10369 if (pathVal == null) return undefined; 10370 pathVal = pathVal[key3]; 10371 if (pathVal && pathVal.then) { 10372 promiseWarning(fullExp); 10373 if (!("$$v" in pathVal)) { 10374 promise = pathVal; 10375 promise.$$v = undefined; 10376 promise.then(function(val) { promise.$$v = val; }); 10377 } 10378 pathVal = pathVal.$$v; 10379 } 10380 10381 if (!key4) return pathVal; 10382 if (pathVal == null) return undefined; 10383 pathVal = pathVal[key4]; 10384 if (pathVal && pathVal.then) { 10385 promiseWarning(fullExp); 10386 if (!("$$v" in pathVal)) { 10387 promise = pathVal; 10388 promise.$$v = undefined; 10389 promise.then(function(val) { promise.$$v = val; }); 10390 } 10391 pathVal = pathVal.$$v; 10392 } 10393 return pathVal; 10394 }; 10395} 10396 10397function simpleGetterFn1(key0, fullExp) { 10398 ensureSafeMemberName(key0, fullExp); 10399 10400 return function simpleGetterFn1(scope, locals) { 10401 if (scope == null) return undefined; 10402 return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; 10403 }; 10404} 10405 10406function simpleGetterFn2(key0, key1, fullExp) { 10407 ensureSafeMemberName(key0, fullExp); 10408 ensureSafeMemberName(key1, fullExp); 10409 10410 return function simpleGetterFn2(scope, locals) { 10411 if (scope == null) return undefined; 10412 scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; 10413 return scope == null ? undefined : scope[key1]; 10414 }; 10415} 10416 10417function getterFn(path, options, fullExp) { 10418 // Check whether the cache has this getter already. 10419 // We can use hasOwnProperty directly on the cache because we ensure, 10420 // see below, that the cache never stores a path called 'hasOwnProperty' 10421 if (getterFnCache.hasOwnProperty(path)) { 10422 return getterFnCache[path]; 10423 } 10424 10425 var pathKeys = path.split('.'), 10426 pathKeysLength = pathKeys.length, 10427 fn; 10428 10429 // When we have only 1 or 2 tokens, use optimized special case closures. 10430 // http://jsperf.com/angularjs-parse-getter/6 10431 if (!options.unwrapPromises && pathKeysLength === 1) { 10432 fn = simpleGetterFn1(pathKeys[0], fullExp); 10433 } else if (!options.unwrapPromises && pathKeysLength === 2) { 10434 fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp); 10435 } else if (options.csp) { 10436 if (pathKeysLength < 6) { 10437 fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, 10438 options); 10439 } else { 10440 fn = function(scope, locals) { 10441 var i = 0, val; 10442 do { 10443 val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], 10444 pathKeys[i++], fullExp, options)(scope, locals); 10445 10446 locals = undefined; // clear after first iteration 10447 scope = val; 10448 } while (i < pathKeysLength); 10449 return val; 10450 }; 10451 } 10452 } else { 10453 var code = 'var p;\n';
10454 forEach(pathKeys, function(key, index) { 10455 ensureSafeMemberName(key, fullExp); 10456 code += 'if(s == null) return undefined;\n' + 10457 's='+ (index 10458 // we simply dereference 's' on any .dot notation 10459 ? 's' 10460 // but if we are first then we check locals first, and if so read it first 10461 : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + 10462 (options.unwrapPromises 10463 ? 'if (s && s.then) {\n' + 10464 ' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' + 10465 ' if (!("$$v" in s)) {\n' + 10466 ' p=s;\n' + 10467 ' p.$$v = undefined;\n' + 10468 ' p.then(function(v) {p.$$v=v;});\n' + 10469 '}\n' + 10470 ' s=s.$$v\n' + 10471 '}\n' 10472 : ''); 10473 }); 10474 code += 'return s;'; 10475 10476 /* jshint -W054 */ 10477 var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning 10478 /* jshint +W054 */ 10479 evaledFnGetter.toString = valueFn(code); 10480 fn = options.unwrapPromises ? function(scope, locals) { 10481 return evaledFnGetter(scope, locals, promiseWarning); 10482 } : evaledFnGetter; 10483 } 10484 10485 // Only cache the value if it's not going to mess up the cache object 10486 // This is more performant that using Object.prototype.hasOwnProperty.call 10487 if (path !== 'hasOwnProperty') { 10488 getterFnCache[path] = fn; 10489 } 10490 return fn; 10491} 10492 10493/////////////////////////////////// 10494 10495/** 10496 * @ngdoc function 10497 * @name ng.$parse 10498 * @function 10499 * 10500 * @description 10501 * 10502 * Converts Angular {@link guide/expression expression} into a function. 10503 * 10504 * <pre> 10505 * var getter = $parse('user.name'); 10506 * var setter = getter.assign; 10507 * var context = {user:{name:'angular'}}; 10508 * var locals = {user:{name:'local'}}; 10509 * 10510 * expect(getter(context)).toEqual('angular'); 10511 * setter(context, 'newValue'); 10512 * expect(context.user.name).toEqual('newValue'); 10513 * expect(getter(context, locals)).toEqual('local'); 10514 * </pre> 10515 * 10516 * 10517 * @param {string} expression String expression to compile. 10518 * @returns {function(context, locals)} a function which represents the compiled expression: 10519 * 10520 * * `context` â `{object}` â an object against which any expressions embedded in the strings 10521 * are evaluated against (typically a scope object). 10522 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 10523 * `context`. 10524 * 10525 * The returned function also has the following properties: 10526 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 10527 * literal. 10528 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 10529 * constant literals. 10530 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 10531 * set to a function to change its value on the given context. 10532 * 10533 */ 10534 10535 10536/** 10537 * @ngdoc object 10538 * @name ng.$parseProvider 10539 * @function 10540 * 10541 * @description 10542 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 10543 * service. 10544 */ 10545function $ParseProvider() { 10546 var cache = {}; 10547 10548 var $parseOptions = { 10549 csp: false, 10550 unwrapPromises: false, 10551 logPromiseWarnings: true 10552 }; 10553 10554 10555 /** 10556 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 10557 * 10558 * @ngdoc method 10559 * @name ng.$parseProvider#unwrapPromises 10560 * @methodOf ng.$parseProvider 10561 * @description 10562 * 10563 * **This feature is deprecated, see deprecation notes below for more info** 10564 * 10565 * If set to true (default is false), $parse will unwrap promises automatically when a promise is 10566 * found at any part of the expression. In other words, if set to true, the expression will always 10567 * result in a non-promise value. 10568 * 10569 * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, 10570 * the fulfillment value is used in place of the promise while evaluating the expression. 10571 * 10572 * **Deprecation notice** 10573 * 10574 * This is a feature that didn't prove to be wildly useful or popular, primarily because of the 10575 * dichotomy between data access in templates (accessed as raw values) and controller code 10576 * (accessed as promises). 10577 * 10578 * In most code we ended up resolving promises manually in controllers anyway and thus unifying 10579 * the model access there. 10580 * 10581 * Other downsides of automatic promise unwrapping: 10582 *
10583 * - when building components it's often desirable to receive the raw promises 10584 * - adds complexity and slows down expression evaluation 10585 * - makes expression code pre-generation unattractive due to the amount of code that needs to be 10586 * generated 10587 * - makes IDE auto-completion and tool support hard 10588 * 10589 * **Warning Logs** 10590 * 10591 * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a 10592 * promise (to reduce the noise, each expression is logged only once). To disable this logging use 10593 * `$parseProvider.logPromiseWarnings(false)` api. 10594 * 10595 * 10596 * @param {boolean=} value New value. 10597 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 10598 * setter. 10599 */ 10600 this.unwrapPromises = function(value) { 10601 if (isDefined(value)) { 10602 $parseOptions.unwrapPromises = !!value; 10603 return this; 10604 } else { 10605 return $parseOptions.unwrapPromises; 10606 } 10607 }; 10608 10609 10610 /** 10611 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 10612 * 10613 * @ngdoc method 10614 * @name ng.$parseProvider#logPromiseWarnings 10615 * @methodOf ng.$parseProvider 10616 * @description 10617 * 10618 * Controls whether Angular should log a warning on any encounter of a promise in an expression. 10619 * 10620 * The default is set to `true`. 10621 * 10622 * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. 10623 * 10624 * @param {boolean=} value New value. 10625 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 10626 * setter. 10627 */ 10628 this.logPromiseWarnings = function(value) { 10629 if (isDefined(value)) { 10630 $parseOptions.logPromiseWarnings = value; 10631 return this; 10632 } else { 10633 return $parseOptions.logPromiseWarnings; 10634 } 10635 }; 10636 10637 10638 this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { 10639 $parseOptions.csp = $sniffer.csp; 10640 10641 promiseWarning = function promiseWarningFn(fullExp) { 10642 if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; 10643 promiseWarningCache[fullExp] = true; 10644 $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + 10645 'Automatic unwrapping of promises in Angular expressions is deprecated.'); 10646 }; 10647 10648 return function(exp) { 10649 var parsedExpression; 10650 10651 switch (typeof exp) { 10652 case 'string': 10653 10654 if (cache.hasOwnProperty(exp)) { 10655 return cache[exp]; 10656 } 10657 10658 var lexer = new Lexer($parseOptions); 10659 var parser = new Parser(lexer, $filter, $parseOptions); 10660 parsedExpression = parser.parse(exp, false); 10661 10662 if (exp !== 'hasOwnProperty') { 10663 // Only cache the value if it's not going to mess up the cache object 10664 // This is more performant that using Object.prototype.hasOwnProperty.call 10665 cache[exp] = parsedExpression; 10666 } 10667 10668 return parsedExpression; 10669 10670 case 'function': 10671 return exp; 10672 10673 default: 10674 return noop; 10675 } 10676 }; 10677 }]; 10678} 10679 10680/** 10681 * @ngdoc service 10682 * @name ng.$q 10683 * @requires $rootScope 10684 * 10685 * @description 10686 * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). 10687 * 10688 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 10689 * interface for interacting with an object that represents the result of an action that is 10690 * performed asynchronously, and may or may not be finished at any given point in time. 10691 * 10692 * From the perspective of dealing with error handling, deferred and promise APIs are to 10693 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 10694 * 10695 * <pre> 10696 * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` 10697 * // are available in the current lexical scope (they could have been injected or passed in). 10698 * 10699 * function asyncGreet(name) { 10700 * var deferred = $q.defer(); 10701 * 10702 * setTimeout(function() { 10703 * // since this fn executes async in a future turn of the event loop, we need to wrap 10704 * // our code into an $apply call so that the model changes are properly observed. 10705 * scope.$apply(function() { 10706 * deferred.notify('About to greet ' + name + '.'); 10707 * 10708 * if (okToGreet(name)) { 10709 * deferred.resolve('Hello, ' + name + '!'); 10710 * } else { 10711 * deferred.reject('Greeting ' + name + ' is not allowed.'); 10712 * } 10713 * }); 10714 * }, 1000); 10715 * 10716 * return deferred.promise; 10717 * } 10718 * 10719 * var promise = asyncGreet('Robin Hood'); 10720 * promise.then(function(greeting) { 10721 * alert('Success: ' + greeting); 10722 * }, function(reason) { 10723 * alert('Failed: ' + reason); 10724 * }, function(update) { 10725 * alert('Got notification: ' + update); 10726 * }); 10727 * </pre> 10728 * 10729 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 10730 * comes in the way of guarantees that promise and deferred APIs make, see 10731 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 10732 *
10733 * Additionally the promise api allows for composition that is very hard to do with the 10734 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 10735 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 10736 * section on serial or parallel joining of promises. 10737 * 10738 * 10739 * # The Deferred API 10740 * 10741 * A new instance of deferred is constructed by calling `$q.defer()`. 10742 * 10743 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 10744 * that can be used for signaling the successful or unsuccessful completion, as well as the status 10745 * of the task. 10746 * 10747 * **Methods** 10748 * 10749 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 10750 * constructed via `$q.reject`, the promise will be rejected instead. 10751 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 10752 * resolving it with a rejection constructed via `$q.reject`. 10753 * - `notify(value)` - provides updates on the status of the promises execution. This may be called 10754 * multiple times before the promise is either resolved or rejected. 10755 * 10756 * **Properties** 10757 * 10758 * - promise â `{Promise}` â promise object associated with this deferred. 10759 * 10760 * 10761 * # The Promise API 10762 * 10763 * A new promise instance is created when a deferred instance is created and can be retrieved by 10764 * calling `deferred.promise`. 10765 * 10766 * The purpose of the promise object is to allow for interested parties to get access to the result 10767 * of the deferred task when it completes. 10768 * 10769 * **Methods** 10770 * 10771 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 10772 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 10773 * as soon as the result is available. The callbacks are called with a single argument: the result 10774 * or rejection reason. Additionally, the notify callback may be called zero or more times to 10775 * provide a progress indication, before the promise is resolved or rejected. 10776 * 10777 * This method *returns a new promise* which is resolved or rejected via the return value of the 10778 * `successCallback`, `errorCallback`. It also notifies via the return value of the 10779 * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback 10780 * method. 10781 * 10782 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 10783 * 10784 * - `finally(callback)` â allows you to observe either the fulfillment or rejection of a promise, 10785 * but to do so without modifying the final value. This is useful to release resources or do some 10786 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 10787 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 10788 * more information. 10789 * 10790 * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as 10791 * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to 10792 * make your code IE8 compatible. 10793 * 10794 * # Chaining promises 10795 * 10796 * Because calling the `then` method of a promise returns a new derived promise, it is easily 10797 * possible to create a chain of promises: 10798 * 10799 * <pre> 10800 * promiseB = promiseA.then(function(result) { 10801 * return result + 1; 10802 * }); 10803 * 10804 * // promiseB will be resolved immediately after promiseA is resolved and its value 10805 * // will be the result of promiseA incremented by 1 10806 * </pre> 10807 * 10808 * It is possible to create chains of any length and since a promise can be resolved with another 10809 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 10810 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 10811 * $http's response interceptors. 10812 * 10813 * 10814 * # Differences between Kris Kowal's Q and $q 10815 * 10816 * There are two main differences: 10817 * 10818 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 10819 * mechanism in angular, which means faster propagation of resolution or rejection into your 10820 * models and avoiding unnecessary browser repaints, which would result in flickering UI.
10821 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains 10822 * all the important functionality needed for common async tasks. 10823 * 10824 * # Testing 10825 * 10826 * <pre> 10827 * it('should simulate promise', inject(function($q, $rootScope) { 10828 * var deferred = $q.defer(); 10829 * var promise = deferred.promise; 10830 * var resolvedValue; 10831 * 10832 * promise.then(function(value) { resolvedValue = value; }); 10833 * expect(resolvedValue).toBeUndefined(); 10834 * 10835 * // Simulate resolving of promise 10836 * deferred.resolve(123); 10837 * // Note that the 'then' function does not get called synchronously. 10838 * // This is because we want the promise API to always be async, whether or not 10839 * // it got called synchronously or asynchronously. 10840 * expect(resolvedValue).toBeUndefined(); 10841 * 10842 * // Propagate promise resolution to 'then' functions using $apply(). 10843 * $rootScope.$apply(); 10844 * expect(resolvedValue).toEqual(123); 10845 * })); 10846 * </pre> 10847 */ 10848function $QProvider() { 10849 10850 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 10851 return qFactory(function(callback) { 10852 $rootScope.$evalAsync(callback); 10853 }, $exceptionHandler); 10854 }]; 10855} 10856 10857 10858/** 10859 * Constructs a promise manager. 10860 * 10861 * @param {function(function)} nextTick Function for executing functions in the next turn. 10862 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 10863 * debugging purposes. 10864 * @returns {object} Promise manager. 10865 */ 10866function qFactory(nextTick, exceptionHandler) { 10867 10868 /** 10869 * @ngdoc 10870 * @name ng.$q#defer 10871 * @methodOf ng.$q 10872 * @description 10873 * Creates a `Deferred` object which represents a task which will finish in the future. 10874 * 10875 * @returns {Deferred} Returns a new instance of deferred. 10876 */ 10877 var defer = function() { 10878 var pending = [], 10879 value, deferred; 10880 10881 deferred = { 10882 10883 resolve: function(val) { 10884 if (pending) { 10885 var callbacks = pending; 10886 pending = undefined; 10887 value = ref(val); 10888 10889 if (callbacks.length) { 10890 nextTick(function() { 10891 var callback; 10892 for (var i = 0, ii = callbacks.length; i < ii; i++) { 10893 callback = callbacks[i]; 10894 value.then(callback[0], callback[1], callback[2]); 10895 } 10896 }); 10897 } 10898 } 10899 }, 10900 10901 10902 reject: function(reason) { 10903 deferred.resolve(reject(reason)); 10904 }, 10905 10906 10907 notify: function(progress) { 10908 if (pending) { 10909 var callbacks = pending; 10910 10911 if (pending.length) { 10912 nextTick(function() { 10913 var callback; 10914 for (var i = 0, ii = callbacks.length; i < ii; i++) { 10915 callback = callbacks[i]; 10916 callback[2](progress); 10917 } 10918 }); 10919 } 10920 } 10921 }, 10922 10923 10924 promise: { 10925 then: function(callback, errback, progressback) { 10926 var result = defer(); 10927 10928 var wrappedCallback = function(value) { 10929 try { 10930 result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); 10931 } catch(e) { 10932 result.reject(e); 10933 exceptionHandler(e); 10934 } 10935 }; 10936 10937 var wrappedErrback = function(reason) { 10938 try { 10939 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 10940 } catch(e) { 10941 result.reject(e); 10942 exceptionHandler(e); 10943 } 10944 }; 10945 10946 var wrappedProgressback = function(progress) { 10947 try { 10948 result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); 10949 } catch(e) { 10950 exceptionHandler(e); 10951 } 10952 }; 10953 10954 if (pending) { 10955 pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); 10956 } else { 10957 value.then(wrappedCallback, wrappedErrback, wrappedProgressback); 10958 } 10959 10960 return result.promise; 10961 }, 10962 10963 "catch": function(callback) { 10964 return this.then(null, callback); 10965 }, 10966 10967 "finally": function(callback) { 10968 10969 function makePromise(value, resolved) { 10970 var result = defer(); 10971 if (resolved) { 10972 result.resolve(value); 10973 } else { 10974 result.reject(value); 10975 } 10976 return result.promise; 10977 } 10978 10979 function handleCallback(value, isResolved) { 10980 var callbackOutput = null; 10981 try { 10982 callbackOutput = (callback ||defaultCallback)(); 10983 } catch(e) { 10984 return makePromise(e, false); 10985 } 10986 if (callbackOutput && isFunction(callbackOutput.then)) { 10987 return callbackOutput.then(function() { 10988 return makePromise(value, isResolved); 10989 }, function(error) { 10990 return makePromise(error, false); 10991 }); 10992 } else { 10993 return makePromise(value, isResolved); 10994 } 10995 } 10996 10997 return this.then(function(value) { 10998 return handleCallback(value, true); 10999 }, function(error) { 11000 return handleCallback(error, false); 11001 }); 11002 } 11003 } 11004 }; 11005 11006 return deferred; 11007 }; 11008 11009 11010 var ref = function(value) { 11011 if (value && isFunction(value.then)) return value; 11012 return { 11013 then: function(callback) { 11014 var result = defer(); 11015 nextTick(function() { 11016 result.resolve(callback(value)); 11017 }); 11018 return result.promise; 11019 } 11020 }; 11021 }; 11022 11023 11024 /** 11025 * @ngdoc 11026 * @name ng.$q#reject 11027 * @methodOf ng.$q 11028 * @description 11029 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 11030 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 11031 * a promise chain, you don't need to worry about it. 11032 * 11033 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 11034 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 11035 * a promise error callback and you want to forward the error to the promise derived from the 11036 * current promise, you have to "rethrow" the error by returning a rejection constructed via 11037 * `reject`. 11038 * 11039 * <pre> 11040 * promiseB = promiseA.then(function(result) { 11041 * // success: do something and resolve promiseB 11042 * // with the old or a new result 11043 * return result; 11044 * }, function(reason) { 11045 * // error: handle the error if possible and
11046 * // resolve promiseB with newPromiseOrValue, 11047 * // otherwise forward the rejection to promiseB 11048 * if (canHandle(reason)) { 11049 * // handle the error and recover 11050 * return newPromiseOrValue; 11051 * } 11052 * return $q.reject(reason); 11053 * }); 11054 * </pre> 11055 * 11056 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 11057 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 11058 */ 11059 var reject = function(reason) { 11060 return { 11061 then: function(callback, errback) { 11062 var result = defer(); 11063 nextTick(function() { 11064 try { 11065 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11066 } catch(e) { 11067 result.reject(e); 11068 exceptionHandler(e); 11069 } 11070 }); 11071 return result.promise; 11072 } 11073 }; 11074 }; 11075 11076 11077 /** 11078 * @ngdoc 11079 * @name ng.$q#when 11080 * @methodOf ng.$q 11081 * @description 11082 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 11083 * This is useful when you are dealing with an object that might or might not be a promise, or if 11084 * the promise comes from a source that can't be trusted. 11085 * 11086 * @param {*} value Value or a promise 11087 * @returns {Promise} Returns a promise of the passed value or promise 11088 */ 11089 var when = function(value, callback, errback, progressback) { 11090 var result = defer(), 11091 done; 11092 11093 var wrappedCallback = function(value) { 11094 try { 11095 return (isFunction(callback) ? callback : defaultCallback)(value); 11096 } catch (e) { 11097 exceptionHandler(e); 11098 return reject(e); 11099 } 11100 }; 11101 11102 var wrappedErrback = function(reason) { 11103 try { 11104 return (isFunction(errback) ? errback : defaultErrback)(reason); 11105 } catch (e) { 11106 exceptionHandler(e); 11107 return reject(e); 11108 } 11109 }; 11110 11111 var wrappedProgressback = function(progress) { 11112 try { 11113 return (isFunction(progressback) ? progressback : defaultCallback)(progress); 11114 } catch (e) { 11115 exceptionHandler(e); 11116 } 11117 }; 11118 11119 nextTick(function() { 11120 ref(value).then(function(value) { 11121 if (done) return; 11122 done = true; 11123 result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); 11124 }, function(reason) { 11125 if (done) return; 11126 done = true; 11127 result.resolve(wrappedErrback(reason)); 11128 }, function(progress) { 11129 if (done) return; 11130 result.notify(wrappedProgressback(progress)); 11131 }); 11132 }); 11133 11134 return result.promise; 11135 }; 11136 11137 11138 function defaultCallback(value) { 11139 return value; 11140 } 11141 11142 11143 function defaultErrback(reason) { 11144 return reject(reason); 11145 } 11146 11147 11148 /** 11149 * @ngdoc 11150 * @name ng.$q#all 11151 * @methodOf ng.$q 11152 * @description 11153 * Combines multiple promises into a single promise that is resolved when all of the input 11154 * promises are resolved. 11155 * 11156 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 11157 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 11158 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 11159 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 11160 * with the same rejection value. 11161 */ 11162 function all(promises) { 11163 var deferred = defer(), 11164 counter = 0, 11165 results = isArray(promises) ? [] : {}; 11166 11167 forEach(promises, function(promise, key) { 11168 counter++; 11169 ref(promise).then(function(value) { 11170 if (results.hasOwnProperty(key)) return; 11171 results[key] = value; 11172 if (!(--counter)) deferred.resolve(results); 11173 }, function(reason) { 11174 if (results.hasOwnProperty(key)) return; 11175 deferred.reject(reason); 11176 }); 11177 }); 11178 11179 if (counter === 0) { 11180 deferred.resolve(results); 11181 } 11182 11183 return deferred.promise; 11184 } 11185 11186 return { 11187 defer: defer, 11188 reject: reject, 11189 when: when, 11190 all: all 11191 }; 11192} 11193 11194/** 11195 * DESIGN NOTES 11196 * 11197 * The design decisions behind the scope are heavily favored for speed and memory consumption. 11198 * 11199 * The typical use of scope is to watch the expressions, which most of the time return the same 11200 * value as last time so we optimize the operation. 11201 * 11202 * Closures construction is expensive in terms of speed as well as memory: 11203 * - No closures, instead use prototypical inheritance for API 11204 * - Internal state needs to be stored on scope directly, which means that private state is 11205 * exposed as $$____ properties 11206 * 11207 * Loop operations are optimized by using while(count--) { ... } 11208 * - this means that in order to keep the same order of execution as addition we have to add 11209 * items to the array at the beginning (shift) instead of at the end (push) 11210 * 11211 * Child scopes are created and removed often 11212 * - Using an array would be slow since inserts in middle are expensive so we use linked list 11213 *
11214 * There are few watches then a lot of observers. This is why you don't want the observer to be 11215 * implemented in the same way as watch. Watch requires return of initialization function which 11216 * are expensive to construct. 11217 */ 11218 11219 11220/** 11221 * @ngdoc object 11222 * @name ng.$rootScopeProvider 11223 * @description 11224 * 11225 * Provider for the $rootScope service. 11226 */ 11227 11228/** 11229 * @ngdoc function 11230 * @name ng.$rootScopeProvider#digestTtl 11231 * @methodOf ng.$rootScopeProvider 11232 * @description 11233 * 11234 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 11235 * assuming that the model is unstable. 11236 * 11237 * The current default is 10 iterations. 11238 * 11239 * In complex applications it's possible that the dependencies between `$watch`s will result in 11240 * several digest iterations. However if an application needs more than the default 10 digest 11241 * iterations for its model to stabilize then you should investigate what is causing the model to 11242 * continuously change during the digest. 11243 * 11244 * Increasing the TTL could have performance implications, so you should not change it without 11245 * proper justification. 11246 * 11247 * @param {number} limit The number of digest iterations. 11248 */ 11249 11250 11251/** 11252 * @ngdoc object 11253 * @name ng.$rootScope 11254 * @description 11255 * 11256 * Every application has a single root {@link ng.$rootScope.Scope scope}. 11257 * All other scopes are descendant scopes of the root scope. Scopes provide separation 11258 * between the model and the view, via a mechanism for watching the model for changes. 11259 * They also provide an event emission/broadcast and subscription facility. See the 11260 * {@link guide/scope developer guide on scopes}. 11261 */ 11262function $RootScopeProvider(){ 11263 var TTL = 10; 11264 var $rootScopeMinErr = minErr('$rootScope'); 11265 var lastDirtyWatch = null; 11266 11267 this.digestTtl = function(value) { 11268 if (arguments.length) { 11269 TTL = value; 11270 } 11271 return TTL; 11272 }; 11273 11274 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 11275 function( $injector, $exceptionHandler, $parse, $browser) { 11276 11277 /** 11278 * @ngdoc function 11279 * @name ng.$rootScope.Scope 11280 * 11281 * @description 11282 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 11283 * {@link AUTO.$injector $injector}. Child scopes are created using the 11284 * {@link ng.$rootScope.Scope#methods_$new $new()} method. (Most scopes are created automatically when 11285 * compiled HTML template is executed.) 11286 * 11287 * Here is a simple scope snippet to show how you can interact with the scope. 11288 * <pre> 11289 * <file src="./test/ng/rootScopeSpec.js" tag="docs1" /> 11290 * </pre> 11291 * 11292 * # Inheritance 11293 * A scope can inherit from a parent scope, as in this example: 11294 * <pre> 11295 var parent = $rootScope; 11296 var child = parent.$new(); 11297 11298 parent.salutation = "Hello"; 11299 child.name = "World"; 11300 expect(child.salutation).toEqual('Hello'); 11301 11302 child.salutation = "Welcome"; 11303 expect(child.salutation).toEqual('Welcome'); 11304 expect(parent.salutation).toEqual('Hello'); 11305 * </pre> 11306 * 11307 * 11308 * @param {Object.<string, function()>=} providers Map of service factory which need to be 11309 * provided for the current scope. Defaults to {@link ng}. 11310 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 11311 * append/override services provided by `providers`. This is handy 11312 * when unit-testing and having the need to override a default 11313 * service. 11314 * @returns {Object} Newly created scope. 11315 * 11316 */ 11317 function Scope() { 11318 this.$id = nextUid(); 11319 this.$$phase = this.$parent = this.$$watchers = 11320 this.$$nextSibling = this.$$prevSibling = 11321 this.$$childHead = this.$$childTail = null; 11322 this['this'] = this.$root = this; 11323 this.$$destroyed = false; 11324 this.$$asyncQueue = []; 11325 this.$$postDigestQueue = []; 11326 this.$$listeners = {}; 11327 this.$$listenerCount = {}; 11328 this.$$isolateBindings = {}; 11329 } 11330 11331 /** 11332 * @ngdoc property 11333 * @name ng.$rootScope.Scope#$id 11334 * @propertyOf ng.$rootScope.Scope 11335 * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for 11336 * debugging. 11337 */ 11338 11339 11340 Scope.prototype = { 11341 constructor: Scope, 11342 /** 11343 * @ngdoc function 11344 * @name ng.$rootScope.Scope#$new 11345 * @methodOf ng.$rootScope.Scope 11346 * @function 11347 *
11348 * @description 11349 * Creates a new child {@link ng.$rootScope.Scope scope}. 11350 * 11351 * The parent scope will propagate the {@link ng.$rootScope.Scope#methods_$digest $digest()} and 11352 * {@link ng.$rootScope.Scope#methods_$digest $digest()} events. The scope can be removed from the 11353 * scope hierarchy using {@link ng.$rootScope.Scope#methods_$destroy $destroy()}. 11354 * 11355 * {@link ng.$rootScope.Scope#methods_$destroy $destroy()} must be called on a scope when it is 11356 * desired for the scope and its child scopes to be permanently detached from the parent and 11357 * thus stop participating in model change detection and listener notification by invoking. 11358 * 11359 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 11360 * parent scope. The scope is isolated, as it can not see parent scope properties. 11361 * When creating widgets, it is useful for the widget to not accidentally read parent 11362 * state. 11363 * 11364 * @returns {Object} The newly created child scope. 11365 * 11366 */ 11367 $new: function(isolate) { 11368 var ChildScope, 11369 child; 11370 11371 if (isolate) { 11372 child = new Scope(); 11373 child.$root = this.$root; 11374 // ensure that there is just one async queue per $rootScope and its children 11375 child.$$asyncQueue = this.$$asyncQueue; 11376 child.$$postDigestQueue = this.$$postDigestQueue; 11377 } else { 11378 ChildScope = function() {}; // should be anonymous; This is so that when the minifier munges 11379 // the name it does not become random set of chars. This will then show up as class 11380 // name in the web inspector. 11381 ChildScope.prototype = this; 11382 child = new ChildScope(); 11383 child.$id = nextUid(); 11384 } 11385 child['this'] = child; 11386 child.$$listeners = {}; 11387 child.$$listenerCount = {}; 11388 child.$parent = this; 11389 child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null; 11390 child.$$prevSibling = this.$$childTail; 11391 if (this.$$childHead) { 11392 this.$$childTail.$$nextSibling = child; 11393 this.$$childTail = child; 11394 } else { 11395 this.$$childHead = this.$$childTail = child; 11396 } 11397 return child; 11398 }, 11399 11400 /** 11401 * @ngdoc function 11402 * @name ng.$rootScope.Scope#$watch 11403 * @methodOf ng.$rootScope.Scope 11404 * @function 11405 * 11406 * @description 11407 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 11408 * 11409 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#methods_$digest 11410 * $digest()} and should return the value that will be watched. (Since 11411 * {@link ng.$rootScope.Scope#methods_$digest $digest()} reruns when it detects changes the 11412 * `watchExpression` can execute multiple times per 11413 * {@link ng.$rootScope.Scope#methods_$digest $digest()} and should be idempotent.) 11414 * - The `listener` is called only when the value from the current `watchExpression` and the 11415 * previous call to `watchExpression` are not equal (with the exception of the initial run, 11416 * see below). The inequality is determined according to 11417 * {@link angular.equals} function. To save the value of the object for later comparison, 11418 * the {@link angular.copy} function is used. It also means that watching complex options 11419 * will have adverse memory and performance implications. 11420 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 11421 * This is achieved by rerunning the watchers until no changes are detected. The rerun 11422 * iteration limit is 10 to prevent an infinite loop deadlock. 11423 * 11424 * 11425 * If you want to be notified whenever {@link ng.$rootScope.Scope#methods_$digest $digest} is called, 11426 * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` 11427 * can execute multiple times per {@link ng.$rootScope.Scope#methods_$digest $digest} cycle when a 11428 * change is detected, be prepared for multiple calls to your listener.) 11429 * 11430 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 11431 * (via {@link ng.$rootScope.Scope#methods_$evalAsync $evalAsync}) to initialize the 11432 * watcher. In rare cases, this is undesirable because the listener is called when the result 11433 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 11434 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 11435 * listener was called due to initialization. 11436 * 11437 * The example below contains an illustration of using a function as your $watch listener 11438 * 11439 * 11440 * # Example 11441 * <pre> 11442 // let's assume that scope was dependency injected as the $rootScope 11443 var scope = $rootScope; 11444 scope.name = 'misko'; 11445 scope.counter = 0; 11446 11447 expect(scope.counter).toEqual(0); 11448 scope.$watch('name', function(newValue, oldValue) { 11449 scope.counter = scope.counter + 1; 11450 }); 11451 expect(scope.counter).toEqual(0); 11452 11453 scope.$digest(); 11454 // no variable change 11455 expect(scope.counter).toEqual(0); 11456 11457 scope.name = 'adam'; 11458 scope.$digest(); 11459 expect(scope.counter).toEqual(1); 11460 11461 11462 11463 // Using a listener function 11464 var food; 11465 scope.foodCounter = 0; 11466 expect(scope.foodCounter).toEqual(0); 11467 scope.$watch( 11468 // This is the listener function 11469 function() { return food; }, 11470 // This is the change handler 11471 function(newValue, oldValue) { 11472 if ( newValue !== oldValue ) { 11473 // Only increment the counter if the value changed 11474 scope.foodCounter = scope.foodCounter + 1; 11475 } 11476 } 11477 ); 11478 // No digest has been run so the counter will be zero 11479 expect(scope.foodCounter).toEqual(0); 11480 11481 // Run the digest but since food has not changed count will still be zero 11482 scope.$digest(); 11483 expect(scope.foodCounter).toEqual(0); 11484 11485 // Update food and run digest. Now the counter will increment 11486 food = 'cheeseburger'; 11487 scope.$digest(); 11488 expect(scope.foodCounter).toEqual(1); 11489 11490 * </pre> 11491 * 11492 * 11493 * 11494 * @param {(function()|string)} watchExpression Expression that is evaluated on each 11495 * {@link ng.$rootScope.Scope#methods_$digest $digest} cycle. A change in the return value triggers 11496 * a call to the `listener`. 11497 * 11498 * - `string`: Evaluated as {@link guide/expression expression} 11499 * - `function(scope)`: called with current `scope` as a parameter. 11500 * @param {(function()|string)=} listener Callback called whenever the return value of 11501 * the `watchExpression` changes. 11502 * 11503 * - `string`: Evaluated as {@link guide/expression expression} 11504 * - `function(newValue, oldValue, scope)`: called with current and previous values as 11505 * parameters. 11506 * 11507 * @param {boolean=} objectEquality Compare object for equality rather than for reference. 11508 * @returns {function()} Returns a deregistration function for this listener. 11509 */ 11510 $watch: function(watchExp, listener, objectEquality) { 11511 var scope = this, 11512 get = compileToFn(watchExp, 'watch'), 11513 array = scope.$$watchers, 11514 watcher = { 11515 fn: listener, 11516 last: initWatchVal, 11517 get: get, 11518 exp: watchExp, 11519 eq: !!objectEquality 11520 }; 11521 11522 lastDirtyWatch = null; 11523 11524 // in the case user pass string, we need to compile it, do we really need this ? 11525 if (!isFunction(listener)) { 11526 var listenFn = compileToFn(listener || noop, 'listener'); 11527 watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; 11528 } 11529 11530 if (typeof watchExp == 'string' && get.constant) { 11531 var originalFn = watcher.fn; 11532 watcher.fn = function(newVal, oldVal, scope) { 11533 originalFn.call(this, newVal, oldVal, scope); 11534 arrayRemove(array, watcher); 11535 }; 11536 } 11537 11538 if (!array) { 11539 array = scope.$$watchers = []; 11540 } 11541 // we use unshift since we use a while loop in $digest for speed. 11542 // the while loop reads in reverse order. 11543 array.unshift(watcher); 11544 11545 return function() { 11546 arrayRemove(array, watcher); 11547 lastDirtyWatch = null; 11548 }; 11549 }, 11550 11551 11552 /** 11553 * @ngdoc function 11554 * @name ng.$rootScope.Scope#$watchCollection 11555 * @methodOf ng.$rootScope.Scope 11556 * @function 11557 *
11558 * @description 11559 * Shallow watches the properties of an object and fires whenever any of the properties change 11560 * (for arrays, this implies watching the array items; for object maps, this implies watching 11561 * the properties). If a change is detected, the `listener` callback is fired. 11562 * 11563 * - The `obj` collection is observed via standard $watch operation and is examined on every 11564 * call to $digest() to see if any items have been added, removed, or moved. 11565 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 11566 * adding, removing, and moving items belonging to an object or array. 11567 * 11568 * 11569 * # Example 11570 * <pre> 11571 $scope.names = ['igor', 'matias', 'misko', 'james']; 11572 $scope.dataCount = 4; 11573 11574 $scope.$watchCollection('names', function(newNames, oldNames) { 11575 $scope.dataCount = newNames.length; 11576 }); 11577 11578 expect($scope.dataCount).toEqual(4); 11579 $scope.$digest(); 11580 11581 //still at 4 ... no changes 11582 expect($scope.dataCount).toEqual(4); 11583 11584 $scope.names.pop(); 11585 $scope.$digest(); 11586 11587 //now there's been a change 11588 expect($scope.dataCount).toEqual(3); 11589 * </pre> 11590 * 11591 * 11592 * @param {string|Function(scope)} obj Evaluated as {@link guide/expression expression}. The 11593 * expression value should evaluate to an object or an array which is observed on each 11594 * {@link ng.$rootScope.Scope#methods_$digest $digest} cycle. Any shallow change within the 11595 * collection will trigger a call to the `listener`. 11596 * 11597 * @param {function(newCollection, oldCollection, scope)} listener a callback function that is 11598 * fired with both the `newCollection` and `oldCollection` as parameters. 11599 * The `newCollection` object is the newly modified data obtained from the `obj` expression 11600 * and the `oldCollection` object is a copy of the former collection data. 11601 * The `scope` refers to the current scope. 11602 * 11603 * @returns {function()} Returns a de-registration function for this listener. When the 11604 * de-registration function is executed, the internal watch operation is terminated. 11605 */ 11606 $watchCollection: function(obj, listener) { 11607 var self = this; 11608 var oldValue; 11609 var newValue; 11610 var changeDetected = 0; 11611 var objGetter = $parse(obj); 11612 var internalArray = []; 11613 var internalObject = {}; 11614 var oldLength = 0; 11615 11616 function $watchCollectionWatch() { 11617 newValue = objGetter(self); 11618 var newLength, key; 11619 11620 if (!isObject(newValue)) { 11621 if (oldValue !== newValue) { 11622 oldValue = newValue; 11623 changeDetected++; 11624 } 11625 } else if (isArrayLike(newValue)) { 11626 if (oldValue !== internalArray) { 11627 // we are transitioning from something which was not an array into array. 11628 oldValue = internalArray; 11629 oldLength = oldValue.length = 0; 11630 changeDetected++; 11631 } 11632 11633 newLength = newValue.length; 11634 11635 if (oldLength !== newLength) { 11636 // if lengths do not match we need to trigger change notification 11637 changeDetected++; 11638 oldValue.length = oldLength = newLength; 11639 } 11640 // copy the items to oldValue and look for changes. 11641 for (var i = 0; i < newLength; i++) { 11642 if (oldValue[i] !== newValue[i]) { 11643 changeDetected++; 11644 oldValue[i] = newValue[i]; 11645 } 11646 } 11647 } else { 11648 if (oldValue !== internalObject) { 11649 // we are transitioning from something which was not an object into object. 11650 oldValue = internalObject = {}; 11651 oldLength = 0; 11652 changeDetected++; 11653 } 11654 // copy the items to oldValue and look for changes. 11655 newLength = 0; 11656 for (key in newValue) { 11657 if (newValue.hasOwnProperty(key)) { 11658 newLength++; 11659 if (oldValue.hasOwnProperty(key)) { 11660 if (oldValue[key] !== newValue[key]) { 11661 changeDetected++; 11662 oldValue[key] = newValue[key]; 11663 } 11664 } else { 11665 oldLength++; 11666 oldValue[key] = newValue[key]; 11667 changeDetected++; 11668 } 11669 } 11670 } 11671 if (oldLength > newLength) { 11672 // we used to have more keys, need to find them and destroy them. 11673 changeDetected++; 11674 for(key in oldValue) { 11675 if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { 11676 oldLength--; 11677 delete oldValue[key]; 11678 } 11679 } 11680 } 11681 } 11682 return changeDetected; 11683 } 11684 11685 function $watchCollectionAction() { 11686 listener(newValue, oldValue, self); 11687 } 11688 11689 return this.$watch($watchCollectionWatch, $watchCollectionAction); 11690 }, 11691 11692 /** 11693 * @ngdoc function 11694 * @name ng.$rootScope.Scope#$digest 11695 * @methodOf ng.$rootScope.Scope 11696 * @function 11697 *
11698 * @description 11699 * Processes all of the {@link ng.$rootScope.Scope#methods_$watch watchers} of the current scope and 11700 * its children. Because a {@link ng.$rootScope.Scope#methods_$watch watcher}'s listener can change 11701 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#methods_$watch watchers} 11702 * until no more listeners are firing. This means that it is possible to get into an infinite 11703 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 11704 * iterations exceeds 10. 11705 * 11706 * Usually, you don't call `$digest()` directly in 11707 * {@link ng.directive:ngController controllers} or in 11708 * {@link ng.$compileProvider#methods_directive directives}. 11709 * Instead, you should call {@link ng.$rootScope.Scope#methods_$apply $apply()} (typically from within 11710 * a {@link ng.$compileProvider#methods_directive directives}), which will force a `$digest()`. 11711 * 11712 * If you want to be notified whenever `$digest()` is called, 11713 * you can register a `watchExpression` function with 11714 * {@link ng.$rootScope.Scope#methods_$watch $watch()} with no `listener`. 11715 * 11716 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 11717 * 11718 * # Example 11719 * <pre> 11720 var scope = ...; 11721 scope.name = 'misko'; 11722 scope.counter = 0; 11723 11724 expect(scope.counter).toEqual(0); 11725 scope.$watch('name', function(newValue, oldValue) { 11726 scope.counter = scope.counter + 1; 11727 }); 11728 expect(scope.counter).toEqual(0); 11729 11730 scope.$digest(); 11731 // no variable change 11732 expect(scope.counter).toEqual(0); 11733 11734 scope.name = 'adam'; 11735 scope.$digest(); 11736 expect(scope.counter).toEqual(1); 11737 * </pre> 11738 * 11739 */ 11740 $digest: function() { 11741 var watch, value, last, 11742 watchers, 11743 asyncQueue = this.$$asyncQueue, 11744 postDigestQueue = this.$$postDigestQueue, 11745 length, 11746 dirty, ttl = TTL, 11747 next, current, target = this, 11748 watchLog = [], 11749 logIdx, logMsg, asyncTask; 11750 11751 beginPhase('$digest'); 11752 11753 lastDirtyWatch = null; 11754 11755 do { // "while dirty" loop 11756 dirty = false; 11757 current = target; 11758 11759 while(asyncQueue.length) { 11760 try { 11761 asyncTask = asyncQueue.shift(); 11762 asyncTask.scope.$eval(asyncTask.expression); 11763 } catch (e) { 11764 clearPhase(); 11765 $exceptionHandler(e); 11766 } 11767 lastDirtyWatch = null; 11768 } 11769 11770 traverseScopesLoop: 11771 do { // "traverse the scopes" loop 11772 if ((watchers = current.$$watchers)) { 11773 // process our watches 11774 length = watchers.length; 11775 while (length--) { 11776 try { 11777 watch = watchers[length]; 11778 // Most common watches are on primitives, in which case we can short 11779 // circuit it with === operator, only when === fails do we use .equals 11780 if (watch) { 11781 if ((value = watch.get(current)) !== (last = watch.last) && 11782 !(watch.eq 11783 ? equals(value, last) 11784 : (typeof value == 'number' && typeof last == 'number' 11785 && isNaN(value) && isNaN(last)))) { 11786 dirty = true; 11787 lastDirtyWatch = watch; 11788 watch.last = watch.eq ? copy(value) : value; 11789 watch.fn(value, ((last === initWatchVal) ? value : last), current); 11790 if (ttl < 5) { 11791 logIdx = 4 - ttl; 11792 if (!watchLog[logIdx]) watchLog[logIdx] = []; 11793 logMsg = (isFunction(watch.exp)) 11794 ? 'fn: ' + (watch.exp.name || watch.exp.toString()) 11795 : watch.exp; 11796 logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); 11797 watchLog[logIdx].push(logMsg); 11798 } 11799 } else if (watch === lastDirtyWatch) { 11800 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 11801 // have already been tested. 11802 dirty = false;
11803 break traverseScopesLoop; 11804 } 11805 } 11806 } catch (e) { 11807 clearPhase(); 11808 $exceptionHandler(e); 11809 } 11810 } 11811 } 11812 11813 // Insanity Warning: scope depth-first traversal 11814 // yes, this code is a bit crazy, but it works and we have tests to prove it! 11815 // this piece should be kept in sync with the traversal in $broadcast 11816 if (!(next = (current.$$childHead || 11817 (current !== target && current.$$nextSibling)))) { 11818 while(current !== target && !(next = current.$$nextSibling)) { 11819 current = current.$parent; 11820 } 11821 } 11822 } while ((current = next)); 11823 11824 // `break traverseScopesLoop;` takes us to here 11825 11826 if((dirty || asyncQueue.length) && !(ttl--)) { 11827 clearPhase(); 11828 throw $rootScopeMinErr('infdig', 11829 '{0} $digest() iterations reached. Aborting!\n' + 11830 'Watchers fired in the last 5 iterations: {1}', 11831 TTL, toJson(watchLog)); 11832 } 11833 11834 } while (dirty || asyncQueue.length); 11835 11836 clearPhase(); 11837 11838 while(postDigestQueue.length) { 11839 try { 11840 postDigestQueue.shift()(); 11841 } catch (e) { 11842 $exceptionHandler(e); 11843 } 11844 } 11845 }, 11846 11847 11848 /** 11849 * @ngdoc event 11850 * @name ng.$rootScope.Scope#$destroy 11851 * @eventOf ng.$rootScope.Scope 11852 * @eventType broadcast on scope being destroyed 11853 * 11854 * @description 11855 * Broadcasted when a scope and its children are being destroyed. 11856 * 11857 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 11858 * clean up DOM bindings before an element is removed from the DOM. 11859 */ 11860 11861 /** 11862 * @ngdoc function 11863 * @name ng.$rootScope.Scope#$destroy 11864 * @methodOf ng.$rootScope.Scope 11865 * @function 11866 * 11867 * @description 11868 * Removes the current scope (and all of its children) from the parent scope. Removal implies 11869 * that calls to {@link ng.$rootScope.Scope#methods_$digest $digest()} will no longer 11870 * propagate to the current scope and its children. Removal also implies that the current 11871 * scope is eligible for garbage collection. 11872 * 11873 * The `$destroy()` is usually used by directives such as 11874 * {@link ng.directive:ngRepeat ngRepeat} for managing the 11875 * unrolling of the loop. 11876 * 11877 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 11878 * Application code can register a `$destroy` event handler that will give it a chance to 11879 * perform any necessary cleanup. 11880 * 11881 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 11882 * clean up DOM bindings before an element is removed from the DOM. 11883 */ 11884 $destroy: function() { 11885 // we can't destroy the root scope or a scope that has been already destroyed 11886 if (this.$$destroyed) return; 11887 var parent = this.$parent; 11888 11889 this.$broadcast('$destroy'); 11890 this.$$destroyed = true; 11891 if (this === $rootScope) return; 11892 11893 forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); 11894 11895 if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 11896 if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 11897 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 11898 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 11899 11900 // This is bogus code that works around Chrome's GC leak 11901 // see: https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 11902 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 11903 this.$$childTail = null; 11904 }, 11905 11906 /** 11907 * @ngdoc function 11908 * @name ng.$rootScope.Scope#$eval 11909 * @methodOf ng.$rootScope.Scope 11910 * @function 11911 *
11912 * @description 11913 * Executes the `expression` on the current scope and returns the result. Any exceptions in 11914 * the expression are propagated (uncaught). This is useful when evaluating Angular 11915 * expressions. 11916 * 11917 * # Example 11918 * <pre> 11919 var scope = ng.$rootScope.Scope(); 11920 scope.a = 1; 11921 scope.b = 2; 11922 11923 expect(scope.$eval('a+b')).toEqual(3); 11924 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 11925 * </pre> 11926 * 11927 * @param {(string|function())=} expression An angular expression to be executed. 11928 * 11929 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 11930 * - `function(scope)`: execute the function with the current `scope` parameter. 11931 * 11932 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 11933 * @returns {*} The result of evaluating the expression. 11934 */ 11935 $eval: function(expr, locals) { 11936 return $parse(expr)(this, locals); 11937 }, 11938 11939 /** 11940 * @ngdoc function 11941 * @name ng.$rootScope.Scope#$evalAsync 11942 * @methodOf ng.$rootScope.Scope 11943 * @function 11944 * 11945 * @description 11946 * Executes the expression on the current scope at a later point in time. 11947 * 11948 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 11949 * that: 11950 * 11951 * - it will execute after the function that scheduled the evaluation (preferably before DOM 11952 * rendering). 11953 * - at least one {@link ng.$rootScope.Scope#methods_$digest $digest cycle} will be performed after 11954 * `expression` execution. 11955 * 11956 * Any exceptions from the execution of the expression are forwarded to the 11957 * {@link ng.$exceptionHandler $exceptionHandler} service. 11958 * 11959 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 11960 * will be scheduled. However, it is encouraged to always call code that changes the model 11961 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 11962 * 11963 * @param {(string|function())=} expression An angular expression to be executed. 11964 * 11965 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 11966 * - `function(scope)`: execute the function with the current `scope` parameter. 11967 * 11968 */ 11969 $evalAsync: function(expr) { 11970 // if we are outside of an $digest loop and this is the first time we are scheduling async 11971 // task also schedule async auto-flush 11972 if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { 11973 $browser.defer(function() { 11974 if ($rootScope.$$asyncQueue.length) { 11975 $rootScope.$digest(); 11976 } 11977 }); 11978 } 11979 11980 this.$$asyncQueue.push({scope: this, expression: expr}); 11981 }, 11982 11983 $$postDigest : function(fn) { 11984 this.$$postDigestQueue.push(fn); 11985 }, 11986 11987 /** 11988 * @ngdoc function 11989 * @name ng.$rootScope.Scope#$apply 11990 * @methodOf ng.$rootScope.Scope 11991 * @function 11992 * 11993 * @description 11994 * `$apply()` is used to execute an expression in angular from outside of the angular 11995 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 11996 * Because we are calling into the angular framework we need to perform proper scope life 11997 * cycle of {@link ng.$exceptionHandler exception handling}, 11998 * {@link ng.$rootScope.Scope#methods_$digest executing watches}. 11999 * 12000 * ## Life cycle 12001 * 12002 * # Pseudo-Code of `$apply()` 12003 * <pre> 12004 function $apply(expr) { 12005 try { 12006 return $eval(expr); 12007 } catch (e) { 12008 $exceptionHandler(e); 12009 } finally { 12010 $root.$digest(); 12011 } 12012 } 12013 * </pre> 12014 * 12015 * 12016 * Scope's `$apply()` method transitions through the following stages: 12017 * 12018 * 1. The {@link guide/expression expression} is executed using the 12019 * {@link ng.$rootScope.Scope#methods_$eval $eval()} method. 12020 * 2. Any exceptions from the execution of the expression are forwarded to the 12021 * {@link ng.$exceptionHandler $exceptionHandler} service.
12022 * 3. The {@link ng.$rootScope.Scope#methods_$watch watch} listeners are fired immediately after the 12023 * expression was executed using the {@link ng.$rootScope.Scope#methods_$digest $digest()} method. 12024 * 12025 * 12026 * @param {(string|function())=} exp An angular expression to be executed. 12027 * 12028 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12029 * - `function(scope)`: execute the function with current `scope` parameter. 12030 * 12031 * @returns {*} The result of evaluating the expression. 12032 */ 12033 $apply: function(expr) { 12034 try { 12035 beginPhase('$apply'); 12036 return this.$eval(expr); 12037 } catch (e) { 12038 $exceptionHandler(e); 12039 } finally { 12040 clearPhase(); 12041 try { 12042 $rootScope.$digest(); 12043 } catch (e) { 12044 $exceptionHandler(e); 12045 throw e; 12046 } 12047 } 12048 }, 12049 12050 /** 12051 * @ngdoc function 12052 * @name ng.$rootScope.Scope#$on 12053 * @methodOf ng.$rootScope.Scope 12054 * @function 12055 * 12056 * @description 12057 * Listens on events of a given type. See {@link ng.$rootScope.Scope#methods_$emit $emit} for 12058 * discussion of event life cycle. 12059 * 12060 * The event listener function format is: `function(event, args...)`. The `event` object 12061 * passed into the listener has the following attributes: 12062 * 12063 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 12064 * `$broadcast`-ed. 12065 * - `currentScope` - `{Scope}`: the current scope which is handling the event. 12066 * - `name` - `{string}`: name of the event. 12067 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 12068 * further event propagation (available only for events that were `$emit`-ed). 12069 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 12070 * to true. 12071 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 12072 * 12073 * @param {string} name Event name to listen on. 12074 * @param {function(event, args...)} listener Function to call when the event is emitted. 12075 * @returns {function()} Returns a deregistration function for this listener. 12076 */ 12077 $on: function(name, listener) { 12078 var namedListeners = this.$$listeners[name]; 12079 if (!namedListeners) { 12080 this.$$listeners[name] = namedListeners = []; 12081 } 12082 namedListeners.push(listener); 12083 12084 var current = this; 12085 do { 12086 if (!current.$$listenerCount[name]) { 12087 current.$$listenerCount[name] = 0; 12088 } 12089 current.$$listenerCount[name]++; 12090 } while ((current = current.$parent)); 12091 12092 var self = this; 12093 return function() { 12094 namedListeners[indexOf(namedListeners, listener)] = null; 12095 decrementListenerCount(self, 1, name); 12096 }; 12097 }, 12098 12099 12100 /** 12101 * @ngdoc function 12102 * @name ng.$rootScope.Scope#$emit 12103 * @methodOf ng.$rootScope.Scope 12104 * @function 12105 * 12106 * @description 12107 * Dispatches an event `name` upwards through the scope hierarchy notifying the 12108 * registered {@link ng.$rootScope.Scope#methods_$on} listeners. 12109 * 12110 * The event life cycle starts at the scope on which `$emit` was called. All 12111 * {@link ng.$rootScope.Scope#methods_$on listeners} listening for `name` event on this scope get 12112 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 12113 * registered listeners along the way. The event will stop propagating if one of the listeners 12114 * cancels it. 12115 * 12116 * Any exception emitted from the {@link ng.$rootScope.Scope#methods_$on listeners} will be passed 12117 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 12118 * 12119 * @param {string} name Event name to emit. 12120 * @param {...*} args Optional set of arguments which will be passed onto the event listeners. 12121 * @return {Object} Event object (see {@link ng.$rootScope.Scope#methods_$on}). 12122 */ 12123 $emit: function(name, args) { 12124 var empty = [], 12125 namedListeners, 12126 scope = this, 12127 stopPropagation = false, 12128 event = { 12129 name: name, 12130 targetScope: scope, 12131 stopPropagation: function() {stopPropagation = true;}, 12132 preventDefault: function() { 12133 event.defaultPrevented = true; 12134 }, 12135 defaultPrevented: false 12136 }, 12137 listenerArgs = concat([event], arguments, 1), 12138 i, length; 12139 12140 do { 12141 namedListeners = scope.$$listeners[name] || empty; 12142 event.currentScope = scope; 12143 for (i=0, length=namedListeners.length; i<length; i++) { 12144 12145 // if listeners were deregistered, defragment the array 12146 if (!namedListeners[i]) { 12147 namedListeners.splice(i, 1); 12148 i--; 12149 length--; 12150 continue; 12151 } 12152 try { 12153 //allow all listeners attached to the current scope to run 12154 namedListeners[i].apply(null, listenerArgs); 12155 } catch (e) { 12156 $exceptionHandler(e); 12157 } 12158 } 12159 //if any listener on the current scope stops propagation, prevent bubbling 12160 if (stopPropagation) return event; 12161 //traverse upwards 12162 scope = scope.$parent; 12163 } while (scope); 12164 12165 return event; 12166 }, 12167 12168 12169 /** 12170 * @ngdoc function 12171 * @name ng.$rootScope.Scope#$broadcast 12172 * @methodOf ng.$rootScope.Scope 12173 * @function 12174 *
12175 * @description 12176 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 12177 * registered {@link ng.$rootScope.Scope#methods_$on} listeners. 12178 * 12179 * The event life cycle starts at the scope on which `$broadcast` was called. All 12180 * {@link ng.$rootScope.Scope#methods_$on listeners} listening for `name` event on this scope get 12181 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 12182 * scope and calls all registered listeners along the way. The event cannot be canceled. 12183 * 12184 * Any exception emitted from the {@link ng.$rootScope.Scope#methods_$on listeners} will be passed 12185 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 12186 * 12187 * @param {string} name Event name to broadcast. 12188 * @param {...*} args Optional set of arguments which will be passed onto the event listeners. 12189 * @return {Object} Event object, see {@link ng.$rootScope.Scope#methods_$on} 12190 */ 12191 $broadcast: function(name, args) { 12192 var target = this, 12193 current = target, 12194 next = target, 12195 event = { 12196 name: name, 12197 targetScope: target, 12198 preventDefault: function() { 12199 event.defaultPrevented = true; 12200 }, 12201 defaultPrevented: false 12202 }, 12203 listenerArgs = concat([event], arguments, 1), 12204 listeners, i, length; 12205 12206 //down while you can, then up and next sibling or up and next sibling until back at root 12207 while ((current = next)) { 12208 event.currentScope = current; 12209 listeners = current.$$listeners[name] || []; 12210 for (i=0, length = listeners.length; i<length; i++) { 12211 // if listeners were deregistered, defragment the array 12212 if (!listeners[i]) { 12213 listeners.splice(i, 1); 12214 i--; 12215 length--; 12216 continue; 12217 } 12218 12219 try { 12220 listeners[i].apply(null, listenerArgs); 12221 } catch(e) { 12222 $exceptionHandler(e); 12223 } 12224 } 12225 12226 // Insanity Warning: scope depth-first traversal 12227 // yes, this code is a bit crazy, but it works and we have tests to prove it! 12228 // this piece should be kept in sync with the traversal in $digest 12229 // (though it differs due to having the extra check for $$listenerCount) 12230 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 12231 (current !== target && current.$$nextSibling)))) { 12232 while(current !== target && !(next = current.$$nextSibling)) { 12233 current = current.$parent; 12234 } 12235 } 12236 } 12237 12238 return event; 12239 } 12240 }; 12241 12242 var $rootScope = new Scope(); 12243 12244 return $rootScope; 12245 12246 12247 function beginPhase(phase) { 12248 if ($rootScope.$$phase) { 12249 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 12250 } 12251 12252 $rootScope.$$phase = phase; 12253 } 12254 12255 function clearPhase() { 12256 $rootScope.$$phase = null; 12257 } 12258 12259 function compileToFn(exp, name) { 12260 var fn = $parse(exp); 12261 assertArgFn(fn, name); 12262 return fn; 12263 } 12264 12265 function decrementListenerCount(current, count, name) { 12266 do { 12267 current.$$listenerCount[name] -= count; 12268 12269 if (current.$$listenerCount[name] === 0) { 12270 delete current.$$listenerCount[name]; 12271 } 12272 } while ((current = current.$parent)); 12273 } 12274 12275 /** 12276 * function used as an initial value for watchers. 12277 * because it's unique we can easily tell it apart from other values 12278 */ 12279 function initWatchVal() {} 12280 }]; 12281} 12282 12283/** 12284 * @description 12285 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 12286 */ 12287function $$SanitizeUriProvider() { 12288 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 12289 imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//; 12290 12291 /** 12292 * @description 12293 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 12294 * urls during a[href] sanitization. 12295 * 12296 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 12297 * 12298 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 12299 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 12300 * regular expression. If a match is found, the original url is
12300written into the dom. Otherwise, 12301 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 12302 * 12303 * @param {RegExp=} regexp New regexp to whitelist urls with. 12304 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 12305 * chaining otherwise. 12306 */ 12307 this.aHrefSanitizationWhitelist = function(regexp) { 12308 if (isDefined(regexp)) { 12309 aHrefSanitizationWhitelist = regexp; 12310 return this; 12311 } 12312 return aHrefSanitizationWhitelist; 12313 }; 12314 12315 12316 /** 12317 * @description 12318 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 12319 * urls during img[src] sanitization. 12320 * 12321 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 12322 * 12323 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 12324 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 12325 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 12326 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 12327 * 12328 * @param {RegExp=} regexp New regexp to whitelist urls with. 12329 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 12330 * chaining otherwise. 12331 */ 12332 this.imgSrcSanitizationWhitelist = function(regexp) { 12333 if (isDefined(regexp)) { 12334 imgSrcSanitizationWhitelist = regexp; 12335 return this; 12336 } 12337 return imgSrcSanitizationWhitelist; 12338 }; 12339 12340 this.$get = function() { 12341 return function sanitizeUri(uri, isImage) { 12342 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 12343 var normalizedVal; 12344 // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case. 12345 if (!msie || msie >= 8 ) { 12346 normalizedVal = urlResolve(uri).href; 12347 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 12348 return 'unsafe:'+normalizedVal; 12349 } 12350 } 12351 return uri; 12352 }; 12353 }; 12354} 12355 12356var $sceMinErr = minErr('$sce'); 12357 12358var SCE_CONTEXTS = { 12359 HTML: 'html', 12360 CSS: 'css', 12361 URL: 'url', 12362 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 12363 // url. (e.g. ng-include, script src, templateUrl) 12364 RESOURCE_URL: 'resourceUrl', 12365 JS: 'js' 12366}; 12367 12368// Helper functions follow. 12369 12370// Copied from: 12371// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 12372// Prereq: s is a string. 12373function escapeForRegexp(s) { 12374 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 12375 replace(/\x08/g, '\\x08'); 12376} 12377 12378 12379function adjustMatcher(matcher) { 12380 if (matcher === 'self') { 12381 return matcher; 12382 } else if (isString(matcher)) { 12383 // Strings match exactly except for 2 wildcards - '*' and '**'. 12384 // '*' matches any character except those from the set ':/.?&'. 12385 // '**' matches any character (like .* in a RegExp). 12386 // More than 2 *'s raises an error as it's ill defined. 12387 if (matcher.indexOf('***') > -1) { 12388 throw $sceMinErr('iwcard', 12389 'Illegal sequence *** in string matcher. String: {0}', matcher); 12390 } 12391 matcher = escapeForRegexp(matcher).
12392 replace('\\*\\*', '.*'). 12393 replace('\\*', '[^:/.?&;]*'); 12394 return new RegExp('^' + matcher + '$'); 12395 } else if (isRegExp(matcher)) { 12396 // The only other type of matcher allowed is a Regexp. 12397 // Match entire URL / disallow partial matches. 12398 // Flags are reset (i.e. no global, ignoreCase or multiline) 12399 return new RegExp('^' + matcher.source + '$'); 12400 } else { 12401 throw $sceMinErr('imatcher', 12402 'Matchers may only be "self", string patterns or RegExp objects'); 12403 } 12404} 12405 12406 12407function adjustMatchers(matchers) { 12408 var adjustedMatchers = []; 12409 if (isDefined(matchers)) { 12410 forEach(matchers, function(matcher) { 12411 adjustedMatchers.push(adjustMatcher(matcher)); 12412 }); 12413 } 12414 return adjustedMatchers; 12415} 12416 12417 12418/** 12419 * @ngdoc service 12420 * @name ng.$sceDelegate 12421 * @function 12422 * 12423 * @description 12424 * 12425 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 12426 * Contextual Escaping (SCE)} services to AngularJS. 12427 * 12428 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 12429 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 12430 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 12431 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 12432 * work because `$sce` delegates to `$sceDelegate` for these operations. 12433 * 12434 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 12435 * 12436 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 12437 * can override it completely to change the behavior of `$sce`, the common case would 12438 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 12439 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 12440 * templates. Refer {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist 12441 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 12442 * ng.$sceDelegateProvider#methods_resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 12443 */ 12444 12445/** 12446 * @ngdoc object 12447 * @name ng.$sceDelegateProvider 12448 * @description 12449 * 12450 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 12451 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 12452 * that the URLs used for sourcing Angular templates are safe. Refer {@link 12453 * ng.$sceDelegateProvider#methods_resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 12454 * {@link ng.$sceDelegateProvider#methods_resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 12455 * 12456 * For the general details about this service in Angular, read the main page for {@link ng.$sce 12457 * Strict Contextual Escaping (SCE)}. 12458 * 12459 * **Example**: Consider the following case. <a name="example"></a> 12460 * 12461 * - your app is hosted at url `http://myapp.example.com/` 12462 * - but some of your templates are hosted on other domains you control such as 12463 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 12464 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 12465 * 12466 * Here is what a secure configuration for this scenario might look like: 12467 * 12468 * <pre class="prettyprint"> 12469 * angular.module('myApp', []).config(function($sceDelegateProvider) { 12470 * $sceDelegateProvider.resourceUrlWhitelist([ 12471 * // Allow same origin resource loads. 12472 * 'self', 12473 * // Allow loading from our assets domain. Notice the difference between * and **. 12474 * 'http://srv*.assets.example.com/**']); 12475 * 12476 * // The blacklist overrides the whitelist so the open redirect here is blocked. 12477 * $sceDelegateProvider.resourceUrlBlacklist([ 12478 * 'http://myapp.example.com/clickThru**']); 12479 * }); 12480 * </pre> 12481 */ 12482 12483function $SceDelegateProvider() { 12484 this.SCE_CONTEXTS = SCE_CONTEXTS; 12485 12486 // Resource URLs can also be trusted by policy. 12487 var resourceUrlWhitelist = ['self'], 12488 resourceUrlBlacklist = []; 12489 12490 /** 12491 * @ngdoc function 12492 * @name ng.sceDelegateProvider#resourceUrlWhitelist 12493 * @methodOf ng.$sceDelegateProvider 12494 * @function 12495 * 12496 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 12497 * provided. This must be an array or null. A snapshot of this array is used so further 12498 * changes to the array are ignored. 12499 * 12500 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 12501 * allowed in this array.
vendor: 4,224 bytes, lines 12502-12628
12502 * 12503 * Note: **an empty whitelist array will block all URLs**! 12504 * 12505 * @return {Array} the currently set whitelist array. 12506 * 12507 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 12508 * same origin resource requests. 12509 * 12510 * @description 12511 * Sets/Gets the whitelist of trusted resource URLs. 12512 */ 12513 this.resourceUrlWhitelist = function (value) { 12514 if (arguments.length) { 12515 resourceUrlWhitelist = adjustMatchers(value); 12516 } 12517 return resourceUrlWhitelist; 12518 }; 12519 12520 /** 12521 * @ngdoc function 12522 * @name ng.sceDelegateProvider#resourceUrlBlacklist 12523 * @methodOf ng.$sceDelegateProvider 12524 * @function 12525 * 12526 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 12527 * provided. This must be an array or null. A snapshot of this array is used so further 12528 * changes to the array are ignored. 12529 * 12530 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 12531 * allowed in this array. 12532 * 12533 * The typical usage for the blacklist is to **block 12534 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 12535 * these would otherwise be trusted but actually return content from the redirected domain. 12536 * 12537 * Finally, **the blacklist overrides the whitelist** and has the final say. 12538 * 12539 * @return {Array} the currently set blacklist array. 12540 * 12541 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 12542 * is no blacklist.) 12543 * 12544 * @description 12545 * Sets/Gets the blacklist of trusted resource URLs. 12546 */ 12547 12548 this.resourceUrlBlacklist = function (value) { 12549 if (arguments.length) { 12550 resourceUrlBlacklist = adjustMatchers(value); 12551 } 12552 return resourceUrlBlacklist; 12553 }; 12554 12555 this.$get = ['$injector', function($injector) { 12556 12557 var htmlSanitizer = function htmlSanitizer(html) { 12558 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 12559 }; 12560 12561 if ($injector.has('$sanitize')) { 12562 htmlSanitizer = $injector.get('$sanitize'); 12563 } 12564 12565 12566 function matchUrl(matcher, parsedUrl) { 12567 if (matcher === 'self') { 12568 return urlIsSameOrigin(parsedUrl); 12569 } else { 12570 // definitely a regex. See adjustMatchers() 12571 return !!matcher.exec(parsedUrl.href); 12572 } 12573 } 12574 12575 function isResourceUrlAllowedByPolicy(url) { 12576 var parsedUrl = urlResolve(url.toString()); 12577 var i, n, allowed = false; 12578 // Ensure that at least one item from the whitelist allows this url. 12579 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 12580 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 12581 allowed = true; 12582 break; 12583 } 12584 } 12585 if (allowed) { 12586 // Ensure that no item from the blacklist blocked this url. 12587 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 12588 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 12589 allowed = false; 12590 break; 12591 } 12592 } 12593 } 12594 return allowed; 12595 } 12596 12597 function generateHolderType(Base) { 12598 var holderType = function TrustedValueHolderType(trustedValue) { 12599 this.$$unwrapTrustedValue = function() { 12600 return trustedValue; 12601 }; 12602 }; 12603 if (Base) { 12604 holderType.prototype = new Base(); 12605 } 12606 holderType.prototype.valueOf = function sceValueOf() { 12607 return this.$$unwrapTrustedValue(); 12608 }; 12609 holderType.prototype.toString = function sceToString() { 12610 return this.$$unwrapTrustedValue().toString(); 12611 }; 12612 return holderType; 12613 } 12614 12615 var trustedValueHolderBase = generateHolderType(), 12616 byType = {}; 12617 12618 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 12619 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 12620 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 12621 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 12622 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 12623 12624 /** 12625 * @ngdoc method 12626 * @name ng.$sceDelegate#trustAs 12627 * @methodOf ng.$sceDelegate 12628 *
12629 * @description 12630 * Returns an object that is trusted by angular for use in specified strict 12631 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 12632 * attribute interpolation, any dom event binding attribute interpolation 12633 * such as for onclick, etc.) that uses the provided value. 12634 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 12635 * 12636 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 12637 * resourceUrl, html, js and css. 12638 * @param {*} value The value that that should be considered trusted/safe. 12639 * @returns {*} A value that can be used to stand in for the provided `value` in places 12640 * where Angular expects a $sce.trustAs() return value. 12641 */ 12642 function trustAs(type, trustedValue) { 12643 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 12644 if (!Constructor) { 12645 throw $sceMinErr('icontext', 12646 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 12647 type, trustedValue); 12648 } 12649 if (trustedValue === null || trustedValue === undefined || trustedValue === '') { 12650 return trustedValue; 12651 } 12652 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 12653 // mutable objects, we ensure here that the value passed in is actually a string. 12654 if (typeof trustedValue !== 'string') { 12655 throw $sceMinErr('itype', 12656 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 12657 type); 12658 } 12659 return new Constructor(trustedValue); 12660 } 12661 12662 /** 12663 * @ngdoc method 12664 * @name ng.$sceDelegate#valueOf 12665 * @methodOf ng.$sceDelegate 12666 * 12667 * @description 12668 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#methods_trustAs 12669 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 12670 * ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. 12671 * 12672 * If the passed parameter is not a value that had been returned by {@link 12673 * ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}, returns it as-is. 12674 * 12675 * @param {*} value The result of a prior {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`} 12676 * call or anything else. 12677 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#methods_trustAs 12678 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 12679 * `value` unchanged. 12680 */ 12681 function valueOf(maybeTrusted) { 12682 if (maybeTrusted instanceof trustedValueHolderBase) { 12683 return maybeTrusted.$$unwrapTrustedValue(); 12684 } else { 12685 return maybeTrusted; 12686 } 12687 } 12688 12689 /** 12690 * @ngdoc method 12691 * @name ng.$sceDelegate#getTrusted 12692 * @methodOf ng.$sceDelegate 12693 * 12694 * @description 12695 * Takes the result of a {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`} call and 12696 * returns the originally supplied value if the queried context type is a supertype of the 12697 * created type. If this condition isn't satisfied, throws an exception. 12698 * 12699 * @param {string} type The kind of context in which this value is to be used. 12700 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#methods_trustAs 12701 * `$sceDelegate.trustAs`} call. 12702 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#methods_trustAs 12703 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 12704 */ 12705 function getTrusted(type, maybeTrusted) { 12706 if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { 12707 return maybeTrusted; 12708 } 12709 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 12710 if (constructor && maybeTrusted instanceof constructor) { 12711 return maybeTrusted.$$unwrapTrustedValue(); 12712 } 12713 // If we get here, then we may only take one of two actions. 12714 // 1. sanitize the value for the requested type, or 12715 // 2. throw an exception. 12716 if (type === SCE_CONTEXTS.RESOURCE_URL) { 12717 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 12718 return maybeTrusted; 12719 } else { 12720 throw $sceMinErr('insecurl', 12721 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 12722 maybeTrusted.toString()); 12723 } 12724 } else if (type === SCE_CONTEXTS.HTML) { 12725 return htmlSanitizer(maybeTrusted); 12726 } 12727 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 12728 } 12729 12730 return { trustAs: trustAs, 12731 getTrusted: getTrusted,
12732 valueOf: valueOf }; 12733 }]; 12734} 12735 12736 12737/** 12738 * @ngdoc object 12739 * @name ng.$sceProvider 12740 * @description 12741 * 12742 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 12743 * - enable/disable Strict Contextual Escaping (SCE) in a module 12744 * - override the default implementation with a custom delegate 12745 * 12746 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 12747 */ 12748 12749/* jshint maxlen: false*/ 12750 12751/** 12752 * @ngdoc service 12753 * @name ng.$sce 12754 * @function 12755 * 12756 * @description 12757 * 12758 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 12759 * 12760 * # Strict Contextual Escaping 12761 * 12762 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 12763 * contexts to result in a value that is marked as safe to use for that context. One example of 12764 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 12765 * to these contexts as privileged or SCE contexts. 12766 * 12767 * As of version 1.2, Angular ships with SCE enabled by default. 12768 * 12769 * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows 12770 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 12771 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
12772 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 12773 * to the top of your HTML document. 12774 * 12775 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 12776 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 12777 * 12778 * Here's an example of a binding in a privileged context: 12779 * 12780 * <pre class="prettyprint"> 12781 * <input ng-model="userHtml"> 12782 * <div ng-bind-html="userHtml"> 12783 * </pre> 12784 * 12785 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 12786 * disabled, this application allows the user to render arbitrary HTML into the DIV. 12787 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 12788 * bindings. (HTML is just one example of a context where rendering user controlled input creates 12789 * security vulnerabilities.) 12790 * 12791 * For the case of HTML, you might use a library, either on the client side, or on the server side, 12792 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 12793 * 12794 * How would you ensure that every place that used these types of bindings was bound to a value that 12795 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 12796 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 12797 * properties/fields and forgot to update the binding to the sanitized value? 12798 * 12799 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 12800 * determine that something explicitly says it's safe to use a value for binding in that
12801 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 12802 * for those values that you can easily tell are safe - because they were received from your server, 12803 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 12804 * allowing only the files in a specific directory to do this. Ensuring that the internal API 12805 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 12806 * 12807 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#methods_trustAs $sce.trustAs} 12808 * (and shorthand methods such as {@link ng.$sce#methods_trustAsHtml $sce.trustAsHtml}, etc.) to 12809 * obtain values that will be accepted by SCE / privileged contexts. 12810 * 12811 * 12812 * ## How does it work? 12813 * 12814 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#methods_getTrusted 12815 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 12816 * ng.$sce#methods_parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 12817 * {@link ng.$sce#methods_getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 12818 * 12819 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 12820 * ng.$sce#methods_parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 12821 * simplified): 12822 * 12823 * <pre class="prettyprint"> 12824 * var ngBindHtmlDirective = ['$sce', function($sce) { 12825 * return function(scope, element, attr) { 12826 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 12827 * element.html(value || ''); 12828 * }); 12829 * }; 12830 * }]; 12831 * </pre> 12832 * 12833 * ## Impact on loading templates 12834 * 12835 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 12836 * `templateUrl`'s specified by {@link guide/directive directives}. 12837 * 12838 * By default, Angular only loads templates from the same domain and protocol as the application 12839 * document. This is done by calling {@link ng.$sce#methods_getTrustedResourceUrl 12840 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 12841 * protocols, you may either either {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelist 12842 * them} or {@link ng.$sce#methods_trustAsResourceUrl wrap it} into a trusted value. 12843 * 12844 * *Please note*: 12845 * The browser's 12846 * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest 12847 * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing (CORS)} 12848 * policy apply in addition to this and may further restrict whether the template is successfully 12849 * loaded. This means that without the right CORS policy, loading templates from a different domain 12850 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 12851 * browsers. 12852 * 12853 * ## This feels like too much overhead for the developer? 12854 * 12855 * It's important to remember that SCE only applies to interpolation expressions. 12856 * 12857 * If your expressions are constant literals, they're automatically trusted and you don't need to 12858 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 12859 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 12860 * 12861 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 12862 * through {@link ng.$sce#methods_getTrusted $sce.getTrusted}. SCE doesn't play a role here. 12863 * 12864 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 12865 * templates in `ng-include` from your application's domain without having to even know about SCE. 12866 * It blocks loading templates from other domains or loading templates over http from an https 12867 * served document. You can change these by setting your own custom {@link 12868 * ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelists} and {@link 12869 * ng.$sceDelegateProvider#methods_resourceUrlBlacklist blacklists} for matching such URLs. 12870 * 12871 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 12872 * application that's secure and can be audited to verify that with much more ease than bolting 12873 * security onto an application later. 12874 * 12875 * <a name="contexts"></a> 12876 * ## What trusted context types are supported? 12877 * 12878 * | Context | Notes | 12879 * |---------------------|----------------| 12880 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. | 12881 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 12882 * | `$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. | 12883 * | `$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. | 12884 * | `$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. | 12885 * 12886 * ## Format of items in {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#methods_resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 12887 *
12888 * Each element in these arrays must be one of the following: 12889 * 12890 * - **'self'** 12891 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 12892 * domain** as the application document using the **same protocol**. 12893 * - **String** (except the special value `'self'`) 12894 * - The string is matched against the full *normalized / absolute URL* of the resource 12895 * being tested (substring matches are not good enough.) 12896 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 12897 * match themselves. 12898 * - `*`: matches zero or more occurances of any character other than one of the following 6 12899 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use 12900 * in a whitelist. 12901 * - `**`: matches zero or more occurances of *any* character. As such, it's not 12902 * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. 12903 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 12904 * not have been the intention.) It's usage at the very end of the path is ok. (e.g. 12905 * http://foo.example.com/templates/**). 12906 * - **RegExp** (*see caveat below*) 12907 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 12908 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 12909 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 12910 * have good test coverage.). For instance, the use of `.` in the regex is correct only in a 12911 * small number of cases. A `.` character in the regex used when matching the scheme or a 12912 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 12913 * is highly recommended to use the string patterns and only fall back to regular expressions 12914 * if they as a last resort. 12915 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 12916 * matched against the **entire** *normalized / absolute URL* of the resource being tested 12917 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 12918 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 12919 * - If you are generating your JavaScript from some other templating engine (not 12920 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 12921 * remember to escape your regular expression (and be aware that you might need more than 12922 * one level of escaping depending on your templating engine and the way you interpolated 12923 * the value.) Do make use of your platform's escaping mechanism as it might be good 12924 * enough before coding your own. e.g. Ruby has 12925 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 12926 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 12927 * Javascript lacks a similar built in function for escaping. Take a look at Google 12928 * Closure library's [goog.string.regExpEscape(s)]( 12929 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 12930 * 12931 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 12932 * 12933 * ## Show me an example using SCE. 12934 * 12935 * @example 12936<example module="mySceApp" deps="angular-sanitize.js"> 12937<file name="index.html"> 12938 <div ng-controller="myAppController as myCtrl"> 12939 <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 12940 <b>User comments</b><br> 12941 By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 12942 $sanitize is available. If $sanitize isn't available, this results in an error instead of an 12943 exploit. 12944 <div class="well"> 12945 <div ng-repeat="userComment in myCtrl.userComments"> 12946 <b>{{userComment.name}}</b>: 12947 <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 12948 <br> 12949 </div> 12950 </div> 12951 </div> 12952</file> 12953 12954<file name="script.js"> 12955 var mySceApp = angular.module('mySceApp', ['ngSanitize']); 12956 12957 mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { 12958 var self = this; 12959 $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 12960 self.userComments = userComments; 12961 }); 12962 self.explicitlyTrustedHtml = $sce.trustAsHtml( 12963 '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
12964 'sanitization."">Hover over this text.</span>'); 12965 }); 12966</file> 12967 12968<file name="test_data.json"> 12969[ 12970 { "name": "Alice", 12971 "htmlComment": 12972 "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 12973 }, 12974 { "name": "Bob", 12975 "htmlComment": "<i>Yes!</i> Am I the only other one?" 12976 } 12977] 12978</file> 12979 12980<file name="scenario.js"> 12981 describe('SCE doc demo', function() { 12982 it('should sanitize untrusted values', function() { 12983 expect(element('.htmlComment').html()).toBe('<span>Is <i>anyone</i> reading this?</span>'); 12984 }); 12985 it('should NOT sanitize explicitly trusted values', function() { 12986 expect(element('#explicitlyTrustedHtml').html()).toBe( 12987 '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 12988 'sanitization."">Hover over this text.</span>'); 12989 }); 12990 }); 12991</file> 12992</example> 12993 * 12994 * 12995 * 12996 * ## Can I disable SCE completely? 12997 * 12998 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 12999 * for little coding overhead. It will be much harder to take an SCE disabled application and 13000 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 13001 * for cases where you have a lot of existing code that was written before SCE was introduced and 13002 * you're migrating them a module at a time. 13003 * 13004 * That said, here's how you can completely disable SCE: 13005 * 13006 * <pre class="prettyprint"> 13007 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 13008 * // Completely disable SCE. For demonstration purposes only! 13009 * // Do not use in new projects. 13010 * $sceProvider.enabled(false); 13011 * }); 13012 * </pre> 13013 * 13014 */ 13015/* jshint maxlen: 100 */ 13016 13017function $SceProvider() { 13018 var enabled = true; 13019 13020 /** 13021 * @ngdoc function 13022 * @name ng.sceProvider#enabled 13023 * @methodOf ng.$sceProvider 13024 * @function 13025 * 13026 * @param {boolean=} value If provided, then enables/disables SCE. 13027 * @return {boolean} true if SCE is enabled, false otherwise. 13028 * 13029 * @description 13030 * Enables/disables SCE and returns the current value. 13031 */ 13032 this.enabled = function (value) { 13033 if (arguments.length) { 13034 enabled = !!value; 13035 } 13036 return enabled; 13037 }; 13038 13039 13040 /* Design notes on the default implementation for SCE. 13041 * 13042 * The API contract for the SCE delegate 13043 * ------------------------------------- 13044 * The SCE delegate object must provide the following 3 methods: 13045 * 13046 * - trustAs(contextEnum, value) 13047 * This method is used to tell the SCE service that the provided value is OK to use in the 13048 * contexts specified by contextEnum. It must return an object that will be accepted by 13049 * getTrusted() for a compatible contextEnum and return this value. 13050 * 13051 * - valueOf(value) 13052 * For values that were not produced by trustAs(), return them as is. For values that were 13053 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 13054 * trustAs is wrapping the given values into some type, this operation unwraps it when given 13055 * such a value. 13056 * 13057 * - getTrusted(contextEnum, value) 13058 * This function should return the a value that is safe to use in the context specified by 13059 * contextEnum or throw and exception otherwise. 13060 * 13061 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 13062 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 13063 * instance, an implementation could maintain a registry of all trusted objects by context. In 13064 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 13065 * return the same object passed in if it was found in the registry under a compatible context or 13066 * throw an exception otherwise. An implementation might only wrap values some of the time based 13067 * on some criteria. getTrusted() might return a value and not throw an exception for special 13068 * constants or objects even if not wrapped. All such implementations fulfill this contract. 13069 * 13070 * 13071 * A note on the inheritance model for SCE contexts 13072 * ------------------------------------------------ 13073 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 13074 * is purely an implementation details. 13075 * 13076 * The contract is simply this: 13077 * 13078 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 13079 * will also succeed. 13080 * 13081 * Inheritance happens to capture this in a natural way. In some future, we
13082 * may not use inheritance anymore. That is OK because no code outside of 13083 * sce.js and sceSpecs.js would need to be aware of this detail. 13084 */ 13085 13086 this.$get = ['$parse', '$sniffer', '$sceDelegate', function( 13087 $parse, $sniffer, $sceDelegate) { 13088 // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows 13089 // the "expression(javascript expression)" syntax which is insecure. 13090 if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { 13091 throw $sceMinErr('iequirks', 13092 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + 13093 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 13094 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 13095 } 13096 13097 var sce = copy(SCE_CONTEXTS); 13098 13099 /** 13100 * @ngdoc function 13101 * @name ng.sce#isEnabled 13102 * @methodOf ng.$sce 13103 * @function 13104 * 13105 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 13106 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 13107 * 13108 * @description 13109 * Returns a boolean indicating if SCE is enabled. 13110 */ 13111 sce.isEnabled = function () { 13112 return enabled; 13113 }; 13114 sce.trustAs = $sceDelegate.trustAs; 13115 sce.getTrusted = $sceDelegate.getTrusted; 13116 sce.valueOf = $sceDelegate.valueOf; 13117 13118 if (!enabled) { 13119 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 13120 sce.valueOf = identity; 13121 } 13122 13123 /** 13124 * @ngdoc method 13125 * @name ng.$sce#parse 13126 * @methodOf ng.$sce 13127 * 13128 * @description 13129 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 13130 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 13131 * wraps the expression in a call to {@link ng.$sce#methods_getTrusted $sce.getTrusted(*type*, 13132 * *result*)} 13133 * 13134 * @param {string} type The kind of SCE context in which this result will be used. 13135 * @param {string} expression String expression to compile. 13136 * @returns {function(context, locals)} a function which represents the compiled expression: 13137 * 13138 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13139 * are evaluated against (typically a scope object). 13140 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13141 * `context`. 13142 */ 13143 sce.parseAs = function sceParseAs(type, expr) { 13144 var parsed = $parse(expr); 13145 if (parsed.literal && parsed.constant) { 13146 return parsed; 13147 } else { 13148 return function sceParseAsTrusted(self, locals) { 13149 return sce.getTrusted(type, parsed(self, locals)); 13150 }; 13151 } 13152 }; 13153 13154 /** 13155 * @ngdoc method 13156 * @name ng.$sce#trustAs 13157 * @methodOf ng.$sce 13158 * 13159 * @description 13160 * Delegates to {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. As such, 13161 * returns an object that is trusted by angular for use in specified strict contextual 13162 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 13163 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 13164 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 13165 * escaping. 13166 * 13167 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 13168 * resource_url, html, js and css. 13169 * @param {*} value The value that that should be considered trusted/safe. 13170 * @returns {*} A value that can be used to stand in for the provided `value` in places 13171 * where Angular expects a $sce.trustAs() return value. 13172 */ 13173 13174 /** 13175 * @ngdoc method 13176 * @name ng.$sce#trustAsHtml 13177 * @methodOf ng.$sce 13178 * 13179 * @description 13180 * Shorthand method. `$sce.trustAsHtml(value)` â 13181 * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 13182 * 13183 * @param {*} value The value to trustAs. 13184 * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedHtml 13185 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 13186 * only accept expressions that are either literal constants or are the 13187 * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) 13188 */ 13189 13190 /** 13191 * @ngdoc method 13192 * @name ng.$sce#trustAsUrl 13193 * @methodOf ng.$sce 13194 *
13195 * @description 13196 * Shorthand method. `$sce.trustAsUrl(value)` â 13197 * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.URL, value)`} 13198 * 13199 * @param {*} value The value to trustAs. 13200 * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedUrl 13201 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 13202 * only accept expressions that are either literal constants or are the 13203 * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) 13204 */ 13205 13206 /** 13207 * @ngdoc method 13208 * @name ng.$sce#trustAsResourceUrl 13209 * @methodOf ng.$sce 13210 * 13211 * @description 13212 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 13213 * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 13214 * 13215 * @param {*} value The value to trustAs. 13216 * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedResourceUrl 13217 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 13218 * only accept expressions that are either literal constants or are the return 13219 * value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) 13220 */ 13221 13222 /** 13223 * @ngdoc method 13224 * @name ng.$sce#trustAsJs 13225 * @methodOf ng.$sce 13226 * 13227 * @description 13228 * Shorthand method. `$sce.trustAsJs(value)` â 13229 * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.JS, value)`} 13230 * 13231 * @param {*} value The value to trustAs. 13232 * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedJs 13233 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 13234 * only accept expressions that are either literal constants or are the 13235 * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.) 13236 */ 13237 13238 /** 13239 * @ngdoc method 13240 * @name ng.$sce#getTrusted 13241 * @methodOf ng.$sce 13242 * 13243 * @description 13244 * Delegates to {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted`}. As such, 13245 * takes the result of a {@link ng.$sce#methods_trustAs `$sce.trustAs`}() call and returns the 13246 * originally supplied value if the queried context type is a supertype of the created type. 13247 * If this condition isn't satisfied, throws an exception. 13248 * 13249 * @param {string} type The kind of context in which this value is to be used. 13250 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#methods_trustAs `$sce.trustAs`} 13251 * call. 13252 * @returns {*} The value the was originally provided to 13253 * {@link ng.$sce#methods_trustAs `$sce.trustAs`} if valid in this context. 13254 * Otherwise, throws an exception. 13255 */ 13256 13257 /** 13258 * @ngdoc method 13259 * @name ng.$sce#getTrustedHtml 13260 * @methodOf ng.$sce 13261 * 13262 * @description 13263 * Shorthand method. `$sce.getTrustedHtml(value)` â 13264 * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 13265 * 13266 * @param {*} value The value to pass to `$sce.getTrusted`. 13267 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 13268 */ 13269 13270 /** 13271 * @ngdoc method 13272 * @name ng.$sce#getTrustedCss 13273 * @methodOf ng.$sce 13274 * 13275 * @description 13276 * Shorthand method. `$sce.getTrustedCss(value)` â 13277 * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 13278 * 13279 * @param {*} value The value to pass to `$sce.getTrusted`. 13280 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 13281 */ 13282 13283 /** 13284 * @ngdoc method 13285 * @name ng.$sce#getTrustedUrl 13286 * @methodOf ng.$sce 13287 * 13288 * @description 13289 * Shorthand method. `$sce.getTrustedUrl(value)` â 13290 * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 13291 * 13292 * @param {*} value The value to pass to `$sce.getTrusted`. 13293 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 13294 */ 13295 13296 /** 13297 * @ngdoc method 13298 * @name ng.$sce#getTrustedResourceUrl 13299 * @methodOf ng.$sce 13300 *
13301 * @description 13302 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 13303 * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 13304 * 13305 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 13306 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 13307 */ 13308 13309 /** 13310 * @ngdoc method 13311 * @name ng.$sce#getTrustedJs 13312 * @methodOf ng.$sce 13313 * 13314 * @description 13315 * Shorthand method. `$sce.getTrustedJs(value)` â 13316 * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 13317 * 13318 * @param {*} value The value to pass to `$sce.getTrusted`. 13319 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 13320 */ 13321 13322 /** 13323 * @ngdoc method 13324 * @name ng.$sce#parseAsHtml 13325 * @methodOf ng.$sce 13326 * 13327 * @description 13328 * Shorthand method. `$sce.parseAsHtml(expression string)` â 13329 * {@link ng.$sce#methods_parse `$sce.parseAs($sce.HTML, value)`} 13330 * 13331 * @param {string} expression String expression to compile. 13332 * @returns {function(context, locals)} a function which represents the compiled expression: 13333 * 13334 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13335 * are evaluated against (typically a scope object). 13336 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13337 * `context`. 13338 */ 13339 13340 /** 13341 * @ngdoc method 13342 * @name ng.$sce#parseAsCss 13343 * @methodOf ng.$sce 13344 * 13345 * @description 13346 * Shorthand method. `$sce.parseAsCss(value)` â 13347 * {@link ng.$sce#methods_parse `$sce.parseAs($sce.CSS, value)`} 13348 * 13349 * @param {string} expression String expression to compile. 13350 * @returns {function(context, locals)} a function which represents the compiled expression: 13351 * 13352 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13353 * are evaluated against (typically a scope object). 13354 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13355 * `context`. 13356 */ 13357 13358 /** 13359 * @ngdoc method 13360 * @name ng.$sce#parseAsUrl 13361 * @methodOf ng.$sce 13362 * 13363 * @description 13364 * Shorthand method. `$sce.parseAsUrl(value)` â 13365 * {@link ng.$sce#methods_parse `$sce.parseAs($sce.URL, value)`} 13366 * 13367 * @param {string} expression String expression to compile. 13368 * @returns {function(context, locals)} a function which represents the compiled expression: 13369 * 13370 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13371 * are evaluated against (typically a scope object). 13372 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13373 * `context`. 13374 */ 13375 13376 /** 13377 * @ngdoc method 13378 * @name ng.$sce#parseAsResourceUrl 13379 * @methodOf ng.$sce 13380 * 13381 * @description 13382 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 13383 * {@link ng.$sce#methods_parse `$sce.parseAs($sce.RESOURCE_URL, value)`} 13384 * 13385 * @param {string} expression String expression to compile. 13386 * @returns {function(context, locals)} a function which represents the compiled expression: 13387 * 13388 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13389 * are evaluated against (typically a scope object). 13390 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13391 * `context`. 13392 */ 13393 13394 /** 13395 * @ngdoc method 13396 * @name ng.$sce#parseAsJs 13397 * @methodOf ng.$sce 13398 * 13399 * @description 13400 * Shorthand method. `$sce.parseAsJs(value)` â 13401 * {@link ng.$sce#methods_parse `$sce.parseAs($sce.JS, value)`} 13402 * 13403 * @param {string} expression String expression to compile. 13404 * @returns {function(context, locals)} a function which represents the compiled expression: 13405 * 13406 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13407 * are evaluated against (typically a scope object). 13408 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13409 * `context`. 13410 */ 13411 13412 // Shorthand delegations. 13413 var parse = sce.parseAs, 13414 getTrusted = sce.getTrusted, 13415 trustAs = sce.trustAs; 13416
13417 forEach(SCE_CONTEXTS, function (enumValue, name) { 13418 var lName = lowercase(name); 13419 sce[camelCase("parse_as_" + lName)] = function (expr) { 13420 return parse(enumValue, expr); 13421 }; 13422 sce[camelCase("get_trusted_" + lName)] = function (value) { 13423 return getTrusted(enumValue, value); 13424 }; 13425 sce[camelCase("trust_as_" + lName)] = function (value) { 13426 return trustAs(enumValue, value); 13427 }; 13428 }); 13429 13430 return sce; 13431 }]; 13432} 13433 13434/** 13435 * !!! This is an undocumented "private" service !!! 13436 * 13437 * @name ng.$sniffer 13438 * @requires $window 13439 * @requires $document 13440 * 13441 * @property {boolean} history Does the browser support html5 history api ? 13442 * @property {boolean} hashchange Does the browser support hashchange event ? 13443 * @property {boolean} transitions Does the browser support CSS transition events ? 13444 * @property {boolean} animations Does the browser support CSS animation events ? 13445 * 13446 * @description 13447 * This is very simple implementation of testing browser's features. 13448 */ 13449function $SnifferProvider() { 13450 this.$get = ['$window', '$document', function($window, $document) { 13451 var eventSupport = {}, 13452 android = 13453 int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 13454 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 13455 document = $document[0] || {}, 13456 documentMode = document.documentMode, 13457 vendorPrefix, 13458 vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, 13459 bodyStyle = document.body && document.body.style, 13460 transitions = false, 13461 animations = false, 13462 match; 13463 13464 if (bodyStyle) { 13465 for(var prop in bodyStyle) { 13466 if(match = vendorRegex.exec(prop)) { 13467 vendorPrefix = match[0]; 13468 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 13469 break; 13470 } 13471 } 13472 13473 if(!vendorPrefix) { 13474 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 13475 } 13476 13477 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 13478 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 13479 13480 if (android && (!transitions||!animations)) { 13481 transitions = isString(document.body.style.webkitTransition); 13482 animations = isString(document.body.style.webkitAnimation); 13483 } 13484 } 13485 13486 13487 return { 13488 // Android has history.pushState, but it does not update location correctly 13489 // so let's not use the history API at all. 13490 // http://code.google.com/p/android/issues/detail?id=17471 13491 // https://github.com/angular/angular.js/issues/904 13492 13493 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 13494 // so let's not use the history API also 13495 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 13496 // jshint -W018 13497 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 13498 // jshint +W018 13499 hashchange: 'onhashchange' in $window && 13500 // IE8 compatible mode lies 13501 (!documentMode || documentMode > 7), 13502 hasEvent: function(event) { 13503 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 13504 // it. In particular the event is not fired when backspace or delete key are pressed or 13505 // when cut operation is performed. 13506 if (event == 'input' && msie == 9) return false; 13507 13508 if (isUndefined(eventSupport[event])) { 13509 var divElm = document.createElement('div'); 13510 eventSupport[event] = 'on' + event in divElm; 13511 } 13512 13513 return eventSupport[event]; 13514 }, 13515 csp: csp(), 13516 vendorPrefix: vendorPrefix, 13517 transitions : transitions, 13518 animations : animations, 13519 android: android, 13520 msie : msie, 13521 msieDocumentMode: documentMode 13522 }; 13523 }]; 13524} 13525 13526function $TimeoutProvider() { 13527 this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', 13528 function($rootScope, $browser, $q, $exceptionHandler) { 13529 var deferreds = {}; 13530 13531 13532 /** 13533 * @ngdoc function 13534 * @name ng.$timeout 13535 * @requires $browser 13536 *
13537 * @description 13538 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 13539 * block and delegates any exceptions to 13540 * {@link ng.$exceptionHandler $exceptionHandler} service. 13541 * 13542 * The return value of registering a timeout function is a promise, which will be resolved when 13543 * the timeout is reached and the timeout function is executed. 13544 * 13545 * To cancel a timeout request, call `$timeout.cancel(promise)`. 13546 * 13547 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 13548 * synchronously flush the queue of deferred functions. 13549 * 13550 * @param {function()} fn A function, whose execution should be delayed. 13551 * @param {number=} [delay=0] Delay in milliseconds. 13552 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 13553 * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block. 13554 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 13555 * promise will be resolved with is the return value of the `fn` function. 13556 * 13557 */ 13558 function timeout(fn, delay, invokeApply) { 13559 var deferred = $q.defer(), 13560 promise = deferred.promise, 13561 skipApply = (isDefined(invokeApply) && !invokeApply), 13562 timeoutId; 13563 13564 timeoutId = $browser.defer(function() { 13565 try { 13566 deferred.resolve(fn()); 13567 } catch(e) { 13568 deferred.reject(e); 13569 $exceptionHandler(e); 13570 } 13571 finally { 13572 delete deferreds[promise.$$timeoutId]; 13573 } 13574 13575 if (!skipApply) $rootScope.$apply(); 13576 }, delay); 13577 13578 promise.$$timeoutId = timeoutId; 13579 deferreds[timeoutId] = deferred; 13580 13581 return promise; 13582 } 13583 13584 13585 /** 13586 * @ngdoc function 13587 * @name ng.$timeout#cancel 13588 * @methodOf ng.$timeout 13589 * 13590 * @description 13591 * Cancels a task associated with the `promise`. As a result of this, the promise will be 13592 * resolved with a rejection. 13593 * 13594 * @param {Promise=} promise Promise returned by the `$timeout` function. 13595 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 13596 * canceled. 13597 */ 13598 timeout.cancel = function(promise) { 13599 if (promise && promise.$$timeoutId in deferreds) { 13600 deferreds[promise.$$timeoutId].reject('canceled'); 13601 delete deferreds[promise.$$timeoutId]; 13602 return $browser.defer.cancel(promise.$$timeoutId); 13603 } 13604 return false; 13605 }; 13606 13607 return timeout; 13608 }]; 13609} 13610 13611// NOTE: The usage of window and document instead of $window and $document here is 13612// deliberate. This service depends on the specific behavior of anchor nodes created by the 13613// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 13614// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 13615// doesn't know about mocked locations and resolves URLs to the real document - which is 13616// exactly the behavior needed here. There is little value is mocking these out for this 13617// service. 13618var urlParsingNode = document.createElement("a"); 13619var originUrl = urlResolve(window.location.href, true); 13620 13621 13622/** 13623 * 13624 * Implementation Notes for non-IE browsers 13625 * ---------------------------------------- 13626 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 13627 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 13628 * URL will be resolved into an absolute URL in the context of the application document. 13629 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 13630 * properties are all populated to reflect the normalized URL. This approach has wide 13631 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 13632 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 13633 * 13634 * Implementation Notes for IE 13635 * --------------------------- 13636 * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other 13637 * browsers. However, the parsed components will not be set if the URL assigned did not specify 13638 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 13639 * work around that by performing the parsing in a 2nd step by taking a previously normalized 13640 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 13641 * properties such as protocol, hostname, port, etc. 13642 * 13643 * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one 13644 * uses the inner HTML approach to assign the URL as part of an HTML snippet - 13645 * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. 13646 * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. 13647 * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that 13648 * method and IE < 8 is unsupported. 13649 * 13650 * References: 13651 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 13652 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 13653 * http://url.spec.whatwg.org/#urlutils 13654 * https://github.com/angular/angular.js/pull/2902 13655 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 13656 * 13657 * @function 13658 * @param {string} url The URL to be parsed. 13659 * @description Normalizes and parses a URL. 13660 * @returns {object} Returns the normalized URL as a dictionary. 13661 * 13662 * | member name | Description | 13663 * |---------------|----------------| 13664 * | href | A normalized version of the provided URL if it was not an absolute URL | 13665 * | protocol | The protocol including the trailing colon | 13666 * | host | The host and port (if the port is non-default) of the normalizedUrl | 13667 * | search | The search params, minus the question mark | 13668 * | hash | The hash string, minus the hash symbol 13669 * | hostname | The hostname 13670 * | port | The port, without ":" 13671 * | pathname | The pathname, beginning with "/" 13672 * 13673 */ 13674function urlResolve(url, base) { 13675 var href = url; 13676 13677 if (msie) { 13678 // Normalize before parse. Refer Implementation Notes on why this is 13679 // done in two steps on IE. 13680 urlParsingNode.setAttribute("href", href); 13681 href = urlParsingNode.href; 13682 } 13683 13684 urlParsingNode.setAttribute('href', href); 13685 13686 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 13687 return { 13688 href: urlParsingNode.href, 13689 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 13690 host: urlParsingNode.host, 13691 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 13692 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 13693 hostname: urlParsingNode.hostname, 13694 port: urlParsingNode.port, 13695 pathname: (urlParsingNode.pathname.charAt(0) === '/') 13696 ? urlParsingNode.pathname 13697 : '/' + urlParsingNode.pathname 13698 }; 13699} 13700 13701/** 13702 * Parse a request URL and determine whether this is a same-origin request as the application document. 13703 * 13704 * @param {string|object} requestUrl The url of the request as a string that will be resolved 13705 * or a parsed URL object. 13706 * @returns {boolean} Whether the request is for the same origin as the application document. 13707 */ 13708function urlIsSameOrigin(requestUrl) { 13709 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 13710 return (parsed.protocol === originUrl.protocol && 13711 parsed.host === originUrl.host); 13712} 13713 13714/** 13715 * @ngdoc object 13716 * @name ng.$window 13717 * 13718 * @description 13719 * A reference to the browser's `window` object. While `window` 13720 * is globally available in JavaScript, it causes testability problems, because 13721 * it is a global variable. In angular we always refer to it through the 13722 * `$window` service, so it may be overridden, removed or mocked for testing. 13723 * 13724 * Expressions, like the one defined for the `ngClick` directive in the example 13725 * below, are evaluated with respect to the current scope. Therefore, there is 13726 * no risk of inadvertently coding in a dependency on a global value in such an 13727 * expression. 13728 * 13729 * @example 13730 <doc:example> 13731 <doc:source> 13732 <script> 13733 function Ctrl($scope, $window) { 13734 $scope.greeting = 'Hello, World!'; 13735 $scope.doGreeting = function(greeting) { 13736 $window.alert(greeting); 13737 }; 13738 } 13739 </script> 13740 <div ng-controller="Ctrl"> 13741 <input type="text" ng-model="greeting" /> 13742 <button ng-click="doGreeting(greeting)">ALERT</button> 13743 </div>
13744 </doc:source> 13745 <doc:scenario> 13746 it('should display the greeting in the input box', function() { 13747 input('greeting').enter('Hello, E2E Tests'); 13748 // If we click the button it will block the test runner 13749 // element(':button').click(); 13750 }); 13751 </doc:scenario> 13752 </doc:example> 13753 */ 13754function $WindowProvider(){ 13755 this.$get = valueFn(window); 13756} 13757 13758/** 13759 * @ngdoc object 13760 * @name ng.$filterProvider 13761 * @description 13762 * 13763 * Filters are just functions which transform input to an output. However filters need to be 13764 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 13765 * annotated with dependencies and is responsible for creating a filter function. 13766 * 13767 * <pre> 13768 * // Filter registration 13769 * function MyModule($provide, $filterProvider) { 13770 * // create a service to demonstrate injection (not always needed) 13771 * $provide.value('greet', function(name){ 13772 * return 'Hello ' + name + '!'; 13773 * }); 13774 * 13775 * // register a filter factory which uses the 13776 * // greet service to demonstrate DI. 13777 * $filterProvider.register('greet', function(greet){ 13778 * // return the filter function which uses the greet service 13779 * // to generate salutation 13780 * return function(text) { 13781 * // filters need to be forgiving so check input validity 13782 * return text && greet(text) || text; 13783 * }; 13784 * }); 13785 * } 13786 * </pre> 13787 * 13788 * The filter function is registered with the `$injector` under the filter name suffix with 13789 * `Filter`. 13790 * 13791 * <pre> 13792 * it('should be the same instance', inject( 13793 * function($filterProvider) { 13794 * $filterProvider.register('reverse', function(){ 13795 * return ...; 13796 * }); 13797 * }, 13798 * function($filter, reverseFilter) { 13799 * expect($filter('reverse')).toBe(reverseFilter); 13800 * }); 13801 * </pre> 13802 * 13803 * 13804 * For more information about how angular filters work, and how to create your own filters, see 13805 * {@link guide/filter Filters} in the Angular Developer Guide. 13806 */ 13807/** 13808 * @ngdoc method 13809 * @name ng.$filterProvider#register 13810 * @methodOf ng.$filterProvider 13811 * @description 13812 * Register filter factory function. 13813 * 13814 * @param {String} name Name of the filter. 13815 * @param {function} fn The filter factory function which is injectable. 13816 */ 13817 13818 13819/** 13820 * @ngdoc function 13821 * @name ng.$filter 13822 * @function 13823 * @description 13824 * Filters are used for formatting data displayed to the user. 13825 * 13826 * The general syntax in templates is as follows: 13827 * 13828 * {{ expression [| filter_name[:parameter_value] ... ] }} 13829 * 13830 * @param {String} name Name of the filter function to retrieve 13831 * @return {Function} the filter function 13832 */ 13833$FilterProvider.$inject = ['$provide']; 13834function $FilterProvider($provide) { 13835 var suffix = 'Filter'; 13836 13837 /** 13838 * @ngdoc function 13839 * @name ng.$controllerProvider#register 13840 * @methodOf ng.$controllerProvider 13841 * @param {string|Object} name Name of the filter function, or an object map of filters where 13842 * the keys are the filter names and the values are the filter factories. 13843 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 13844 * of the registered filter instances. 13845 */ 13846 function register(name, factory) { 13847 if(isObject(name)) { 13848 var filters = {}; 13849 forEach(name, function(filter, key) { 13850 filters[key] = register(key, filter); 13851 }); 13852 return filters; 13853 } else { 13854 return $provide.factory(name + suffix, factory); 13855 } 13856 } 13857 this.register = register; 13858 13859 this.$get = ['$injector', function($injector) { 13860 return function(name) { 13861 return $injector.get(name + suffix); 13862 }; 13863 }]; 13864 13865 //////////////////////////////////////// 13866 13867 /* global 13868 currencyFilter: false, 13869 dateFilter: false, 13870 filterFilter: false, 13871 jsonFilter: false, 13872 limitToFilter: false, 13873 lowercaseFilter: false, 13874 numberFilter: false, 13875 orderByFilter: false, 13876 uppercaseFilter: false, 13877 */ 13878 13879 register('currency', currencyFilter); 13880 register('date', dateFilter); 13881 register('filter', filterFilter); 13882 register('json', jsonFilter); 13883 register('limitTo', limitToFilter); 13884 register('lowercase', lowercaseFilter); 13885 register('number', numberFilter); 13886 register('orderBy', orderByFilter); 13887 register('uppercase', uppercaseFilter); 13888} 13889 13890/** 13891 * @ngdoc filter 13892 * @name ng.filter:filter 13893 * @function 13894 * 13895 * @description 13896 * Selects a subset of items from `array` and returns it as a new array. 13897 * 13898 * @param {Array} array The source array. 13899 * @param {string|Object|function()} expression The predicate to be used for selecting items from 13900 * `array`. 13901 * 13902 * Can be one of: 13903 * 13904 * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against
13905 * the contents of the `array`. All strings or objects with string properties in `array` that contain this string 13906 * will be returned. The predicate can be negated by prefixing the string with `!`. 13907 * 13908 * - `Object`: A pattern object can be used to filter specific properties on objects contained 13909 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 13910 * which have property `name` containing "M" and property `phone` containing "1". A special 13911 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 13912 * property of the object. That's equivalent to the simple substring match with a `string` 13913 * as described above. 13914 * 13915 * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is 13916 * called for each element of `array`. The final result is an array of those elements that 13917 * the predicate returned true for. 13918 * 13919 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 13920 * determining if the expected value (from the filter expression) and actual value (from 13921 * the object in the array) should be considered a match. 13922 * 13923 * Can be one of: 13924 * 13925 * - `function(actual, expected)`: 13926 * The function will be given the object value and the predicate value to compare and 13927 * should return true if the item should be included in filtered result. 13928 * 13929 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. 13930 * this is essentially strict comparison of expected and actual. 13931 * 13932 * - `false|undefined`: A short hand for a function which will look for a substring match in case 13933 * insensitive way. 13934 * 13935 * @example 13936 <doc:example> 13937 <doc:source> 13938 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 13939 {name:'Mary', phone:'800-BIG-MARY'}, 13940 {name:'Mike', phone:'555-4321'}, 13941 {name:'Adam', phone:'555-5678'}, 13942 {name:'Julie', phone:'555-8765'}, 13943 {name:'Juliette', phone:'555-5678'}]"></div> 13944 13945 Search: <input ng-model="searchText"> 13946 <table id="searchTextResults"> 13947 <tr><th>Name</th><th>Phone</th></tr> 13948 <tr ng-repeat="friend in friends | filter:searchText"> 13949 <td>{{friend.name}}</td> 13950 <td>{{friend.phone}}</td> 13951 </tr> 13952 </table> 13953 <hr> 13954 Any: <input ng-model="search.$"> <br> 13955 Name only <input ng-model="search.name"><br> 13956 Phone only <input ng-model="search.phone"><br> 13957 Equality <input type="checkbox" ng-model="strict"><br> 13958 <table id="searchObjResults"> 13959 <tr><th>Name</th><th>Phone</th></tr> 13960 <tr ng-repeat="friend in friends | filter:search:strict"> 13961 <td>{{friend.name}}</td> 13962 <td>{{friend.phone}}</td> 13963 </tr> 13964 </table>
13965 </doc:source> 13966 <doc:scenario> 13967 it('should search across all fields when filtering with a string', function() { 13968 input('searchText').enter('m'); 13969 expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')). 13970 toEqual(['Mary', 'Mike', 'Adam']); 13971 13972 input('searchText').enter('76'); 13973 expect(repeater('#searchTextResults tr', 'friend in friends').column('friend.name')). 13974 toEqual(['John', 'Julie']); 13975 }); 13976 13977 it('should search in specific fields when filtering with a predicate object', function() { 13978 input('search.$').enter('i'); 13979 expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')). 13980 toEqual(['Mary', 'Mike', 'Julie', 'Juliette']); 13981 }); 13982 it('should use a equal comparison when comparator is true', function() { 13983 input('search.name').enter('Julie'); 13984 input('strict').check(); 13985 expect(repeater('#searchObjResults tr', 'friend in friends').column('friend.name')). 13986 toEqual(['Julie']); 13987 }); 13988 </doc:scenario> 13989 </doc:example> 13990 */ 13991function filterFilter() { 13992 return function(array, expression, comparator) { 13993 if (!isArray(array)) return array; 13994 13995 var comparatorType = typeof(comparator), 13996 predicates = []; 13997 13998 predicates.check = function(value) { 13999 for (var j = 0; j < predicates.length; j++) { 14000 if(!predicates[j](value)) { 14001 return false; 14002 } 14003 } 14004 return true; 14005 }; 14006 14007 if (comparatorType !== 'function') { 14008 if (comparatorType === 'boolean' && comparator) { 14009 comparator = function(obj, text) { 14010 return angular.equals(obj, text); 14011 }; 14012 } else { 14013 comparator = function(obj, text) { 14014 text = (''+text).toLowerCase(); 14015 return (''+obj).toLowerCase().indexOf(text) > -1; 14016 }; 14017 } 14018 } 14019 14020 var search = function(obj, text){ 14021 if (typeof text == 'string' && text.charAt(0) === '!') { 14022 return !search(obj, text.substr(1)); 14023 } 14024 switch (typeof obj) { 14025 case "boolean": 14026 case "number": 14027 case "string": 14028 return comparator(obj, text); 14029 case "object": 14030 switch (typeof text) { 14031 case "object": 14032 return comparator(obj, text); 14033 default: 14034 for ( var objKey in obj) { 14035 if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { 14036 return true; 14037 } 14038 } 14039 break; 14040 } 14041 return false; 14042 case "array": 14043 for ( var i = 0; i < obj.length; i++) { 14044 if (search(obj[i], text)) { 14045 return true; 14046 } 14047 } 14048 return false; 14049 default: 14050 return false; 14051 } 14052 }; 14053 switch (typeof expression) { 14054 case "boolean": 14055 case "number": 14056 case "string": 14057 // Set up expression object and fall through 14058 expression = {$:expression}; 14059 // jshint -W086 14060 case "object": 14061 // jshint +W086 14062 for (var key in expression) { 14063 (function(path) { 14064 if (typeof expression[path] == 'undefined') return; 14065 predicates.push(function(value) { 14066 return search(path == '$' ? value : getter(value, path), expression[path]); 14067 }); 14068 })(key); 14069 } 14070 break; 14071 case 'function': 14072 predicates.push(expression); 14073 break; 14074 default: 14075 return array; 14076 } 14077 var filtered = []; 14078 for ( var j = 0; j < array.length; j++) { 14079 var value = array[j]; 14080 if (predicates.check(value)) { 14081 filtered.push(value); 14082 } 14083 } 14084 return filtered; 14085 }; 14086} 14087 14088/** 14089 * @ngdoc filter 14090 * @name ng.filter:currency 14091 * @function 14092 * 14093 * @description 14094 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 14095 * symbol for current locale is used. 14096 * 14097 * @param {number} amount Input to filter. 14098 * @param {string=} symbol Currency symbol or identifier to be displayed. 14099 * @returns {string} Formatted number. 14100 * 14101 * 14102 * @example 14103 <doc:example> 14104 <doc:source> 14105 <script> 14106 function Ctrl($scope) { 14107 $scope.amount = 1234.56; 14108 } 14109 </script> 14110 <div ng-controller="Ctrl"> 14111 <input type="number" ng-model="amount"> <br> 14112 default currency symbol ($): {{amount | currency}}<br> 14113 custom currency identifier (USD$): {{amount | currency:"USD$"}} 14114 </div>
14115 </doc:source> 14116 <doc:scenario> 14117 it('should init with 1234.56', function() { 14118 expect(binding('amount | currency')).toBe('$1,234.56'); 14119 expect(binding('amount | currency:"USD$"')).toBe('USD$1,234.56'); 14120 }); 14121 it('should update', function() { 14122 input('amount').enter('-1234'); 14123 expect(binding('amount | currency')).toBe('($1,234.00)'); 14124 expect(binding('amount | currency:"USD$"')).toBe('(USD$1,234.00)'); 14125 }); 14126 </doc:scenario> 14127 </doc:example> 14128 */ 14129currencyFilter.$inject = ['$locale']; 14130function currencyFilter($locale) { 14131 var formats = $locale.NUMBER_FORMATS; 14132 return function(amount, currencySymbol){ 14133 if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; 14134 return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). 14135 replace(/\u00A4/g, currencySymbol); 14136 }; 14137} 14138 14139/** 14140 * @ngdoc filter 14141 * @name ng.filter:number 14142 * @function 14143 * 14144 * @description 14145 * Formats a number as text. 14146 * 14147 * If the input is not a number an empty string is returned. 14148 * 14149 * @param {number|string} number Number to format. 14150 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 14151 * If this is not provided then the fraction size is computed from the current locale's number 14152 * formatting pattern. In the case of the default locale, it will be 3. 14153 * @returns {string} Number rounded to decimalPlaces and places a â,â after each third digit. 14154 * 14155 * @example 14156 <doc:example> 14157 <doc:source> 14158 <script> 14159 function Ctrl($scope) { 14160 $scope.val = 1234.56789; 14161 } 14162 </script> 14163 <div ng-controller="Ctrl"> 14164 Enter number: <input ng-model='val'><br> 14165 Default formatting: {{val | number}}<br> 14166 No fractions: {{val | number:0}}<br> 14167 Negative number: {{-val | number:4}} 14168 </div> 14169 </doc:source> 14170 <doc:scenario> 14171 it('should format numbers', function() { 14172 expect(binding('val | number')).toBe('1,234.568'); 14173 expect(binding('val | number:0')).toBe('1,235'); 14174 expect(binding('-val | number:4')).toBe('-1,234.5679'); 14175 }); 14176 14177 it('should update', function() { 14178 input('val').enter('3374.333'); 14179 expect(binding('val | number')).toBe('3,374.333'); 14180 expect(binding('val | number:0')).toBe('3,374'); 14181 expect(binding('-val | number:4')).toBe('-3,374.3330'); 14182 }); 14183 </doc:scenario> 14184 </doc:example> 14185 */ 14186 14187 14188numberFilter.$inject = ['$locale']; 14189function numberFilter($locale) { 14190 var formats = $locale.NUMBER_FORMATS; 14191 return function(number, fractionSize) { 14192 return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 14193 fractionSize); 14194 }; 14195} 14196 14197var DECIMAL_SEP = '.'; 14198function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 14199 if (isNaN(number) || !isFinite(number)) return ''; 14200 14201 var isNegative = number < 0; 14202 number = Math.abs(number); 14203 var numStr = number + '', 14204 formatedText = '', 14205 parts = []; 14206 14207 var hasExponent = false; 14208 if (numStr.indexOf('e') !== -1) { 14209 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 14210 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 14211 numStr = '0'; 14212 } else { 14213 formatedText = numStr; 14214 hasExponent = true; 14215 } 14216 } 14217 14218 if (!hasExponent) { 14219 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 14220 14221 // determine fractionSize if it is not specified 14222 if (isUndefined(fractionSize)) { 14223 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 14224 } 14225 14226 var pow = Math.pow(10, fractionSize); 14227 number = Math.round(number * pow) / pow; 14228 var fraction = ('' + number).split(DECIMAL_SEP); 14229 var whole = fraction[0]; 14230 fraction = fraction[1] || ''; 14231 14232 var i, pos = 0, 14233 lgroup = pattern.lgSize, 14234 group = pattern.gSize; 14235 14236 if (whole.length >= (lgroup + group)) { 14237 pos = whole.length - lgroup; 14238 for (i = 0; i < pos; i++) { 14239 if ((pos - i)%group === 0 && i !== 0) { 14240 formatedText += groupSep; 14241 } 14242 formatedText += whole.charAt(i); 14243 } 14244 } 14245 14246 for (i = pos; i < whole.length; i++) { 14247 if ((whole.length - i)%lgroup === 0 && i !== 0) { 14248 formatedText += groupSep; 14249 } 14250 formatedText += whole.charAt(i); 14251 } 14252 14253 // format fraction part. 14254 while(fraction.length < fractionSize) { 14255 fraction += '0'; 14256 } 14257 14258 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 14259 } else { 14260 14261 if (fractionSize > 0 && number > -1 && number < 1) { 14262 formatedText = number.toFixed(fractionSize); 14263 } 14264 } 14265 14266 parts.push(isNegative ? pattern.negPre : pattern.posPre); 14267 parts.push(formatedText); 14268 parts.push(isNegative ? pattern.negSuf : pattern.posSuf); 14269 return parts.join(''); 14270} 14271 14272function padNumber(num, digits, trim) { 14273 var neg = ''; 14274 if (num < 0) { 14275 neg = '-'; 14276 num = -num; 14277 } 14278 num = '' + num; 14279 while(num.length < digits) num = '0' + num; 14280 if (trim) 14281 num = num.substr(num.length - digits); 14282 return neg + num; 14283} 14284 14285 14286function dateGetter(name, size, offset, trim) { 14287 offset = offset || 0; 14288 return function(date) { 14289 var value = date['get' + name](); 14290 if (offset > 0 || value > -offset) 14291 value += offset; 14292 if (value === 0 && offset == -12 ) value = 12; 14293 return padNumber(value, size, trim); 14294 }; 14295} 14296 14297function dateStrGetter(name, shortForm) { 14298 return function(date, formats) { 14299 var value = date['get' + name](); 14300 var get = uppercase(shortForm ? ('SHORT' + name) : name); 14301 14302 return formats[get][value]; 14303 }; 14304} 14305 14306function timeZoneGetter(date) { 14307 var zone = -1 * date.getTimezoneOffset(); 14308 var paddedZone = (zone >= 0) ? "+" : ""; 14309 14310 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 14311 padNumber(Math.abs(zone % 60), 2); 14312 14313 return paddedZone; 14314} 14315 14316function ampmGetter(date, formats) { 14317 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 14318} 14319 14320var DATE_FORMATS = { 14321 yyyy: dateGetter('FullYear', 4), 14322 yy: dateGetter('FullYear', 2, 0, true), 14323 y: dateGetter('FullYear', 1), 14324 MMMM: dateStrGetter('Month'),
14325 MMM: dateStrGetter('Month', true), 14326 MM: dateGetter('Month', 2, 1), 14327 M: dateGetter('Month', 1, 1), 14328 dd: dateGetter('Date', 2), 14329 d: dateGetter('Date', 1), 14330 HH: dateGetter('Hours', 2), 14331 H: dateGetter('Hours', 1), 14332 hh: dateGetter('Hours', 2, -12), 14333 h: dateGetter('Hours', 1, -12), 14334 mm: dateGetter('Minutes', 2), 14335 m: dateGetter('Minutes', 1), 14336 ss: dateGetter('Seconds', 2), 14337 s: dateGetter('Seconds', 1), 14338 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 14339 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 14340 sss: dateGetter('Milliseconds', 3), 14341 EEEE: dateStrGetter('Day'), 14342 EEE: dateStrGetter('Day', true), 14343 a: ampmGetter, 14344 Z: timeZoneGetter 14345}; 14346 14347var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, 14348 NUMBER_STRING = /^\-?\d+$/; 14349 14350/** 14351 * @ngdoc filter 14352 * @name ng.filter:date 14353 * @function 14354 * 14355 * @description 14356 * Formats `date` to a string based on the requested `format`. 14357 * 14358 * `format` string can be composed of the following elements: 14359 * 14360 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 14361 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 14362 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 14363 * * `'MMMM'`: Month in year (January-December) 14364 * * `'MMM'`: Month in year (Jan-Dec) 14365 * * `'MM'`: Month in year, padded (01-12) 14366 * * `'M'`: Month in year (1-12) 14367 * * `'dd'`: Day in month, padded (01-31) 14368 * * `'d'`: Day in month (1-31) 14369 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 14370 * * `'EEE'`: Day in Week, (Sun-Sat) 14371 * * `'HH'`: Hour in day, padded (00-23) 14372 * * `'H'`: Hour in day (0-23) 14373 * * `'hh'`: Hour in am/pm, padded (01-12) 14374 * * `'h'`: Hour in am/pm, (1-12) 14375 * * `'mm'`: Minute in hour, padded (00-59) 14376 * * `'m'`: Minute in hour (0-59) 14377 * * `'ss'`: Second in minute, padded (00-59) 14378 * * `'s'`: Second in minute (0-59) 14379 * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) 14380 * * `'a'`: am/pm marker 14381 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 14382 * 14383 * `format` string can also be one of the following predefined 14384 * {@link guide/i18n localizable formats}: 14385 * 14386 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 14387 * (e.g. Sep 3, 2010 12:05:08 pm) 14388 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) 14389 * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale 14390 * (e.g. Friday, September 3, 2010) 14391 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 14392 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 14393 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 14394 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) 14395 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) 14396 * 14397 * `format` string can contain literal values. These need to be quoted with single quotes (e.g. 14398 * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence 14399 * (e.g. `"h 'o''clock'"`). 14400 * 14401 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 14402 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its 14403 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 14404 * specified in the string input, the time is considered to be in the local timezone. 14405 * @param {string=} format Formatting rules (see Description). If not specified, 14406 * `mediumDate` is used. 14407 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 14408 * 14409 * @example 14410 <doc:example> 14411 <doc:source> 14412 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 14413 {{1288323623006 | date:'medium'}}<br> 14414 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 14415 {{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}<br>
14416 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 14417 {{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}<br> 14418 </doc:source> 14419 <doc:scenario> 14420 it('should format date', function() { 14421 expect(binding("1288323623006 | date:'medium'")). 14422 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 14423 expect(binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")). 14424 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 14425 expect(binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")). 14426 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 14427 }); 14428 </doc:scenario> 14429 </doc:example> 14430 */ 14431dateFilter.$inject = ['$locale']; 14432function dateFilter($locale) { 14433 14434 14435 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 14436 // 1 2 3 4 5 6 7 8 9 10 11 14437 function jsonStringToDate(string) { 14438 var match; 14439 if (match = string.match(R_ISO8601_STR)) { 14440 var date = new Date(0), 14441 tzHour = 0, 14442 tzMin = 0, 14443 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 14444 timeSetter = match[8] ? date.setUTCHours : date.setHours; 14445 14446 if (match[9]) { 14447 tzHour = int(match[9] + match[10]); 14448 tzMin = int(match[9] + match[11]); 14449 } 14450 dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); 14451 var h = int(match[4]||0) - tzHour; 14452 var m = int(match[5]||0) - tzMin; 14453 var s = int(match[6]||0); 14454 var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); 14455 timeSetter.call(date, h, m, s, ms); 14456 return date; 14457 } 14458 return string; 14459 } 14460 14461 14462 return function(date, format) { 14463 var text = '', 14464 parts = [], 14465 fn, match; 14466 14467 format = format || 'mediumDate'; 14468 format = $locale.DATETIME_FORMATS[format] || format; 14469 if (isString(date)) { 14470 if (NUMBER_STRING.test(date)) { 14471 date = int(date); 14472 } else { 14473 date = jsonStringToDate(date); 14474 } 14475 } 14476 14477 if (isNumber(date)) { 14478 date = new Date(date); 14479 } 14480 14481 if (!isDate(date)) { 14482 return date; 14483 } 14484 14485 while(format) { 14486 match = DATE_FORMATS_SPLIT.exec(format); 14487 if (match) { 14488 parts = concat(parts, match, 1); 14489 format = parts.pop(); 14490 } else { 14491 parts.push(format); 14492 format = null; 14493 } 14494 } 14495 14496 forEach(parts, function(value){ 14497 fn = DATE_FORMATS[value]; 14498 text += fn ? fn(date, $locale.DATETIME_FORMATS) 14499 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 14500 }); 14501 14502 return text; 14503 }; 14504} 14505 14506 14507/** 14508 * @ngdoc filter 14509 * @name ng.filter:json 14510 * @function 14511 * 14512 * @description 14513 * Allows you to convert a JavaScript object into JSON string. 14514 * 14515 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 14516 * the binding is automatically converted to JSON. 14517 * 14518 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 14519 * @returns {string} JSON string. 14520 * 14521 * 14522 * @example: 14523 <doc:example> 14524 <doc:source> 14525 <pre>{{ {'name':'value'} | json }}</pre>
14526 </doc:source> 14527 <doc:scenario> 14528 it('should jsonify filtered objects', function() { 14529 expect(binding("{'name':'value'}")).toMatch(/\{\n "name": ?"value"\n}/); 14530 }); 14531 </doc:scenario> 14532 </doc:example> 14533 * 14534 */ 14535function jsonFilter() { 14536 return function(object) { 14537 return toJson(object, true); 14538 }; 14539} 14540 14541 14542/** 14543 * @ngdoc filter 14544 * @name ng.filter:lowercase 14545 * @function 14546 * @description 14547 * Converts string to lowercase. 14548 * @see angular.lowercase 14549 */ 14550var lowercaseFilter = valueFn(lowercase); 14551 14552 14553/** 14554 * @ngdoc filter 14555 * @name ng.filter:uppercase 14556 * @function 14557 * @description 14558 * Converts string to uppercase. 14559 * @see angular.uppercase 14560 */ 14561var uppercaseFilter = valueFn(uppercase); 14562 14563/** 14564 * @ngdoc function 14565 * @name ng.filter:limitTo 14566 * @function 14567 * 14568 * @description 14569 * Creates a new array or string containing only a specified number of elements. The elements 14570 * are taken from either the beginning or the end of the source array or string, as specified by 14571 * the value and sign (positive or negative) of `limit`. 14572 * 14573 * @param {Array|string} input Source array or string to be limited. 14574 * @param {string|number} limit The length of the returned array or string. If the `limit` number 14575 * is positive, `limit` number of items from the beginning of the source array/string are copied. 14576 * If the number is negative, `limit` number of items from the end of the source array/string 14577 * are copied. The `limit` will be trimmed if it exceeds `array.length` 14578 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 14579 * had less than `limit` elements. 14580 * 14581 * @example 14582 <doc:example> 14583 <doc:source> 14584 <script> 14585 function Ctrl($scope) { 14586 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 14587 $scope.letters = "abcdefghi"; 14588 $scope.numLimit = 3; 14589 $scope.letterLimit = 3; 14590 } 14591 </script> 14592 <div ng-controller="Ctrl"> 14593 Limit {{numbers}} to: <input type="integer" ng-model="numLimit"> 14594 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 14595 Limit {{letters}} to: <input type="integer" ng-model="letterLimit"> 14596 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 14597 </div> 14598 </doc:source> 14599 <doc:scenario> 14600 it('should limit the number array to first three items', function() { 14601 expect(element('.doc-example-live input[ng-model=numLimit]').val()).toBe('3'); 14602 expect(element('.doc-example-live input[ng-model=letterLimit]').val()).toBe('3'); 14603 expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3]'); 14604 expect(binding('letters | limitTo:letterLimit')).toEqual('abc'); 14605 }); 14606 14607 it('should update the output when -3 is entered', function() { 14608 input('numLimit').enter(-3); 14609 input('letterLimit').enter(-3); 14610 expect(binding('numbers | limitTo:numLimit')).toEqual('[7,8,9]'); 14611 expect(binding('letters | limitTo:letterLimit')).toEqual('ghi'); 14612 }); 14613 14614 it('should not exceed the maximum size of input array', function() { 14615 input('numLimit').enter(100); 14616 input('letterLimit').enter(100); 14617 expect(binding('numbers | limitTo:numLimit')).toEqual('[1,2,3,4,5,6,7,8,9]'); 14618 expect(binding('letters | limitTo:letterLimit')).toEqual('abcdefghi'); 14619 }); 14620 </doc:scenario> 14621 </doc:example> 14622 */ 14623function limitToFilter(){ 14624 return function(input, limit) { 14625 if (!isArray(input) && !isString(input)) return input; 14626 14627 limit = int(limit); 14628 14629 if (isString(input)) { 14630 //NaN check on limit 14631 if (limit) { 14632 return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); 14633 } else { 14634 return ""; 14635 } 14636 } 14637 14638 var out = [], 14639 i, n; 14640 14641 // if abs(limit) exceeds maximum length, trim it 14642 if (limit > input.length) 14643 limit = input.length; 14644 else if (limit < -input.length) 14645 limit = -input.length; 14646 14647 if (limit > 0) { 14648 i = 0; 14649 n = limit; 14650 } else { 14651 i = input.length + limit; 14652 n = input.length; 14653 } 14654 14655 for (; i<n; i++) { 14656 out.push(input[i]); 14657 } 14658 14659 return out; 14660 }; 14661} 14662 14663/** 14664 * @ngdoc function 14665 * @name ng.filter:orderBy 14666 * @function 14667 * 14668 * @description 14669 * Orders a specified `array` by the `expression` predicate. 14670 * 14671 * @param {Array} array The array to sort. 14672 * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be 14673 * used by the comparator to determine the order of elements. 14674 * 14675 * Can be one of: 14676 * 14677 * - `function`: Getter function. The result of this function will be sorted using the 14678 * `<`, `=`, `>` operator. 14679 * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' 14680 * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control 14681 * ascending or descending sort order (for example, +name or -name). 14682 * - `Array`: An array of function or string predicates. The first predicate in the array 14683 * is used for sorting, but when two items are equivalent, the next predicate is used. 14684 * 14685 * @param {boolean=} reverse Reverse the order the array. 14686 * @returns {Array} Sorted copy of the source array. 14687 * 14688 * @example 14689 <doc:example> 14690 <doc:source> 14691 <script> 14692 function Ctrl($scope) { 14693 $scope.friends = 14694 [{name:'John', phone:'555-1212', age:10}, 14695 {name:'Mary', phone:'555-9876', age:19}, 14696 {name:'Mike', phone:'555-4321', age:21}, 14697 {name:'Adam', phone:'555-5678', age:35}, 14698 {name:'Julie', phone:'555-8765', age:29}] 14699 $scope.predicate = '-age'; 14700 } 14701 </script> 14702 <div ng-controller="Ctrl"> 14703 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 14704 <hr/> 14705 [ <a href="" ng-click="predicate=''">unsorted</a> ] 14706 <table class="friend"> 14707 <tr> 14708 <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a> 14709 (<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th> 14710 <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> 14711 <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th> 14712 </tr> 14713 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 14714 <td>{{friend.name}}</td> 14715 <td>{{friend.phone}}</td> 14716 <td>{{friend.age}}</td> 14717 </tr> 14718 </table> 14719 </div>
14720 </doc:source> 14721 <doc:scenario> 14722 it('should be reverse ordered by aged', function() { 14723 expect(binding('predicate')).toBe('-age'); 14724 expect(repeater('table.friend', 'friend in friends').column('friend.age')). 14725 toEqual(['35', '29', '21', '19', '10']); 14726 expect(repeater('table.friend', 'friend in friends').column('friend.name')). 14727 toEqual(['Adam', 'Julie', 'Mike', 'Mary', 'John']); 14728 }); 14729 14730 it('should reorder the table when user selects different predicate', function() { 14731 element('.doc-example-live a:contains("Name")').click(); 14732 expect(repeater('table.friend', 'friend in friends').column('friend.name')). 14733 toEqual(['Adam', 'John', 'Julie', 'Mary', 'Mike']); 14734 expect(repeater('table.friend', 'friend in friends').column('friend.age')). 14735 toEqual(['35', '10', '29', '19', '21']); 14736 14737 element('.doc-example-live a:contains("Phone")').click(); 14738 expect(repeater('table.friend', 'friend in friends').column('friend.phone')). 14739 toEqual(['555-9876', '555-8765', '555-5678', '555-4321', '555-1212']); 14740 expect(repeater('table.friend', 'friend in friends').column('friend.name')). 14741 toEqual(['Mary', 'Julie', 'Adam', 'Mike', 'John']); 14742 }); 14743 </doc:scenario> 14744 </doc:example> 14745 */ 14746orderByFilter.$inject = ['$parse']; 14747function orderByFilter($parse){ 14748 return function(array, sortPredicate, reverseOrder) { 14749 if (!isArray(array)) return array; 14750 if (!sortPredicate) return array; 14751 sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; 14752 sortPredicate = map(sortPredicate, function(predicate){ 14753 var descending = false, get = predicate || identity; 14754 if (isString(predicate)) { 14755 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 14756 descending = predicate.charAt(0) == '-'; 14757 predicate = predicate.substring(1); 14758 } 14759 get = $parse(predicate); 14760 } 14761 return reverseComparator(function(a,b){ 14762 return compare(get(a),get(b)); 14763 }, descending); 14764 }); 14765 var arrayCopy = []; 14766 for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } 14767 return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); 14768 14769 function comparator(o1, o2){ 14770 for ( var i = 0; i < sortPredicate.length; i++) { 14771 var comp = sortPredicate[i](o1, o2); 14772 if (comp !== 0) return comp; 14773 } 14774 return 0; 14775 } 14776 function reverseComparator(comp, descending) { 14777 return toBoolean(descending) 14778 ? function(a,b){return comp(b,a);} 14779 : comp; 14780 } 14781 function compare(v1, v2){ 14782 var t1 = typeof v1; 14783 var t2 = typeof v2; 14784 if (t1 == t2) { 14785 if (t1 == "string") { 14786 v1 = v1.toLowerCase(); 14787 v2 = v2.toLowerCase(); 14788 } 14789 if (v1 === v2) return 0; 14790 return v1 < v2 ? -1 : 1; 14791 } else { 14792 return t1 < t2 ? -1 : 1; 14793 } 14794 } 14795 }; 14796} 14797 14798function ngDirective(directive) { 14799 if (isFunction(directive)) { 14800 directive = { 14801 link: directive 14802 }; 14803 } 14804 directive.restrict = directive.restrict || 'AC'; 14805 return valueFn(directive); 14806} 14807 14808/** 14809 * @ngdoc directive 14810 * @name ng.directive:a 14811 * @restrict E 14812 * 14813 * @description 14814 * Modifies the default behavior of the html A tag so that the default action is prevented when 14815 * the href attribute is empty. 14816 * 14817 * This change permits the easy creation of action links with the `ngClick` directive 14818 * without changing the location or causing page reloads, e.g.: 14819 * `<a href="" ng-click="list.addItem()">Add Item</a>` 14820 */ 14821var htmlAnchorDirective = valueFn({ 14822 restrict: 'E', 14823 compile: function(element, attr) { 14824 14825 if (msie <= 8) { 14826 14827 // turn <a href ng-click="..">link</a> into a stylable link in IE 14828 // but only if it doesn't have name attribute, in which case it's an anchor 14829 if (!attr.href && !attr.name) { 14830 attr.$set('href', ''); 14831 } 14832 14833 // add a comment node to anchors to workaround IE bug that causes element content to be reset 14834 // to new attribute content if attribute is updated with value containing @ and element also 14835 // contains value with @ 14836 // see issue #1949 14837 element.append(document.createComment('IE fix')); 14838 } 14839 14840 if (!attr.href && !attr.xlinkHref && !attr.name) { 14841 return function(scope, element) { 14842 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 14843 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 14844 'xlink:href' : 'href'; 14845 element.on('click', function(event){ 14846 // if we have no href url, then don't navigate anywhere. 14847 if (!element.attr(href)) { 14848 event.preventDefault(); 14849 } 14850 }); 14851 }; 14852 } 14853 } 14854}); 14855 14856/** 14857 * @ngdoc directive 14858 * @name ng.directive:ngHref 14859 * @restrict A 14860 * @priority 99 14861 * 14862 * @description 14863 * Using Angular markup like `{{hash}}` in an href attribute will 14864 * make the link go to the wrong URL if the user clicks it before 14865 * Angular has a chance to replace the `{{hash}}
14865` markup with its 14866 * value. Until Angular replaces the markup the link will be broken 14867 * and will most likely return a 404 error. 14868 * 14869 * The `ngHref` directive solves this problem. 14870 * 14871 * The wrong way to write it: 14872 * <pre> 14873 * <a href="http://www.gravatar.com/avatar/{{hash}}"/> 14874 * </pre> 14875 * 14876 * The correct way to write it: 14877 * <pre> 14878 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/> 14879 * </pre> 14880 * 14881 * @element A 14882 * @param {template} ngHref any string which can contain `{{}}` markup. 14883 * 14884 * @example 14885 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 14886 * in links and their different behaviors: 14887 <doc:example> 14888 <doc:source> 14889 <input ng-model="value" /><br /> 14890 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 14891 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 14892 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 14893 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 14894 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 14895 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 14896 </doc:source> 14897 <doc:scenario> 14898 it('should execute ng-click but not reload when href without value', function() { 14899 element('#link-1').click(); 14900 expect(input('value').val()).toEqual('1'); 14901 expect(element('#link-1').attr('href')).toBe(""); 14902 }); 14903 14904 it('should execute ng-click but not reload when href empty string', function() { 14905 element('#link-2').click(); 14906 expect(input('value').val()).toEqual('2'); 14907 expect(element('#link-2').attr('href')).toBe(""); 14908 }); 14909 14910 it('should execute ng-click and change url when ng-href specified', function() { 14911 expect(element('#link-3').attr('href')).toBe("/123"); 14912 14913 element('#link-3').click(); 14914 expect(browser().window().path()).toEqual('/123'); 14915 }); 14916 14917 it('should execute ng-click but not reload when href empty string and name specified', function() { 14918 element('#link-4').click(); 14919 expect(input('value').val()).toEqual('4'); 14920 expect(element('#link-4').attr('href')).toBe(''); 14921 }); 14922 14923 it('should execute ng-click but not reload when no href but name specified', function() { 14924 element('#link-5').click(); 14925 expect(input('value').val()).toEqual('5'); 14926 expect(element('#link-5').attr('href')).toBe(undefined); 14927 }); 14928 14929 it('should only change url when only ng-href', function() { 14930 input('value').enter('6'); 14931 expect(element('#link-6').attr('href')).toBe('6'); 14932 14933 element('#link-6').click(); 14934 expect(browser().location().url()).toEqual('/6'); 14935 }); 14936 </doc:scenario> 14937 </doc:example> 14938 */ 14939 14940/** 14941 * @ngdoc directive 14942 * @name ng.directive:ngSrc 14943 * @restrict A 14944 * @priority 99 14945 * 14946 * @description 14947 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 14948 * work right: The browser will fetch from the URL with the literal 14949 * text `{{hash}}` until Angular replaces the expression inside 14950 * `{{hash}}`. The `ngSrc` directive solves this problem. 14951 * 14952 * The buggy way to write it: 14953 * <pre> 14954 * <img src="http://www.gravatar.com/avatar/{{hash}}"/> 14955 * </pre> 14956 * 14957 * The correct way to write it: 14958 * <pre> 14959 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/> 14960 * </pre> 14961 * 14962 * @element IMG 14963 * @param {template} ngSrc any string which can contain `{{}}` markup. 14964 */ 14965 14966/** 14967 * @ngdoc directive 14968 * @name ng.directive:ngSrcset 14969 * @restrict A 14970 * @priority 99 14971 * 14972 * @description 14973 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 14974 * work right: The browser will fetch from the URL with the literal 14975 * text `{{hash}}` until Angular replaces the expression inside 14976 * `{{hash}}`. The `ngSrcset` directive solves this problem. 14977 * 14978 * The buggy way to write it: 14979 * <pre> 14980 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 14981 * </pre> 14982 * 14983 * The correct way to write it: 14984 * <pre> 14985 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 14986 * </pre> 14987 * 14988 * @element IMG 14989 * @param {template} ngSrcset any string which can contain `{{}}` markup. 14990 */ 14991 14992/** 14993 * @ngdoc directive 14994 * @name ng.directive:ngDisabled 14995 * @restrict A 14996 * @priority 100 14997 * 14998 * @description 14999 *
15000 * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: 15001 * <pre> 15002 * <div ng-init="scope = { isDisabled: false }"> 15003 * <button disabled="{{scope.isDisabled}}">Disabled</button> 15004 * </div> 15005 * </pre> 15006 * 15007 * The HTML specification does not require browsers to preserve the values of boolean attributes 15008 * such as disabled. (Their presence means true and their absence means false.) 15009 * If we put an Angular interpolation expression into such an attribute then the 15010 * binding information would be lost when the browser removes the attribute. 15011 * The `ngDisabled` directive solves this problem for the `disabled` attribute. 15012 * This complementary directive is not removed by the browser and so provides 15013 * a permanent reliable place to store the binding information. 15014 * 15015 * @example 15016 <doc:example> 15017 <doc:source> 15018 Click me to toggle: <input type="checkbox" ng-model="checked"><br/> 15019 <button ng-model="button" ng-disabled="checked">Button</button> 15020 </doc:source> 15021 <doc:scenario> 15022 it('should toggle button', function() { 15023 expect(element('.doc-example-live :button').prop('disabled')).toBeFalsy(); 15024 input('checked').check(); 15025 expect(element('.doc-example-live :button').prop('disabled')).toBeTruthy(); 15026 }); 15027 </doc:scenario> 15028 </doc:example> 15029 * 15030 * @element INPUT 15031 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 15032 * then special attribute "disabled" will be set on the element 15033 */ 15034 15035 15036/** 15037 * @ngdoc directive 15038 * @name ng.directive:ngChecked 15039 * @restrict A 15040 * @priority 100 15041 * 15042 * @description 15043 * The HTML specification does not require browsers to preserve the values of boolean attributes 15044 * such as checked. (Their presence means true and their absence means false.) 15045 * If we put an Angular interpolation expression into such an attribute then the 15046 * binding information would be lost when the browser removes the attribute. 15047 * The `ngChecked` directive solves this problem for the `checked` attribute. 15048 * This complementary directive is not removed by the browser and so provides 15049 * a permanent reliable place to store the binding information. 15050 * @example 15051 <doc:example> 15052 <doc:source> 15053 Check me to check both: <input type="checkbox" ng-model="master"><br/> 15054 <input id="checkSlave" type="checkbox" ng-checked="master"> 15055 </doc:source> 15056 <doc:scenario> 15057 it('should check both checkBoxes', function() { 15058 expect(element('.doc-example-live #checkSlave').prop('checked')).toBeFalsy(); 15059 input('master').check(); 15060 expect(element('.doc-example-live #checkSlave').prop('checked')).toBeTruthy(); 15061 }); 15062 </doc:scenario> 15063 </doc:example> 15064 * 15065 * @element INPUT 15066 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 15067 * then special attribute "checked" will be set on the element 15068 */ 15069 15070 15071/** 15072 * @ngdoc directive 15073 * @name ng.directive:ngReadonly 15074 * @restrict A 15075 * @priority 100 15076 * 15077 * @description 15078 * The HTML specification does not require browsers to preserve the values of boolean attributes 15079 * such as readonly. (Their presence means true and their absence means false.) 15080 * If we put an Angular interpolation expression into such an attribute then the 15081 * binding information would be lost when the browser removes the attribute. 15082 * The `ngReadonly` directive solves this problem for the `readonly` attribute. 15083 * This complementary directive is not removed by the browser and so provides 15084 * a permanent reliable place to store the binding information. 15085 * @example 15086 <doc:example> 15087 <doc:source> 15088 Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/> 15089 <input type="text" ng-readonly="checked" value="I'm Angular"/> 15090 </doc:source> 15091 <doc:scenario> 15092 it('should toggle readonly attr', function() { 15093 expect(element('.doc-example-live :text').prop('readonly')).toBeFalsy(); 15094 input('checked').check(); 15095 expect(element('.doc-example-live :text').prop('readonly')).toBeTruthy(); 15096 }); 15097 </doc:scenario> 15098 </doc:example> 15099 * 15100 * @element INPUT 15101 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 15102 * then special attribute "readonly" will be set on the element 15103 */ 15104 15105 15106/** 15107 * @ngdoc directive 15108 * @name ng.directive:ngSelected 15109 * @restrict A 15110 * @priority 100 15111 * 15112 * @description 15113 * The HTML specification does not require browsers to preserve the values of boolean attributes 15114 * such as selected. (Their presence means true and their absence means false.) 15115 * If we put an Angular interpolation expression into such an attribute then the 15116 * binding information would be lost when the browser removes the attribute. 15117 * The `ngSelected` directive solves this problem for the `selected` atttribute. 15118 * This complementary directive is not removed by the browser and so provides 15119 * a permanent reliable place to store the binding information. 15120 * 15121 * @example 15122 <doc:example> 15123 <doc:source> 15124 Check me to select: <input type="checkbox" ng-model="selected"><br/> 15125 <select> 15126 <option>Hello!</option> 15127 <option id="greet" ng-selected="selected">Greetings!</option> 15128 </select> 15129 </doc:source> 15130 <doc:scenario> 15131 it('should select Greetings!', function() { 15132 expect(element('.doc-example-live #greet').prop('selected')).toBeFalsy(); 15133 input('selected').check(); 15134 expect(element('.doc-example-live #greet').prop('selected')).toBeTruthy(); 15135 }); 15136 </doc:scenario> 15137 </doc:example> 15138 * 15139 * @element OPTION 15140 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 15141 * then special attribute "selected" will be set on the element 15142 */ 15143 15144/** 15145 * @ngdoc directive 15146 * @name ng.directive:ngOpen 15147 * @restrict A 15148 * @priority 100 15149 * 15150 * @description 15151 * The HTML specification does not require browsers to preserve the values of boolean attributes 15152 * such as open. (Their presence means true and their absence means false.) 15153 * If we put an Angular interpolation expression into such an attribute then the 15154 * binding information would be lost when the browser removes the attribute. 15155 * The `ngOpen` directive solves this problem for the `open` attribute. 15156 * This complementary directive is not removed by the browser and so provides 15157 * a permanent reliable place to store the binding information. 15158 * @example 15159 <doc:example> 15160 <doc:source> 15161 Check me check multiple: <input type="checkbox" ng-model="open"><br/> 15162 <details id="details" ng-open="open"> 15163 <summary>Show/Hide me</summary> 15164 </details> 15165 </doc:source> 15166 <doc:scenario> 15167 it('should toggle open', function() { 15168 expect(element('#details').prop('open')).toBeFalsy(); 15169 input('open').check(); 15170 expect(element('#details').prop('open')).toBeTruthy(); 15171 }); 15172 </doc:scenario>
15173 </doc:example> 15174 * 15175 * @element DETAILS 15176 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 15177 * then special attribute "open" will be set on the element 15178 */ 15179 15180var ngAttributeAliasDirectives = {}; 15181 15182 15183// boolean attrs are evaluated 15184forEach(BOOLEAN_ATTR, function(propName, attrName) { 15185 // binding to multiple is not supported 15186 if (propName == "multiple") return; 15187 15188 var normalized = directiveNormalize('ng-' + attrName); 15189 ngAttributeAliasDirectives[normalized] = function() { 15190 return { 15191 priority: 100, 15192 link: function(scope, element, attr) { 15193 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 15194 attr.$set(attrName, !!value); 15195 }); 15196 } 15197 }; 15198 }; 15199}); 15200 15201 15202// ng-src, ng-srcset, ng-href are interpolated 15203forEach(['src', 'srcset', 'href'], function(attrName) { 15204 var normalized = directiveNormalize('ng-' + attrName); 15205 ngAttributeAliasDirectives[normalized] = function() { 15206 return { 15207 priority: 99, // it needs to run after the attributes are interpolated 15208 link: function(scope, element, attr) { 15209 attr.$observe(normalized, function(value) { 15210 if (!value) 15211 return; 15212 15213 attr.$set(attrName, value); 15214 15215 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 15216 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 15217 // to set the property as well to achieve the desired effect. 15218 // we use attr[attrName] value since $set can sanitize the url. 15219 if (msie) element.prop(attrName, attr[attrName]); 15220 }); 15221 } 15222 }; 15223 }; 15224}); 15225 15226/* global -nullFormCtrl */ 15227var nullFormCtrl = { 15228 $addControl: noop, 15229 $removeControl: noop, 15230 $setValidity: noop, 15231 $setDirty: noop, 15232 $setPristine: noop 15233}; 15234 15235/** 15236 * @ngdoc object 15237 * @name ng.directive:form.FormController 15238 * 15239 * @property {boolean} $pristine True if user has not interacted with the form yet. 15240 * @property {boolean} $dirty True if user has already interacted with the form. 15241 * @property {boolean} $valid True if all of the containing forms and controls are valid. 15242 * @property {boolean} $invalid True if at least one containing control or form is invalid. 15243 * 15244 * @property {Object} $error Is an object hash, containing references to all invalid controls or 15245 * forms, where: 15246 * 15247 * - keys are validation tokens (error names), 15248 * - values are arrays of controls or forms that are invalid for given error name. 15249 * 15250 * 15251 * Built-in validation tokens: 15252 * 15253 * - `email` 15254 * - `max` 15255 * - `maxlength` 15256 * - `min` 15257 * - `minlength` 15258 * - `number` 15259 * - `pattern` 15260 * - `required` 15261 * - `url` 15262 * 15263 * @description 15264 * `FormController` keeps track of all its controls and nested forms as well as state of them, 15265 * such as being valid/invalid or dirty/pristine. 15266 * 15267 * Each {@link ng.directive:form form} directive creates an instance 15268 * of `FormController`. 15269 * 15270 */ 15271//asks for $scope to fool the BC controller module 15272FormController.$inject = ['$element', '$attrs', '$scope']; 15273function FormController(element, attrs) { 15274 var form = this, 15275 parentForm = element.parent().controller('form') || nullFormCtrl, 15276 invalidCount = 0, // used to easily determine if we are valid 15277 errors = form.$error = {}, 15278 controls = []; 15279 15280 // init state 15281 form.$name = attrs.name || attrs.ngForm; 15282 form.$dirty = false; 15283 form.$pristine = true; 15284 form.$valid = true; 15285 form.$invalid = false; 15286 15287 parentForm.$addControl(form); 15288 15289 // Setup initial state of the control 15290 element.addClass(PRISTINE_CLASS); 15291 toggleValidCss(true); 15292 15293 // convenience method for easy toggling of classes 15294 function toggleValidCss(isValid, validationErrorKey) { 15295 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 15296 element. 15297 removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). 15298 addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 15299 } 15300 15301 /** 15302 * @ngdoc function 15303 * @name ng.directive:form.FormController#$addControl 15304 * @methodOf ng.directive:form.FormController 15305 * 15306 * @description 15307 * Register a control with the form. 15308 * 15309 * Input elements using ngModelController do this automatically when they are linked. 15310 */ 15311 form.$addControl = function(control) { 15312 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 15313 // and not added to the scope. Now we throw an error. 15314 assertNotHasOwnProperty(control.$name, 'input'); 15315 controls.push(control); 15316 15317 if (control.$name) { 15318 form[control.$name] = control; 15319 } 15320 }; 15321 15322 /** 15323 * @ngdoc function
15324 * @name ng.directive:form.FormController#$removeControl 15325 * @methodOf ng.directive:form.FormController 15326 * 15327 * @description 15328 * Deregister a control from the form. 15329 * 15330 * Input elements using ngModelController do this automatically when they are destroyed. 15331 */ 15332 form.$removeControl = function(control) { 15333 if (control.$name && form[control.$name] === control) { 15334 delete form[control.$name]; 15335 } 15336 forEach(errors, function(queue, validationToken) { 15337 form.$setValidity(validationToken, true, control); 15338 }); 15339 15340 arrayRemove(controls, control); 15341 }; 15342 15343 /** 15344 * @ngdoc function 15345 * @name ng.directive:form.FormController#$setValidity 15346 * @methodOf ng.directive:form.FormController 15347 * 15348 * @description 15349 * Sets the validity of a form control. 15350 * 15351 * This method will also propagate to parent forms. 15352 */ 15353 form.$setValidity = function(validationToken, isValid, control) { 15354 var queue = errors[validationToken]; 15355 15356 if (isValid) { 15357 if (queue) { 15358 arrayRemove(queue, control); 15359 if (!queue.length) { 15360 invalidCount--; 15361 if (!invalidCount) { 15362 toggleValidCss(isValid); 15363 form.$valid = true; 15364 form.$invalid = false; 15365 } 15366 errors[validationToken] = false; 15367 toggleValidCss(true, validationToken); 15368 parentForm.$setValidity(validationToken, true, form); 15369 } 15370 } 15371 15372 } else { 15373 if (!invalidCount) { 15374 toggleValidCss(isValid); 15375 } 15376 if (queue) { 15377 if (includes(queue, control)) return; 15378 } else { 15379 errors[validationToken] = queue = []; 15380 invalidCount++; 15381 toggleValidCss(false, validationToken); 15382 parentForm.$setValidity(validationToken, false, form); 15383 } 15384 queue.push(control); 15385 15386 form.$valid = false; 15387 form.$invalid = true; 15388 } 15389 }; 15390 15391 /** 15392 * @ngdoc function 15393 * @name ng.directive:form.FormController#$setDirty 15394 * @methodOf ng.directive:form.FormController 15395 * 15396 * @description 15397 * Sets the form to a dirty state. 15398 * 15399 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 15400 * state (ng-dirty class). This method will also propagate to parent forms. 15401 */ 15402 form.$setDirty = function() { 15403 element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); 15404 form.$dirty = true; 15405 form.$pristine = false; 15406 parentForm.$setDirty(); 15407 }; 15408 15409 /** 15410 * @ngdoc function 15411 * @name ng.directive:form.FormController#$setPristine 15412 * @methodOf ng.directive:form.FormController 15413 * 15414 * @description 15415 * Sets the form to its pristine state. 15416 * 15417 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 15418 * state (ng-pristine class). This method will also propagate to all the controls contained 15419 * in this form. 15420 * 15421 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 15422 * saving or resetting it. 15423 */ 15424 form.$setPristine = function () { 15425 element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); 15426 form.$dirty = false; 15427 form.$pristine = true; 15428 forEach(controls, function(control) { 15429 control.$setPristine(); 15430 }); 15431 }; 15432} 15433 15434 15435/** 15436 * @ngdoc directive 15437 * @name ng.directive:ngForm 15438 * @restrict EAC 15439 * 15440 * @description 15441 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 15442 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 15443 * sub-group of controls needs to be determined. 15444 * 15445 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 15446 * related scope, under this name. 15447 * 15448 */ 15449 15450 /** 15451 * @ngdoc directive 15452 * @name ng.directive:form 15453 * @restrict E 15454 * 15455 * @description 15456 * Directive that instantiates 15457 * {@link ng.directive:form.FormController FormController}. 15458 * 15459 * If the `name` attribute is specified, the form controller is published onto the current scope under 15460 * this name. 15461 * 15462 * # Alias: {@link ng.directive:ngForm `ngForm`} 15463 * 15464 * In Angular forms can be nested. This means that the outer form is valid when all of the child 15465 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 15466 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to 15467 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when
15468 * using Angular validation directives in forms that are dynamically generated using the 15469 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` 15470 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an 15471 * `ngForm` directive and nest these in an outer `form` element. 15472 * 15473 * 15474 * # CSS classes 15475 * - `ng-valid` is set if the form is valid. 15476 * - `ng-invalid` is set if the form is invalid. 15477 * - `ng-pristine` is set if the form is pristine. 15478 * - `ng-dirty` is set if the form is dirty. 15479 * 15480 * 15481 * # Submitting a form and preventing the default action 15482 * 15483 * Since the role of forms in client-side Angular applications is different than in classical 15484 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 15485 * page reload that sends the data to the server. Instead some javascript logic should be triggered 15486 * to handle the form submission in an application-specific way. 15487 * 15488 * For this reason, Angular prevents the default action (form submission to the server) unless the 15489 * `<form>` element has an `action` attribute specified. 15490 * 15491 * You can use one of the following two ways to specify what javascript method should be called when 15492 * a form is submitted: 15493 * 15494 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 15495 * - {@link ng.directive:ngClick ngClick} directive on the first 15496 * button or input field of type submit (input[type=submit]) 15497 * 15498 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 15499 * or {@link ng.directive:ngClick ngClick} directives. 15500 * This is because of the following form submission rules in the HTML specification: 15501 * 15502 * - If a form has only one input field then hitting enter in this field triggers form submit 15503 * (`ngSubmit`) 15504 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 15505 * doesn't trigger submit 15506 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 15507 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 15508 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 15509 * 15510 * @param {string=} name Name of the form. If specified, the form controller will be published into 15511 * related scope, under this name. 15512 * 15513 * @example 15514 <doc:example> 15515 <doc:source> 15516 <script> 15517 function Ctrl($scope) { 15518 $scope.userType = 'guest'; 15519 } 15520 </script> 15521 <form name="myForm" ng-controller="Ctrl"> 15522 userType: <input name="input" ng-model="userType" required> 15523 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 15524 <tt>userType = {{userType}}</tt><br> 15525 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br> 15526 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br> 15527 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 15528 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 15529 </form> 15530 </doc:source> 15531 <doc:scenario> 15532 it('should initialize to model', function() { 15533 expect(binding('userType')).toEqual('guest'); 15534 expect(binding('myForm.input.$valid')).toEqual('true'); 15535 }); 15536 15537 it('should be invalid if empty', function() { 15538 input('userType').enter(''); 15539 expect(binding('userType')).toEqual(''); 15540 expect(binding('myForm.input.$valid')).toEqual('false'); 15541 }); 15542 </doc:scenario> 15543 </doc:example> 15544 */ 15545var formDirectiveFactory = function(isNgForm) { 15546 return ['$timeout', function($timeout) { 15547 var formDirective = { 15548 name: 'form', 15549 restrict: isNgForm ? 'EAC' : 'E', 15550 controller: FormController, 15551 compile: function() { 15552 return { 15553 pre: function(scope, formElement, attr, controller) { 15554 if (!attr.action) { 15555 // we can't use jq events because if a form is destroyed during submission the default 15556 // action is not prevented. see #1238 15557 // 15558 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 15559 // page reload if the form was destroyed by submission of the form via a click handler 15560 // on a button in the form. Looks like an IE9 specific bug. 15561 var preventDefaultListener = function(event) { 15562 event.preventDefault 15563 ? event.preventDefault() 15564 : event.returnValue = false; // IE 15565 }; 15566 15567 addEventListenerFn(formElement[0], 'submit', preventDefaultListener); 15568 15569 // unregister the preventDefault listener so that we don't not leak memory but in a 15570 // way that will achieve the prevention of the default action. 15571 formElement.on('$destroy', function() { 15572 $timeout(function() { 15573 removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); 15574 }, 0, false); 15575 }); 15576 } 15577 15578 var parentFormCtrl = formElement.parent().controller('form'), 15579 alias = attr.name || attr.ngForm; 15580 15581 if (alias) { 15582 setter(scope, alias, controller, alias); 15583 } 15584 if (parentFormCtrl) { 15585 formElement.on('$destroy', function() { 15586 parentFormCtrl.$removeControl(controller); 15587 if (alias) { 15588 setter(scope, alias, undefined, alias); 15589 } 15590 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 15591 }); 15592 } 15593 } 15594 }; 15595 } 15596 }; 15597 15598 return formDirective; 15599 }]; 15600}; 15601 15602var formDirective = formDirectiveFactory(); 15603var ngFormDirective = formDirectiveFactory(true); 15604 15605/* global 15606 15607 -VALID_CLASS, 15608 -INVALID_CLASS, 15609 -PRISTINE_CLASS,
15610 -DIRTY_CLASS 15611*/ 15612 15613var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 15614var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i; 15615var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; 15616 15617var inputType = { 15618 15619 /** 15620 * @ngdoc inputType 15621 * @name ng.directive:input.text 15622 * 15623 * @description 15624 * Standard HTML text input with angular data binding. 15625 * 15626 * @param {string} ngModel Assignable angular expression to data-bind to. 15627 * @param {string=} name Property name of the form under which the control is published. 15628 * @param {string=} required Adds `required` validation error key if the value is not entered. 15629 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 15630 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 15631 * `required` when you want to data-bind to the `required` attribute. 15632 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 15633 * minlength. 15634 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 15635 * maxlength. 15636 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 15637 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 15638 * patterns defined as scope expressions. 15639 * @param {string=} ngChange Angular expression to be executed when input changes due to user 15640 * interaction with the input element. 15641 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 15642 * 15643 * @example 15644 <doc:example> 15645 <doc:source> 15646 <script> 15647 function Ctrl($scope) { 15648 $scope.text = 'guest'; 15649 $scope.word = /^\s*\w*\s*$/; 15650 } 15651 </script> 15652 <form name="myForm" ng-controller="Ctrl"> 15653 Single word: <input type="text" name="input" ng-model="text" 15654 ng-pattern="word" required ng-trim="false"> 15655 <span class="error" ng-show="myForm.input.$error.required"> 15656 Required!</span> 15657 <span class="error" ng-show="myForm.input.$error.pattern"> 15658 Single word only!</span> 15659 15660 <tt>text = {{text}}</tt><br/> 15661 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 15662 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 15663 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 15664 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 15665 </form> 15666 </doc:source> 15667 <doc:scenario> 15668 it('should initialize to model', function() { 15669 expect(binding('text')).toEqual('guest'); 15670 expect(binding('myForm.input.$valid')).toEqual('true'); 15671 }); 15672 15673 it('should be invalid if empty', function() { 15674 input('text').enter(''); 15675 expect(binding('text')).toEqual(''); 15676 expect(binding('myForm.input.$valid')).toEqual('false'); 15677 }); 15678 15679 it('should be invalid if multi word', function() { 15680 input('text').enter('hello world'); 15681 expect(binding('myForm.input.$valid')).toEqual('false'); 15682 }); 15683 15684 it('should not be trimmed', function() { 15685 input('text').enter('untrimmed '); 15686 expect(binding('text')).toEqual('untrimmed '); 15687 expect(binding('myForm.input.$valid')).toEqual('true'); 15688 }); 15689 </doc:scenario> 15690 </doc:example> 15691 */ 15692 'text': textInputType, 15693 15694 15695 /** 15696 * @ngdoc inputType 15697 * @name ng.directive:input.number 15698 * 15699 * @description 15700 * Text input with number validation and transformation. Sets the `number` validation 15701 * error if not a valid number. 15702 * 15703 * @param {string} ngModel Assignable angular expression to data-bind to. 15704 * @param {string=} name Property name of the form under which the control is published. 15705 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 15706 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 15707 * @param {string=} required Sets `required` validation error key if the value is not entered. 15708 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 15709 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 15710 * `required` when you want to data-bind to the `required` attribute. 15711 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 15712 * minlength. 15713 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 15714 * maxlength. 15715 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 15716 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 15717 * patterns defined as scope expressions. 15718 * @param {string=} ngChange Angular expression to be executed when input changes due to user 15719 * interaction with the input element. 15720 * 15721 * @example 15722 <doc:example> 15723 <doc:source> 15724 <script> 15725 function Ctrl($scope) { 15726 $scope.value = 12; 15727 } 15728 </script> 15729 <form name="myForm" ng-controller="Ctrl"> 15730 Number: <input type="number" name="input" ng-model="value" 15731 min="0" max="99" required>
15732 <span class="error" ng-show="myForm.input.$error.required"> 15733 Required!</span> 15734 <span class="error" ng-show="myForm.input.$error.number"> 15735 Not valid number!</span> 15736 <tt>value = {{value}}</tt><br/> 15737 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 15738 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 15739 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 15740 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 15741 </form> 15742 </doc:source> 15743 <doc:scenario> 15744 it('should initialize to model', function() { 15745 expect(binding('value')).toEqual('12'); 15746 expect(binding('myForm.input.$valid')).toEqual('true'); 15747 }); 15748 15749 it('should be invalid if empty', function() { 15750 input('value').enter(''); 15751 expect(binding('value')).toEqual(''); 15752 expect(binding('myForm.input.$valid')).toEqual('false'); 15753 }); 15754 15755 it('should be invalid if over max', function() { 15756 input('value').enter('123'); 15757 expect(binding('value')).toEqual(''); 15758 expect(binding('myForm.input.$valid')).toEqual('false'); 15759 }); 15760 </doc:scenario> 15761 </doc:example> 15762 */ 15763 'number': numberInputType, 15764 15765 15766 /** 15767 * @ngdoc inputType 15768 * @name ng.directive:input.url 15769 * 15770 * @description 15771 * Text input with URL validation. Sets the `url` validation error key if the content is not a 15772 * valid URL. 15773 * 15774 * @param {string} ngModel Assignable angular expression to data-bind to. 15775 * @param {string=} name Property name of the form under which the control is published. 15776 * @param {string=} required Sets `required` validation error key if the value is not entered. 15777 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 15778 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 15779 * `required` when you want to data-bind to the `required` attribute. 15780 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 15781 * minlength. 15782 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 15783 * maxlength. 15784 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 15785 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 15786 * patterns defined as scope expressions. 15787 * @param {string=} ngChange Angular expression to be executed when input changes due to user 15788 * interaction with the input element. 15789 * 15790 * @example 15791 <doc:example> 15792 <doc:source> 15793 <script> 15794 function Ctrl($scope) { 15795 $scope.text = 'http://google.com'; 15796 } 15797 </script> 15798 <form name="myForm" ng-controller="Ctrl"> 15799 URL: <input type="url" name="input" ng-model="text" required> 15800 <span class="error" ng-show="myForm.input.$error.required"> 15801 Required!</span> 15802 <span class="error" ng-show="myForm.input.$error.url"> 15803 Not valid url!</span> 15804 <tt>text = {{text}}</tt><br/> 15805 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 15806 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 15807 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 15808 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 15809 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 15810 </form> 15811 </doc:source> 15812 <doc:scenario> 15813 it('should initialize to model', function() { 15814 expect(binding('text')).toEqual('http://google.com'); 15815 expect(binding('myForm.input.$valid')).toEqual('true'); 15816 }); 15817 15818 it('should be invalid if empty', function() { 15819 input('text').enter(''); 15820 expect(binding('text')).toEqual(''); 15821 expect(binding('myForm.input.$valid')).toEqual('false'); 15822 }); 15823 15824 it('should be invalid if not url', function() { 15825 input('text').enter('xxx'); 15826 expect(binding('myForm.input.$valid')).toEqual('false'); 15827 }); 15828 </doc:scenario> 15829 </doc:example> 15830 */ 15831 'url': urlInputType, 15832 15833 15834 /** 15835 * @ngdoc inputType 15836 * @name ng.directive:input.email 15837 * 15838 * @description 15839 * Text input with email validation. Sets the `email` validation error key if not a valid email 15840 * address. 15841 * 15842 * @param {string} ngModel Assignable angular expression to data-bind to. 15843 * @param {string=} name Property name of the form under which the control is published. 15844 * @param {string=} required Sets `required` validation error key if the value is not entered. 15845 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 15846 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 15847 * `required` when you want to data-bind to the `required` attribute. 15848 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 15849 * minlength. 15850 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 15851 * maxlength. 15852 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 15853 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 15854 * patterns defined as scope expressions. 15855 * @param {string=} ngChange Angular expression to be executed when input changes due to user 15856 * interaction with the input element. 15857 * 15858 * @example 15859 <doc:example> 15860 <doc:source> 15861 <script> 15862 function Ctrl($scope) { 15863 $scope.text = '[email protected]'; 15864 } 15865 </script> 15866 <form name="myForm" ng-controller="Ctrl"> 15867 Email: <input type="email" name="input" ng-model="text" required>
15868 <span class="error" ng-show="myForm.input.$error.required"> 15869 Required!</span> 15870 <span class="error" ng-show="myForm.input.$error.email"> 15871 Not valid email!</span> 15872 <tt>text = {{text}}</tt><br/> 15873 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 15874 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 15875 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 15876 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 15877 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 15878 </form> 15879 </doc:source> 15880 <doc:scenario> 15881 it('should initialize to model', function() { 15882 expect(binding('text')).toEqual('[email protected]'); 15883 expect(binding('myForm.input.$valid')).toEqual('true'); 15884 }); 15885 15886 it('should be invalid if empty', function() { 15887 input('text').enter(''); 15888 expect(binding('text')).toEqual(''); 15889 expect(binding('myForm.input.$valid')).toEqual('false'); 15890 }); 15891 15892 it('should be invalid if not email', function() { 15893 input('text').enter('xxx'); 15894 expect(binding('myForm.input.$valid')).toEqual('false'); 15895 }); 15896 </doc:scenario> 15897 </doc:example> 15898 */ 15899 'email': emailInputType, 15900 15901 15902 /** 15903 * @ngdoc inputType 15904 * @name ng.directive:input.radio 15905 * 15906 * @description 15907 * HTML radio button. 15908 * 15909 * @param {string} ngModel Assignable angular expression to data-bind to. 15910 * @param {string} value The value to which the expression should be set when selected. 15911 * @param {string=} name Property name of the form under which the control is published. 15912 * @param {string=} ngChange Angular expression to be executed when input changes due to user 15913 * interaction with the input element. 15914 * @param {string} ngValue Angular expression which sets the value to which the expression should 15915 * be set when selected. 15916 * 15917 * @example 15918 <doc:example> 15919 <doc:source> 15920 <script> 15921 function Ctrl($scope) { 15922 $scope.color = 'blue'; 15923 $scope.specialValue = { 15924 "id": "12345", 15925 "value": "green" 15926 }; 15927 } 15928 </script> 15929 <form name="myForm" ng-controller="Ctrl"> 15930 <input type="radio" ng-model="color" value="red"> Red <br/> 15931 <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/> 15932 <input type="radio" ng-model="color" value="blue"> Blue <br/> 15933 <tt>color = {{color | json}}</tt><br/> 15934 </form> 15935 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 15936 </doc:source> 15937 <doc:scenario> 15938 it('should change state', function() { 15939 expect(binding('color')).toEqual('"blue"'); 15940 15941 input('color').select('red'); 15942 expect(binding('color')).toEqual('"red"'); 15943 }); 15944 </doc:scenario> 15945 </doc:example> 15946 */ 15947 'radio': radioInputType, 15948 15949 15950 /** 15951 * @ngdoc inputType 15952 * @name ng.directive:input.checkbox 15953 * 15954 * @description 15955 * HTML checkbox. 15956 * 15957 * @param {string} ngModel Assignable angular expression to data-bind to. 15958 * @param {string=} name Property name of the form under which the control is published. 15959 * @param {string=} ngTrueValue The value to which the expression should be set when selected. 15960 * @param {string=} ngFalseValue The value to which the expression should be set when not selected. 15961 * @param {string=} ngChange Angular expression to be executed when input changes due to user 15962 * interaction with the input element. 15963 * 15964 * @example 15965 <doc:example> 15966 <doc:source> 15967 <script> 15968 function Ctrl($scope) { 15969 $scope.value1 = true; 15970 $scope.value2 = 'YES' 15971 } 15972 </script> 15973 <form name="myForm" ng-controller="Ctrl"> 15974 Value1: <input type="checkbox" ng-model="value1"> <br/> 15975 Value2: <input type="checkbox" ng-model="value2" 15976 ng-true-value="YES" ng-false-value="NO"> <br/> 15977 <tt>value1 = {{value1}}</tt><br/> 15978 <tt>value2 = {{value2}}</tt><br/> 15979 </form> 15980 </doc:source> 15981 <doc:scenario> 15982 it('should change state', function() { 15983 expect(binding('value1')).toEqual('true'); 15984 expect(binding('value2')).toEqual('YES'); 15985 15986 input('value1').check(); 15987 input('value2').check(); 15988 expect(binding('value1')).toEqual('false'); 15989 expect(binding('value2')).toEqual('NO'); 15990 }); 15991 </doc:scenario> 15992 </doc:example> 15993 */ 15994 'checkbox': checkboxInputType, 15995 15996 'hidden': noop, 15997 'button': noop, 15998 'submit': noop, 15999 'reset': noop 16000}; 16001 16002// A helper function to call $setValidity and return the value / undefined, 16003// a pattern that is repeated a lot in the input validation logic. 16004function validate(ctrl, validatorName, validity, value){ 16005 ctrl.$setValidity(validatorName, validity); 16006 return validity ? value : undefined; 16007} 16008 16009function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16010 // In composition mode, users are still inputing intermediate text buffer, 16011 // hold the listener until composition is done. 16012 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 16013 if (!$sniffer.android) { 16014 var composing = false;
16015 16016 element.on('compositionstart', function(data) { 16017 composing = true; 16018 }); 16019 16020 element.on('compositionend', function() { 16021 composing = false; 16022 }); 16023 } 16024 16025 var listener = function() { 16026 if (composing) return; 16027 var value = element.val(); 16028 16029 // By default we will trim the value 16030 // If the attribute ng-trim exists we will avoid trimming 16031 // e.g. <input ng-model="foo" ng-trim="false"> 16032 if (toBoolean(attr.ngTrim || 'T')) { 16033 value = trim(value); 16034 } 16035 16036 if (ctrl.$viewValue !== value) { 16037 if (scope.$$phase) { 16038 ctrl.$setViewValue(value); 16039 } else { 16040 scope.$apply(function() { 16041 ctrl.$setViewValue(value); 16042 }); 16043 } 16044 } 16045 }; 16046 16047 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 16048 // input event on backspace, delete or cut 16049 if ($sniffer.hasEvent('input')) { 16050 element.on('input', listener); 16051 } else { 16052 var timeout; 16053 16054 var deferListener = function() { 16055 if (!timeout) { 16056 timeout = $browser.defer(function() { 16057 listener(); 16058 timeout = null; 16059 }); 16060 } 16061 }; 16062 16063 element.on('keydown', function(event) { 16064 var key = event.keyCode; 16065 16066 // ignore 16067 // command modifiers arrows 16068 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 16069 16070 deferListener(); 16071 }); 16072 16073 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 16074 if ($sniffer.hasEvent('paste')) { 16075 element.on('paste cut', deferListener); 16076 } 16077 } 16078 16079 // if user paste into input using mouse on older browser 16080 // or form autocomplete on newer browser, we need "change" event to catch it 16081 element.on('change', listener); 16082 16083 ctrl.$render = function() { 16084 element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); 16085 }; 16086 16087 // pattern validator 16088 var pattern = attr.ngPattern, 16089 patternValidator, 16090 match; 16091 16092 if (pattern) { 16093 var validateRegex = function(regexp, value) { 16094 return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value); 16095 }; 16096 match = pattern.match(/^\/(.*)\/([gim]*)$/); 16097 if (match) { 16098 pattern = new RegExp(match[1], match[2]); 16099 patternValidator = function(value) { 16100 return validateRegex(pattern, value); 16101 }; 16102 } else { 16103 patternValidator = function(value) { 16104 var patternObj = scope.$eval(pattern); 16105 16106 if (!patternObj || !patternObj.test) { 16107 throw minErr('ngPattern')('noregexp', 16108 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, 16109 patternObj, startingTag(element)); 16110 } 16111 return validateRegex(patternObj, value); 16112 }; 16113 } 16114 16115 ctrl.$formatters.push(patternValidator); 16116 ctrl.$parsers.push(patternValidator); 16117 } 16118 16119 // min length validator 16120 if (attr.ngMinlength) { 16121 var minlength = int(attr.ngMinlength); 16122 var minLengthValidator = function(value) { 16123 return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value); 16124 }; 16125 16126 ctrl.$parsers.push(minLengthValidator); 16127 ctrl.$formatters.push(minLengthValidator); 16128 } 16129 16130 // max length validator 16131 if (attr.ngMaxlength) { 16132 var maxlength = int(attr.ngMaxlength); 16133 var maxLengthValidator = function(value) { 16134 return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value); 16135 }; 16136 16137 ctrl.$parsers.push(maxLengthValidator); 16138 ctrl.$formatters.push(maxLengthValidator); 16139 } 16140} 16141 16142function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16143 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 16144 16145 ctrl.$parsers.push(function(value) { 16146 var empty = ctrl.$isEmpty(value); 16147 if (empty || NUMBER_REGEXP.test(value)) { 16148 ctrl.$setValidity('number', true); 16149 return value === '' ? null : (empty ? value : parseFloat(value)); 16150 } else { 16151 ctrl.$setValidity('number', false); 16152 return undefined; 16153 } 16154 }); 16155 16156 ctrl.$formatters.push(function(value) { 16157 return ctrl.$isEmpty(value) ? '' : '' + value; 16158 }); 16159 16160 if (attr.min) { 16161 var minValidator = function(value) { 16162 var min = parseFloat(attr.min); 16163 return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); 16164 }; 16165 16166 ctrl.$parsers.push(minValidator); 16167 ctrl.$formatters.push(minValidator); 16168 } 16169 16170 if (attr.max) { 16171 var maxValidator = function(value) { 16172 var max = parseFloat(attr.max); 16173 return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); 16174 }; 16175 16176 ctrl.$parsers.push(maxValidator); 16177 ctrl.$formatters.push(maxValidator); 16178 } 16179 16180 ctrl.$formatters.push(function(value) { 16181 return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); 16182 }); 16183} 16184 16185function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16186 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 16187 16188 var urlValidator = function(value) { 16189 return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); 16190 }; 16191 16192 ctrl.$formatters.push(urlValidator); 16193 ctrl.$parsers.push(urlValidator); 16194} 16195 16196function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16197 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 16198 16199 var emailValidator = function(value) { 16200 return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); 16201 }; 16202 16203 ctrl.$formatters.push(emailValidator); 16204 ctrl.$parsers.push(emailValidator); 16205} 16206 16207function radioInputType(scope, element, attr, ctrl) { 16208 // make the name unique, if not defined 16209 if (isUndefined(attr.name)) { 16210 element.attr('name', nextUid()); 16211 } 16212 16213 element.on('click', function() { 16214 if (element[0].checked) { 16215 scope.$apply(function() { 16216 ctrl.$setViewValue(attr.value); 16217 }); 16218 } 16219 }); 16220 16221 ctrl.$render = function() { 16222 var value = attr.value; 16223 element[0].checked = (value == ctrl.$viewValue); 16224 }; 16225 16226 attr.$observe('value', ctrl.$render); 16227} 16228 16229function checkboxInputType(scope, element, attr, ctrl) { 16230 var trueValue = attr.ngTrueValue, 16231 falseValue = attr.ngFalseValue; 16232 16233 if (!isString(trueValue)) trueValue = true; 16234 if (!isString(falseValue)) falseValue = false;
16235 16236 element.on('click', function() { 16237 scope.$apply(function() { 16238 ctrl.$setViewValue(element[0].checked); 16239 }); 16240 }); 16241 16242 ctrl.$render = function() { 16243 element[0].checked = ctrl.$viewValue; 16244 }; 16245 16246 // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. 16247 ctrl.$isEmpty = function(value) { 16248 return value !== trueValue; 16249 }; 16250 16251 ctrl.$formatters.push(function(value) { 16252 return value === trueValue; 16253 }); 16254 16255 ctrl.$parsers.push(function(value) { 16256 return value ? trueValue : falseValue; 16257 }); 16258} 16259 16260 16261/** 16262 * @ngdoc directive 16263 * @name ng.directive:textarea 16264 * @restrict E 16265 * 16266 * @description 16267 * HTML textarea element control with angular data-binding. The data-binding and validation 16268 * properties of this element are exactly the same as those of the 16269 * {@link ng.directive:input input element}. 16270 * 16271 * @param {string} ngModel Assignable angular expression to data-bind to. 16272 * @param {string=} name Property name of the form under which the control is published. 16273 * @param {string=} required Sets `required` validation error key if the value is not entered. 16274 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16275 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16276 * `required` when you want to data-bind to the `required` attribute. 16277 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16278 * minlength. 16279 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16280 * maxlength. 16281 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16282 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16283 * patterns defined as scope expressions. 16284 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16285 * interaction with the input element. 16286 */ 16287 16288 16289/** 16290 * @ngdoc directive 16291 * @name ng.directive:input 16292 * @restrict E 16293 * 16294 * @description 16295 * HTML input element control with angular data-binding. Input control follows HTML5 input types 16296 * and polyfills the HTML5 validation behavior for older browsers. 16297 * 16298 * @param {string} ngModel Assignable angular expression to data-bind to. 16299 * @param {string=} name Property name of the form under which the control is published. 16300 * @param {string=} required Sets `required` validation error key if the value is not entered. 16301 * @param {boolean=} ngRequired Sets `required` attribute if set to true 16302 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16303 * minlength. 16304 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16305 * maxlength. 16306 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16307 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16308 * patterns defined as scope expressions. 16309 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16310 * interaction with the input element. 16311 * 16312 * @example 16313 <doc:example> 16314 <doc:source> 16315 <script> 16316 function Ctrl($scope) { 16317 $scope.user = {name: 'guest', last: 'visitor'}; 16318 } 16319 </script> 16320 <div ng-controller="Ctrl"> 16321 <form name="myForm"> 16322 User name: <input type="text" name="userName" ng-model="user.name" required> 16323 <span class="error" ng-show="myForm.userName.$error.required"> 16324 Required!</span><br> 16325 Last name: <input type="text" name="lastName" ng-model="user.last" 16326 ng-minlength="3" ng-maxlength="10"> 16327 <span class="error" ng-show="myForm.lastName.$error.minlength"> 16328 Too short!</span> 16329 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 16330 Too long!</span><br> 16331 </form> 16332 <hr> 16333 <tt>user = {{user}}</tt><br/> 16334 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br> 16335 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br> 16336 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br> 16337 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br> 16338 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 16339 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 16340 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br> 16341 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br> 16342 </div> 16343 </doc:source> 16344 <doc:scenario> 16345 it('should initialize to model', function() { 16346 expect(binding('user')).toEqual('{"name":"guest","last":"visitor"}'); 16347 expect(binding('myForm.userName.$valid')).toEqual('true'); 16348 expect(binding('myForm.$valid')).toEqual('true'); 16349 }); 16350 16351 it('should be invalid if empty when required', function() {
16352 input('user.name').enter(''); 16353 expect(binding('user')).toEqual('{"last":"visitor"}'); 16354 expect(binding('myForm.userName.$valid')).toEqual('false'); 16355 expect(binding('myForm.$valid')).toEqual('false'); 16356 }); 16357 16358 it('should be valid if empty when min length is set', function() { 16359 input('user.last').enter(''); 16360 expect(binding('user')).toEqual('{"name":"guest","last":""}'); 16361 expect(binding('myForm.lastName.$valid')).toEqual('true'); 16362 expect(binding('myForm.$valid')).toEqual('true'); 16363 }); 16364 16365 it('should be invalid if less than required min length', function() { 16366 input('user.last').enter('xx'); 16367 expect(binding('user')).toEqual('{"name":"guest"}'); 16368 expect(binding('myForm.lastName.$valid')).toEqual('false'); 16369 expect(binding('myForm.lastName.$error')).toMatch(/minlength/); 16370 expect(binding('myForm.$valid')).toEqual('false'); 16371 }); 16372 16373 it('should be invalid if longer than max length', function() { 16374 input('user.last').enter('some ridiculously long name'); 16375 expect(binding('user')) 16376 .toEqual('{"name":"guest"}'); 16377 expect(binding('myForm.lastName.$valid')).toEqual('false'); 16378 expect(binding('myForm.lastName.$error')).toMatch(/maxlength/); 16379 expect(binding('myForm.$valid')).toEqual('false'); 16380 }); 16381 </doc:scenario> 16382 </doc:example> 16383 */ 16384var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { 16385 return { 16386 restrict: 'E', 16387 require: '?ngModel', 16388 link: function(scope, element, attr, ctrl) { 16389 if (ctrl) { 16390 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, 16391 $browser); 16392 } 16393 } 16394 }; 16395}]; 16396 16397var VALID_CLASS = 'ng-valid', 16398 INVALID_CLASS = 'ng-invalid', 16399 PRISTINE_CLASS = 'ng-pristine', 16400 DIRTY_CLASS = 'ng-dirty'; 16401 16402/** 16403 * @ngdoc object 16404 * @name ng.directive:ngModel.NgModelController 16405 * 16406 * @property {string} $viewValue Actual string value in the view. 16407 * @property {*} $modelValue The value in the model, that the control is bound to. 16408 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 16409 the control reads value from the DOM. Each function is called, in turn, passing the value 16410 through to the next. Used to sanitize / convert the value as well as validation. 16411 For validation, the parsers should update the validity state using 16412 {@link ng.directive:ngModel.NgModelController#methods_$setValidity $setValidity()}, 16413 and return `undefined` for invalid values. 16414 16415 * 16416 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 16417 the model value changes. Each function is called, in turn, passing the value through to the 16418 next. Used to format / convert values for display in the control and validation. 16419 * <pre> 16420 * function formatter(value) { 16421 * if (value) { 16422 * return value.toUpperCase(); 16423 * } 16424 * } 16425 * ngModel.$formatters.push(formatter); 16426 * </pre> 16427 * 16428 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 16429 * view value has changed. It is called with no arguments, and its return value is ignored. 16430 * This can be used in place of additional $watches against the model value. 16431 * 16432 * @property {Object} $error An object hash with all errors as keys. 16433 * 16434 * @property {boolean} $pristine True if user has not interacted with the control yet. 16435 * @property {boolean} $dirty True if user has already interacted with the control. 16436 * @property {boolean} $valid True if there is no error. 16437 * @property {boolean} $invalid True if at least one error on the control. 16438 * 16439 * @description 16440 * 16441 * `NgModelController` provides API for the `ng-model` directive. The controller contains 16442 * services for data-binding, validation, CSS updates, and value formatting and parsing. It 16443 * purposefully does not contain any logic which deals with DOM rendering or listening to 16444 * DOM events. Such DOM related logic should be provided by other directives which make use of 16445 * `NgModelController` for data-binding. 16446 * 16447 * ## Custom Control Example 16448 * This example shows how to use `NgModelController` with a custom control to achieve 16449 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 16450 * collaborate together to achieve the desired result. 16451 *
16452 * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element 16453 * contents be edited in place by the user. This will not work on older browsers. 16454 * 16455 * <example module="customControl"> 16456 <file name="style.css"> 16457 [contenteditable] { 16458 border: 1px solid black; 16459 background-color: white; 16460 min-height: 20px; 16461 } 16462 16463 .ng-invalid { 16464 border: 1px solid red; 16465 } 16466 16467 </file> 16468 <file name="script.js"> 16469 angular.module('customControl', []). 16470 directive('contenteditable', function() { 16471 return { 16472 restrict: 'A', // only activate on element attribute 16473 require: '?ngModel', // get a hold of NgModelController 16474 link: function(scope, element, attrs, ngModel) { 16475 if(!ngModel) return; // do nothing if no ng-model 16476 16477 // Specify how UI should be updated 16478 ngModel.$render = function() { 16479 element.html(ngModel.$viewValue || ''); 16480 }; 16481 16482 // Listen for change events to enable binding 16483 element.on('blur keyup change', function() { 16484 scope.$apply(read); 16485 }); 16486 read(); // initialize 16487 16488 // Write data to the model 16489 function read() { 16490 var html = element.html(); 16491 // When we clear the content editable the browser leaves a <br> behind 16492 // If strip-br attribute is provided then we strip this out 16493 if( attrs.stripBr && html == '<br>' ) { 16494 html = ''; 16495 } 16496 ngModel.$setViewValue(html); 16497 } 16498 } 16499 }; 16500 }); 16501 </file> 16502 <file name="index.html"> 16503 <form name="myForm"> 16504 <div contenteditable 16505 name="myWidget" ng-model="userContent" 16506 strip-br="true" 16507 required>Change me!</div> 16508 <span ng-show="myForm.myWidget.$error.required">Required!</span> 16509 <hr> 16510 <textarea ng-model="userContent"></textarea> 16511 </form> 16512 </file> 16513 <file name="scenario.js"> 16514 it('should data-bind and become invalid', function() { 16515 var contentEditable = element('[contenteditable]'); 16516 16517 expect(contentEditable.text()).toEqual('Change me!'); 16518 input('userContent').enter(''); 16519 expect(contentEditable.text()).toEqual(''); 16520 expect(contentEditable.prop('className')).toMatch(/ng-invalid-required/); 16521 }); 16522 </file> 16523 * </example> 16524 * 16525 * 16526 */ 16527var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', 16528 function($scope, $exceptionHandler, $attr, $element, $parse) { 16529 this.$viewValue = Number.NaN; 16530 this.$modelValue = Number.NaN; 16531 this.$parsers = []; 16532 this.$formatters = []; 16533 this.$viewChangeListeners = []; 16534 this.$pristine = true; 16535 this.$dirty = false; 16536 this.$valid = true; 16537 this.$invalid = false; 16538 this.$name = $attr.name; 16539 16540 var ngModelGet = $parse($attr.ngModel), 16541 ngModelSet = ngModelGet.assign; 16542 16543 if (!ngModelSet) { 16544 throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 16545 $attr.ngModel, startingTag($element)); 16546 } 16547 16548 /** 16549 * @ngdoc function 16550 * @name ng.directive:ngModel.NgModelController#$render 16551 * @methodOf ng.directive:ngModel.NgModelController 16552 * 16553 * @description 16554 * Called when the view needs to be updated. It is expected that the user of the ng-model 16555 * directive will implement this method. 16556 */ 16557 this.$render = noop; 16558 16559 /** 16560 * @ngdoc function 16561 * @name { ng.directive:ngModel.NgModelController#$isEmpty 16562 * @methodOf ng.directive:ngModel.NgModelController 16563 * 16564 * @description 16565 * This is called when we need to determine if the value of the input is empty. 16566 * 16567 * For instance, the required directive does this to work out if the input has data or not. 16568 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 16569 * 16570 * You can override this for input directives whose concept of being empty is different to the 16571 * default. The `checkboxInputType` directive does this because in
16571its case a value of `false` 16572 * implies empty. 16573 */ 16574 this.$isEmpty = function(value) { 16575 return isUndefined(value) || value === '' || value === null || value !== value; 16576 }; 16577 16578 var parentForm = $element.inheritedData('$formController') || nullFormCtrl, 16579 invalidCount = 0, // used to easily determine if we are valid 16580 $error = this.$error = {}; // keep invalid keys here 16581 16582 16583 // Setup initial state of the control 16584 $element.addClass(PRISTINE_CLASS); 16585 toggleValidCss(true); 16586 16587 // convenience method for easy toggling of classes 16588 function toggleValidCss(isValid, validationErrorKey) { 16589 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 16590 $element. 16591 removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey). 16592 addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 16593 } 16594 16595 /** 16596 * @ngdoc function 16597 * @name ng.directive:ngModel.NgModelController#$setValidity 16598 * @methodOf ng.directive:ngModel.NgModelController 16599 * 16600 * @description 16601 * Change the validity state, and notifies the form when the control changes validity. (i.e. it 16602 * does not notify form if given validator is already marked as invalid). 16603 * 16604 * This method should be called by validators - i.e. the parser or formatter functions. 16605 * 16606 * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign 16607 * to `$error[validationErrorKey]=isValid` so that it is available for data-binding. 16608 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 16609 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 16610 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 16611 * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). 16612 */ 16613 this.$setValidity = function(validationErrorKey, isValid) { 16614 // Purposeful use of ! here to cast isValid to boolean in case it is undefined 16615 // jshint -W018 16616 if ($error[validationErrorKey] === !isValid) return; 16617 // jshint +W018 16618 16619 if (isValid) { 16620 if ($error[validationErrorKey]) invalidCount--; 16621 if (!invalidCount) { 16622 toggleValidCss(true); 16623 this.$valid = true; 16624 this.$invalid = false; 16625 } 16626 } else { 16627 toggleValidCss(false); 16628 this.$invalid = true; 16629 this.$valid = false; 16630 invalidCount++; 16631 } 16632 16633 $error[validationErrorKey] = !isValid; 16634 toggleValidCss(isValid, validationErrorKey); 16635 16636 parentForm.$setValidity(validationErrorKey, isValid, this); 16637 }; 16638 16639 /** 16640 * @ngdoc function 16641 * @name ng.directive:ngModel.NgModelController#$setPristine 16642 * @methodOf ng.directive:ngModel.NgModelController 16643 * 16644 * @description 16645 * Sets the control to its pristine state. 16646 * 16647 * This method can be called to remove the 'ng-dirty' class and set the control to its pristine 16648 * state (ng-pristine class). 16649 */ 16650 this.$setPristine = function () { 16651 this.$dirty = false; 16652 this.$pristine = true; 16653 $element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS); 16654 }; 16655 16656 /** 16657 * @ngdoc function 16658 * @name ng.directive:ngModel.NgModelController#$setViewValue 16659 * @methodOf ng.directive:ngModel.NgModelController 16660 * 16661 * @description 16662 * Update the view value. 16663 * 16664 * This method should be called when the view value changes, typically from within a DOM event handler. 16665 * For example {@link ng.directive:input input} and 16666 * {@link ng.directive:select select} directives call it. 16667 * 16668 * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, 16669 * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to 16670 * `$modelValue` and the **expression** specified in the `ng-model` attribute. 16671 * 16672 * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. 16673 * 16674 * Note that calling this function does not trigger a `$digest`. 16675 * 16676 * @param {string} value Value from the view. 16677 */ 16678 this.$setViewValue = function(value) { 16679 this.$viewValue = value; 16680 16681 // change to dirty 16682 if (this.$pristine) { 16683 this.$dirty = true; 16684 this.$pristine = false;
16685 $element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS); 16686 parentForm.$setDirty(); 16687 } 16688 16689 forEach(this.$parsers, function(fn) { 16690 value = fn(value); 16691 }); 16692 16693 if (this.$modelValue !== value) { 16694 this.$modelValue = value; 16695 ngModelSet($scope, value); 16696 forEach(this.$viewChangeListeners, function(listener) { 16697 try { 16698 listener(); 16699 } catch(e) { 16700 $exceptionHandler(e); 16701 } 16702 }); 16703 } 16704 }; 16705 16706 // model -> value 16707 var ctrl = this; 16708 16709 $scope.$watch(function ngModelWatch() { 16710 var value = ngModelGet($scope); 16711 16712 // if scope model value and ngModel value are out of sync 16713 if (ctrl.$modelValue !== value) { 16714 16715 var formatters = ctrl.$formatters, 16716 idx = formatters.length; 16717 16718 ctrl.$modelValue = value; 16719 while(idx--) { 16720 value = formatters[idx](value); 16721 } 16722 16723 if (ctrl.$viewValue !== value) { 16724 ctrl.$viewValue = value; 16725 ctrl.$render(); 16726 } 16727 } 16728 16729 return value; 16730 }); 16731}]; 16732 16733 16734/** 16735 * @ngdoc directive 16736 * @name ng.directive:ngModel 16737 * 16738 * @element input 16739 * 16740 * @description 16741 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 16742 * property on the scope using {@link ng.directive:ngModel.NgModelController NgModelController}, 16743 * which is created and exposed by this directive. 16744 * 16745 * `ngModel` is responsible for: 16746 * 16747 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 16748 * require. 16749 * - Providing validation behavior (i.e. required, number, email, url). 16750 * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). 16751 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`). 16752 * - Registering the control with its parent {@link ng.directive:form form}. 16753 * 16754 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 16755 * current scope. If the property doesn't already exist on this scope, it will be created 16756 * implicitly and added to the scope. 16757 * 16758 * For best practices on using `ngModel`, see: 16759 * 16760 * - {@link https://github.com/angular/angular.js/wiki/Understanding-Scopes} 16761 * 16762 * For basic examples, how to use `ngModel`, see: 16763 * 16764 * - {@link ng.directive:input input} 16765 * - {@link ng.directive:input.text text} 16766 * - {@link ng.directive:input.checkbox checkbox} 16767 * - {@link ng.directive:input.radio radio} 16768 * - {@link ng.directive:input.number number} 16769 * - {@link ng.directive:input.email email} 16770 * - {@link ng.directive:input.url url} 16771 * - {@link ng.directive:select select} 16772 * - {@link ng.directive:textarea textarea} 16773 * 16774 */ 16775var ngModelDirective = function() { 16776 return { 16777 require: ['ngModel', '^?form'], 16778 controller: NgModelController, 16779 link: function(scope, element, attr, ctrls) { 16780 // notify others, especially parent forms 16781 16782 var modelCtrl = ctrls[0], 16783 formCtrl = ctrls[1] || nullFormCtrl; 16784 16785 formCtrl.$addControl(modelCtrl); 16786 16787 scope.$on('$destroy', function() { 16788 formCtrl.$removeControl(modelCtrl); 16789 }); 16790 } 16791 }; 16792}; 16793 16794 16795/** 16796 * @ngdoc directive 16797 * @name ng.directive:ngChange 16798 * 16799 * @description 16800 * Evaluate the given expression when the user changes the input. 16801 * The expression is evaluated immediately, unlike the JavaScript onchange event 16802 * which only triggers at the end of a change (usually, when the user leaves the 16803 * form element or presses the return key). 16804 * The expression is not evaluated when the value change is coming from the model. 16805 * 16806 * Note, this directive requires `ngModel` to be present. 16807 * 16808 * @element input 16809 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 16810 * in input value. 16811 * 16812 * @example 16813 * <doc:example> 16814 * <doc:source> 16815 * <script> 16816 * function Controller($scope) { 16817 * $scope.counter = 0; 16818 * $scope.change = function() { 16819 * $scope.counter++; 16820 * }; 16821 * } 16822 * </script> 16823 * <div ng-controller="Controller"> 16824 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 16825 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 16826 * <label for="ng-change-example2">Confirmed</label><br /> 16827 * debug = {{confirmed}}<br /> 16828 * counter = {{counter}}
16829 * </div> 16830 * </doc:source> 16831 * <doc:scenario> 16832 * it('should evaluate the expression if changing from view', function() { 16833 * expect(binding('counter')).toEqual('0'); 16834 * element('#ng-change-example1').click(); 16835 * expect(binding('counter')).toEqual('1'); 16836 * expect(binding('confirmed')).toEqual('true'); 16837 * }); 16838 * 16839 * it('should not evaluate the expression if changing from model', function() { 16840 * element('#ng-change-example2').click(); 16841 * expect(binding('counter')).toEqual('0'); 16842 * expect(binding('confirmed')).toEqual('true'); 16843 * }); 16844 * </doc:scenario> 16845 * </doc:example> 16846 */ 16847var ngChangeDirective = valueFn({ 16848 require: 'ngModel', 16849 link: function(scope, element, attr, ctrl) { 16850 ctrl.$viewChangeListeners.push(function() { 16851 scope.$eval(attr.ngChange); 16852 }); 16853 } 16854}); 16855 16856 16857var requiredDirective = function() { 16858 return { 16859 require: '?ngModel', 16860 link: function(scope, elm, attr, ctrl) { 16861 if (!ctrl) return; 16862 attr.required = true; // force truthy in case we are on non input element 16863 16864 var validator = function(value) { 16865 if (attr.required && ctrl.$isEmpty(value)) { 16866 ctrl.$setValidity('required', false); 16867 return; 16868 } else { 16869 ctrl.$setValidity('required', true); 16870 return value; 16871 } 16872 }; 16873 16874 ctrl.$formatters.push(validator); 16875 ctrl.$parsers.unshift(validator); 16876 16877 attr.$observe('required', function() { 16878 validator(ctrl.$viewValue); 16879 }); 16880 } 16881 }; 16882}; 16883 16884 16885/** 16886 * @ngdoc directive 16887 * @name ng.directive:ngList 16888 * 16889 * @description 16890 * Text input that converts between a delimited string and an array of strings. The delimiter 16891 * can be a fixed string (by default a comma) or a regular expression. 16892 * 16893 * @element input 16894 * @param {string=} ngList optional delimiter that should be used to split the value. If 16895 * specified in form `/something/` then the value will be converted into a regular expression. 16896 * 16897 * @example 16898 <doc:example> 16899 <doc:source> 16900 <script> 16901 function Ctrl($scope) { 16902 $scope.names = ['igor', 'misko', 'vojta']; 16903 } 16904 </script> 16905 <form name="myForm" ng-controller="Ctrl"> 16906 List: <input name="namesInput" ng-model="names" ng-list required> 16907 <span class="error" ng-show="myForm.namesInput.$error.required"> 16908 Required!</span> 16909 <br> 16910 <tt>names = {{names}}</tt><br/> 16911 <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 16912 <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 16913 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16914 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16915 </form> 16916 </doc:source> 16917 <doc:scenario> 16918 it('should initialize to model', function() { 16919 expect(binding('names')).toEqual('["igor","misko","vojta"]'); 16920 expect(binding('myForm.namesInput.$valid')).toEqual('true'); 16921 expect(element('span.error').css('display')).toBe('none'); 16922 }); 16923 16924 it('should be invalid if empty', function() { 16925 input('names').enter(''); 16926 expect(binding('names')).toEqual(''); 16927 expect(binding('myForm.namesInput.$valid')).toEqual('false'); 16928 expect(element('span.error').css('display')).not().toBe('none'); 16929 }); 16930 </doc:scenario> 16931 </doc:example> 16932 */ 16933var ngListDirective = function() { 16934 return { 16935 require: 'ngModel', 16936 link: function(scope, element, attr, ctrl) { 16937 var match = /\/(.*)\//.exec(attr.ngList), 16938 separator = match && new RegExp(match[1]) || attr.ngList || ','; 16939 16940 var parse = function(viewValue) { 16941 // If the viewValue is invalid (say required but empty) it will be `undefined` 16942 if (isUndefined(viewValue)) return; 16943 16944 var list = []; 16945 16946 if (viewValue) { 16947 forEach(viewValue.split(separator), function(value) { 16948 if (value) list.push(trim(value)); 16949 }); 16950 } 16951 16952 return list; 16953 }; 16954 16955 ctrl.$parsers.push(parse); 16956 ctrl.$formatters.push(function(value) { 16957 if (isArray(value)) { 16958 return value.join(', '); 16959 } 16960 16961 return undefined; 16962 }); 16963 16964 // Override the standard $isEmpty because an empty array means the input is empty. 16965 ctrl.$isEmpty = function(value) { 16966 return !value || !value.length; 16967 }; 16968 } 16969 }; 16970}; 16971 16972 16973var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 16974/** 16975 * @ngdoc directive 16976 * @name ng.directive:ngValue 16977 * 16978 * @description 16979 * Binds the given expression to the value of `input[select]` or `input[radio]`, so 16980 * that when the element is selected, the `ngModel` of that element is set to the 16981 * bound value. 16982 * 16983 * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as 16984 * shown below. 16985 * 16986 * @element input 16987 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 16988 * of the `input` element 16989 * 16990 * @example 16991 <doc:example> 16992 <doc:source> 16993 <script> 16994 function Ctrl($scope) { 16995 $scope.names = ['pizza', 'unicorns', 'robots']; 16996 $scope.my = { favorite: 'unicorns' }; 16997 } 16998 </script> 16999 <form ng-controller="Ctrl"> 17000 <h2>Which is your favorite?</h2> 17001 <label ng-repeat="name in names" for="{{name}}"> 17002 {{name}} 17003 <input type="radio" 17004 ng-model="my.favorite" 17005 ng-value="name" 17006 id="{{name}}" 17007 name="favorite"> 17008 </label> 17009 <div>You chose {{my.favorite}}</div> 17010 </form> 17011 </doc:source> 17012 <doc:scenario> 17013 it('should initialize to model', function() { 17014 expect(binding('my.favorite')).toEqual('unicorns'); 17015 }); 17016 it('should bind the values to the inputs', function() { 17017 input('my.favorite').select('pizza'); 17018 expect(binding('my.favorite')).toEqual('pizza'); 17019 }); 17020 </doc:scenario> 17021 </doc:example> 17022 */ 17023var ngValueDirective = function() { 17024 return { 17025 priority: 100, 17026 compile: function(tpl, tplAttr) { 17027 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 17028 return function ngValueConstantLink(scope, elm, attr) { 17029 attr.$set('value', scope.$eval(attr.ngValue)); 17030 }; 17031 } else { 17032 return function ngValueLink(scope, elm, attr) { 17033 scope.$watch(attr.ngValue, function valueWatchAction(value) { 17034 attr.$set('value', value); 17035 }); 17036 }; 17037 } 17038 } 17039 }; 17040}; 17041 17042/** 17043 * @ngdoc directive 17044 * @name ng.directive:ngBind 17045 * @restrict AC 17046 * 17047 * @description 17048 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 17049 * with the value of a given expression, and to update the text content when the value of that 17050 * expression changes. 17051 * 17052 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 17053 * `{{ expression }}` which is similar but less verbose. 17054 * 17055 * It is preferrable to use `ngBind` instead of `{{ expression }}` when a template is momentarily
17056 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 17057 * element attribute, it makes the bindings invisible to the user while the page is loading. 17058 * 17059 * An alternative solution to this problem would be using the 17060 * {@link ng.directive:ngCloak ngCloak} directive. 17061 * 17062 * 17063 * @element ANY 17064 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 17065 * 17066 * @example 17067 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 17068 <doc:example> 17069 <doc:source> 17070 <script> 17071 function Ctrl($scope) { 17072 $scope.name = 'Whirled'; 17073 } 17074 </script> 17075 <div ng-controller="Ctrl"> 17076 Enter name: <input type="text" ng-model="name"><br> 17077 Hello <span ng-bind="name"></span>! 17078 </div> 17079 </doc:source> 17080 <doc:scenario> 17081 it('should check ng-bind', function() { 17082 expect(using('.doc-example-live').binding('name')).toBe('Whirled'); 17083 using('.doc-example-live').input('name').enter('world'); 17084 expect(using('.doc-example-live').binding('name')).toBe('world'); 17085 }); 17086 </doc:scenario> 17087 </doc:example> 17088 */ 17089var ngBindDirective = ngDirective(function(scope, element, attr) { 17090 element.addClass('ng-binding').data('$binding', attr.ngBind); 17091 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 17092 // We are purposefully using == here rather than === because we want to 17093 // catch when value is "null or undefined" 17094 // jshint -W041 17095 element.text(value == undefined ? '' : value); 17096 }); 17097}); 17098 17099 17100/** 17101 * @ngdoc directive 17102 * @name ng.directive:ngBindTemplate 17103 * 17104 * @description 17105 * The `ngBindTemplate` directive specifies that the element 17106 * text content should be replaced with the interpolation of the template 17107 * in the `ngBindTemplate` attribute. 17108 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 17109 * expressions. This directive is needed since some HTML elements 17110 * (such as TITLE and OPTION) cannot contain SPAN elements. 17111 * 17112 * @element ANY 17113 * @param {string} ngBindTemplate template of form 17114 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 17115 * 17116 * @example 17117 * Try it here: enter text in text box and watch the greeting change. 17118 <doc:example> 17119 <doc:source> 17120 <script> 17121 function Ctrl($scope) { 17122 $scope.salutation = 'Hello'; 17123 $scope.name = 'World'; 17124 } 17125 </script> 17126 <div ng-controller="Ctrl"> 17127 Salutation: <input type="text" ng-model="salutation"><br> 17128 Name: <input type="text" ng-model="name"><br> 17129 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 17130 </div> 17131 </doc:source> 17132 <doc:scenario> 17133 it('should check ng-bind', function() { 17134 expect(using('.doc-example-live').binding('salutation')). 17135 toBe('Hello'); 17136 expect(using('.doc-example-live').binding('name')). 17137 toBe('World'); 17138 using('.doc-example-live').input('salutation').enter('Greetings'); 17139 using('.doc-example-live').input('name').enter('user'); 17140 expect(using('.doc-example-live').binding('salutation')). 17141 toBe('Greetings'); 17142 expect(using('.doc-example-live').binding('name')). 17143 toBe('user'); 17144 }); 17145 </doc:scenario> 17146 </doc:example> 17147 */ 17148var ngBindTemplateDirective = ['$interpolate', function($interpolate) { 17149 return function(scope, element, attr) { 17150 // TODO: move this to scenario runner 17151 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 17152 element.addClass('ng-binding').data('$binding', interpolateFn); 17153 attr.$observe('ngBindTemplate', function(value) { 17154 element.text(value); 17155 }); 17156 }; 17157}]; 17158 17159 17160/** 17161 * @ngdoc directive 17162 * @name ng.directive:ngBindHtml 17163 * 17164 * @description 17165 * Creates a binding that will innerHTML the result of evaluating the `expression` into the current 17166 * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link 17167 * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` 17168 * is available, for example, by including {@link ngSanitize}
17168 in your module's dependencies (not in 17169 * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to 17170 * an explicitly trusted value via {@link ng.$sce#methods_trustAsHtml $sce.trustAsHtml}. See the example 17171 * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. 17172 * 17173 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 17174 * will have an exception (instead of an exploit.) 17175 * 17176 * @element ANY 17177 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 17178 * 17179 * @example 17180 Try it here: enter text in text box and watch the greeting change. 17181 17182 <example module="ngBindHtmlExample" deps="angular-sanitize.js"> 17183 <file name="index.html"> 17184 <div ng-controller="ngBindHtmlCtrl"> 17185 <p ng-bind-html="myHTML"></p> 17186 </div> 17187 </file> 17188 17189 <file name="script.js"> 17190 angular.module('ngBindHtmlExample', ['ngSanitize']) 17191 17192 .controller('ngBindHtmlCtrl', ['$scope', function ngBindHtmlCtrl($scope) { 17193 $scope.myHTML = 17194 'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>'; 17195 }]); 17196 </file> 17197 17198 <file name="scenario.js"> 17199 it('should check ng-bind-html', function() { 17200 expect(using('.doc-example-live').binding('myHTML')). 17201 toBe( 17202 'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>' 17203 ); 17204 }); 17205 </file> 17206 </example> 17207 */ 17208var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { 17209 return function(scope, element, attr) { 17210 element.addClass('ng-binding').data('$binding', attr.ngBindHtml); 17211 17212 var parsed = $parse(attr.ngBindHtml); 17213 function getStringValue() { return (parsed(scope) || '').toString(); } 17214 17215 scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { 17216 element.html($sce.getTrustedHtml(parsed(scope)) || ''); 17217 }); 17218 }; 17219}]; 17220 17221function classDirective(name, selector) { 17222 name = 'ngClass' + name; 17223 return function() { 17224 return { 17225 restrict: 'AC', 17226 link: function(scope, element, attr) { 17227 var oldVal; 17228 17229 scope.$watch(attr[name], ngClassWatchAction, true); 17230 17231 attr.$observe('class', function(value) { 17232 ngClassWatchAction(scope.$eval(attr[name])); 17233 }); 17234 17235 17236 if (name !== 'ngClass') { 17237 scope.$watch('$index', function($index, old$index) { 17238 // jshint bitwise: false 17239 var mod = $index & 1; 17240 if (mod !== old$index & 1) { 17241 var classes = flattenClasses(scope.$eval(attr[name])); 17242 mod === selector ?
17243 attr.$addClass(classes) : 17244 attr.$removeClass(classes); 17245 } 17246 }); 17247 } 17248 17249 17250 function ngClassWatchAction(newVal) { 17251 if (selector === true || scope.$index % 2 === selector) { 17252 var newClasses = flattenClasses(newVal || ''); 17253 if(!oldVal) { 17254 attr.$addClass(newClasses); 17255 } else if(!equals(newVal,oldVal)) { 17256 attr.$updateClass(newClasses, flattenClasses(oldVal)); 17257 } 17258 } 17259 oldVal = copy(newVal); 17260 } 17261 17262 17263 function flattenClasses(classVal) { 17264 if(isArray(classVal)) { 17265 return classVal.join(' '); 17266 } else if (isObject(classVal)) { 17267 var classes = [], i = 0; 17268 forEach(classVal, function(v, k) { 17269 if (v) { 17270 classes.push(k); 17271 } 17272 }); 17273 return classes.join(' '); 17274 } 17275 17276 return classVal; 17277 } 17278 } 17279 }; 17280 }; 17281} 17282 17283/** 17284 * @ngdoc directive 17285 * @name ng.directive:ngClass 17286 * @restrict AC 17287 * 17288 * @description 17289 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 17290 * an expression that represents all classes to be added. 17291 * 17292 * The directive won't add duplicate classes if a particular class was already set. 17293 * 17294 * When the expression changes, the previously added classes are removed and only then the 17295 * new classes are added. 17296 * 17297 * @animations 17298 * add - happens just before the class is applied to the element 17299 * remove - happens just before the class is removed from the element 17300 * 17301 * @element ANY 17302 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 17303 * of the evaluation can be a string representing space delimited class 17304 * names, an array, or a map of class names to boolean values. In the case of a map, the 17305 * names of the properties whose values are truthy will be added as css classes to the 17306 * element. 17307 * 17308 * @example Example that demonstrates basic bindings via ngClass directive. 17309 <example> 17310 <file name="index.html"> 17311 <p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p> 17312 <input type="checkbox" ng-model="deleted"> deleted (apply "strike" class)<br> 17313 <input type="checkbox" ng-model="important"> important (apply "bold" class)<br> 17314 <input type="checkbox" ng-model="error"> error (apply "red" class) 17315 <hr> 17316 <p ng-class="style">Using String Syntax</p> 17317 <input type="text" ng-model="style" placeholder="Type: bold strike red"> 17318 <hr> 17319 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 17320 <input ng-model="style1" placeholder="Type: bold, strike or red"><br> 17321 <input ng-model="style2" placeholder="Type: bold, strike or red"><br> 17322 <input ng-model="style3" placeholder="Type: bold, strike or red"><br> 17323 </file> 17324 <file name="style.css"> 17325 .strike { 17326 text-decoration: line-through; 17327 } 17328 .bold { 17329 font-weight: bold; 17330 } 17331 .red { 17332 color: red; 17333 } 17334 </file> 17335 <file name="scenario.js"> 17336 it('should let you toggle the class', function() { 17337 17338 expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/bold/); 17339 expect(element('.doc-example-live p:first').prop('className')).not().toMatch(/red/); 17340 17341 input('important').check(); 17342 expect(element('.doc-example-live p:first').prop('className')).toMatch(/bold/); 17343 17344 input('error').check(); 17345 expect(element('.doc-example-live p:first').prop('className')).toMatch(/red/); 17346 }); 17347 17348 it('should let you toggle string example', function() { 17349 expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe(''); 17350 input('style').enter('red'); 17351 expect(element('.doc-example-live p:nth-of-type(2)').prop('className')).toBe('red'); 17352 }); 17353 17354 it('array example should have 3 classes', function() { 17355 expect(element('.doc-example-live p:last').prop('className')).toBe(''); 17356 input('style1').enter('bold'); 17357 input('style2').enter('strike'); 17358 input('style3').enter('red'); 17359 expect(element('.doc-example-live p:last').prop('className')).toBe('bold strike red'); 17360 }); 17361 </file> 17362 </example> 17363 17364 ## Animations 17365 17366 The example below demonstrates how to perform animations using ngClass. 17367 17368 <example animations="true"> 17369 <file name="index.html"> 17370 <input type="button" value="set" ng-click="myVar='my-class'"> 17371 <input type="button" value="clear" ng-click="myVar=''"> 17372 <br>
17373 <span class="base-class" ng-class="myVar">Sample Text</span> 17374 </file> 17375 <file name="style.css"> 17376 .base-class { 17377 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 17378 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 17379 } 17380 17381 .base-class.my-class { 17382 color: red; 17383 font-size:3em; 17384 } 17385 </file> 17386 <file name="scenario.js"> 17387 it('should check ng-class', function() { 17388 expect(element('.doc-example-live span').prop('className')).not(). 17389 toMatch(/my-class/); 17390 17391 using('.doc-example-live').element(':button:first').click(); 17392 17393 expect(element('.doc-example-live span').prop('className')). 17394 toMatch(/my-class/); 17395 17396 using('.doc-example-live').element(':button:last').click(); 17397 17398 expect(element('.doc-example-live span').prop('className')).not(). 17399 toMatch(/my-class/); 17400 }); 17401 </file> 17402 </example> 17403 17404 17405 ## ngClass and pre-existing CSS3 Transitions/Animations 17406 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 17407 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 17408 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 17409 to view the step by step details of {@link ngAnimate.$animate#methods_addclass $animate.addClass} and 17410 {@link ngAnimate.$animate#methods_removeclass $animate.removeClass}. 17411 */ 17412var ngClassDirective = classDirective('', true); 17413 17414/** 17415 * @ngdoc directive 17416 * @name ng.directive:ngClassOdd 17417 * @restrict AC 17418 * 17419 * @description 17420 * The `ngClassOdd` and `ngClassEven` directives work exactly as 17421 * {@link ng.directive:ngClass ngClass}, except they work in 17422 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 17423 * 17424 * This directive can be applied only within the scope of an 17425 * {@link ng.directive:ngRepeat ngRepeat}. 17426 * 17427 * @element ANY 17428 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 17429 * of the evaluation can be a string representing space delimited class names or an array. 17430 * 17431 * @example 17432 <example> 17433 <file name="index.html"> 17434 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 17435 <li ng-repeat="name in names"> 17436 <span ng-class-odd="'odd'" ng-class-even="'even'"> 17437 {{name}} 17438 </span> 17439 </li> 17440 </ol> 17441 </file> 17442 <file name="style.css"> 17443 .odd { 17444 color: red; 17445 } 17446 .even { 17447 color: blue; 17448 } 17449 </file> 17450 <file name="scenario.js"> 17451 it('should check ng-class-odd and ng-class-even', function() { 17452 expect(element('.doc-example-live li:first span').prop('className')). 17453 toMatch(/odd/); 17454 expect(element('.doc-example-live li:last span').prop('className')). 17455 toMatch(/even/); 17456 }); 17457 </file> 17458 </example> 17459 */ 17460var ngClassOddDirective = classDirective('Odd', 0); 17461 17462/** 17463 * @ngdoc directive 17464 * @name ng.directive:ngClassEven 17465 * @restrict AC 17466 * 17467 * @description 17468 * The `ngClassOdd` and `ngClassEven` directives work exactly as 17469 * {@link ng.directive:ngClass ngClass}, except they work in 17470 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 17471 * 17472 * This directive can be applied only within the scope of an 17473 * {@link ng.directive:ngRepeat ngRepeat}. 17474 * 17475 * @element ANY 17476 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 17477 * result of the evaluation can be a string representing space delimited class names or an array. 17478 * 17479 * @example 17480 <example> 17481 <file name="index.html"> 17482 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 17483 <li ng-repeat="name in names"> 17484 <span ng-class-odd="'odd'" ng-class-even="'even'"> 17485 {{name}} 17486 </span> 17487 </li> 17488 </ol> 17489 </file> 17490 <file name="style.css"> 17491 .odd { 17492 color: red; 17493 } 17494 .even { 17495 color: blue; 17496 } 17497 </file> 17498 <file name="scenario.js"> 17499 it('should check ng-class-odd and ng-class-even', function() { 17500 expect(element('.doc-example-live li:first span').prop('className')). 17501 toMatch(/odd/); 17502 expect(element('.doc-example-live li:last span').prop('className')). 17503 toMatch(/even/); 17504 }); 17505 </file> 17506 </example> 17507 */ 17508var ngClassEvenDirective = classDirective('Even', 1); 17509 17510/** 17511 * @ngdoc directive 17512 * @name ng.directive:ngCloak 17513 * @restrict AC 17514 * 17515 * @description 17516 * The `ngCloak` directive is used to prevent the Angular html template from being briefly
17517 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 17518 * directive to avoid the undesirable flicker effect caused by the html template display. 17519 * 17520 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 17521 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 17522 * of the browser view. 17523 * 17524 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 17525 * `angular.min.js`. 17526 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 17527 * 17528 * <pre> 17529 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 17530 * display: none !important; 17531 * } 17532 * </pre> 17533 * 17534 * When this css rule is loaded by the browser, all html elements (including their children) that 17535 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 17536 * during the compilation of the template it deletes the `ngCloak` element attribute, making 17537 * the compiled element visible. 17538 * 17539 * For the best result, the `angular.js` script must be loaded in the head section of the html 17540 * document; alternatively, the css rule above must be included in the external stylesheet of the 17541 * application. 17542 * 17543 * Legacy browsers, like IE7, do not provide attribute selector support (a
17543dded in CSS 2.1) so they 17544 * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css 17545 * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. 17546 * 17547 * @element ANY 17548 * 17549 * @example 17550 <doc:example> 17551 <doc:source> 17552 <div id="template1" ng-cloak>{{ 'hello' }}</div> 17553 <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div> 17554 </doc:source> 17555 <doc:scenario> 17556 it('should remove the template directive and css class', function() { 17557 expect(element('.doc-example-live #template1').attr('ng-cloak')). 17558 not().toBeDefined(); 17559 expect(element('.doc-example-live #template2').attr('ng-cloak')). 17560 not().toBeDefined(); 17561 }); 17562 </doc:scenario> 17563 </doc:example> 17564 * 17565 */ 17566var ngCloakDirective = ngDirective({ 17567 compile: function(element, attr) { 17568 attr.$set('ngCloak', undefined); 17569 element.removeClass('ng-cloak'); 17570 } 17571}); 17572 17573/** 17574 * @ngdoc directive 17575 * @name ng.directive:ngController 17576 * 17577 * @description 17578 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 17579 * supports the principles behind the Model-View-Controller design pattern. 17580 * 17581 * MVC components in angular: 17582 * 17583 * * Model â The Model is scope properties; scopes are attached to the DOM where scope properties 17584 * are accessed through bindings. 17585 * * View â The template (HTML with data bindings) that is rendered into the View. 17586 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 17587 * logic behind the application to decorate the scope with functions and values 17588 * 17589 * Note that you can also attach controllers to the DOM by declaring it in a route definition 17590 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 17591 * again using `ng-controller` in the template itself. This will cause the controller to be attached 17592 * and executed twice. 17593 * 17594 * @element ANY 17595 * @scope 17596 * @param {expression} ngController Name of a globally accessible constructor function or an 17597 * {@link guide/expression expression} that on the current scope evaluates to a 17598 * constructor function. The controller instance can be published into a scope property 17599 * by specifying `as propertyName`. 17600 * 17601 * @example 17602 * Here is a simple form for editing user contact information. Adding, removing, clearing, and 17603 * greeting are methods declared on the controller (see source tab). These methods can 17604 * easily be called from the angular markup. Notice that the scope becomes the `this` for the 17605 * controller's instance. This allows for easy access to the view data from the controller. Also 17606 * notice that any changes to the data are automatically reflected in the View without the need 17607 * for a manual update. The example is shown in two different declaration styles you may use 17608 * according to preference. 17609 <doc:example> 17610 <doc:source> 17611 <script> 17612 function SettingsController1() { 17613 this.name = "John Smith"; 17614 this.contacts = [ 17615 {type: 'phone', value: '408 555 1212'}, 17616 {type: 'email', value: '[email protected]'} ]; 17617 }; 17618 17619 SettingsController1.prototype.greet = function() { 17620 alert(this.name); 17621 }; 17622 17623 SettingsController1.prototype.addContact = function() { 17624 this.contacts.push({type: 'email', value: '[email protected]'}); 17625 }; 17626 17627 SettingsController1.prototype.removeContact = function(contactToRemove) { 17628 var index = this.contacts.indexOf(contactToRemove); 17629 this.contacts.splice(index, 1); 17630 }; 17631 17632 SettingsController1.prototype.clearContact = function(contact) { 17633 contact.type = 'phone'; 17634 contact.value = ''; 17635 }; 17636 </script> 17637 <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 17638 Name: <input type="text" ng-model="settings.name"/> 17639 [ <a href="" ng-click="settings.greet()">greet</a> ]<br/> 17640 Contact: 17641 <ul> 17642 <li ng-repeat="contact in settings.contacts"> 17643 <select ng-model="contact.type"> 17644 <option>phone</option> 17645 <option>email</option> 17646 </select> 17647 <input type="text" ng-model="contact.value"/> 17648 [ <a href="" ng-click="settings.clearContact(contact)">clear</a> 17649 | <a href="" ng-click="settings.removeContact(contact)">X</a> ] 17650 </li> 17651 <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li> 17652 </ul> 17653 </div>
17654 </doc:source> 17655 <doc:scenario> 17656 it('should check controller as', function() { 17657 expect(element('#ctrl-as-exmpl>:input').val()).toBe('John Smith'); 17658 expect(element('#ctrl-as-exmpl li:nth-child(1) input').val()) 17659 .toBe('408 555 1212'); 17660 expect(element('#ctrl-as-exmpl li:nth-child(2) input').val()) 17661 .toBe('[email protected]'); 17662 17663 element('#ctrl-as-exmpl li:first a:contains("clear")').click(); 17664 expect(element('#ctrl-as-exmpl li:first input').val()).toBe(''); 17665 17666 element('#ctrl-as-exmpl li:last a:contains("add")').click(); 17667 expect(element('#ctrl-as-exmpl li:nth-child(3) input').val()) 17668 .toBe('[email protected]'); 17669 }); 17670 </doc:scenario> 17671 </doc:example> 17672 <doc:example> 17673 <doc:source> 17674 <script> 17675 function SettingsController2($scope) { 17676 $scope.name = "John Smith"; 17677 $scope.contacts = [ 17678 {type:'phone', value:'408 555 1212'}, 17679 {type:'email', value:'[email protected]'} ]; 17680 17681 $scope.greet = function() { 17682 alert(this.name); 17683 }; 17684 17685 $scope.addContact = function() { 17686 this.contacts.push({type:'email', value:'[email protected]'}); 17687 }; 17688 17689 $scope.removeContact = function(contactToRemove) { 17690 var index = this.contacts.indexOf(contactToRemove); 17691 this.contacts.splice(index, 1); 17692 }; 17693 17694 $scope.clearContact = function(contact) { 17695 contact.type = 'phone'; 17696 contact.value = ''; 17697 }; 17698 } 17699 </script> 17700 <div id="ctrl-exmpl" ng-controller="SettingsController2"> 17701 Name: <input type="text" ng-model="name"/> 17702 [ <a href="" ng-click="greet()">greet</a> ]<br/> 17703 Contact: 17704 <ul> 17705 <li ng-repeat="contact in contacts"> 17706 <select ng-model="contact.type"> 17707 <option>phone</option> 17708 <option>email</option> 17709 </select> 17710 <input type="text" ng-model="contact.value"/> 17711 [ <a href="" ng-click="clearContact(contact)">clear</a> 17712 | <a href="" ng-click="removeContact(contact)">X</a> ] 17713 </li> 17714 <li>[ <a href="" ng-click="addContact()">add</a> ]</li> 17715 </ul> 17716 </div> 17717 </doc:source> 17718 <doc:scenario> 17719 it('should check controller', function() { 17720 expect(element('#ctrl-exmpl>:input').val()).toBe('John Smith'); 17721 expect(element('#ctrl-exmpl li:nth-child(1) input').val()) 17722 .toBe('408 555 1212'); 17723 expect(element('#ctrl-exmpl li:nth-child(2) input').val()) 17724 .toBe('[email protected]'); 17725 17726 element('#ctrl-exmpl li:first a:contains("clear")').click(); 17727 expect(element('#ctrl-exmpl li:first input').val()).toBe(''); 17728 17729 element('#ctrl-exmpl li:last a:contains("add")').click(); 17730 expect(element('#ctrl-exmpl li:nth-child(3) input').val()) 17731 .toBe('[email protected]'); 17732 }); 17733 </doc:scenario> 17734 </doc:example> 17735 17736 */ 17737var ngControllerDirective = [function() { 17738 return { 17739 scope: true, 17740 controller: '@', 17741 priority: 500 17742 }; 17743}]; 17744 17745/** 17746 * @ngdoc directive 17747 * @name ng.directive:ngCsp 17748 * 17749 * @element html 17750 * @description 17751 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. 17752 * 17753 * This is necessary when developing things like Google Chrome Extensions. 17754 * 17755 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). 17756 * For us to be compatible, we just need to implement the "getterFn" in $parse without violating 17757 * any of these restrictions. 17758 * 17759 * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` 17760 * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will 17761 * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will 17762 * be raised. 17763 * 17764 * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically
17765 * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). 17766 * To make those directives work in CSP mode, include the `angular-csp.css` manually. 17767 * 17768 * In order to use this feature put the `ngCsp` directive on the root element of the application. 17769 * 17770 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 17771 * 17772 * @example 17773 * This example shows how to apply the `ngCsp` directive to the `html` tag. 17774 <pre> 17775 <!doctype html> 17776 <html ng-app ng-csp> 17777 ... 17778 ... 17779 </html> 17780 </pre> 17781 */ 17782 17783// ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap 17784// the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute 17785// anywhere in the current doc 17786 17787/** 17788 * @ngdoc directive 17789 * @name ng.directive:ngClick 17790 * 17791 * @description 17792 * The ngClick directive allows you to specify custom behavior when 17793 * an element is clicked. 17794 * 17795 * @element ANY 17796 * @priority 0 17797 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 17798 * click. (Event object is available as `$event`) 17799 * 17800 * @example 17801 <doc:example> 17802 <doc:source> 17803 <button ng-click="count = count + 1" ng-init="count=0"> 17804 Increment 17805 </button> 17806 count: {{count}} 17807 </doc:source> 17808 <doc:protractor> 17809 it('should check ng-click', function() { 17810 expect(element(by.binding('count')).getText()).toMatch('0'); 17811 element(by.css('.doc-example-live button')).click(); 17812 expect(element(by.binding('count')).getText()).toMatch('1'); 17813 }); 17814 </doc:protractor> 17815 </doc:example> 17816 */ 17817/* 17818 * A directive that allows creation of custom onclick handlers that are defined as angular 17819 * expressions and are compiled and executed within the current scope. 17820 * 17821 * Events that are handled via these handler are always configured not to propagate further. 17822 */ 17823var ngEventDirectives = {}; 17824forEach( 17825 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 17826 function(name) { 17827 var directiveName = directiveNormalize('ng-' + name); 17828 ngEventDirectives[directiveName] = ['$parse', function($parse) { 17829 return { 17830 compile: function($element, attr) { 17831 var fn = $parse(attr[directiveName]); 17832 return function(scope, element, attr) { 17833 element.on(lowercase(name), function(event) { 17834 scope.$apply(function() { 17835 fn(scope, {$event:event}); 17836 }); 17837 }); 17838 }; 17839 } 17840 }; 17841 }]; 17842 } 17843); 17844 17845/** 17846 * @ngdoc directive 17847 * @name ng.directive:ngDblclick 17848 * 17849 * @description 17850 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 17851 * 17852 * @element ANY 17853 * @priority 0 17854 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 17855 * a dblclick. (The Event object is available as `$event`) 17856 * 17857 * @example 17858 <doc:example> 17859 <doc:source> 17860 <button ng-dblclick="count = count + 1" ng-init="count=0"> 17861 Increment (on double click) 17862 </button> 17863 count: {{count}} 17864 </doc:source> 17865 </doc:example> 17866 */ 17867 17868 17869/** 17870 * @ngdoc directive 17871 * @name ng.directive:ngMousedown 17872 * 17873 * @description 17874 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 17875 * 17876 * @element ANY 17877 * @priority 0 17878 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 17879 * mousedown. (Event object is available as `$event`) 17880 * 17881 * @example 17882 <doc:example> 17883 <doc:source> 17884 <button ng-mousedown="count = count + 1" ng-init="count=0"> 17885 Increment (on mouse down) 17886 </button> 17887 count: {{count}} 17888 </doc:source> 17889 </doc:example> 17890 */ 17891 17892 17893/** 17894 * @ngdoc directive 17895 * @name ng.directive:ngMouseup 17896 * 17897 * @description 17898 * Specify custom behavior on mouseup event. 17899 * 17900 * @element ANY 17901 * @priority 0 17902 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 17903 * mouseup. (Event object is available as `$event`) 17904 * 17905 * @example 17906 <doc:example> 17907 <doc:source> 17908 <button ng-mouseup="count = count + 1" ng-init="count=0"> 17909 Increment (on mouse up) 17910 </button> 17911 count: {{count}}
17912 </doc:source> 17913 </doc:example> 17914 */ 17915 17916/** 17917 * @ngdoc directive 17918 * @name ng.directive:ngMouseover 17919 * 17920 * @description 17921 * Specify custom behavior on mouseover event. 17922 * 17923 * @element ANY 17924 * @priority 0 17925 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 17926 * mouseover. (Event object is available as `$event`) 17927 * 17928 * @example 17929 <doc:example> 17930 <doc:source> 17931 <button ng-mouseover="count = count + 1" ng-init="count=0"> 17932 Increment (when mouse is over) 17933 </button> 17934 count: {{count}} 17935 </doc:source> 17936 </doc:example> 17937 */ 17938 17939 17940/** 17941 * @ngdoc directive 17942 * @name ng.directive:ngMouseenter 17943 * 17944 * @description 17945 * Specify custom behavior on mouseenter event. 17946 * 17947 * @element ANY 17948 * @priority 0 17949 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 17950 * mouseenter. (Event object is available as `$event`) 17951 * 17952 * @example 17953 <doc:example> 17954 <doc:source> 17955 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 17956 Increment (when mouse enters) 17957 </button> 17958 count: {{count}} 17959 </doc:source> 17960 </doc:example> 17961 */ 17962 17963 17964/** 17965 * @ngdoc directive 17966 * @name ng.directive:ngMouseleave 17967 * 17968 * @description 17969 * Specify custom behavior on mouseleave event. 17970 * 17971 * @element ANY 17972 * @priority 0 17973 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 17974 * mouseleave. (Event object is available as `$event`) 17975 * 17976 * @example 17977 <doc:example> 17978 <doc:source> 17979 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 17980 Increment (when mouse leaves) 17981 </button> 17982 count: {{count}} 17983 </doc:source> 17984 </doc:example> 17985 */ 17986 17987 17988/** 17989 * @ngdoc directive 17990 * @name ng.directive:ngMousemove 17991 * 17992 * @description 17993 * Specify custom behavior on mousemove event. 17994 * 17995 * @element ANY 17996 * @priority 0 17997 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 17998 * mousemove. (Event object is available as `$event`) 17999 * 18000 * @example 18001 <doc:example> 18002 <doc:source> 18003 <button ng-mousemove="count = count + 1" ng-init="count=0"> 18004 Increment (when mouse moves) 18005 </button> 18006 count: {{count}} 18007 </doc:source> 18008 </doc:example> 18009 */ 18010 18011 18012/** 18013 * @ngdoc directive 18014 * @name ng.directive:ngKeydown 18015 * 18016 * @description 18017 * Specify custom behavior on keydown event. 18018 * 18019 * @element ANY 18020 * @priority 0 18021 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 18022 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 18023 * 18024 * @example 18025 <doc:example> 18026 <doc:source> 18027 <input ng-keydown="count = count + 1" ng-init="count=0"> 18028 key down count: {{count}} 18029 </doc:source> 18030 </doc:example> 18031 */ 18032 18033 18034/** 18035 * @ngdoc directive 18036 * @name ng.directive:ngKeyup 18037 * 18038 * @description 18039 * Specify custom behavior on keyup event. 18040 * 18041 * @element ANY 18042 * @priority 0 18043 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 18044 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 18045 * 18046 * @example 18047 <doc:example> 18048 <doc:source> 18049 <input ng-keyup="count = count + 1" ng-init="count=0"> 18050 key up count: {{count}} 18051 </doc:source> 18052 </doc:example> 18053 */ 18054 18055 18056/** 18057 * @ngdoc directive 18058 * @name ng.directive:ngKeypress 18059 * 18060 * @description 18061 * Specify custom behavior on keypress event. 18062 * 18063 * @element ANY 18064 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 18065 * keypress. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 18066 * 18067 * @example 18068 <doc:example> 18069 <doc:source> 18070 <input ng-keypress="count = count + 1" ng-init="count=0"> 18071 key press count: {{count}} 18072 </doc:source> 18073 </doc:example> 18074 */ 18075 18076 18077/** 18078 * @ngdoc directive 18079 * @name ng.directive:ngSubmit 18080 * 18081 * @description 18082 * Enables binding angular expressions to onsubmit events. 18083 * 18084 * Additionally it prevents the default action (which for form means sending the request to the 18085 * server and reloading the current page) **but only if the form does not contain an `action` 18086 * attribute**. 18087 * 18088 * @element form 18089 * @priority 0 18090 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. (Event object is available as `$event`) 18091 * 18092 * @example 18093 <doc:example> 18094 <doc:source> 18095 <script> 18096 function Ctrl($scope) { 18097 $scope.list = []; 18098 $scope.text = 'hello'; 18099 $scope.submit = function() { 18100 if (this.text) { 18101 this.list.push(this.text); 18102 this.text = ''; 18103 } 18104 }; 18105 } 18106 </script> 18107 <form ng-submit="submit()" ng-controller="Ctrl"> 18108 Enter text and hit enter: 18109 <input type="text" ng-model="text" name="text" /> 18110 <input type="submit" id="submit" value="Submit" /> 18111 <pre>list={{list}}</pre> 18112 </form>
18113 </doc:source> 18114 <doc:scenario> 18115 it('should check ng-submit', function() { 18116 expect(binding('list')).toBe('[]'); 18117 element('.doc-example-live #submit').click(); 18118 expect(binding('list')).toBe('["hello"]'); 18119 expect(input('text').val()).toBe(''); 18120 }); 18121 it('should ignore empty strings', function() { 18122 expect(binding('list')).toBe('[]'); 18123 element('.doc-example-live #submit').click(); 18124 element('.doc-example-live #submit').click(); 18125 expect(binding('list')).toBe('["hello"]'); 18126 }); 18127 </doc:scenario> 18128 </doc:example> 18129 */ 18130 18131/** 18132 * @ngdoc directive 18133 * @name ng.directive:ngFocus 18134 * 18135 * @description 18136 * Specify custom behavior on focus event. 18137 * 18138 * @element window, input, select, textarea, a 18139 * @priority 0 18140 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 18141 * focus. (Event object is available as `$event`) 18142 * 18143 * @example 18144 * See {@link ng.directive:ngClick ngClick} 18145 */ 18146 18147/** 18148 * @ngdoc directive 18149 * @name ng.directive:ngBlur 18150 * 18151 * @description 18152 * Specify custom behavior on blur event. 18153 * 18154 * @element window, input, select, textarea, a 18155 * @priority 0 18156 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 18157 * blur. (Event object is available as `$event`) 18158 * 18159 * @example 18160 * See {@link ng.directive:ngClick ngClick} 18161 */ 18162 18163/** 18164 * @ngdoc directive 18165 * @name ng.directive:ngCopy 18166 * 18167 * @description 18168 * Specify custom behavior on copy event. 18169 * 18170 * @element window, input, select, textarea, a 18171 * @priority 0 18172 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 18173 * copy. (Event object is available as `$event`) 18174 * 18175 * @example 18176 <doc:example> 18177 <doc:source> 18178 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 18179 copied: {{copied}} 18180 </doc:source> 18181 </doc:example> 18182 */ 18183 18184/** 18185 * @ngdoc directive 18186 * @name ng.directive:ngCut 18187 * 18188 * @description 18189 * Specify custom behavior on cut event. 18190 * 18191 * @element window, input, select, textarea, a 18192 * @priority 0 18193 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 18194 * cut. (Event object is available as `$event`) 18195 * 18196 * @example 18197 <doc:example> 18198 <doc:source> 18199 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 18200 cut: {{cut}} 18201 </doc:source> 18202 </doc:example> 18203 */ 18204 18205/** 18206 * @ngdoc directive 18207 * @name ng.directive:ngPaste 18208 * 18209 * @description 18210 * Specify custom behavior on paste event. 18211 * 18212 * @element window, input, select, textarea, a 18213 * @priority 0 18214 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 18215 * paste. (Event object is available as `$event`) 18216 * 18217 * @example 18218 <doc:example> 18219 <doc:source> 18220 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 18221 pasted: {{paste}} 18222 </doc:source> 18223 </doc:example> 18224 */ 18225 18226/** 18227 * @ngdoc directive 18228 * @name ng.directive:ngIf 18229 * @restrict A 18230 * 18231 * @description 18232 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 18233 * {expression}. If the expression assigned to `ngIf` evaluates to a false 18234 * value then the element is removed from the DOM, otherwise a clone of the 18235 * element is reinserted into the DOM. 18236 * 18237 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 18238 * element in the DOM rather than changing its visibility via the `display` css property. A common 18239 * case when this difference is significant is when using css selectors that rely on an element's 18240 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes. 18241 * 18242 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 18243 * is created when the element is restored. The scope created within `ngIf` inherits from 18244 * its parent scope using 18245 * {@link https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance prototypal inheritance}. 18246 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 18247 * a javascript primitive defined in the parent scope. In this case any modifications made to the 18248 * variable within the child scope will override (hide) the value in the parent scope. 18249 * 18250 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 18251 * is if an element's class attribute is directly modified after it's compiled, using something like 18252 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 18253 * the added class will be lost because the original compiled state is used to regenerate the element. 18254 * 18255 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 18256 * and `leave` effects. 18257 * 18258 * @animations 18259 * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container 18260 * leave - happens just before the ngIf contents are removed from the DOM 18261 * 18262 * @element ANY 18263 * @scope 18264 * @priority 600 18265 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 18266 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 18267 * element is added to the DOM tree. 18268 * 18269 * @example 18270 <example animations="true"> 18271 <file name="index.html"> 18272 Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/> 18273 Show when checked:
18274 <span ng-if="checked" class="animate-if"> 18275 I'm removed when the checkbox is unchecked. 18276 </span> 18277 </file> 18278 <file name="animations.css"> 18279 .animate-if { 18280 background:white; 18281 border:1px solid black; 18282 padding:10px; 18283 } 18284 18285 .animate-if.ng-enter, .animate-if.ng-leave { 18286 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18287 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18288 } 18289 18290 .animate-if.ng-enter, 18291 .animate-if.ng-leave.ng-leave-active { 18292 opacity:0; 18293 } 18294 18295 .animate-if.ng-leave, 18296 .animate-if.ng-enter.ng-enter-active { 18297 opacity:1; 18298 } 18299 </file> 18300 </example> 18301 */ 18302var ngIfDirective = ['$animate', function($animate) { 18303 return { 18304 transclude: 'element', 18305 priority: 600, 18306 terminal: true, 18307 restrict: 'A', 18308 $$tlb: true, 18309 link: function ($scope, $element, $attr, ctrl, $transclude) { 18310 var block, childScope; 18311 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 18312 18313 if (toBoolean(value)) { 18314 if (!childScope) { 18315 childScope = $scope.$new(); 18316 $transclude(childScope, function (clone) { 18317 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 18318 // Note: We only need the first/last node of the cloned nodes. 18319 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 18320 // by a directive with templateUrl when it's template arrives. 18321 block = { 18322 clone: clone 18323 }; 18324 $animate.enter(clone, $element.parent(), $element); 18325 }); 18326 } 18327 } else { 18328 18329 if (childScope) { 18330 childScope.$destroy(); 18331 childScope = null; 18332 } 18333 18334 if (block) { 18335 $animate.leave(getBlockElements(block.clone)); 18336 block = null; 18337 } 18338 } 18339 }); 18340 } 18341 }; 18342}]; 18343 18344/** 18345 * @ngdoc directive 18346 * @name ng.directive:ngInclude 18347 * @restrict ECA 18348 * 18349 * @description 18350 * Fetches, compiles and includes an external HTML fragment. 18351 * 18352 * By default, the template URL is restricted to the same domain and protocol as the 18353 * application document. This is done by calling {@link ng.$sce#methods_getTrustedResourceUrl 18354 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 18355 * you may either {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelist them} or 18356 * {@link ng.$sce#methods_trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 18357 * ng.$sce Strict Contextual Escaping}. 18358 * 18359 * In addition, the browser's 18360 * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest 18361 * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing 18362 * (CORS)} policy may further restrict whether the template is successfully loaded. 18363 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 18364 * access on some browsers. 18365 * 18366 * @animations 18367 * enter - animation is used to bring new content into the browser. 18368 * leave - animation is used to animate existing content away. 18369 * 18370 * The enter and leave animation occur concurrently. 18371 * 18372 * @scope 18373 * @priority 400 18374 * 18375 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 18376 * make sure you wrap it in quotes, e.g. `src="'myPartialTemplate.html'"`. 18377 * @param {string=} onload Expression to evaluate when a new partial is loaded. 18378 * 18379 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 18380 * $anchorScroll} to scroll the viewport after the content is loaded. 18381 * 18382 * - If the attribute is not set, disable scrolling. 18383 * - If the attribute is set without value, enable scrolling. 18384 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 18385 * 18386 * @example 18387 <example animations="true"> 18388 <file name="index.html"> 18389 <div ng-controller="Ctrl"> 18390 <select ng-model="template" ng-options="t.name for t in templates"> 18391 <option value="">(blank)</option> 18392 </select> 18393 url of the template: <tt>{{template.url}}</tt> 18394 <hr/> 18395 <div class="slide-animate-container"> 18396 <div class="slide-animate" ng-include="template.url"></div> 18397 </div>
18398 </div> 18399 </file> 18400 <file name="script.js"> 18401 function Ctrl($scope) { 18402 $scope.templates = 18403 [ { name: 'template1.html', url: 'template1.html'} 18404 , { name: 'template2.html', url: 'template2.html'} ]; 18405 $scope.template = $scope.templates[0]; 18406 } 18407 </file> 18408 <file name="template1.html"> 18409 Content of template1.html 18410 </file> 18411 <file name="template2.html"> 18412 Content of template2.html 18413 </file> 18414 <file name="animations.css"> 18415 .slide-animate-container { 18416 position:relative; 18417 background:white; 18418 border:1px solid black; 18419 height:40px; 18420 overflow:hidden; 18421 } 18422 18423 .slide-animate { 18424 padding:10px; 18425 } 18426 18427 .slide-animate.ng-enter, .slide-animate.ng-leave { 18428 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18429 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18430 18431 position:absolute; 18432 top:0; 18433 left:0; 18434 right:0; 18435 bottom:0; 18436 display:block; 18437 padding:10px; 18438 } 18439 18440 .slide-animate.ng-enter { 18441 top:-50px; 18442 } 18443 .slide-animate.ng-enter.ng-enter-active { 18444 top:0; 18445 } 18446 18447 .slide-animate.ng-leave { 18448 top:0; 18449 } 18450 .slide-animate.ng-leave.ng-leave-active { 18451 top:50px; 18452 } 18453 </file> 18454 <file name="scenario.js"> 18455 it('should load template1.html', function() { 18456 expect(element('.doc-example-live [ng-include]').text()). 18457 toMatch(/Content of template1.html/); 18458 }); 18459 it('should load template2.html', function() { 18460 select('template').option('1'); 18461 expect(element('.doc-example-live [ng-include]').text()). 18462 toMatch(/Content of template2.html/); 18463 }); 18464 it('should change to blank', function() { 18465 select('template').option(''); 18466 expect(element('.doc-example-live [ng-include]')).toBe(undefined); 18467 }); 18468 </file> 18469 </example> 18470 */ 18471 18472 18473/** 18474 * @ngdoc event 18475 * @name ng.directive:ngInclude#$includeContentRequested 18476 * @eventOf ng.directive:ngInclude 18477 * @eventType emit on the scope ngInclude was declared in 18478 * @description 18479 * Emitted every time the ngInclude content is requested. 18480 */ 18481 18482 18483/** 18484 * @ngdoc event 18485 * @name ng.directive:ngInclude#$includeContentLoaded 18486 * @eventOf ng.directive:ngInclude 18487 * @eventType emit on the current ngInclude scope 18488 * @description 18489 * Emitted every time the ngInclude content is reloaded. 18490 */ 18491var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce', 18492 function($http, $templateCache, $anchorScroll, $animate, $sce) { 18493 return { 18494 restrict: 'ECA', 18495 priority: 400, 18496 terminal: true, 18497 transclude: 'element', 18498 controller: angular.noop, 18499 compile: function(element, attr) { 18500 var srcExp = attr.ngInclude || attr.src, 18501 onloadExp = attr.onload || '', 18502 autoScrollExp = attr.autoscroll; 18503 18504 return function(scope, $element, $attr, ctrl, $transclude) { 18505 var changeCounter = 0, 18506 currentScope, 18507 currentElement; 18508 18509 var cleanupLastIncludeContent = function() { 18510 if (currentScope) { 18511 currentScope.$destroy(); 18512 currentScope = null; 18513 } 18514 if(currentElement) { 18515 $animate.leave(currentElement); 18516 currentElement = null; 18517 } 18518 }; 18519 18520 scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { 18521 var afterAnimation = function() { 18522 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 18523 $anchorScroll(); 18524 } 18525 }; 18526 var thisChangeId = ++changeCounter; 18527 18528 if (src) { 18529 $http.get(src, {cache: $templateCache}).success(function(response) { 18530 if (thisChangeId !== changeCounter) return; 18531 var newScope = scope.$new(); 18532 ctrl.template = response; 18533 18534 // Note: This will also link all children of ng-include that were contained in the original 18535 // html. If that content contains controllers, ... they could pollute/change the scope. 18536 // However, using ng-include on an element with additional content does not make sense... 18537 // Note: We can't remove them in the cloneAttchFn of $transclude as that 18538 // function is called before linking the content, which would apply child 18539 // directives to non existing elements. 18540 var clone = $transclude(newScope, function(clone) { 18541 cleanupLastIncludeContent(); 18542 $animate.enter(clone, null, $element, afterAnimation); 18543 }); 18544 18545 currentScope = newScope; 18546 currentElement = clone; 18547 18548 currentScope.$emit('$includeContentLoaded'); 18549 scope.$eval(onloadExp); 18550 }).error(function() { 18551 if (thisChangeId === changeCounter) cleanupLastIncludeContent(); 18552 }); 18553 scope.$emit('$includeContentRequested'); 18554 } else { 18555 cleanupLastIncludeContent(); 18556 ctrl.template = null; 18557 } 18558 }); 18559 }; 18560 } 18561 }; 18562}]; 18563 18564// This directive is called during the $transclude call of the first `ngInclude` directive. 18565// It will replace and compile the content of the element with the loaded template. 18566// We need this directive so that the element content is already filled when 18567// the link function of another directive on the same element as ngInclude 18568// is called. 18569var ngIncludeFillContentDirective = ['$compile', 18570 function($compile) { 18571 return { 18572 restrict: 'ECA', 18573 priority: -400, 18574 require: 'ngInclude', 18575 link: function(scope, $element, $attr, ctrl) { 18576 $element.html(ctrl.template); 18577 $compile($element.contents())(scope); 18578 } 18579 }; 18580 }]; 18581 18582/** 18583 * @ngdoc directive 18584 * @name ng.directive:ngInit 18585 * @restrict AC 18586 * 18587 * @description 18588 * The `ngInit` directive allows you to evaluate an expression in the 18589 * current scope. 18590 * 18591 * <div class="alert alert-error"> 18592 * The only appropriate use of `ngInit` is for aliasing special properties of 18593 * {@link api/ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you 18594 * should use {@link guide/controller controllers} rather than `ngInit` 18595 * to initialize values on a scope. 18596 * </div> 18597 * <div class="alert alert-warning"> 18598 * **Note**: If you have assignment in `ngInit` along with {@link api/ng.$filter `$filter`}, make
18599 * sure you have parenthesis for correct precedence: 18600 * <pre class="prettyprint"> 18601 * <div ng-init="test1 = (data | orderBy:'name')"></div> 18602 * </pre> 18603 * </div> 18604 * 18605 * @priority 450 18606 * 18607 * @element ANY 18608 * @param {expression} ngInit {@link guide/expression Expression} to eval. 18609 * 18610 * @example 18611 <doc:example> 18612 <doc:source> 18613 <script> 18614 function Ctrl($scope) { 18615 $scope.list = [['a', 'b'], ['c', 'd']]; 18616 } 18617 </script> 18618 <div ng-controller="Ctrl"> 18619 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 18620 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 18621 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 18622 </div> 18623 </div> 18624 </div> 18625 </doc:source> 18626 <doc:scenario> 18627 it('should alias index positions', function() { 18628 expect(element('.example-init').text()) 18629 .toBe('list[ 0 ][ 0 ] = a;' + 18630 'list[ 0 ][ 1 ] = b;' + 18631 'list[ 1 ][ 0 ] = c;' + 18632 'list[ 1 ][ 1 ] = d;'); 18633 }); 18634 </doc:scenario> 18635 </doc:example> 18636 */ 18637var ngInitDirective = ngDirective({ 18638 priority: 450, 18639 compile: function() { 18640 return { 18641 pre: function(scope, element, attrs) { 18642 scope.$eval(attrs.ngInit); 18643 } 18644 }; 18645 } 18646}); 18647 18648/** 18649 * @ngdoc directive 18650 * @name ng.directive:ngNonBindable 18651 * @restrict AC 18652 * @priority 1000 18653 * 18654 * @description 18655 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 18656 * DOM element. This is useful if the element contains what appears to be Angular directives and 18657 * bindings but which should be ignored by Angular. This could be the case if you have a site that 18658 * displays snippets of code, for instance. 18659 * 18660 * @element ANY 18661 * 18662 * @example 18663 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 18664 * but the one wrapped in `ngNonBindable` is left alone. 18665 * 18666 * @example 18667 <doc:example> 18668 <doc:source> 18669 <div>Normal: {{1 + 2}}</div> 18670 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 18671 </doc:source> 18672 <doc:scenario> 18673 it('should check ng-non-bindable', function() { 18674 expect(using('.doc-example-live').binding('1 + 2')).toBe('3'); 18675 expect(using('.doc-example-live').element('div:last').text()). 18676 toMatch(/1 \+ 2/); 18677 }); 18678 </doc:scenario> 18679 </doc:example> 18680 */ 18681var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 18682 18683/** 18684 * @ngdoc directive 18685 * @name ng.directive:ngPluralize 18686 * @restrict EA 18687 * 18688 * @description 18689 * # Overview 18690 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 18691 * These rules are bundled with angular.js, but can be overridden 18692 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 18693 * by specifying the mappings between 18694 * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html 18695 * plural categories} and the strings to be displayed. 18696 * 18697 * # Plural categories and explicit number rules 18698 * There are two 18699 * {@link http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html 18700 * plural categories} in Angular's default en-US locale: "one" and "other". 18701 * 18702 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 18703 * any number that is not 1), an explicit number rule can only match one number. For example, the 18704 * explicit number rule for "3" matches the number 3. There are examples of plural categories 18705 * and explicit number rules throughout the rest of this documentation. 18706 * 18707 * # Configuring ngPluralize 18708 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 18709 * You can also provide an optional attribute, `offset`. 18710 * 18711 * The value of the `count` attribute can be either a string or an {@link guide/expression 18712 * Angular expression}; these are evaluated on the current scope for its bound value. 18713 * 18714 * The `when` attribute specifies the mappings between plural categories and the actual 18715 * string to be displayed. The value of the attribute should be a JSON object. 18716 * 18717 * The following example shows how to configure ngPluralize: 18718 * 18719 * <pre> 18720 * <ng-pluralize count="personCount"
18721 when="{'0': 'Nobody is viewing.', 18722 * 'one': '1 person is viewing.', 18723 * 'other': '{} people are viewing.'}"> 18724 * </ng-pluralize> 18725 *</pre> 18726 * 18727 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 18728 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 18729 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 18730 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 18731 * show "a dozen people are viewing". 18732 * 18733 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 18734 * into pluralized strings. In the previous example, Angular will replace `{}` with 18735 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 18736 * for <span ng-non-bindable>{{numberExpression}}</span>. 18737 * 18738 * # Configuring ngPluralize with offset 18739 * The `offset` attribute allows further customization of pluralized text, which can result in 18740 * a better user experience. For example, instead of the message "4 people are viewing this document", 18741 * you might display "John, Kate and 2 others are viewing this document". 18742 * The offset attribute allows you to offset a number by any desired value. 18743 * Let's take a look at an example: 18744 * 18745 * <pre> 18746 * <ng-pluralize count="personCount" offset=2 18747 * when="{'0': 'Nobody is viewing.', 18748 * '1': '{{person1}} is viewing.', 18749 * '2': '{{person1}} and {{person2}} are viewing.', 18750 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 18751 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 18752 * </ng-pluralize> 18753 * </pre> 18754 * 18755 * Notice that we are still using two plural categories(one, other), but we added 18756 * three explicit number rules 0, 1 and 2. 18757 * When one person, perhaps John, views the document, "John is viewing" will be shown. 18758 * When three people view the document, no explicit number rule is found, so 18759 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 18760 * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" 18761 * is shown. 18762 * 18763 * Note that when you specify offsets, you must provide explicit number rules for 18764 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 18765 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 18766 * plural categories "one" and "other". 18767 * 18768 * @param {string|expression} count The variable to be bounded to. 18769 * @param {string} when The mapping between plural category to its corresponding strings. 18770 * @param {number=} offset Offset to deduct from the total number. 18771 * 18772 * @example 18773 <doc:example> 18774 <doc:source> 18775 <script> 18776 function Ctrl($scope) { 18777 $scope.person1 = 'Igor'; 18778 $scope.person2 = 'Misko'; 18779 $scope.personCount = 1; 18780 } 18781 </script> 18782 <div ng-controller="Ctrl"> 18783 Person 1:<input type="text" ng-model="person1" value="Igor" /><br/> 18784 Person 2:<input type="text" ng-model="person2" value="Misko" /><br/> 18785 Number of People:<input type="text" ng-model="personCount" value="1" /><br/> 18786 18787 <!--- Example with simple pluralization rules for en locale ---> 18788 Without Offset: 18789 <ng-pluralize count="personCount" 18790 when="{'0': 'Nobody is viewing.', 18791 'one': '1 person is viewing.', 18792 'other': '{} people are viewing.'}"> 18793 </ng-pluralize><br> 18794 18795 <!--- Example with offset ---> 18796 With Offset(2): 18797 <ng-pluralize count="personCount" offset=2 18798 when="{'0': 'Nobody is viewing.', 18799 '1': '{{person1}} is viewing.', 18800 '2': '{{person1}} and {{person2}} are viewing.', 18801 'one': '{{person1}}, {{person2}} and one other person are viewing.',
18802 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 18803 </ng-pluralize> 18804 </div> 18805 </doc:source> 18806 <doc:scenario> 18807 it('should show correct pluralized string', function() { 18808 expect(element('.doc-example-live ng-pluralize:first').text()). 18809 toBe('1 person is viewing.'); 18810 expect(element('.doc-example-live ng-pluralize:last').text()). 18811 toBe('Igor is viewing.'); 18812 18813 using('.doc-example-live').input('personCount').enter('0'); 18814 expect(element('.doc-example-live ng-pluralize:first').text()). 18815 toBe('Nobody is viewing.'); 18816 expect(element('.doc-example-live ng-pluralize:last').text()). 18817 toBe('Nobody is viewing.'); 18818 18819 using('.doc-example-live').input('personCount').enter('2'); 18820 expect(element('.doc-example-live ng-pluralize:first').text()). 18821 toBe('2 people are viewing.'); 18822 expect(element('.doc-example-live ng-pluralize:last').text()). 18823 toBe('Igor and Misko are viewing.'); 18824 18825 using('.doc-example-live').input('personCount').enter('3'); 18826 expect(element('.doc-example-live ng-pluralize:first').text()). 18827 toBe('3 people are viewing.'); 18828 expect(element('.doc-example-live ng-pluralize:last').text()). 18829 toBe('Igor, Misko and one other person are viewing.'); 18830 18831 using('.doc-example-live').input('personCount').enter('4'); 18832 expect(element('.doc-example-live ng-pluralize:first').text()). 18833 toBe('4 people are viewing.'); 18834 expect(element('.doc-example-live ng-pluralize:last').text()). 18835 toBe('Igor, Misko and 2 other people are viewing.'); 18836 }); 18837 18838 it('should show data-binded names', function() { 18839 using('.doc-example-live').input('personCount').enter('4'); 18840 expect(element('.doc-example-live ng-pluralize:last').text()). 18841 toBe('Igor, Misko and 2 other people are viewing.'); 18842 18843 using('.doc-example-live').input('person1').enter('Di'); 18844 using('.doc-example-live').input('person2').enter('Vojta'); 18845 expect(element('.doc-example-live ng-pluralize:last').text()). 18846 toBe('Di, Vojta and 2 other people are viewing.'); 18847 }); 18848 </doc:scenario> 18849 </doc:example> 18850 */ 18851var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { 18852 var BRACE = /{}/g; 18853 return { 18854 restrict: 'EA', 18855 link: function(scope, element, attr) { 18856 var numberExp = attr.count, 18857 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 18858 offset = attr.offset || 0, 18859 whens = scope.$eval(whenExp) || {}, 18860 whensExpFns = {}, 18861 startSymbol = $interpolate.startSymbol(), 18862 endSymbol = $interpolate.endSymbol(), 18863 isWhen = /^when(Minus)?(.+)$/; 18864 18865 forEach(attr, function(expression, attributeName) { 18866 if (isWhen.test(attributeName)) { 18867 whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = 18868 element.attr(attr.$attr[attributeName]); 18869 } 18870 }); 18871 forEach(whens, function(expression, key) { 18872 whensExpFns[key] = 18873 $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + 18874 offset + endSymbol)); 18875 }); 18876 18877 scope.$watch(function ngPluralizeWatch() { 18878 var value = parseFloat(scope.$eval(numberExp)); 18879 18880 if (!isNaN(value)) { 18881 //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, 18882 //check it against pluralization rules in $locale service 18883 if (!(value in whens)) value = $locale.pluralCat(value - offset); 18884 return whensExpFns[value](scope, element, true); 18885 } else { 18886 return ''; 18887 } 18888 }, function ngPluralizeWatchAction(newVal) { 18889 element.text(newVal); 18890 }); 18891 } 18892 }; 18893}]; 18894 18895/** 18896 * @ngdoc directive 18897 * @name ng.directive:ngRepeat 18898 * 18899 * @description 18900 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 18901 * instance gets its own scope, where the given loop variable is set to the current collection item, 18902 * and `$index` is set to the item index or key. 18903 * 18904 * Special properties are exposed on the local scope of each template instance, including: 18905 * 18906 * | Variable | Type | Details | 18907 * |-----------|-----------------|-----------------------------------------------------------------------------| 18908 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 18909 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 18910 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 18911 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 18912 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 18913 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 18914 *
18915 * Creating aliases for these properties is possible with {@link api/ng.directive:ngInit `ngInit`}. 18916 * This may be useful when, for instance, nesting ngRepeats. 18917 * 18918 * # Special repeat start and end points 18919 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 18920 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 18921 * 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) 18922 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 18923 * 18924 * The example below makes use of this feature: 18925 * <pre> 18926 * <header ng-repeat-start="item in items"> 18927 * Header {{ item }} 18928 * </header> 18929 * <div class="body"> 18930 * Body {{ item }} 18931 * </div> 18932 * <footer ng-repeat-end> 18933 * Footer {{ item }} 18934 * </footer> 18935 * </pre> 18936 * 18937 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 18938 * <pre> 18939 * <header> 18940 * Header A 18941 * </header> 18942 * <div class="body"> 18943 * Body A 18944 * </div> 18945 * <footer> 18946 * Footer A 18947 * </footer> 18948 * <header> 18949 * Header B 18950 * </header> 18951 * <div class="body"> 18952 * Body B 18953 * </div> 18954 * <footer> 18955 * Footer B 18956 * </footer> 18957 * </pre> 18958 * 18959 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 18960 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 18961 * 18962 * @animations 18963 * enter - when a new item is added to the list or when an item is revealed after a filter 18964 * leave - when an item is removed from the list or when an item is filtered out 18965 * move - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 18966 * 18967 * @element ANY 18968 * @scope 18969 * @priority 1000 18970 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 18971 * formats are currently supported: 18972 * 18973 * * `variable in expression` â where variable is the user defined loop variable and `expression` 18974 * is a scope expression giving the collection to enumerate. 18975 * 18976 * For example: `album in artist.albums`. 18977 * 18978 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 18979 * and `expression` is the scope expression giving the collection to enumerate. 18980 * 18981 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 18982 * 18983 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking function 18984 * which can be used to associate the objects in the collection with the DOM elements. If no tracking function 18985 * is specified the ng-repeat associates elements by identity in the collection. It is an error to have 18986 * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are 18987 * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, 18988 * before specifying a tracking expression. 18989 * 18990 * For example: `item in items` is equivalent to `item in items track by $id(item)'. This implies that the DOM elements 18991 * will be associated by item identity in the array. 18992 * 18993 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 18994 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 18995 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 18996 * element in the same way in the DOM. 18997 * 18998 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 18999 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 19000 * property is same. 19001 * 19002 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 19003 * to items in conjunction with a tracking expression. 19004 * 19005 * @example 19006 * This example initializes the scope to a list of names and 19007 * then uses `ngRepeat` to display every person: 19008 <example animations="true"> 19009 <file name="index.html"> 19010 <div ng-init="friends = [ 19011 {name:'John', age:25, gender:'boy'}, 19012 {name:'Jessie', age:30, gender:'girl'}, 19013 {name:'Johanna', age:28, gender:'girl'}, 19014 {name:'Joy', age:15, gender:'girl'}, 19015 {name:'Mary', age:28, gender:'girl'}, 19016 {name:'Peter', age:95, gender:'boy'}, 19017 {name:'Sebastian', age:50, gender:'boy'}, 19018 {name:'Erika', age:27, gender:'girl'}, 19019 {name:'Patrick', age:40, gender:'boy'}, 19020 {name:'Samantha', age:60, gender:'girl'} 19021 ]"> 19022 I have {{friends.length}} friends. They are: 19023 <input type="search" ng-model="q" placeholder="filter friends..." /> 19024 <ul class="example-animate-container"> 19025 <li class="animate-repeat" ng-repeat="friend in friends | filter:q"> 19026 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 19027 </li> 19028 </ul> 19029 </div> 19030 </file> 19031 <file name="animations.css"> 19032 .example-animate-container { 19033 background:white; 19034 border:1px solid black; 19035 list-style:none; 19036 margin:0; 19037 padding:0 10px; 19038 } 19039 19040 .animate-repeat { 19041 line-height:40px; 19042 list-style:none; 19043 box-sizing:border-box; 19044 } 19045 19046 .animate-repeat.ng-move, 19047 .animate-repeat.ng-enter, 19048 .animate-repeat.ng-leave { 19049 -webkit-transition:all linear 0.5s; 19050 transition:all linear 0.5s; 19051 } 19052 19053 .animate-repeat.ng-leave.ng-leave-active, 19054 .animate-repeat.ng-move, 19055 .animate-repeat.ng-enter { 19056 opacity:0; 19057 max-height:0; 19058 } 19059 19060 .animate-repeat.ng-leave,
19061 .animate-repeat.ng-move.ng-move-active, 19062 .animate-repeat.ng-enter.ng-enter-active { 19063 opacity:1; 19064 max-height:40px; 19065 } 19066 </file> 19067 <file name="scenario.js"> 19068 it('should render initial data set', function() { 19069 var r = using('.doc-example-live').repeater('ul li'); 19070 expect(r.count()).toBe(10); 19071 expect(r.row(0)).toEqual(["1","John","25"]); 19072 expect(r.row(1)).toEqual(["2","Jessie","30"]); 19073 expect(r.row(9)).toEqual(["10","Samantha","60"]); 19074 expect(binding('friends.length')).toBe("10"); 19075 }); 19076 19077 it('should update repeater when filter predicate changes', function() { 19078 var r = using('.doc-example-live').repeater('ul li'); 19079 expect(r.count()).toBe(10); 19080 19081 input('q').enter('ma'); 19082 19083 expect(r.count()).toBe(2); 19084 expect(r.row(0)).toEqual(["1","Mary","28"]); 19085 expect(r.row(1)).toEqual(["2","Samantha","60"]); 19086 }); 19087 </file> 19088 </example> 19089 */ 19090var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 19091 var NG_REMOVED = '$$NG_REMOVED'; 19092 var ngRepeatMinErr = minErr('ngRepeat'); 19093 return { 19094 transclude: 'element', 19095 priority: 1000, 19096 terminal: true, 19097 $$tlb: true, 19098 link: function($scope, $element, $attr, ctrl, $transclude){ 19099 var expression = $attr.ngRepeat; 19100 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), 19101 trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, 19102 lhs, rhs, valueIdentifier, keyIdentifier, 19103 hashFnLocals = {$id: hashKey}; 19104 19105 if (!match) { 19106 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 19107 expression); 19108 } 19109 19110 lhs = match[1]; 19111 rhs = match[2]; 19112 trackByExp = match[3]; 19113 19114 if (trackByExp) { 19115 trackByExpGetter = $parse(trackByExp); 19116 trackByIdExpFn = function(key, value, index) { 19117 // assign key, value, and $index to the locals so that they can be used in hash functions 19118 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 19119 hashFnLocals[valueIdentifier] = value; 19120 hashFnLocals.$index = index; 19121 return trackByExpGetter($scope, hashFnLocals); 19122 }; 19123 } else { 19124 trackByIdArrayFn = function(key, value) { 19125 return hashKey(value); 19126 }; 19127 trackByIdObjFn = function(key) { 19128 return key; 19129 }; 19130 } 19131 19132 match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); 19133 if (!match) { 19134 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 19135 lhs); 19136 } 19137 valueIdentifier = match[3] || match[1]; 19138 keyIdentifier = match[2]; 19139 19140 // Store a list of elements from previous run. This is a hash where key is the item from the 19141 // iterator, and the value is objects with following properties. 19142 // - scope: bound scope 19143 // - element: previous element. 19144 // - index: position 19145 var lastBlockMap = {}; 19146 19147 //watch props 19148 $scope.$watchCollection(rhs, function ngRepeatAction(collection){ 19149 var index, length, 19150 previousNode = $element[0], // current position of the node 19151 nextNode, 19152 // Same as lastBlockMap but it has the current state. It will become the 19153 // lastBlockMap on the next iteration. 19154 nextBlockMap = {}, 19155 arrayLength, 19156 childScope, 19157 key, value, // key/value of iteration 19158 trackById, 19159 trackByIdFn, 19160 collectionKeys, 19161 block, // last object information {scope, element, id} 19162 nextBlockOrder = [], 19163 elementsToRemove; 19164 19165 19166 if (isArrayLike(collection)) { 19167 collectionKeys = collection; 19168 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 19169 } else { 19170 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 19171 // if object, extract keys, sort them and use to determine order of iteration over obj props 19172 collectionKeys = []; 19173 for (key in collection) { 19174 if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { 19175 collectionKeys.push(key); 19176 } 19177 } 19178 collectionKeys.sort(); 19179 } 19180 19181 arrayLength = collectionKeys.length; 19182 19183 // locate existing items 19184 length = nextBlockOrder.length = collectionKeys.length; 19185 for(index = 0; index < length; index++) { 19186 key = (collection === collectionKeys) ? index : collectionKeys[index]; 19187 value = collection[key]; 19188 trackById = trackByIdFn(key, value, index); 19189 assertNotHasOwnProperty(trackById, '`track by` id'); 19190 if(lastBlockMap.hasOwnProperty(trackById)) { 19191 block = lastBlockMap[trackById]; 19192 delete lastBlockMap[trackById]; 19193 nextBlockMap[trackById] = block; 19194 nextBlockOrder[index] = block; 19195 } else if (nextBlockMap.hasOwnProperty(trackById)) { 19196 // restore lastBlockMap
19197 forEach(nextBlockOrder, function(block) { 19198 if (block && block.scope) lastBlockMap[block.id] = block; 19199 }); 19200 // This is a duplicate and we need to throw an error 19201 throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}", 19202 expression, trackById); 19203 } else { 19204 // new never before seen block 19205 nextBlockOrder[index] = { id: trackById }; 19206 nextBlockMap[trackById] = false; 19207 } 19208 } 19209 19210 // remove existing items 19211 for (key in lastBlockMap) { 19212 // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn 19213 if (lastBlockMap.hasOwnProperty(key)) { 19214 block = lastBlockMap[key]; 19215 elementsToRemove = getBlockElements(block.clone); 19216 $animate.leave(elementsToRemove); 19217 forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); 19218 block.scope.$destroy(); 19219 } 19220 } 19221 19222 // we are not using forEach for perf reasons (trying to avoid #call) 19223 for (index = 0, length = collectionKeys.length; index < length; index++) { 19224 key = (collection === collectionKeys) ? index : collectionKeys[index]; 19225 value = collection[key]; 19226 block = nextBlockOrder[index]; 19227 if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]); 19228 19229 if (block.scope) { 19230 // if we have already seen this object, then we need to reuse the 19231 // associated scope/element 19232 childScope = block.scope; 19233 19234 nextNode = previousNode; 19235 do { 19236 nextNode = nextNode.nextSibling; 19237 } while(nextNode && nextNode[NG_REMOVED]); 19238 19239 if (getBlockStart(block) != nextNode) { 19240 // existing item which got moved 19241 $animate.move(getBlockElements(block.clone), null, jqLite(previousNode)); 19242 } 19243 previousNode = getBlockEnd(block); 19244 } else { 19245 // new item which we don't know about 19246 childScope = $scope.$new(); 19247 } 19248 19249 childScope[valueIdentifier] = value; 19250 if (keyIdentifier) childScope[keyIdentifier] = key; 19251 childScope.$index = index; 19252 childScope.$first = (index === 0); 19253 childScope.$last = (index === (arrayLength - 1)); 19254 childScope.$middle = !(childScope.$first || childScope.$last); 19255 // jshint bitwise: false 19256 childScope.$odd = !(childScope.$even = (index&1) === 0); 19257 // jshint bitwise: true 19258 19259 if (!block.scope) { 19260 $transclude(childScope, function(clone) { 19261 clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); 19262 $animate.enter(clone, null, jqLite(previousNode)); 19263 previousNode = clone; 19264 block.scope = childScope; 19265 // Note: We only need the first/last node of the cloned nodes. 19266 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 19267 // by a directive with templateUrl when it's template arrives. 19268 block.clone = clone; 19269 nextBlockMap[block.id] = block; 19270 }); 19271 } 19272 } 19273 lastBlockMap = nextBlockMap; 19274 }); 19275 } 19276 }; 19277 19278 function getBlockStart(block) { 19279 return block.clone[0]; 19280 } 19281 19282 function getBlockEnd(block) { 19283 return block.clone[block.clone.length - 1]; 19284 } 19285}]; 19286 19287/** 19288 * @ngdoc directive 19289 * @name ng.directive:ngShow 19290 * 19291 * @description 19292 * The `ngShow` directive shows or hides the given HTML element based on the expression 19293 * provided to the ngShow attribute. The element is shown or hidden by removing or adding 19294 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 19295 * in AngularJS and sets the display style to none (using an !important flag). 19296 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 19297 * 19298 * <pre> 19299 * <!-- when $scope.myValue is truthy (element is visible) --> 19300 * <div ng-show="myValue"></div> 19301 * 19302 * <!-- when $scope.myValue is falsy (element is hidden) --> 19303 * <div ng-show="myValue" class="ng-hide"></div> 19304 * </pre> 19305 * 19306 * When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute 19307 * on the element causing it to become hidden. When true, the ng-hide CSS class is removed 19308 * from the element causing the element not to appear hidden. 19309 * 19310 * ## Why is !important used? 19311 * 19312 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 19313 * can be easily overridden by heavier selectors. For example, something as simple 19314 * as changing the display style on a HTML list item would make hidden elements appear visible.
19315 * This also becomes a bigger issue when dealing with CSS frameworks. 19316 * 19317 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 19318 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 19319 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 19320 * 19321 * ### Overriding .ng-hide 19322 * 19323 * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by 19324 * restating the styles for the .ng-hide class in CSS: 19325 * <pre> 19326 * .ng-hide { 19327 * //!annotate CSS Specificity|Not to worry, this will override the AngularJS default... 19328 * display:block!important; 19329 * 19330 * //this is just another form of hiding an element 19331 * position:absolute; 19332 * top:-9999px; 19333 * left:-9999px; 19334 * } 19335 * </pre> 19336 * 19337 * Just remember to include the important flag so the CSS override will function. 19338 * 19339 * <div class="alert alert-warning"> 19340 * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):<br /> 19341 * "f" / "0" / "false" / "no" / "n" / "[]" 19342 * </div> 19343 * 19344 * ## A note about animations with ngShow 19345 * 19346 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 19347 * is true and false. This system works like the animation system present with ngClass except that 19348 * you must also include the !important flag to override the display property 19349 * so that you can perform an animation when the element is hidden during the time of the animation. 19350 * 19351 * <pre> 19352 * // 19353 * //a working example can be found at the bottom of this page 19354 * // 19355 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 19356 * transition:0.5s linear all; 19357 * display:block!important; 19358 * } 19359 * 19360 * .my-element.ng-hide-add { ... } 19361 * .my-element.ng-hide-add.ng-hide-add-active { ... } 19362 * .my-element.ng-hide-remove { ... } 19363 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 19364 * </pre> 19365 * 19366 * @animations 19367 * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible 19368 * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden 19369 * 19370 * @element ANY 19371 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 19372 * then the element is shown or hidden respectively. 19373 * 19374 * @example 19375 <example animations="true"> 19376 <file name="index.html"> 19377 Click me: <input type="checkbox" ng-model="checked"><br/> 19378 <div> 19379 Show: 19380 <div class="check-element animate-show" ng-show="checked"> 19381 <span class="icon-thumbs-up"></span> I show up when your checkbox is checked. 19382 </div> 19383 </div> 19384 <div> 19385 Hide: 19386 <div class="check-element animate-show" ng-hide="checked"> 19387 <span class="icon-thumbs-down"></span> I hide when your checkbox is checked. 19388 </div> 19389 </div> 19390 </file> 19391 <file name="animations.css"> 19392 .animate-show { 19393 -webkit-transition:all linear 0.5s; 19394 transition:all linear 0.5s; 19395 line-height:20px; 19396 opacity:1; 19397 padding:10px; 19398 border:1px solid black; 19399 background:white; 19400 } 19401 19402 .animate-show.ng-hide-add, 19403 .animate-show.ng-hide-remove { 19404 display:block!important; 19405 } 19406 19407 .animate-show.ng-hide { 19408 line-height:0; 19409 opacity:0; 19410 padding:0 10px; 19411 } 19412 19413 .check-element { 19414 padding:10px; 19415 border:1px solid black; 19416 background:white; 19417 } 19418 </file> 19419 <file name="scenario.js"> 19420 it('should check ng-show / ng-hide', function() { 19421 expect(element('.doc-example-live span:first:hidden').count()).toEqual(1); 19422 expect(element('.doc-example-live span:last:visible').count()).toEqual(1); 19423 19424 input('checked').check(); 19425 19426 expect(element('.doc-example-live span:first:visible').count()).toEqual(1); 19427 expect(element('.doc-example-live span:last:hidden').count()).toEqual(1); 19428 }); 19429 </file> 19430 </example> 19431 */ 19432var ngShowDirective = ['$animate', function($animate) { 19433 return function(scope, element, attr) { 19434 scope.$watch(attr.ngShow, function ngShowWatchAction(value){ 19435 $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); 19436 }); 19437 }; 19438}]; 19439 19440 19441/** 19442 * @ngdoc directive 19443 * @name ng.directive:ngHide 19444 * 19445 * @description 19446 * The `ngHide` directive shows or hides the given HTML element based on the expression 19447 * provided to the ngHide attribute. The element is shown or hidden by removing or adding 19448 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 19449 * in AngularJS and sets the display style to none (using an !important flag). 19450 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 19451 * 19452 * <pre> 19453 * <!-- when $scope.myValue is truthy (element is hidden) --> 19454 * <div ng-hide="myValue"></div> 19455 * 19456 * <!-- when $scope.myValue is falsy (element is visible) --> 19457 * <div ng-hide="myValue" class="ng-hide"></div> 19458 * </pre> 19459 * 19460 * When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute 19461 * on the element causing it to become hidden. When false, the ng-hide CSS class is removed 19462 * from the element causing the element not to appear hidden. 19463 * 19464 * ## Why is !important used? 19465 * 19466 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 19467 * can be easily overridden by heavier selectors. For example, something as simple 19468 * as changing the display style on a HTML list item would make hidden elements appear visible.
19469 * This also becomes a bigger issue when dealing with CSS frameworks. 19470 * 19471 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 19472 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 19473 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 19474 * 19475 * ### Overriding .ng-hide 19476 * 19477 * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by 19478 * restating the styles for the .ng-hide class in CSS: 19479 * <pre> 19480 * .ng-hide { 19481 * //!annotate CSS Specificity|Not to worry, this will override the AngularJS default... 19482 * display:block!important; 19483 * 19484 * //this is just another form of hiding an element 19485 * position:absolute; 19486 * top:-9999px; 19487 * left:-9999px; 19488 * } 19489 * </pre> 19490 * 19491 * Just remember to include the important flag so the CSS override will function. 19492 * 19493 * <div class="alert alert-warning"> 19494 * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):<br /> 19495 * "f" / "0" / "false" / "no" / "n" / "[]" 19496 * </div> 19497 * 19498 * ## A note about animations with ngHide 19499 * 19500 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 19501 * is true and false. This system works like the animation system present with ngClass, except that 19502 * you must also include the !important flag to override the display property so 19503 * that you can perform an animation when the element is hidden during the time of the animation. 19504 * 19505 * <pre> 19506 * // 19507 * //a working example can be found at the bottom of this page 19508 * // 19509 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 19510 * transition:0.5s linear all; 19511 * display:block!important; 19512 * } 19513 * 19514 * .my-element.ng-hide-add { ... } 19515 * .my-element.ng-hide-add.ng-hide-add-active { ... } 19516 * .my-element.ng-hide-remove { ... } 19517 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 19518 * </pre> 19519 * 19520 * @animations 19521 * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden 19522 * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible 19523 * 19524 * @element ANY 19525 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 19526 * the element is shown or hidden respectively. 19527 * 19528 * @example 19529 <example animations="true"> 19530 <file name="index.html"> 19531 Click me: <input type="checkbox" ng-model="checked"><br/> 19532 <div> 19533 Show: 19534 <div class="check-element animate-hide" ng-show="checked"> 19535 <span class="icon-thumbs-up"></span> I show up when your checkbox is checked. 19536 </div> 19537 </div> 19538 <div> 19539 Hide: 19540 <div class="check-element animate-hide" ng-hide="checked"> 19541 <span class="icon-thumbs-down"></span> I hide when your checkbox is checked. 19542 </div> 19543 </div> 19544 </file> 19545 <file name="animations.css"> 19546 .animate-hide { 19547 -webkit-transition:all linear 0.5s; 19548 transition:all linear 0.5s; 19549 line-height:20px; 19550 opacity:1; 19551 padding:10px; 19552 border:1px solid black; 19553 background:white; 19554 } 19555 19556 .animate-hide.ng-hide-add, 19557 .animate-hide.ng-hide-remove { 19558 display:block!important; 19559 } 19560 19561 .animate-hide.ng-hide { 19562 line-height:0; 19563 opacity:0; 19564 padding:0 10px; 19565 } 19566 19567 .check-element { 19568 padding:10px; 19569 border:1px solid black; 19570 background:white; 19571 } 19572 </file> 19573 <file name="scenario.js"> 19574 it('should check ng-show / ng-hide', function() { 19575 expect(element('.doc-example-live .check-element:first:hidden').count()).toEqual(1); 19576 expect(element('.doc-example-live .check-element:last:visible').count()).toEqual(1); 19577 19578 input('checked').check(); 19579 19580 expect(element('.doc-example-live .check-element:first:visible').count()).toEqual(1); 19581 expect(element('.doc-example-live .check-element:last:hidden').count()).toEqual(1); 19582 }); 19583 </file> 19584 </example> 19585 */ 19586var ngHideDirective = ['$animate', function($animate) { 19587 return function(scope, element, attr) { 19588 scope.$watch(attr.ngHide, function ngHideWatchAction(value){ 19589 $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); 19590 }); 19591 }; 19592}]; 19593 19594/** 19595 * @ngdoc directive 19596 * @name ng.directive:ngStyle 19597 * @restrict AC 19598 * 19599 * @description 19600 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 19601 * 19602 * @element ANY 19603 * @param {expression} ngStyle {@link guide/expression Expression} which evals to an 19604 * object whose keys are CSS style names and values are corresponding values for those CSS 19605 * keys. 19606 * 19607 * @example 19608 <example> 19609 <file name="index.html"> 19610 <input type="button" value="set" ng-click="myStyle={color:'red'}"> 19611 <input type="button" value="clear" ng-click="myStyle={}"> 19612 <br/>
19613 <span ng-style="myStyle">Sample Text</span> 19614 <pre>myStyle={{myStyle}}</pre> 19615 </file> 19616 <file name="style.css"> 19617 span { 19618 color: black; 19619 } 19620 </file> 19621 <file name="scenario.js"> 19622 it('should check ng-style', function() { 19623 expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); 19624 element('.doc-example-live :button[value=set]').click(); 19625 expect(element('.doc-example-live span').css('color')).toBe('rgb(255, 0, 0)'); 19626 element('.doc-example-live :button[value=clear]').click(); 19627 expect(element('.doc-example-live span').css('color')).toBe('rgb(0, 0, 0)'); 19628 }); 19629 </file> 19630 </example> 19631 */ 19632var ngStyleDirective = ngDirective(function(scope, element, attr) { 19633 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 19634 if (oldStyles && (newStyles !== oldStyles)) { 19635 forEach(oldStyles, function(val, style) { element.css(style, '');}); 19636 } 19637 if (newStyles) element.css(newStyles); 19638 }, true); 19639}); 19640 19641/** 19642 * @ngdoc directive 19643 * @name ng.directive:ngSwitch 19644 * @restrict EA 19645 * 19646 * @description 19647 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 19648 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 19649 * as specified in the template. 19650 * 19651 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 19652 * from the template cache), `ngSwitch` simply choses one of the nested elements and makes it visible based on which element 19653 * matches the value obtained from the evaluated expression. In other words, you define a container element 19654 * (where you place the directive), place an expression on the **`on="..."` attribute** 19655 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 19656 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 19657 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 19658 * attribute is displayed. 19659 * 19660 * <div class="alert alert-info"> 19661 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 19662 * as literal string values to match against. 19663 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the 19664 * value of the expression `$scope.someVal`. 19665 * </div> 19666 19667 * @animations 19668 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 19669 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 19670 * 19671 * @usage 19672 * <ANY ng-switch="expression"> 19673 * <ANY ng-switch-when="matchValue1">...</ANY> 19674 * <ANY ng-switch-when="matchValue2">...</ANY> 19675 * <ANY ng-switch-default>...</ANY> 19676 * </ANY> 19677 * 19678 * 19679 * @scope 19680 * @priority 800 19681 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>. 19682 * @paramDescription 19683 * On child elements add: 19684 * 19685 * * `ngSwitchWhen`: the case statement to match against. If match then this 19686 * case will be displayed. If the same match appears multiple times, all the 19687 * elements will be displayed. 19688 * * `ngSwitchDefault`: the default case when no other case match. If there 19689 * are multiple default cases, all of them will be displayed when no other 19690 * case match. 19691 * 19692 * 19693 * @example 19694 <example animations="true"> 19695 <file name="index.html"> 19696 <div ng-controller="Ctrl"> 19697 <select ng-model="selection" ng-options="item for item in items"> 19698 </select> 19699 <tt>selection={{selection}}</tt> 19700 <hr/> 19701 <div class="animate-switch-container" 19702 ng-switch on="selection"> 19703 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 19704 <div class="animate-switch" ng-switch-when="home">Home Span</div> 19705 <div class="animate-switch" ng-switch-default>default</div> 19706 </div> 19707 </div> 19708 </file> 19709 <file name="script.js"> 19710 function Ctrl($scope) { 19711 $scope.items = ['settings', 'home', 'other']; 19712 $scope.selection = $scope.items[0]; 19713 } 19714 </file> 19715 <file name="animations.css"> 19716 .animate-switch-container { 19717 position:relative; 19718 background:white; 19719 border:1px solid black; 19720 height:40px; 19721 overflow:hidden; 19722 } 19723 19724 .animate-switch { 19725 padding:10px; 19726 } 19727 19728 .animate-switch.ng-animate { 19729 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19730 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19731 19732 position:absolute; 19733 top:0; 19734 left:0; 19735 right:0; 19736 bottom:0; 19737 } 19738 19739 .animate-switch.ng-leave.ng-leave-active, 19740 .animate-switch.ng-enter { 19741 top:-50px; 19742 } 19743 .animate-switch.ng-leave, 19744 .animate-switch.ng-enter.ng-enter-active { 19745 top:0; 19746 } 19747 </file> 19748 <file name="scenario.js"> 19749 it('should start in settings', function() { 19750 expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Settings Div/); 19751 }); 19752 it('should change to home', function() { 19753 select('selection').option('home'); 19754 expect(element('.doc-example-live [ng-switch]').text()).toMatch(/Home Span/); 19755 }); 19756 it('should select default', function() { 19757 select('selection').option('other'); 19758 expect(element('.doc-example-live [ng-switch]').text()).toMatch(/default/); 19759 }); 19760 </file> 19761 </example> 19762 */ 19763var ngSwitchDirective = ['$animate', function($animate) { 19764 return { 19765 restrict: 'EA', 19766 require: 'ngSwitch', 19767 19768 // asks for $scope to fool the BC controller module 19769 controller: ['$scope', function ngSwitchController() { 19770 this.cases = {}; 19771 }], 19772 link: function(scope, element, attr, ngSwitchController) { 19773 var watchExpr = attr.ngSwitch || attr.on, 19774 selectedTranscludes, 19775 selectedElements, 19776 selectedScopes = []; 19777 19778 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 19779 for (var i= 0, ii=selectedScopes.length; i<ii; i++) { 19780 selectedScopes[i].$destroy(); 19781 $animate.leave(selectedElements[i]); 19782 } 19783 19784 selectedElements = []; 19785 selectedScopes = []; 19786 19787 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 19788 scope.$eval(attr.change);
19789 forEach(selectedTranscludes, function(selectedTransclude) { 19790 var selectedScope = scope.$new(); 19791 selectedScopes.push(selectedScope); 19792 selectedTransclude.transclude(selectedScope, function(caseElement) { 19793 var anchor = selectedTransclude.element; 19794 19795 selectedElements.push(caseElement); 19796 $animate.enter(caseElement, anchor.parent(), anchor); 19797 }); 19798 }); 19799 } 19800 }); 19801 } 19802 }; 19803}]; 19804 19805var ngSwitchWhenDirective = ngDirective({ 19806 transclude: 'element', 19807 priority: 800, 19808 require: '^ngSwitch', 19809 link: function(scope, element, attrs, ctrl, $transclude) { 19810 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 19811 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 19812 } 19813}); 19814 19815var ngSwitchDefaultDirective = ngDirective({ 19816 transclude: 'element', 19817 priority: 800, 19818 require: '^ngSwitch', 19819 link: function(scope, element, attr, ctrl, $transclude) { 19820 ctrl.cases['?'] = (ctrl.cases['?'] || []); 19821 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 19822 } 19823}); 19824 19825/** 19826 * @ngdoc directive 19827 * @name ng.directive:ngTransclude 19828 * @restrict AC 19829 * 19830 * @description 19831 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 19832 * 19833 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 19834 * 19835 * @element ANY 19836 * 19837 * @example 19838 <doc:example module="transclude"> 19839 <doc:source> 19840 <script> 19841 function Ctrl($scope) { 19842 $scope.title = 'Lorem Ipsum'; 19843 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 19844 } 19845 19846 angular.module('transclude', []) 19847 .directive('pane', function(){ 19848 return { 19849 restrict: 'E', 19850 transclude: true, 19851 scope: { title:'@' }, 19852 template: '<div style="border: 1px solid black;">' + 19853 '<div style="background-color: gray">{{title}}</div>' + 19854 '<div ng-transclude></div>' + 19855 '</div>' 19856 }; 19857 }); 19858 </script> 19859 <div ng-controller="Ctrl"> 19860 <input ng-model="title"><br> 19861 <textarea ng-model="text"></textarea> <br/> 19862 <pane title="{{title}}">{{text}}</pane> 19863 </div> 19864 </doc:source> 19865 <doc:scenario> 19866 it('should have transcluded', function() { 19867 input('title').enter('TITLE'); 19868 input('text').enter('TEXT'); 19869 expect(binding('title')).toEqual('TITLE'); 19870 expect(binding('text')).toEqual('TEXT'); 19871 }); 19872 </doc:scenario> 19873 </doc:example> 19874 * 19875 */ 19876var ngTranscludeDirective = ngDirective({ 19877 controller: ['$element', '$transclude', function($element, $transclude) { 19878 if (!$transclude) { 19879 throw minErr('ngTransclude')('orphan', 19880 'Illegal use of ngTransclude directive in the template! ' + 19881 'No parent directive that requires a transclusion found. ' + 19882 'Element: {0}', 19883 startingTag($element)); 19884 } 19885 19886 // remember the transclusion fn but call it during linking so that we don't process transclusion before directives on 19887 // the parent element even when the transclusion replaces the current element. (we can't use priority here because 19888 // that applies only to compile fns and not controllers 19889 this.$transclude = $transclude; 19890 }], 19891 19892 link: function($scope, $element, $attrs, controller) { 19893 controller.$transclude(function(clone) { 19894 $element.empty(); 19895 $element.append(clone); 19896 }); 19897 } 19898}); 19899 19900/** 19901 * @ngdoc directive 19902 * @name ng.directive:script 19903 * @restrict E 19904 * 19905 * @description 19906 * Load the content of a `<script>` element into {@link api/ng.$templateCache `$templateCache`}, so that the 19907 * template can be used by {@link api/ng.directive:ngInclude `ngInclude`}, 19908 * {@link api/ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 19909 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 19910 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 19911 * 19912 * @param {'text/ng-template'} type Must be set to `'text/ng-template'`. 19913 * @param {string} id Cache name of the template. 19914 * 19915 * @example 19916 <doc:example> 19917 <doc:source> 19918 <script type="text/ng-template" id="/tpl.html"> 19919 Content of the template. 19920 </script> 19921 19922 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 19923 <div id="tpl-content" ng-include src="currentTpl"></div> 19924 </doc:source> 19925 <doc:scenario> 19926 it('should load template defined inside script tag', function() {
19927 element('#tpl-link').click(); 19928 expect(element('#tpl-content').text()).toMatch(/Content of the template/); 19929 }); 19930 </doc:scenario> 19931 </doc:example> 19932 */ 19933var scriptDirective = ['$templateCache', function($templateCache) { 19934 return { 19935 restrict: 'E', 19936 terminal: true, 19937 compile: function(element, attr) { 19938 if (attr.type == 'text/ng-template') { 19939 var templateUrl = attr.id, 19940 // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent 19941 text = element[0].text; 19942 19943 $templateCache.put(templateUrl, text); 19944 } 19945 } 19946 }; 19947}]; 19948 19949var ngOptionsMinErr = minErr('ngOptions'); 19950/** 19951 * @ngdoc directive 19952 * @name ng.directive:select 19953 * @restrict E 19954 * 19955 * @description 19956 * HTML `SELECT` element with angular data-binding. 19957 * 19958 * # `ngOptions` 19959 * 19960 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 19961 * elements for the `<select>` element using the array or object obtained by evaluating the 19962 * `ngOptions` comprehension_expression. 19963 * 19964 * When an item in the `<select>` menu is selected, the array element or object property 19965 * represented by the selected option will be bound to the model identified by the `ngModel` 19966 * directive. 19967 * 19968 * <div class="alert alert-warning"> 19969 * **Note:** `ngModel` compares by reference, not value. This is important when binding to an 19970 * array of objects. See an example {@link http://jsfiddle.net/qWzTb/ in this jsfiddle}. 19971 * </div> 19972 * 19973 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 19974 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 19975 * option. See example below for demonstration. 19976 * 19977 * <div class="alert alert-warning"> 19978 * **Note:** `ngOptions` provides iterator facility for `<option>` element which should be used instead 19979 * of {@link ng.directive:ngRepeat ngRepeat} when you want the 19980 * `select` model to be bound to a non-string value. This is because an option element can only 19981 * be bound to string values at present. 19982 * </div> 19983 * 19984 * @param {string} ngModel Assignable angular expression to data-bind to. 19985 * @param {string=} name Property name of the form under which the control is published. 19986 * @param {string=} required The control is considered valid only if value is entered. 19987 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 19988 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 19989 * `required` when you want to data-bind to the `required` attribute. 19990 * @param {comprehension_expression=} ngOptions in one of the following forms: 19991 * 19992 * * for array data sources: 19993 * * `label` **`for`** `value` **`in`** `array` 19994 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 19995 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 19996 * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 19997 * * for object data sources: 19998 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 19999 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 20000 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 20001 * * `select` **`as`** `label` **`group by`** `group` 20002 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 20003 * 20004 * Where: 20005 * 20006 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 20007 * * `value`: local variable which will refer to each item in the `array` or each property value 20008 * of `object` during iteration. 20009 * * `key`: local variable which will refer to a property name in `object` during iteration. 20010 * * `label`: The result of this expression will be the label for `<option>` element. The 20011 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 20012 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 20013 * element. If not specified, `select` expression will default to `value`. 20014 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 20015 * DOM element. 20016 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 20017 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 20018 * `value` variable (e.g. `value.propertyName`). 20019 * 20020 * @example 20021 <doc:example> 20022 <doc:source> 20023 <script> 20024 function MyCntrl($scope) { 20025 $scope.colors = [ 20026 {name:'black', shade:'dark'}, 20027 {name:'white', shade:'light'}, 20028 {name:'red', shade:'dark'}, 20029 {name:'blue', shade:'dark'}, 20030 {name:'yellow', shade:'light'} 20031 ]; 20032 $scope.color = $scope.colors[2]; // red 20033 } 20034 </script> 20035 <div ng-controller="MyCntrl"> 20036 <ul> 20037 <li ng-repeat="color in colors"> 20038 Name: <input ng-model="color.name"> 20039 [<a href ng-click="colors.splice($index, 1)">X</a>] 20040 </li> 20041 <li> 20042 [<a href ng-click="colors.push({})">add</a>] 20043 </li> 20044 </ul> 20045 <hr/> 20046 Color (null not allowed): 20047 <select ng-model="color" ng-options="c.name for c in colors"></select><br> 20048 20049 Color (null allowed):
20050 <span class="nullable"> 20051 <select ng-model="color" ng-options="c.name for c in colors"> 20052 <option value="">-- choose color --</option> 20053 </select> 20054 </span><br/> 20055 20056 Color grouped by shade: 20057 <select ng-model="color" ng-options="c.name group by c.shade for c in colors"> 20058 </select><br/> 20059 20060 20061 Select <a href ng-click="color={name:'not in list'}">bogus</a>.<br> 20062 <hr/> 20063 Currently selected: {{ {selected_color:color} }} 20064 <div style="border:solid 1px black; height:20px" 20065 ng-style="{'background-color':color.name}"> 20066 </div> 20067 </div> 20068 </doc:source> 20069 <doc:scenario> 20070 it('should check ng-options', function() { 20071 expect(binding('{selected_color:color}')).toMatch('red'); 20072 select('color').option('0'); 20073 expect(binding('{selected_color:color}')).toMatch('black'); 20074 using('.nullable').select('color').option(''); 20075 expect(binding('{selected_color:color}')).toMatch('null'); 20076 }); 20077 </doc:scenario> 20078 </doc:example> 20079 */ 20080 20081var ngOptionsDirective = valueFn({ terminal: true }); 20082// jshint maxlen: false 20083var selectDirective = ['$compile', '$parse', function($compile, $parse) { 20084 //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 20085 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]+?))?$/, 20086 nullModelCtrl = {$setViewValue: noop}; 20087// jshint maxlen: 100 20088 20089 return { 20090 restrict: 'E', 20091 require: ['select', '?ngModel'], 20092 controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 20093 var self = this, 20094 optionsMap = {}, 20095 ngModelCtrl = nullModelCtrl, 20096 nullOption, 20097 unknownOption; 20098 20099 20100 self.databound = $attrs.ngModel; 20101 20102 20103 self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { 20104 ngModelCtrl = ngModelCtrl_; 20105 nullOption = nullOption_; 20106 unknownOption = unknownOption_; 20107 }; 20108 20109 20110 self.addOption = function(value) { 20111 assertNotHasOwnProperty(value, '"option value"'); 20112 optionsMap[value] = true; 20113 20114 if (ngModelCtrl.$viewValue == value) { 20115 $element.val(value); 20116 if (unknownOption.parent()) unknownOption.remove(); 20117 } 20118 }; 20119 20120 20121 self.removeOption = function(value) { 20122 if (this.hasOption(value)) { 20123 delete optionsMap[value]; 20124 if (ngModelCtrl.$viewValue == value) { 20125 this.renderUnknownOption(value); 20126 } 20127 } 20128 }; 20129 20130 20131 self.renderUnknownOption = function(val) { 20132 var unknownVal = '? ' + hashKey(val) + ' ?'; 20133 unknownOption.val(unknownVal); 20134 $element.prepend(unknownOption); 20135 $element.val(unknownVal); 20136 unknownOption.prop('selected', true); // needed for IE 20137 }; 20138 20139 20140 self.hasOption = function(value) { 20141 return optionsMap.hasOwnProperty(value); 20142 }; 20143 20144 $scope.$on('$destroy', function() { 20145 // disable unknown option so that we don't do work when the whole select is being destroyed 20146 self.renderUnknownOption = noop; 20147 }); 20148 }], 20149 20150 link: function(scope, element, attr, ctrls) { 20151 // if ngModel is not defined, we don't need to do anything 20152 if (!ctrls[1]) return; 20153 20154 var selectCtrl = ctrls[0], 20155 ngModelCtrl = ctrls[1], 20156 multiple = attr.multiple, 20157 optionsExp = attr.ngOptions, 20158 nullOption = false, // if false, user will not be able to select it (used by ngOptions) 20159 emptyOption, 20160 // we can't just jqLite('<option>') since jqLite is not smart enough 20161 // to create it in <select> and IE barfs otherwise. 20162 optionTemplate = jqLite(document.createElement('option')), 20163 optGroupTemplate =jqLite(document.createElement('optgroup')), 20164 unknownOption = optionTemplate.clone(); 20165 20166 // find "null" option 20167 for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) { 20168 if (children[i].value === '') { 20169 emptyOption = nullOption = children.eq(i); 20170 break; 20171 } 20172 } 20173 20174 selectCtrl.init(ngModelCtrl, nullOption, unknownOption); 20175 20176 // required validator 20177 if (multiple) { 20178 ngModelCtrl.$isEmpty = function(value) { 20179 return !value || value.length === 0; 20180 }; 20181 } 20182 20183 if (optionsExp) setupAsOptions(scope, element, ngModelCtrl); 20184 else if (multiple) setupAsMultiple(scope, element, ngModelCtrl); 20185 else setupAsSingle(scope, element, ngModelCtrl, selectCtrl); 20186 20187
20188 //////////////////////////// 20189 20190 20191 20192 function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) { 20193 ngModelCtrl.$render = function() { 20194 var viewValue = ngModelCtrl.$viewValue; 20195 20196 if (selectCtrl.hasOption(viewValue)) { 20197 if (unknownOption.parent()) unknownOption.remove(); 20198 selectElement.val(viewValue); 20199 if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy 20200 } else { 20201 if (isUndefined(viewValue) && emptyOption) { 20202 selectElement.val(''); 20203 } else { 20204 selectCtrl.renderUnknownOption(viewValue); 20205 } 20206 } 20207 }; 20208 20209 selectElement.on('change', function() { 20210 scope.$apply(function() { 20211 if (unknownOption.parent()) unknownOption.remove(); 20212 ngModelCtrl.$setViewValue(selectElement.val()); 20213 }); 20214 }); 20215 } 20216 20217 function setupAsMultiple(scope, selectElement, ctrl) { 20218 var lastView; 20219 ctrl.$render = function() { 20220 var items = new HashMap(ctrl.$viewValue); 20221 forEach(selectElement.find('option'), function(option) { 20222 option.selected = isDefined(items.get(option.value)); 20223 }); 20224 }; 20225 20226 // we have to do it on each watch since ngModel watches reference, but 20227 // we need to work of an array, so we need to see if anything was inserted/removed 20228 scope.$watch(function selectMultipleWatch() { 20229 if (!equals(lastView, ctrl.$viewValue)) { 20230 lastView = copy(ctrl.$viewValue); 20231 ctrl.$render(); 20232 } 20233 }); 20234 20235 selectElement.on('change', function() { 20236 scope.$apply(function() { 20237 var array = []; 20238 forEach(selectElement.find('option'), function(option) { 20239 if (option.selected) { 20240 array.push(option.value); 20241 } 20242 }); 20243 ctrl.$setViewValue(array); 20244 }); 20245 }); 20246 } 20247 20248 function setupAsOptions(scope, selectElement, ctrl) { 20249 var match; 20250 20251 if (! (match = optionsExp.match(NG_OPTIONS_REGEXP))) { 20252 throw ngOptionsMinErr('iexp', 20253 "Expected expression in form of " + 20254 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 20255 " but got '{0}'. Element: {1}", 20256 optionsExp, startingTag(selectElement)); 20257 } 20258 20259 var displayFn = $parse(match[2] || match[1]), 20260 valueName = match[4] || match[6], 20261 keyName = match[5], 20262 groupByFn = $parse(match[3] || ''), 20263 valueFn = $parse(match[2] ? match[1] : valueName), 20264 valuesFn = $parse(match[7]), 20265 track = match[8], 20266 trackFn = track ? $parse(match[8]) : null, 20267 // This is an array of array of existing option groups in DOM. 20268 // We try to reuse these if possible 20269 // - optionGroupsCache[0] is the options with no option group 20270 // - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element 20271 optionGroupsCache = [[{element: selectElement, label:''}]]; 20272 20273 if (nullOption) { 20274 // compile the element since there might be bindings in it 20275 $compile(nullOption)(scope); 20276 20277 // remove the class, which is added automatically because we recompile the element and it 20278 // becomes the compilation root 20279 nullOption.removeClass('ng-scope'); 20280 20281 // we need to remove it before calling selectElement.empty() because otherwise IE will 20282 // remove the label from the element. wtf? 20283 nullOption.remove(); 20284 } 20285 20286 // clear contents, we'll add what's needed based on the model 20287 selectElement.empty(); 20288 20289 selectElement.on('change', function() { 20290 scope.$apply(function() { 20291 var optionGroup, 20292 collection = valuesFn(scope) || [], 20293 locals = {}, 20294 key, value, optionElement, index, groupIndex, length, groupLength, trackIndex; 20295 20296 if (multiple) { 20297 value = []; 20298 for (groupIndex = 0, groupLength = optionGroupsCache.length; 20299 groupIndex < groupLength; 20300 groupIndex++) { 20301 // list of options for that group. (first item has the parent) 20302 optionGroup = optionGroupsCache[groupIndex]; 20303 20304 for(index = 1, length = optionGroup.length; index < length; index++) { 20305 if ((optionElement = optionGroup[index].element)[0].selected) { 20306 key = optionElement.val(); 20307 if (keyName) locals[keyName] = key; 20308 if (trackFn) { 20309 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 20310 locals[valueName] = collection[trackIndex]; 20311 if (trackFn(scope, locals) == key) break; 20312 } 20313 } else { 20314 locals[valueName] = collection[key]; 20315 } 20316 value.push(valueFn(scope, locals)); 20317 } 20318 } 20319 } 20320 } else { 20321 key = selectElement.val(); 20322 if (key == '?') { 20323 value = undefined; 20324 } else if (key === ''){ 20325 value = null; 20326 } else { 20327 if (trackFn) { 20328 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 20329 locals[valueName] = collection[trackIndex]; 20330 if (trackFn(scope, locals) == key) { 20331 value = valueFn(scope, locals); 20332 break; 20333 } 20334 } 20335 } else { 20336 locals[valueName] = collection[key]; 20337 if (keyName) locals[keyName] = key; 20338 value = valueFn(scope, locals); 20339 } 20340 } 20341 } 20342 ctrl.$setViewValue(value); 20343 }); 20344 }); 20345 20346 ctrl.$render = render; 20347 20348 // TODO(vojta): can't we optimize this ? 20349 scope.$watch(render); 20350 20351 function render() { 20352 // Temporary location for the option groups before we render them 20353 var optionGroups = {'':[]}, 20354 optionGroupNames = [''], 20355 optionGroupName, 20356 optionGroup, 20357 option, 20358 existingParent, existingOptions, existingOption, 20359 modelValue = ctrl.$modelValue, 20360 values = valuesFn(scope) || [], 20361 keys = keyName ? sortedKeys(values) : values, 20362 key, 20363 groupLength, length, 20364 groupIndex, index, 20365 locals = {}, 20366 selected, 20367 selectedSet = false, // nothing is selected yet 20368 lastElement, 20369 element, 20370 label; 20371 20372 if (multiple) { 20373 if (trackFn && isArray(modelValue)) { 20374 selectedSet = new HashMap([]); 20375 for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) { 20376 locals[valueName] = modelValue[trackIndex]; 20377 selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]); 20378 } 20379 } else { 20380 selectedSet = new HashMap(modelValue); 20381 } 20382 } 20383 20384 // We now build up the list of options we need (we merge later) 20385 for (index = 0; length = keys.length, index < length; index++) { 20386 20387 key = index; 20388 if (keyName) { 20389 key = keys[index]; 20390 if ( key.charAt(0) === '$' ) continue; 20391 locals[keyName] = key; 20392 } 20393 20394 locals[valueName] = values[key]; 20395 20396 optionGroupName = groupByFn(scope, locals) || ''; 20397 if (!(optionGroup = optionGroups[optionGroupName])) { 20398 optionGroup = optionGroups[optionGroupName] = []; 20399 optionGroupNames.push(optionGroupName); 20400 } 20401 if (multiple) { 20402 selected = isDefined( 20403 selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals)) 20404 ); 20405 } else { 20406 if (trackFn) { 20407 var modelCast = {}; 20408 modelCast[valueName] = modelValue; 20409 selected = trackFn(scope, modelCast) === trackFn(scope, locals); 20410 } else { 20411 selected = modelValue === valueFn(scope, locals); 20412 } 20413 selectedSet = selectedSet || selected; // see if at least one item is selected 20414 } 20415 label = displayFn(scope, locals); // what will be seen by the user 20416 20417 // doing displayFn(scope, locals) || '' overwrites zero values 20418 label = isDefined(label) ? label : ''; 20419 optionGroup.push({ 20420 // either the index into array or key from object 20421 id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index), 20422 label: label, 20423 selected: selected // determine if we should be selected 20424 }); 20425 } 20426 if (!multiple) { 20427 if (nullOption || modelValue === null) { 20428 // insert null option if we have a placeholder, or the model is null 20429 optionGroups[''].unshift({id:'', label:'', selected:!selectedSet}); 20430 } else if (!selectedSet) { 20431 // option could not be found, we have to insert the undefined item 20432 optionGroups[''].unshift({id:'?', label:'', selected:true}); 20433 } 20434 } 20435 20436 // Now we need to update the list of DOM nodes to match the optionGroups we computed above 20437 for (groupIndex = 0, groupLength = optionGroupNames.length; 20438 groupIndex < groupLength; 20439 groupIndex++) { 20440 // current option group name or '' if no group 20441 optionGroupName = optionGroupNames[groupIndex]; 20442 20443 // list of options for that group. (first item has the parent) 20444 optionGroup = optionGroups[optionGroupName]; 20445 20446 if (optionGroupsCache.length <= groupIndex) { 20447 // we need to grow the optionGroups 20448 existingParent = { 20449 element: optGroupTemplate.clone().attr('label', optionGroupName), 20450 label: optionGroup.label 20451 }; 20452 existingOptions = [existingParent]; 20453 optionGroupsCache.push(existingOptions); 20454 selectElement.append(existingParent.element); 20455 } else { 20456 existingOptions = optionGroupsCache[groupIndex]; 20457 existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element 20458 20459 // update the OPTGROUP label if not the same. 20460 if (existingParent.label != optionGroupName) { 20461 existingParent.element.attr('label', existingParent.label = optionGroupName); 20462 } 20463 } 20464 20465 lastElement = null; // start at the beginning 20466 for(index = 0, length = optionGroup.length; index < length; index++) { 20467 option = optionGroup[index]; 20468 if ((existingOption = existingOptions[index+1])) { 20469 // reuse elements 20470 lastElement = existingOption.element; 20471 if (existingOption.label !== option.label) { 20472 lastElement.text(existingOption.label = option.label); 20473 } 20474 if (existingOption.id !== option.id) { 20475 lastElement.val(existingOption.id = option.id); 20476 } 20477 // lastElement.prop('selected') provided by jQuery has side-effects 20478 if (lastElement[0].selected !== option.selected) {
20479 lastElement.prop('selected', (existingOption.selected = option.selected)); 20480 } 20481 } else { 20482 // grow elements 20483 20484 // if it's a null option 20485 if (option.id === '' && nullOption) { 20486 // put back the pre-compiled element 20487 element = nullOption; 20488 } else { 20489 // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but 20490 // in this version of jQuery on some browser the .text() returns a string 20491 // rather then the element. 20492 (element = optionTemplate.clone()) 20493 .val(option.id) 20494 .attr('selected', option.selected) 20495 .text(option.label); 20496 } 20497 20498 existingOptions.push(existingOption = { 20499 element: element, 20500 label: option.label, 20501 id: option.id, 20502 selected: option.selected 20503 }); 20504 if (lastElement) { 20505 lastElement.after(element); 20506 } else { 20507 existingParent.element.append(element); 20508 } 20509 lastElement = element; 20510 } 20511 } 20512 // remove any excessive OPTIONs in a group 20513 index++; // increment since the existingOptions[0] is parent element not OPTION 20514 while(existingOptions.length > index) { 20515 existingOptions.pop().element.remove(); 20516 } 20517 } 20518 // remove any excessive OPTGROUPs from select 20519 while(optionGroupsCache.length > groupIndex) { 20520 optionGroupsCache.pop()[0].element.remove(); 20521 } 20522 } 20523 } 20524 } 20525 }; 20526}]; 20527 20528var optionDirective = ['$interpolate', function($interpolate) { 20529 var nullSelectCtrl = { 20530 addOption: noop, 20531 removeOption: noop 20532 }; 20533 20534 return { 20535 restrict: 'E', 20536 priority: 100, 20537 compile: function(element, attr) { 20538 if (isUndefined(attr.value)) { 20539 var interpolateFn = $interpolate(element.text(), true); 20540 if (!interpolateFn) { 20541 attr.$set('value', element.text()); 20542 } 20543 } 20544 20545 return function (scope, element, attr) { 20546 var selectCtrlName = '$selectController', 20547 parent = element.parent(), 20548 selectCtrl = parent.data(selectCtrlName) || 20549 parent.parent().data(selectCtrlName); // in case we are in optgroup 20550 20551 if (selectCtrl && selectCtrl.databound) { 20552 // For some reason Opera defaults to true and if not overridden this messes up the repeater. 20553 // We don't want the view to drive the initialization of the model anyway. 20554 element.prop('selected', false); 20555 } else { 20556 selectCtrl = nullSelectCtrl; 20557 } 20558 20559 if (interpolateFn) { 20560 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 20561 attr.$set('value', newVal); 20562 if (newVal !== oldVal) selectCtrl.removeOption(oldVal); 20563 selectCtrl.addOption(newVal); 20564 }); 20565 } else { 20566 selectCtrl.addOption(attr.value); 20567 } 20568 20569 element.on('$destroy', function() { 20570 selectCtrl.removeOption(attr.value); 20571 }); 20572 }; 20573 } 20574 }; 20575}]; 20576 20577var styleDirective = valueFn({ 20578 restrict: 'E', 20579 terminal: true 20580}); 20581 20582 //try to bind to jquery now so that one can write angular.element().read() 20583 //but we will rebind on bootstrap again. 20584 bindJQuery(); 20585 20586 publishExternalAPI(angular); 20587 20588 jqLite(document).ready(function() { 20589 angularInit(document, bootstrap); 20590 }); 20591 20592})(window, document); 20593 20594!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;}</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.