1/** 2 * @license AngularJS v1.2.16 3 * (c) 2010-2014 Google, Inc. http://angularjs.org 4 * License: MIT 5 */ 6(function (window, document, undefined) { 7 'use strict'; 8 9 /** 10 * @description 11 * 12 * This object provides a utility for producing rich Error messages within 13 * Angular. It can be called as follows: 14 * 15 * var exampleMinErr = minErr('example'); 16 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); 17 * 18 * The above creates an instance of minErr in the example namespace. The 19 * resulting error will have a namespaced error code of example.one. The 20 * resulting error will replace {0} with the value of foo, and {1} with the 21 * value of bar. The object is not restricted in the number of arguments it can 22 * take. 23 * 24 * If fewer arguments are specified than necessary for interpolation, the extra 25 * interpolation markers will be preserved in the final string. 26 * 27 * Since data will be parsed statically during a build step, some restrictions 28 * are applied with respect to how minErr instances are created and called. 29 * Instances should have names of the form namespaceMinErr for a minErr created 30 * using minErr('namespace') . Error codes, namespaces and template strings 31 * should all be static strings, not variables or general expressions. 32 * 33 * @param {string} module The namespace to use for the new minErr instance. 34 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 35 */ 36 37 function minErr(module) { 38 return function () { 39 var code = arguments[0], 40 prefix = '[' + (module ? module + ':' : '') + code + '] ', 41 template = arguments[1], 42 templateArgs = arguments, 43 stringify = function (obj) { 44 if (typeof obj === 'function') { 45 return obj.toString().replace(/ \{[\s\S]*$/, ''); 46 } else if (typeof obj === 'undefined') { 47 return 'undefined'; 48 } else if (typeof obj !== 'string') { 49 return JSON.stringify(obj); 50 } 51 return obj; 52 }, 53 message, i; 54 55 message = prefix + template.replace(/\{\d+\}/g, function (match) { 56 var index = +match.slice(1, -1), arg; 57 58 if (index + 2 < templateArgs.length) { 59 arg = templateArgs[index + 2]; 60 if (typeof arg === 'function') { 61 return arg.toString().replace(/ ?\{[\s\S]*$/, ''); 62 } else if (typeof arg === 'undefined') { 63 return 'undefined'; 64 } else if (typeof arg !== 'string') { 65 return toJson(arg); 66 } 67 return arg; 68 } 69 return match; 70 }); 71 72 message = message + '\nhttp://errors.angularjs.org/1.2.16/' + 73 (module ? module + '/' : '') + code; 74 for (i = 2; i < arguments.length; i++) { 75 message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' + 76 encodeURIComponent(stringify(arguments[i])); 77 } 78 79 return new Error(message); 80 }; 81 } 82 83 /* We need to tell jshint what variables are being exported */ 84 /* global 85 -angular, 86 -msie, 87 -jqLite, 88 -jQuery, 89 -slice, 90 -push, 91 -toString, 92 -ngMinErr, 93 -_angular, 94 -angularModule, 95 -nodeName_, 96 -uid, 97 98 -lowercase, 99 -uppercase, 100 -manualLowercase, 101 -manualUppercase, 102 -nodeName_, 103 -isArrayLike, 104 -forEach, 105 -sortedKeys, 106 -forEachSorted, 107 -reverseParams, 108 -nextUid, 109 -setHashKey, 110 -extend, 111 -int, 112 -inherit, 113 -noop, 114 -identity, 115 -valueFn, 116 -isUndefined, 117 -isDefined, 118 -isObject, 119 -isString, 120 -isNumber, 121 -isDate, 122 -isArray, 123 -isFunction, 124 -isRegExp, 125 -isWindow, 126 -isScope, 127 -isFile, 128 -isBlob, 129 -isBoolean, 130 -trim, 131 -isElement, 132 -makeMap, 133 -map, 134 -size, 135 -includes, 136 -indexOf, 137 -arrayRemove, 138 -isLeafNode, 139 -copy, 140 -shallowCopy, 141 -equals, 142 -csp, 143 -concat, 144 -sliceArgs, 145 -bind,
146 -toJsonReplacer, 147 -toJson, 148 -fromJson, 149 -toBoolean, 150 -startingTag, 151 -tryDecodeURIComponent, 152 -parseKeyValue, 153 -toKeyValue, 154 -encodeUriSegment, 155 -encodeUriQuery, 156 -angularInit, 157 -bootstrap, 158 -snake_case, 159 -bindJQuery, 160 -assertArg, 161 -assertArgFn, 162 -assertNotHasOwnProperty, 163 -getter, 164 -getBlockElements, 165 -hasOwnProperty, 166 167 */ 168 169//////////////////////////////////// 170 171 /** 172 * @ngdoc module 173 * @name ng 174 * @module ng 175 * @description 176 * 177 * # ng (core module) 178 * The ng module is loaded by default when an AngularJS application is started. The module itself 179 * contains the essential components for an AngularJS application to function. The table below 180 * lists a high level breakdown of each of the services/factories, filters, directives and testing 181 * components available within this core module. 182 * 183 * <div doc-module-components="ng"></div> 184 */ 185 186 /** 187 * @ngdoc function 188 * @name angular.lowercase 189 * @module ng 190 * @function 191 * 192 * @description Converts the specified string to lowercase. 193 * @param {string} string String to be converted to lowercase. 194 * @returns {string} Lowercased string. 195 */ 196 var lowercase = function (string) { 197 return isString(string) ? string.toLowerCase() : string; 198 }; 199 var hasOwnProperty = Object.prototype.hasOwnProperty; 200 201 /** 202 * @ngdoc function 203 * @name angular.uppercase 204 * @module ng 205 * @function 206 * 207 * @description Converts the specified string to uppercase. 208 * @param {string} string String to be converted to uppercase. 209 * @returns {string} Uppercased string. 210 */ 211 var uppercase = function (string) { 212 return isString(string) ? string.toUpperCase() : string; 213 }; 214 215 216 var manualLowercase = function (s) { 217 /* jshint bitwise: false */ 218 return isString(s) 219 ? s.replace(/[A-Z]/g, function (ch) { 220 return String.fromCharCode(ch.charCodeAt(0) | 32); 221 }) 222 : s; 223 }; 224 var manualUppercase = function (s) { 225 /* jshint bitwise: false */ 226 return isString(s) 227 ? s.replace(/[a-z]/g, function (ch) { 228 return String.fromCharCode(ch.charCodeAt(0) & ~32); 229 }) 230 : s; 231 }; 232 233 234// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 235// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 236// with correct but slower alternatives. 237 if ('i' !== 'I'.toLowerCase()) { 238 lowercase = manualLowercase; 239 uppercase = manualUppercase; 240 } 241 242 243 var /** holds major version number for IE or NaN for real browsers */ 244 msie, 245 jqLite, // delay binding since jQuery could be loaded after us. 246 jQuery, // delay binding 247 slice = [].slice, 248 push = [].push, 249 toString = Object.prototype.toString, 250 ngMinErr = minErr('ng'), 251 252 253 _angular = window.angular, 254 /** @name angular */ 255 angular = window.angular || (window.angular = {}), 256 angularModule, 257 nodeName_, 258 uid = ['0', '0', '0']; 259 260 /** 261 * IE 11 changed the format of the UserAgent string. 262 * See http://msdn.microsoft.com/en-us/library/ms537503.aspx 263 */ 264 msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); 265 if (isNaN(msie)) { 266 msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); 267 } 268 269 270 /** 271 * @private 272 * @param {*} obj 273 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 274 * String ...) 275 */ 276 function isArrayLike(obj) { 277 if (obj == null || isWindow(obj)) { 278 return false; 279 } 280 281 var length = obj.length; 282 283 if (obj.nodeType === 1 && length) { 284 return true; 285 } 286 287 return isString(obj) || isArray(obj) || length === 0 || 288 typeof length === 'number' && length > 0 && (length - 1) in obj; 289 } 290 291 /** 292 * @ngdoc function 293 * @name angular.forEach 294 * @module ng 295 * @function 296 *
297 * @description 298 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 299 * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` 300 * is the value of an object property or an array element and `key` is the object property key or 301 * array element index. Specifying a `context` for the function is optional. 302 * 303 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 304 * using the `hasOwnProperty` method. 305 * 306 ```js 307 var values = {name: 'misko', gender: 'male'}; 308 var log = []; 309 angular.forEach(values, function(value, key){ 310 this.push(key + ': ' + value); 311 }, log); 312 expect(log).toEqual(['name: misko', 'gender: male']); 313 ``` 314 * 315 * @param {Object|Array} obj Object to iterate over. 316 * @param {Function} iterator Iterator function. 317 * @param {Object=} context Object to become context (`this`) for the iterator function. 318 * @returns {Object|Array} Reference to `obj`. 319 */ 320 function forEach(obj, iterator, context) { 321 var key; 322 if (obj) { 323 if (isFunction(obj)) { 324 for (key in obj) { 325 // Need to check if hasOwnProperty exists, 326 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 327 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 328 iterator.call(context, obj[key], key); 329 } 330 } 331 } else if (obj.forEach && obj.forEach !== forEach) { 332 obj.forEach(iterator, context); 333 } else if (isArrayLike(obj)) { 334 for (key = 0; key < obj.length; key++) 335 iterator.call(context, obj[key], key); 336 } else { 337 for (key in obj) { 338 if (obj.hasOwnProperty(key)) { 339 iterator.call(context, obj[key], key); 340 } 341 } 342 } 343 } 344 return obj; 345 } 346 347 function sortedKeys(obj) { 348 var keys = []; 349 for (var key in obj) { 350 if (obj.hasOwnProperty(key)) { 351 keys.push(key); 352 } 353 } 354 return keys.sort(); 355 } 356 357 function forEachSorted(obj, iterator, context) { 358 var keys = sortedKeys(obj); 359 for (var i = 0; i < keys.length; i++) { 360 iterator.call(context, obj[keys[i]], keys[i]); 361 } 362 return keys; 363 } 364 365 366 /** 367 * when using forEach the params are value, key, but it is often useful to have key, value. 368 * @param {function(string, *)} iteratorFn 369 * @returns {function(*, string)} 370 */ 371 function reverseParams(iteratorFn) { 372 return function (value, key) { 373 iteratorFn(key, value); 374 }; 375 } 376 377 /** 378 * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric 379 * characters such as '012ABC'. The reason why we are not using simply a number counter is that 380 * the number string gets longer over time, and it can also overflow, where as the nextId 381 * will grow much slower, it is a string, and it will never overflow. 382 * 383 * @returns {string} an unique alpha-numeric string 384 */ 385 function nextUid() { 386 var index = uid.length; 387 var digit; 388 389 while (index) { 390 index--;
391 digit = uid[index].charCodeAt(0); 392 if (digit == 57 /*'9'*/) { 393 uid[index] = 'A'; 394 return uid.join(''); 395 } 396 if (digit == 90 /*'Z'*/) { 397 uid[index] = '0'; 398 } else { 399 uid[index] = String.fromCharCode(digit + 1); 400 return uid.join(''); 401 } 402 } 403 uid.unshift('0'); 404 return uid.join(''); 405 } 406 407 408 /** 409 * Set or clear the hashkey for an object. 410 * @param obj object 411 * @param h the hashkey (!truthy to delete the hashkey) 412 */ 413 function setHashKey(obj, h) { 414 if (h) { 415 obj.$$hashKey = h; 416 } 417 else { 418 delete obj.$$hashKey; 419 } 420 } 421 422 /** 423 * @ngdoc function 424 * @name angular.extend 425 * @module ng 426 * @function 427 * 428 * @description 429 * Extends the destination object `dst` by copying all of the properties from the `src` object(s) 430 * to `dst`. You can specify multiple `src` objects. 431 * 432 * @param {Object} dst Destination object. 433 * @param {...Object} src Source object(s). 434 * @returns {Object} Reference to `dst`. 435 */ 436 function extend(dst) { 437 var h = dst.$$hashKey; 438 forEach(arguments, function (obj) { 439 if (obj !== dst) { 440 forEach(obj, function (value, key) { 441 dst[key] = value; 442 }); 443 } 444 }); 445 446 setHashKey(dst, h); 447 return dst; 448 } 449 450 function int(str) { 451 return parseInt(str, 10); 452 } 453 454 455 function inherit(parent, extra) { 456 return extend(new (extend(function () { 457 }, {prototype: parent}))(), extra); 458 } 459 460 /** 461 * @ngdoc function 462 * @name angular.noop 463 * @module ng 464 * @function 465 * 466 * @description 467 * A function that performs no operations. This function can be useful when writing code in the 468 * functional style. 469 ```js 470 function foo(callback) { 471 var result = calculateResult(); 472 (callback || angular.noop)(result); 473 } 474 ``` 475 */ 476 function noop() { 477 } 478 479 noop.$inject = []; 480 481 482 /** 483 * @ngdoc function 484 * @name angular.identity 485 * @module ng 486 * @function 487 * 488 * @description 489 * A function that returns its first argument. This function is useful when writing code in the 490 * functional style. 491 * 492 ```js 493 function transformer(transformationFn, value) { 494 return (transformationFn || angular.identity)(value); 495 }; 496 ``` 497 */ 498 function identity($) { 499 return $; 500 } 501 502 identity.$inject = []; 503 504 505 function valueFn(value) { 506 return function () { 507 return value; 508 }; 509 } 510 511 /** 512 * @ngdoc function 513 * @name angular.isUndefined 514 * @module ng 515 * @function 516 * 517 * @description 518 * Determines if a reference is undefined. 519 * 520 * @param {*} value Reference to check. 521 * @returns {boolean} True if `value` is undefined. 522 */ 523 function isUndefined(value) { 524 return typeof value === 'undefined'; 525 } 526 527 528 /** 529 * @ngdoc function 530 * @name angular.isDefined 531 * @module ng 532 * @function 533 * 534 * @description 535 * Determines if a reference is defined. 536 * 537 * @param {*} value Reference to check. 538 * @returns {boolean} True if `value` is defined. 539 */ 540 function isDefined(value) { 541 return typeof value !== 'undefined'; 542 } 543 544 545 /** 546 * @ngdoc function 547 * @name angular.isObject 548 * @module ng 549 * @function 550 * 551 * @description 552 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 553 * considered to be objects. Note that JavaScript arrays are objects. 554 * 555 * @param {*} value Reference to check. 556 * @returns {boolean} True if `value` is an `Object` but not `null`. 557 */ 558 function isObject(value) { 559 return value != null && typeof value === 'object'; 560 } 561 562 563 /** 564 * @ngdoc function 565 * @name angular.isString 566 * @module ng 567 * @function 568 *
569 * @description 570 * Determines if a reference is a `String`. 571 * 572 * @param {*} value Reference to check. 573 * @returns {boolean} True if `value` is a `String`. 574 */ 575 function isString(value) { 576 return typeof value === 'string'; 577 } 578 579 580 /** 581 * @ngdoc function 582 * @name angular.isNumber 583 * @module ng 584 * @function 585 * 586 * @description 587 * Determines if a reference is a `Number`. 588 * 589 * @param {*} value Reference to check. 590 * @returns {boolean} True if `value` is a `Number`. 591 */ 592 function isNumber(value) { 593 return typeof value === 'number'; 594 } 595 596 597 /** 598 * @ngdoc function 599 * @name angular.isDate 600 * @module ng 601 * @function 602 * 603 * @description 604 * Determines if a value is a date. 605 * 606 * @param {*} value Reference to check. 607 * @returns {boolean} True if `value` is a `Date`. 608 */ 609 function isDate(value) { 610 return toString.call(value) === '[object Date]'; 611 } 612 613 614 /** 615 * @ngdoc function 616 * @name angular.isArray 617 * @module ng 618 * @function 619 * 620 * @description 621 * Determines if a reference is an `Array`. 622 * 623 * @param {*} value Reference to check. 624 * @returns {boolean} True if `value` is an `Array`. 625 */ 626 function isArray(value) { 627 return toString.call(value) === '[object Array]'; 628 } 629 630 631 /** 632 * @ngdoc function 633 * @name angular.isFunction 634 * @module ng 635 * @function 636 * 637 * @description 638 * Determines if a reference is a `Function`. 639 * 640 * @param {*} value Reference to check. 641 * @returns {boolean} True if `value` is a `Function`. 642 */ 643 function isFunction(value) { 644 return typeof value === 'function'; 645 } 646 647 648 /** 649 * Determines if a value is a regular expression object. 650 * 651 * @private 652 * @param {*} value Reference to check. 653 * @returns {boolean} True if `value` is a `RegExp`. 654 */ 655 function isRegExp(value) { 656 return toString.call(value) === '[object RegExp]'; 657 } 658 659 660 /** 661 * Checks if `obj` is a window object. 662 * 663 * @private 664 * @param {*} obj Object to check 665 * @returns {boolean} True if `obj` is a window obj. 666 */ 667 function isWindow(obj) { 668 return obj && obj.document && obj.location && obj.alert && obj.setInterval; 669 } 670 671 672 function isScope(obj) { 673 return obj && obj.$evalAsync && obj.$watch; 674 } 675 676 677 function isFile(obj) { 678 return toString.call(obj) === '[object File]'; 679 } 680 681 682 function isBlob(obj) { 683 return toString.call(obj) === '[object Blob]'; 684 } 685 686 687 function isBoolean(value) { 688 return typeof value === 'boolean'; 689 } 690 691 692 var trim = (function () { 693 // native trim is way faster: http://jsperf.com/angular-trim-test 694 // but IE doesn't have it... :-( 695 // TODO: we should move this into IE/ES5 polyfill 696 if (!String.prototype.trim) { 697 return function (value) { 698 return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value; 699 }; 700 } 701 return function (value) { 702 return isString(value) ? value.trim() : value; 703 }; 704 })(); 705 706 707 /** 708 * @ngdoc function 709 * @name angular.isElement 710 * @module ng 711 * @function 712 * 713 * @description 714 * Determines if a reference is a DOM element (or wrapped jQuery element). 715 * 716 * @param {*} value Reference to check. 717 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 718 */ 719 function isElement(node) { 720 return !!(node && 721 (node.nodeName // we are a direct element 722 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 723 } 724 725 /** 726 * @param str 'key1,key2,...' 727 * @returns {object} in the form of {key1:true, key2:true, ...} 728 */ 729 function makeMap(str) { 730 var obj = {}, items = str.split(","), i; 731 for (i = 0; i < items.length; i++) 732 obj[ items[i] ] = true; 733 return obj; 734 } 735 736 737 if (msie < 9) { 738 nodeName_ = function (element) { 739 element = element.nodeName ? element : element[0]; 740 return (element.scopeName && element.scopeName != 'HTML') 741 ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; 742 }; 743 } else { 744 nodeName_ = function (element) { 745 return element.nodeName ? element.nodeName : element[0].nodeName; 746 }; 747 } 748 749 750 function map(obj, iterator, context) { 751 var results = [];
752 forEach(obj, function (value, index, list) { 753 results.push(iterator.call(context, value, index, list)); 754 }); 755 return results; 756 } 757 758 759 /** 760 * @description 761 * Determines the number of elements in an array, the number of properties an object has, or 762 * the length of a string. 763 * 764 * Note: This function is used to augment the Object type in Angular expressions. See 765 * {@link angular.Object} for more information about Angular arrays. 766 * 767 * @param {Object|Array|string} obj Object, array, or string to inspect. 768 * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object 769 * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. 770 */ 771 function size(obj, ownPropsOnly) { 772 var count = 0, key; 773 774 if (isArray(obj) || isString(obj)) { 775 return obj.length; 776 } else if (isObject(obj)) { 777 for (key in obj) 778 if (!ownPropsOnly || obj.hasOwnProperty(key)) 779 count++; 780 } 781 782 return count; 783 } 784 785 786 function includes(array, obj) { 787 return indexOf(array, obj) != -1; 788 } 789 790 function indexOf(array, obj) { 791 if (array.indexOf) return array.indexOf(obj); 792 793 for (var i = 0; i < array.length; i++) { 794 if (obj === array[i]) return i; 795 } 796 return -1; 797 } 798 799 function arrayRemove(array, value) { 800 var index = indexOf(array, value); 801 if (index >= 0) 802 array.splice(index, 1); 803 return value; 804 } 805 806 function isLeafNode(node) { 807 if (node) { 808 switch (node.nodeName) { 809 case "OPTION": 810 case "PRE": 811 case "TITLE": 812 return true; 813 } 814 } 815 return false; 816 } 817 818 /** 819 * @ngdoc function 820 * @name angular.copy 821 * @module ng 822 * @function 823 * 824 * @description 825 * Creates a deep copy of `source`, which should be an object or an array. 826 * 827 * * If no destination is supplied, a copy of the object or array is created. 828 * * If a destination is provided, all of its elements (for array) or properties (for objects) 829 * are deleted and then all elements/properties from the source are copied to it. 830 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 831 * * If `source` is identical to 'destination' an exception will be thrown. 832 * 833 * @param {*} source The source that will be used to make a copy. 834 * Can be any type, including primitives, `null`, and `undefined`. 835 * @param {(Object|Array)=} destination Destination into which the source is copied. If 836 * provided, must be of the same type as `source`. 837 * @returns {*} The copy or updated `destination`, if `destination` was specified. 838 * 839 * @example 840 <example> 841 <file name="index.jsp"> 842 <div ng-controller="Controller"> 843 <form novalidate class="simple-form"> 844 Name: <input type="text" ng-model="user.name" /><br /> 845 E-mail: <input type="email" ng-model="user.email" /><br /> 846 Gender: <input type="radio" ng-model="user.gender" value="male" />male 847 <input type="radio" ng-model="user.gender" value="female" />female<br /> 848 <button ng-click="reset()">RESET</button> 849 <button ng-click="update(user)">SAVE</button> 850 </form> 851 <pre>form = {{user | json}}</pre> 852 <pre>master = {{master | json}}</pre>
853 </div> 854 855 <script> 856 function Controller($scope) { 857 $scope.master= {}; 858 859 $scope.update = function(user) { 860 // Example with 1 argument 861 $scope.master= angular.copy(user); 862 }; 863 864 $scope.reset = function() { 865 // Example with 2 arguments 866 angular.copy($scope.master, $scope.user); 867 }; 868 869 $scope.reset(); 870 } 871 </script> 872 </file> 873 </example> 874 */ 875 function copy(source, destination) { 876 if (isWindow(source) || isScope(source)) { 877 throw ngMinErr('cpws', 878 "Can't copy! Making copies of Window or Scope instances is not supported."); 879 } 880 881 if (!destination) { 882 destination = source; 883 if (source) { 884 if (isArray(source)) { 885 destination = copy(source, []); 886 } else if (isDate(source)) { 887 destination = new Date(source.getTime()); 888 } else if (isRegExp(source)) { 889 destination = new RegExp(source.source); 890 } else if (isObject(source)) { 891 destination = copy(source, {}); 892 } 893 } 894 } else { 895 if (source === destination) throw ngMinErr('cpi', 896 "Can't copy! Source and destination are identical."); 897 if (isArray(source)) { 898 destination.length = 0; 899 for (var i = 0; i < source.length; i++) { 900 destination.push(copy(source[i])); 901 } 902 } else { 903 var h = destination.$$hashKey; 904 forEach(destination, function (value, key) { 905 delete destination[key]; 906 }); 907 for (var key in source) { 908 destination[key] = copy(source[key]); 909 } 910 setHashKey(destination, h); 911 } 912 } 913 return destination; 914 } 915 916 /** 917 * Create a shallow copy of an object 918 */ 919 function shallowCopy(src, dst) { 920 dst = dst || {}; 921 922 for (var key in src) { 923 // shallowCopy is only ever called by $compile nodeLinkFn, which has control over src 924 // so we don't need to worry about using our custom hasOwnProperty here 925 if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { 926 dst[key] = src[key]; 927 } 928 } 929 930 return dst; 931 } 932 933 934 /** 935 * @ngdoc function 936 * @name angular.equals 937 * @module ng 938 * @function 939 * 940 * @description 941 * Determines if two objects or two values are equivalent. Supports value types, regular 942 * expressions, arrays and objects. 943 * 944 * Two objects or values are considered equivalent if at least one of the following is true: 945 * 946 * * Both objects or values pass `===` comparison. 947 * * Both objects or values are of the same type and all of their properties are equal by 948 * comparing them with `angular.equals`. 949 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 950 * * Both values represent the same regular expression (In JavasScript, 951 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 952 * representation matches). 953 * 954 * During a property comparison, properties of `function` type and properties with names 955 * that begin with `$` are ignored. 956 * 957 * Scope and DOMWindow objects are being compared only by identify (`===`). 958 * 959 * @param {*} o1 Object or value to compare. 960 * @param {*} o2 Object or value to compare. 961 * @returns {boolean} True if arguments are equal. 962 */ 963 function equals(o1, o2) { 964 if (o1 === o2) return true; 965 if (o1 === null || o2 === null) return false; 966 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 967 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 968 if (t1 == t2) { 969 if (t1 == 'object') { 970 if (isArray(o1)) { 971 if (!isArray(o2)) return false;
972 if ((length = o1.length) == o2.length) { 973 for (key = 0; key < length; key++) { 974 if (!equals(o1[key], o2[key])) return false; 975 } 976 return true; 977 } 978 } else if (isDate(o1)) { 979 return isDate(o2) && o1.getTime() == o2.getTime(); 980 } else if (isRegExp(o1) && isRegExp(o2)) { 981 return o1.toString() == o2.toString(); 982 } else { 983 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false; 984 keySet = {}; 985 for (key in o1) { 986 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 987 if (!equals(o1[key], o2[key])) return false; 988 keySet[key] = true; 989 } 990 for (key in o2) { 991 if (!keySet.hasOwnProperty(key) && 992 key.charAt(0) !== '$' && 993 o2[key] !== undefined && !isFunction(o2[key])) return false; 994 } 995 return true; 996 } 997 } 998 } 999 return false; 1000 } 1001 1002 1003 function csp() { 1004 return (document.securityPolicy && document.securityPolicy.isActive) || 1005 (document.querySelector && !!(document.querySelector('[ng-csp]') || document.querySelector('[data-ng-csp]'))); 1006 } 1007 1008 1009 function concat(array1, array2, index) { 1010 return array1.concat(slice.call(array2, index)); 1011 } 1012 1013 function sliceArgs(args, startIndex) { 1014 return slice.call(args, startIndex || 0); 1015 } 1016 1017
1018 /* jshint -W101 */ 1019 /** 1020 * @ngdoc function 1021 * @name angular.bind 1022 * @module ng 1023 * @function 1024 * 1025 * @description 1026 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1027 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1028 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1029 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1030 * 1031 * @param {Object} self Context which `fn` should be evaluated in. 1032 * @param {function()} fn Function to be bound. 1033 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1034 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1035 */ 1036 /* jshint +W101 */ 1037 function bind(self, fn) { 1038 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1039 if (isFunction(fn) && !(fn instanceof RegExp)) { 1040 return curryArgs.length 1041 ? function () { 1042 return arguments.length 1043 ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) 1044 : fn.apply(self, curryArgs); 1045 } 1046 : function () { 1047 return arguments.length 1048 ? fn.apply(self, arguments) 1049 : fn.call(self); 1050 }; 1051 } else { 1052 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1053 return fn; 1054 } 1055 } 1056 1057 1058 function toJsonReplacer(key, value) { 1059 var val = value; 1060 1061 if (typeof key === 'string' && key.charAt(0) === '$') { 1062 val = undefined; 1063 } else if (isWindow(value)) { 1064 val = '$WINDOW'; 1065 } else if (value && document === value) { 1066 val = '$DOCUMENT'; 1067 } else if (isScope(value)) { 1068 val = '$SCOPE'; 1069 } 1070 1071 return val; 1072 } 1073 1074 1075 /** 1076 * @ngdoc function 1077 * @name angular.toJson 1078 * @module ng 1079 * @function 1080 * 1081 * @description 1082 * Serializes input into a JSON-formatted string. Properties with leading $ characters will be 1083 * stripped since angular uses this notation internally. 1084 * 1085 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1086 * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace. 1087 * @returns {string|undefined} JSON-ified string representing `obj`. 1088 */ 1089 function toJson(obj, pretty) { 1090 if (typeof obj === 'undefined') return undefined; 1091 return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); 1092 } 1093 1094 1095 /** 1096 * @ngdoc function 1097 * @name angular.fromJson 1098 * @module ng 1099 * @function 1100 * 1101 * @description 1102 * Deserializes a JSON string. 1103 * 1104 * @param {string} json JSON string to deserialize. 1105 * @returns {Object|Array|string|number} Deserialized thingy. 1106 */ 1107 function fromJson(json) { 1108 return isString(json) 1109 ? JSON.parse(json) 1110 : json; 1111 } 1112 1113 1114 function toBoolean(value) { 1115 if (typeof value === 'function') { 1116 value = true; 1117 } else if (value && value.length !== 0) { 1118 var v = lowercase("" + value); 1119 value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); 1120 } else { 1121 value = false; 1122 } 1123 return value; 1124 } 1125 1126 /** 1127 * @returns {string} Returns the string representation of the element. 1128 */ 1129 function startingTag(element) { 1130 element = jqLite(element).clone(); 1131 try { 1132 // turns out IE does not let you set .html() on elements which 1133 // are not allowed to have children. So we just ignore it. 1134 element.empty(); 1135 } catch (e) { 1136 } 1137 // As Per DOM Standards 1138 var TEXT_NODE = 3; 1139 var elemHtml = jqLite('<div>').append(element).html(); 1140 try { 1141 return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : 1142 elemHtml. 1143 match(/^(<[^>]+>)/)[1]. 1144 replace(/^<([\w\-]+)/, function (match, nodeName) { 1145 return '<' + lowercase(nodeName); 1146 }); 1147 } catch (e) { 1148 return lowercase(elemHtml); 1149 } 1150 1151 } 1152 1153 1154///////////////////////////////////////////////// 1155 1156 /** 1157 * Tries to decode the URI component without throwing an exception. 1158 * 1159 * @private 1160 * @param str value potential URI component to check. 1161 * @returns {boolean} True if `value` can be decoded 1162 * with the decodeURIComponent function. 1163 */ 1164 function tryDecodeURIComponent(value) { 1165 try { 1166 return decodeURIComponent(value); 1167 } catch (e) {
vendor: 5,051 bytes, lines 1168-1289
1168 // Ignore any invalid uri component 1169 } 1170 } 1171 1172 1173 /** 1174 * Parses an escaped url query string into key-value pairs. 1175 * @returns {Object.<string,boolean|Array>} 1176 */ 1177 function parseKeyValue(/**string*/keyValue) { 1178 var obj = {}, key_value, key; 1179 forEach((keyValue || "").split('&'), function (keyValue) { 1180 if (keyValue) { 1181 key_value = keyValue.split('='); 1182 key = tryDecodeURIComponent(key_value[0]); 1183 if (isDefined(key)) { 1184 var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; 1185 if (!obj[key]) { 1186 obj[key] = val; 1187 } else if (isArray(obj[key])) { 1188 obj[key].push(val); 1189 } else { 1190 obj[key] = [obj[key], val]; 1191 } 1192 } 1193 } 1194 }); 1195 return obj; 1196 } 1197 1198 function toKeyValue(obj) { 1199 var parts = []; 1200 forEach(obj, function (value, key) { 1201 if (isArray(value)) { 1202 forEach(value, function (arrayValue) { 1203 parts.push(encodeUriQuery(key, true) + 1204 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1205 }); 1206 } else { 1207 parts.push(encodeUriQuery(key, true) + 1208 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1209 } 1210 }); 1211 return parts.length ? parts.join('&') : ''; 1212 } 1213 1214 1215 /** 1216 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1217 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1218 * segments: 1219 * segment = *pchar 1220 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1221 * pct-encoded = "%" HEXDIG HEXDIG 1222 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1223 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1224 * / "*" / "+" / "," / ";" / "=" 1225 */ 1226 function encodeUriSegment(val) { 1227 return encodeUriQuery(val, true). 1228 replace(/%26/gi, '&'). 1229 replace(/%3D/gi, '='). 1230 replace(/%2B/gi, '+'); 1231 } 1232 1233 1234 /** 1235 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1236 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1237 * encoded per http://tools.ietf.org/html/rfc3986: 1238 * query = *( pchar / "/" / "?" ) 1239 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1240 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1241 * pct-encoded = "%" HEXDIG HEXDIG 1242 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1243 * / "*" / "+" / "," / ";" / "=" 1244 */ 1245 function encodeUriQuery(val, pctEncodeSpaces) { 1246 return encodeURIComponent(val). 1247 replace(/%40/gi, '@'). 1248 replace(/%3A/gi, ':'). 1249 replace(/%24/g, '$'). 1250 replace(/%2C/gi, ','). 1251 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1252 } 1253 1254 1255 /** 1256 * @ngdoc directive 1257 * @name ngApp 1258 * @module ng 1259 * 1260 * @element ANY 1261 * @param {angular.Module} ngApp an optional application 1262 * {@link angular.module module} name to load. 1263 * 1264 * @description 1265 * 1266 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1267 * designates the **root element** of the application and is typically placed near the root element 1268 * of the page - e.g. on the `<body>` or `<html>` tags. 1269 * 1270 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` 1271 * found in the document will be used to define the root element to auto-bootstrap as an 1272 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1273 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1274 * 1275 * You can specify an **AngularJS module** to be used as the root module for the application. This 1276 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and 1277 * should contain the application code needed or have dependencies on other modules that will 1278 * contain the code. See {@link angular.module} for more information. 1279 * 1280 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1281 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1282 * would not be resolved to `3`. 1283 * 1284 * `ngApp` is the easiest, and most common, way to bootstrap an application. 1285 * 1286 <example module="ngAppDemo"> 1287 <file name="index.jsp"> 1288 <div ng-controller="ngAppDemoController"> 1289 I can add: {{a}} + {{b}} = {{ a+b }}
1290 </div> 1291 </file> 1292 <file name="script.js"> 1293 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1294 $scope.a = 1; 1295 $scope.b = 2; 1296 }); 1297 </file> 1298 </example> 1299 * 1300 */ 1301 function angularInit(element, bootstrap) { 1302 var elements = [element], 1303 appElement, 1304 module, 1305 names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], 1306 NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; 1307 1308 function append(element) { 1309 element && elements.push(element); 1310 } 1311 1312 forEach(names, function (name) { 1313 names[name] = true; 1314 append(document.getElementById(name)); 1315 name = name.replace(':', '\\:'); 1316 if (element.querySelectorAll) { 1317 forEach(element.querySelectorAll('.' + name), append); 1318 forEach(element.querySelectorAll('.' + name + '\\:'), append); 1319 forEach(element.querySelectorAll('[' + name + ']'), append); 1320 } 1321 }); 1322 1323 forEach(elements, function (element) { 1324 if (!appElement) { 1325 var className = ' ' + element.className + ' '; 1326 var match = NG_APP_CLASS_REGEXP.exec(className); 1327 if (match) { 1328 appElement = element; 1329 module = (match[2] || '').replace(/\s+/g, ','); 1330 } else { 1331 forEach(element.attributes, function (attr) { 1332 if (!appElement && names[attr.name]) { 1333 appElement = element; 1334 module = attr.value; 1335 } 1336 }); 1337 } 1338 } 1339 }); 1340 if (appElement) { 1341 bootstrap(appElement, module ? [module] : []); 1342 } 1343 } 1344 1345 /** 1346 * @ngdoc function 1347 * @name angular.bootstrap 1348 * @module ng 1349 * @description 1350 * Use this function to manually start up angular application. 1351 * 1352 * See: {@link guide/bootstrap Bootstrap} 1353 * 1354 * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. 1355 * They must use {@link ng.directive:ngApp ngApp}. 1356 * 1357 * Angular will detect if it has been loaded into the browser more than once and only allow the 1358 * first loaded script to be bootstrapped and will report a warning to the browser console for 1359 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1360 * multiple instances of Angular try to work on the DOM. 1361 * 1362 * <example name="multi-bootstrap" module="multi-bootstrap"> 1363 * <file name="index.jsp"> 1364 * <script src="../../../angular.js"></script> 1365 * <div ng-controller="BrokenTable"> 1366 * <table> 1367 * <tr> 1368 * <th ng-repeat="heading in headings">{{heading}}</th> 1369 * </tr> 1370 * <tr ng-repeat="filling in fillings"> 1371 * <td ng-repeat="fill in filling">{{fill}}</td> 1372 * </tr> 1373 * </table> 1374 * </div> 1375 * </file> 1376 * <file name="controller.js"> 1377 * var app = angular.module('multi-bootstrap', []) 1378 * 1379 * .controller('BrokenTable', function($scope) { 1380 * $scope.headings = ['One', 'Two', 'Three']; 1381 * $scope.fillings = [[1, 2, 3], ['A', 'B', 'C'], [7, 8, 9]]; 1382 * }); 1383 * </file> 1384 * <file name="protractor.js" type="protractor"> 1385 * it('should only insert one table cell for each item in $scope.fillings', function() { 1386 * expect(element.all(by.css('td')).count()) 1387 * .toBe(9); 1388 * }); 1389 * </file> 1390 * </example> 1391 * 1392 * @param {DOMElement} element DOM element which is the root of angular application. 1393 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1394 * Each item in the array should be the name of a predefined module or a (DI annotated) 1395 * function that will be invoked by the injector as a run block. 1396 * See: {@link angular.module modules} 1397 * @returns {auto.$injector} Returns the newly created injector for this app. 1398 */ 1399 function bootstrap(element, modules) { 1400 var doBootstrap = function () { 1401 element = jqLite(element); 1402 1403 if (element.injector()) { 1404 var tag = (element[0] === document) ? 'document' : startingTag(element); 1405 throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); 1406 } 1407 1408 modules = modules || []; 1409 modules.unshift(['$provide', function ($provide) { 1410 $provide.value('$rootElement', element); 1411 }]); 1412 modules.unshift('ng'); 1413 var injector = createInjector(modules); 1414 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', 1415 function (scope, element, compile, injector, animate) { 1416 scope.$apply(function () { 1417 element.data('$injector', injector); 1418 compile(element)(scope); 1419 }); 1420 }] 1421 ); 1422 return injector; 1423 }; 1424 1425 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1426 1427 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1428 return doBootstrap(); 1429 } 1430 1431 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1432 angular.resumeBootstrap = function (extraModules) {
1433 forEach(extraModules, function (module) { 1434 modules.push(module); 1435 }); 1436 doBootstrap(); 1437 }; 1438 } 1439 1440 var SNAKE_CASE_REGEXP = /[A-Z]/g; 1441 1442 function snake_case(name, separator) { 1443 separator = separator || '_'; 1444 return name.replace(SNAKE_CASE_REGEXP, function (letter, pos) { 1445 return (pos ? separator : '') + letter.toLowerCase(); 1446 }); 1447 } 1448 1449 function bindJQuery() { 1450 // bind to jQuery if present; 1451 jQuery = window.jQuery; 1452 // reset to jQuery or default to us. 1453 if (jQuery) { 1454 jqLite = jQuery; 1455 extend(jQuery.fn, { 1456 scope: JQLitePrototype.scope, 1457 isolateScope: JQLitePrototype.isolateScope, 1458 controller: JQLitePrototype.controller, 1459 injector: JQLitePrototype.injector, 1460 inheritedData: JQLitePrototype.inheritedData 1461 }); 1462 // Method signature: 1463 // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) 1464 jqLitePatchJQueryRemove('remove', true, true, false); 1465 jqLitePatchJQueryRemove('empty', false, false, false); 1466 jqLitePatchJQueryRemove('html', false, false, true); 1467 } else { 1468 jqLite = JQLite; 1469 } 1470 angular.element = jqLite; 1471 } 1472 1473 /** 1474 * throw error if the argument is falsy. 1475 */ 1476 function assertArg(arg, name, reason) { 1477 if (!arg) { 1478 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1479 } 1480 return arg; 1481 } 1482 1483 function assertArgFn(arg, name, acceptArrayAnnotation) { 1484 if (acceptArrayAnnotation && isArray(arg)) { 1485 arg = arg[arg.length - 1]; 1486 } 1487 1488 assertArg(isFunction(arg), name, 'not a function, got ' + 1489 (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1490 return arg; 1491 } 1492 1493 /** 1494 * throw error if the name given is hasOwnProperty 1495 * @param {String} name the name to test 1496 * @param {String} context the context in which the name is used, such as module or directive 1497 */ 1498 function assertNotHasOwnProperty(name, context) { 1499 if (name === 'hasOwnProperty') { 1500 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1501 } 1502 } 1503 1504 /** 1505 * Return the value accessible from the object by path. Any undefined traversals are ignored 1506 * @param {Object} obj starting object 1507 * @param {String} path path to traverse 1508 * @param {boolean} [bindFnToScope=true] 1509 * @returns {Object} value as accessible by path 1510 */ 1511//TODO(misko): this function needs to be removed 1512 function getter(obj, path, bindFnToScope) { 1513 if (!path) return obj; 1514 var keys = path.split('.'); 1515 var key; 1516 var lastInstance = obj; 1517 var len = keys.length; 1518 1519 for (var i = 0; i < len; i++) { 1520 key = keys[i]; 1521 if (obj) { 1522 obj = (lastInstance = obj)[key]; 1523 } 1524 } 1525 if (!bindFnToScope && isFunction(obj)) { 1526 return bind(lastInstance, obj); 1527 } 1528 return obj; 1529 } 1530 1531 /** 1532 * Return the DOM siblings between the first and last node in the given array. 1533 * @param {Array} array like object 1534 * @returns {DOMElement}
1534 object containing the elements 1535 */ 1536 function getBlockElements(nodes) { 1537 var startNode = nodes[0], 1538 endNode = nodes[nodes.length - 1]; 1539 if (startNode === endNode) { 1540 return jqLite(startNode); 1541 } 1542 1543 var element = startNode; 1544 var elements = [element]; 1545 1546 do { 1547 element = element.nextSibling; 1548 if (!element) break; 1549 elements.push(element); 1550 } while (element !== endNode); 1551 1552 return jqLite(elements); 1553 } 1554 1555 /** 1556 * @ngdoc type 1557 * @name angular.Module 1558 * @module ng 1559 * @description 1560 * 1561 * Interface for configuring angular {@link angular.module modules}. 1562 */ 1563 1564 function setupModuleLoader(window) { 1565 1566 var $injectorMinErr = minErr('$injector'); 1567 var ngMinErr = minErr('ng'); 1568 1569 function ensure(obj, name, factory) { 1570 return obj[name] || (obj[name] = factory()); 1571 } 1572 1573 var angular = ensure(window, 'angular', Object); 1574 1575 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1576 angular.$$minErr = angular.$$minErr || minErr; 1577 1578 return ensure(angular, 'module', function () { 1579 /** @type {Object.<string, angular.Module>} */ 1580 var modules = {}; 1581 1582 /** 1583 * @ngdoc function 1584 * @name angular.module 1585 * @module ng 1586 * @description 1587 * 1588 * The `angular.module` is a global place for creating, registering and retrieving Angular 1589 * modules. 1590 * All modules (angular core or 3rd party) that should be available to an application must be 1591 * registered using this mechanism. 1592 * 1593 * When passed two or more arguments, a new module is created. If passed only one argument, an 1594 * existing module (the name passed as the first argument to `module`) is retrieved. 1595 * 1596 * 1597 * # Module 1598 * 1599 * A module is a collection of services, directives, filters, and configuration information. 1600 * `angular.module` is used to configure the {@link auto.$injector $injector}. 1601 * 1602 * ```js 1603 * // Create a new module 1604 * var myModule = angular.module('myModule', []); 1605 * 1606 * // register a new service 1607 * myModule.value('appName', 'MyCoolApp'); 1608 * 1609 * // configure existing services inside initialization blocks. 1610 * myModule.config(['$locationProvider', function($locationProvider) { 1611 * // Configure existing providers 1612 * $locationProvider.hashPrefix('!'); 1613 * }]); 1614 * ``` 1615 * 1616 * Then you can create an injector and load your modules like this: 1617 * 1618 * ```js 1619 * var injector = angular.injector(['ng', 'myModule']) 1620 * ``` 1621 * 1622 * However it's more likely that you'll just use 1623 * {@link ng.directive:ngApp ngApp} or 1624 * {@link angular.bootstrap} to simplify this process for you. 1625 * 1626 * @param {!string} name The name of the module to create or retrieve. 1627 <<<<<* @param {!Array.<string>=} requires If specified then new module is being created. If 1628 >>>>>* unspecified then the module is being retrieved for further configuration. 1629 * @param {Function} configFn Optional configuration function for the module. Same as 1630 * {@link angular.Module#config Module#config()}. 1631 * @returns {module} new module with the {@link angular.Module} api. 1632 */ 1633 return function module(name, requires, configFn) { 1634 var assertNotHasOwnProperty = function (name, context) { 1635 if (name === 'hasOwnProperty') { 1636 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 1637 } 1638 }; 1639 1640 assertNotHasOwnProperty(name, 'module'); 1641 if (requires && modules.hasOwnProperty(name)) { 1642 modules[name] = null; 1643 } 1644 return ensure(modules, name, function () { 1645 if (!requires) { 1646 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 1647 "the module name or forgot to load it. If registering a module ensure that you " + 1648 "specify the dependencies as the second argument.", name); 1649 } 1650
1651 /** @type {!Array.<Array.<*>>} */ 1652 var invokeQueue = []; 1653 1654 /** @type {!Array.<Function>} */ 1655 var runBlocks = []; 1656 1657 var config = invokeLater('$injector', 'invoke'); 1658 1659 /** @type {angular.Module} */ 1660 var moduleInstance = { 1661 // Private state 1662 _invokeQueue: invokeQueue, 1663 _runBlocks: runBlocks, 1664 1665 /** 1666 * @ngdoc property 1667 * @name angular.Module#requires 1668 * @module ng 1669 * @returns {Array.<string>} List of module names which must be loaded before this module. 1670 * @description 1671 * Holds the list of modules which the injector will load before the current module is 1672 * loaded. 1673 */ 1674 requires: requires, 1675 1676 /** 1677 * @ngdoc property 1678 * @name angular.Module#name 1679 * @module ng 1680 * @returns {string} Name of the module. 1681 * @description 1682 */ 1683 name: name, 1684 1685 1686 /** 1687 * @ngdoc method 1688 * @name angular.Module#provider 1689 * @module ng 1690 * @param {string} name service name 1691 * @param {Function} providerType Construction function for creating new instance of the 1692 * service. 1693 * @description 1694 * See {@link auto.$provide#provider $provide.provider()}. 1695 */ 1696 provider: invokeLater('$provide', 'provider'), 1697 1698 /** 1699 * @ngdoc method 1700 * @name angular.Module#factory 1701 * @module ng 1702 * @param {string} name service name 1703 * @param {Function} providerFunction Function for creating new instance of the service. 1704 * @description 1705 * See {@link auto.$provide#factory $provide.factory()}. 1706 */ 1707 factory: invokeLater('$provide', 'factory'), 1708 1709 /** 1710 * @ngdoc method 1711 * @name angular.Module#service 1712 * @module ng 1713 * @param {string} name service name 1714 * @param {Function} constructor A constructor function that will be instantiated. 1715 * @description 1716 * See {@link auto.$provide#service $provide.service()}. 1717 */ 1718 service: invokeLater('$provide', 'service'), 1719 1720 /** 1721 * @ngdoc method 1722 * @name angular.Module#value 1723 * @module ng 1724 * @param {string} name service name 1725 * @param {*} object Service instance object. 1726 * @description 1727 * See {@link auto.$provide#value $provide.value()}. 1728 */ 1729 value: invokeLater('$provide', 'value'), 1730 1731 /** 1732 * @ngdoc method 1733 * @name angular.Module#constant 1734 * @module ng 1735 * @param {string} name constant name 1736 * @param {*} object Constant value. 1737 * @description 1738 * Because the constant are fixed, they get applied before other provide methods. 1739 * See {@link auto.$provide#constant $provide.constant()}. 1740 */ 1741 constant: invokeLater('$provide', 'constant', 'unshift'), 1742 1743 /** 1744 * @ngdoc method 1745 * @name angular.Module#animation 1746 * @module ng 1747 * @param {string} name animation name 1748 * @param {Function} animationFactory Factory function for creating new instance of an 1749 * animation.
1750 * @description 1751 * 1752 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 1753 * 1754 * 1755 * Defines an animation hook that can be later used with 1756 * {@link ngAnimate.$animate $animate} service and directives that use this service. 1757 * 1758 * ```js 1759 * module.animation('.animation-name', function($inject1, $inject2) { 1760 * return { 1761 * eventName : function(element, done) { 1762 * //code to run the animation 1763 * //once complete, then run done() 1764 * return function cancellationFunction(element) { 1765 * //code to cancel the animation 1766 * } 1767 * } 1768 * } 1769 * }) 1770 * ``` 1771 * 1772 * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and 1773 * {@link ngAnimate ngAnimate module} for more information. 1774 */ 1775 animation: invokeLater('$animateProvider', 'register'), 1776 1777 /** 1778 * @ngdoc method 1779 * @name angular.Module#filter 1780 * @module ng 1781 * @param {string} name Filter name. 1782 * @param {Function} filterFactory Factory function for creating new instance of filter. 1783 * @description 1784 * See {@link ng.$filterProvider#register $filterProvider.register()}. 1785 */ 1786 filter: invokeLater('$filterProvider', 'register'), 1787 1788 /** 1789 * @ngdoc method 1790 * @name angular.Module#controller 1791 * @module ng 1792 * @param {string|Object} name Controller name, or an object map of controllers where the 1793 * keys are the names and the values are the constructors. 1794 * @param {Function} constructor Controller constructor function. 1795 * @description 1796 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 1797 */ 1798 controller: invokeLater('$controllerProvider', 'register'), 1799 1800 /** 1801 * @ngdoc method 1802 * @name angular.Module#directive 1803 * @module ng 1804 * @param {string|Object} name Directive name, or an object map of directives where the 1805 * keys are the names and the values are the factories. 1806 * @param {Function} directiveFactory Factory function for creating new instance of 1807 * directives. 1808 * @description 1809 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 1810 */ 1811 directive: invokeLater('$compileProvider', 'directive'), 1812 1813 /** 1814 * @ngdoc method 1815 * @name angular.Module#config 1816 * @module ng 1817 * @param {Function} configFn Execute this function on module load. Useful for service 1818 * configuration. 1819 * @description 1820 * Use this method to register work which needs to be performed on module loading. 1821 */ 1822 config: config, 1823 1824 /** 1825 * @ngdoc method 1826 * @name angular.Module#run 1827 * @module ng 1828 * @param {Function} initializationFn Execute this function after injector creation. 1829 * Useful for application initialization.
1830 * @description 1831 * Use this method to register work which should be performed when the injector is done 1832 * loading all modules. 1833 */ 1834 run: function (block) { 1835 runBlocks.push(block); 1836 return this; 1837 } 1838 }; 1839 1840 if (configFn) { 1841 config(configFn); 1842 } 1843 1844 return moduleInstance; 1845 1846 /** 1847 * @param {string} provider 1848 * @param {string} method 1849 * @param {String=} insertMethod 1850 * @returns {angular.Module} 1851 */ 1852 function invokeLater(provider, method, insertMethod) { 1853 return function () { 1854 invokeQueue[insertMethod || 'push']([provider, method, arguments]); 1855 return moduleInstance; 1856 }; 1857 } 1858 }); 1859 }; 1860 }); 1861 1862 } 1863 1864 /* global 1865 angularModule: true, 1866 version: true, 1867 1868 $LocaleProvider, 1869 $CompileProvider, 1870 1871 htmlAnchorDirective, 1872 inputDirective, 1873 inputDirective, 1874 formDirective, 1875 scriptDirective, 1876 selectDirective, 1877 styleDirective, 1878 optionDirective, 1879 ngBindDirective, 1880 ngBindHtmlDirective, 1881 ngBindTemplateDirective, 1882 ngClassDirective, 1883 ngClassEvenDirective, 1884 ngClassOddDirective, 1885 ngCspDirective, 1886 ngCloakDirective, 1887 ngControllerDirective, 1888 ngFormDirective, 1889 ngHideDirective, 1890 ngIfDirective, 1891 ngIncludeDirective, 1892 ngIncludeFillContentDirective, 1893 ngInitDirective, 1894 ngNonBindableDirective, 1895 ngPluralizeDirective, 1896 ngRepeatDirective, 1897 ngShowDirective, 1898 ngStyleDirective, 1899 ngSwitchDirective, 1900 ngSwitchWhenDirective, 1901 ngSwitchDefaultDirective, 1902 ngOptionsDirective, 1903 ngTranscludeDirective, 1904 ngModelDirective, 1905 ngListDirective, 1906 ngChangeDirective, 1907 requiredDirective, 1908 requiredDirective, 1909 ngValueDirective, 1910 ngAttributeAliasDirectives, 1911 ngEventDirectives, 1912 1913 $AnchorScrollProvider, 1914 $AnimateProvider, 1915 $BrowserProvider, 1916 $CacheFactoryProvider, 1917 $ControllerProvider, 1918 $DocumentProvider, 1919 $ExceptionHandlerProvider, 1920 $FilterProvider, 1921 $InterpolateProvider, 1922 $IntervalProvider, 1923 $HttpProvider, 1924 $HttpBackendProvider, 1925 $LocationProvider, 1926 $LogProvider, 1927 $ParseProvider, 1928 $RootScopeProvider, 1929 $QProvider, 1930 $$SanitizeUriProvider, 1931 $SceProvider, 1932 $SceDelegateProvider, 1933 $SnifferProvider, 1934 $TemplateCacheProvider, 1935 $TimeoutProvider, 1936 $$RAFProvider, 1937 $$AsyncCallbackProvider, 1938 $WindowProvider 1939 */ 1940 1941 1942 /** 1943 * @ngdoc object 1944 * @name angular.version 1945 * @module ng 1946 * @description 1947 * An object that contains information about the current AngularJS version. This object has the 1948 * following properties: 1949 * 1950 * - `full` â `{string}` â Full version string, such as "0.9.18". 1951 * - `major` â `{number}` â Major version number, such as "0". 1952 * - `minor` â `{number}` â Minor version number, such as "9". 1953 * - `dot` â `{number}` â Dot version number, such as "18". 1954 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 1955 */ 1956 var version = { 1957 full: '1.2.16', // all of these placeholder strings will be replaced by grunt's 1958 major: 1, // package task 1959 minor: 2, 1960 dot: 16, 1961 codeName: 'badger-enumeration' 1962 }; 1963 1964 1965 function publishExternalAPI(angular) { 1966 extend(angular, { 1967 'bootstrap': bootstrap, 1968 'copy': copy, 1969 'extend': extend, 1970 'equals': equals, 1971 'element': jqLite, 1972 'forEach': forEach, 1973 'injector': createInjector, 1974 'noop': noop, 1975 'bind': bind, 1976 'toJson': toJson, 1977 'fromJson': fromJson, 1978 'identity': identity, 1979 'isUndefined': isUndefined, 1980 'isDefined': isDefined, 1981 'isString': isString, 1982 'isFunction': isFunction, 1983 'isObject': isObject, 1984 'isNumber': isNumber, 1985 'isElement': isElement, 1986 'isArray': isArray, 1987 'version': version, 1988 'isDate': isDate, 1989 'lowercase': lowercase, 1990 'uppercase': uppercase, 1991 'callbacks': {counter: 0}, 1992 '$$minErr': minErr, 1993 '$$csp': csp 1994 }); 1995 1996 angularModule = setupModuleLoader(window); 1997 try { 1998 angularModule('ngLocale'); 1999 } catch (e) { 2000 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 2001 } 2002 2003 angularModule('ng', ['ngLocale'], ['$provide', 2004 function ngModule($provide) {
2005 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2006 $provide.provider({ 2007 $$sanitizeUri: $$SanitizeUriProvider 2008 }); 2009 $provide.provider('$compile', $CompileProvider). 2010 directive({ 2011 a: htmlAnchorDirective, 2012 input: inputDirective, 2013 textarea: inputDirective, 2014 form: formDirective, 2015 script: scriptDirective, 2016 select: selectDirective, 2017 style: styleDirective, 2018 option: optionDirective, 2019 ngBind: ngBindDirective, 2020 ngBindHtml: ngBindHtmlDirective, 2021 ngBindTemplate: ngBindTemplateDirective, 2022 ngClass: ngClassDirective, 2023 ngClassEven: ngClassEvenDirective, 2024 ngClassOdd: ngClassOddDirective, 2025 ngCloak: ngCloakDirective, 2026 ngController: ngControllerDirective, 2027 ngForm: ngFormDirective, 2028 ngHide: ngHideDirective, 2029 ngIf: ngIfDirective, 2030 ngInclude: ngIncludeDirective, 2031 ngInit: ngInitDirective, 2032 ngNonBindable: ngNonBindableDirective, 2033 ngPluralize: ngPluralizeDirective, 2034 ngRepeat: ngRepeatDirective, 2035 ngShow: ngShowDirective, 2036 ngStyle: ngStyleDirective, 2037 ngSwitch: ngSwitchDirective, 2038 ngSwitchWhen: ngSwitchWhenDirective, 2039 ngSwitchDefault: ngSwitchDefaultDirective, 2040 ngOptions: ngOptionsDirective, 2041 ngTransclude: ngTranscludeDirective, 2042 ngModel: ngModelDirective, 2043 ngList: ngListDirective, 2044 ngChange: ngChangeDirective, 2045 required: requiredDirective, 2046 ngRequired: requiredDirective, 2047 ngValue: ngValueDirective 2048 }). 2049 directive({ 2050 ngInclude: ngIncludeFillContentDirective 2051 }). 2052 directive(ngAttributeAliasDirectives). 2053 directive(ngEventDirectives); 2054 $provide.provider({ 2055 $anchorScroll: $AnchorScrollProvider, 2056 $animate: $AnimateProvider, 2057 $browser: $BrowserProvider, 2058 $cacheFactory: $CacheFactoryProvider, 2059 $controller: $ControllerProvider, 2060 $document: $DocumentProvider, 2061 $exceptionHandler: $ExceptionHandlerProvider, 2062 $filter: $FilterProvider, 2063 $interpolate: $InterpolateProvider, 2064 $interval: $IntervalProvider, 2065 $http: $HttpProvider, 2066 $httpBackend: $HttpBackendProvider, 2067 $location: $LocationProvider, 2068 $log: $LogProvider, 2069 $parse: $ParseProvider, 2070 $rootScope: $RootScopeProvider, 2071 $q: $QProvider, 2072 $sce: $SceProvider, 2073 $sceDelegate: $SceDelegateProvider, 2074 $sniffer: $SnifferProvider, 2075 $templateCache: $TemplateCacheProvider, 2076 $timeout: $TimeoutProvider, 2077 $window: $WindowProvider, 2078 $$rAF: $$RAFProvider, 2079 $$asyncCallback: $$AsyncCallbackProvider 2080 }); 2081 } 2082 ]); 2083 } 2084 2085 /* global 2086 2087 -JQLitePrototype, 2088 -addEventListenerFn, 2089 -removeEventListenerFn, 2090 -BOOLEAN_ATTR 2091 */ 2092 2093////////////////////////////////// 2094//JQLite 2095////////////////////////////////// 2096 2097 /** 2098 * @ngdoc function 2099 * @name angular.element 2100 * @module ng 2101 * @function 2102 *
2103 * @description 2104 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2105 * 2106 * If jQuery is available, `angular.element` is an alias for the 2107 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2108 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." 2109 * 2110 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows 2111 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most 2112 * commonly needed functionality with the goal of having a very small footprint.</div> 2113 * 2114 * To use jQuery, simply load it before `DOMContentLoaded` event fired. 2115 * 2116 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or 2117 * jqLite; they are never raw DOM references.</div> 2118 * 2119 * ## Angular's jqLite 2120 * jqLite provides only the following jQuery methods: 2121 * 2122 * - [`addClass()`](http://api.jquery.com/addClass/) 2123 * - [`after()`](http://api.jquery.com/after/) 2124 * - [`append()`](http://api.jquery.com/append/) 2125 * - [`attr()`](http://api.jquery.com/attr/) 2126 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2127 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2128 * - [`clone()`](http://api.jquery.com/clone/) 2129 * - [`contents()`](http://api.jquery.com/contents/) 2130 * - [`css()`](http://api.jquery.com/css/) 2131 * - [`data()`](http://api.jquery.com/data/) 2132 * - [`empty()`](http://api.jquery.com/empty/) 2133 * - [`eq()`](http://api.jquery.com/eq/) 2134 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2135 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2136 * - [`html()`](http://api.jquery.com/html/) 2137 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2138 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2139 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors 2140 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2141 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2142 * - [`prepend()`](http://api.jquery.com/prepend/) 2143 * - [`prop()`](http://api.jquery.com/prop/) 2144 * - [`ready()`](http://api.jquery.com/ready/) 2145 * - [`remove()`](http://api.jquery.com/remove/) 2146 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2147 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2148 * - [`removeData()`](http://api.jquery.com/removeData/) 2149 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2150 * - [`text()`](http://api.jquery.com/text/) 2151 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2152 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2153 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces 2154 * - [`val()`](http://api.jquery.com/val/) 2155 * - [`wrap()`](http://api.jquery.com/wrap/) 2156 * 2157 * ## jQuery/jqLite Extras 2158 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2159 * 2160 * ### Events 2161 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction apis and fires this event 2162 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2163 * element before it is removed. 2164 * 2165 * ### Methods 2166 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2167 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2168 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2169 * `'ngModel'`). 2170 * - `injector()` - retrieves the injector of the current element or its parent. 2171 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2172 * element or its parent. 2173 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2174 * current element. This getter should be used only on elements that contain a directive which starts a new isolate
2175 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2176 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2177 * parent element is reached. 2178 * 2179 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2180 * @returns {Object} jQuery object. 2181 */ 2182 2183 var jqCache = JQLite.cache = {}, 2184 jqName = JQLite.expando = 'ng-' + new Date().getTime(), 2185 jqId = 1, 2186 addEventListenerFn = (window.document.addEventListener 2187 ? function (element, type, fn) { 2188 element.addEventListener(type, fn, false); 2189 } 2190 : function (element, type, fn) { 2191 element.attachEvent('on' + type, fn); 2192 }), 2193 removeEventListenerFn = (window.document.removeEventListener 2194 ? function (element, type, fn) { 2195 element.removeEventListener(type, fn, false); 2196 } 2197 : function (element, type, fn) { 2198 element.detachEvent('on' + type, fn); 2199 }); 2200 2201 /* 2202 * !!! This is an undocumented "private" function !!! 2203 */ 2204 var jqData = JQLite._data = function (node) { 2205 //jQuery always returns an object on cache miss 2206 return this.cache[node[this.expando]] || {}; 2207 }; 2208 2209 function jqNextId() { 2210 return ++jqId; 2211 } 2212 2213 2214 var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2215 var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2216 var jqLiteMinErr = minErr('jqLite'); 2217 2218 /** 2219 * Converts snake_case to camelCase. 2220 * Also there is special case for Moz prefix starting with upper case letter. 2221 * @param name Name to normalize 2222 */ 2223 function camelCase(name) { 2224 return name. 2225 replace(SPECIAL_CHARS_REGEXP,function (_, separator, letter, offset) { 2226 return offset ? letter.toUpperCase() : letter; 2227 }). 2228 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2229 } 2230 2231///////////////////////////////////////////// 2232// jQuery mutation patch 2233// 2234// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a 2235// $destroy event on all DOM nodes being removed. 2236// 2237///////////////////////////////////////////// 2238 2239 function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { 2240 var originalJqFn = jQuery.fn[name]; 2241 originalJqFn = originalJqFn.$original || originalJqFn; 2242 removePatch.$original = originalJqFn; 2243 jQuery.fn[name] = removePatch; 2244 2245 function removePatch(param) { 2246 // jshint -W040 2247 var list = filterElems && param ? [this.filter(param)] : [this], 2248 fireEvent = dispatchThis, 2249 set, setIndex, setLength, 2250 element, childIndex, childLength, children; 2251 2252 if (!getterIfNoArguments || param != null) { 2253 while (list.length) { 2254 set = list.shift(); 2255 for (setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { 2256 element = jqLite(set[setIndex]); 2257 if (fireEvent) { 2258 element.triggerHandler('$destroy'); 2259 } else { 2260 fireEvent = !fireEvent; 2261 } 2262 for (childIndex = 0, childLength = (children = element.children()).length; 2263 childIndex < childLength; 2264 childIndex++) { 2265 list.push(jQuery(children[childIndex])); 2266 } 2267 } 2268 } 2269 } 2270 return originalJqFn.apply(this, arguments); 2271 } 2272 } 2273 2274 var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; 2275 var HTML_REGEXP = /<|&#?\w+;/; 2276 var TAG_NAME_REGEXP = /<([\w:]+)/; 2277 var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; 2278 2279 var wrapMap = { 2280 'option': [1, '<select multiple="multiple">', '</select>'], 2281 2282 'thead': [1, '<table>', '</table>'], 2283 'col': [2, '<table><colgroup>', '</colgroup></table>'], 2284 'tr': [2, '<table><tbody>', '</tbody></table>'], 2285 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2286 '_default': [0, "", ""] 2287 }; 2288 2289 wrapMap.optgroup = wrapMap.option; 2290 wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2291 wrapMap.th = wrapMap.td; 2292 2293 function jqLiteIsTextNode(html) { 2294 return !HTML_REGEXP.test(html); 2295 } 2296 2297 function jqLiteBuildFragment(html, context) { 2298 var elem, tmp, tag, wrap, 2299 fragment = context.createDocumentFragment(), 2300 nodes = [], i, j, jj; 2301 2302 if (jqLiteIsTextNode(html)) { 2303 // Convert non-html into a text node 2304 nodes.push(context.createTextNode(html)); 2305 } else { 2306 tmp = fragment.appendChild(context.createElement('div')); 2307 // Convert html into DOM nodes 2308 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2309 wrap = wrapMap[tag] || wrapMap._default; 2310 tmp.innerHTML = '<div> </div>' + 2311 wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2312 tmp.removeChild(tmp.firstChild); 2313 2314 // Descend through wrappers to the right content 2315 i = wrap[0]; 2316 while (i--) { 2317 tmp = tmp.lastChild; 2318 } 2319 2320 for (j = 0, jj = tmp.childNodes.length; j < jj; ++j) nodes.push(tmp.childNodes[j]); 2321 2322 tmp = fragment.firstChild; 2323 tmp.textContent = ""; 2324 } 2325 2326 // Remove wrapper from fragment 2327 fragment.textContent = ""; 2328 fragment.innerHTML = ""; // Clear inner HTML 2329 return nodes; 2330 } 2331 2332 function jqLiteParseHTML(html, context) { 2333 context = context || document; 2334 var parsed; 2335 2336 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2337 return [context.createElement(parsed[1])]; 2338 } 2339 2340 return jqLiteBuildFragment(html, context); 2341 } 2342 2343///////////////////////////////////////////// 2344 function JQLite(element) { 2345 if (element instanceof JQLite) { 2346 return element; 2347 } 2348 if (isString(element)) { 2349 element = trim(element); 2350 } 2351 if (!(this instanceof JQLite)) { 2352 if (isString(element) && element.charAt(0) != '<') { 2353 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2354 } 2355 return new JQLite(element); 2356 } 2357 2358 if (isString(element)) { 2359 jqLiteAddNodes(this, jqLiteParseHTML(element)); 2360 var fragment = jqLite(document.createDocumentFragment()); 2361 fragment.append(this); 2362 } else { 2363 jqLiteAddNodes(this, element); 2364 } 2365 } 2366 2367 function jqLiteClone(element) { 2368 return element.cloneNode(true); 2369 } 2370 2371 function jqLiteDealoc(element) { 2372 jqLiteRemoveData(element); 2373 for (var i = 0, children = element.childNodes || []; i < children.length; i++) { 2374 jqLiteDealoc(children[i]); 2375 } 2376 } 2377 2378 function jqLiteOff(element, type, fn, unsupported) {
2379 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2380 2381 var events = jqLiteExpandoStore(element, 'events'), 2382 handle = jqLiteExpandoStore(element, 'handle'); 2383 2384 if (!handle) return; //no listeners registered 2385 2386 if (isUndefined(type)) { 2387 forEach(events, function (eventHandler, type) { 2388 removeEventListenerFn(element, type, eventHandler); 2389 delete events[type]; 2390 }); 2391 } else { 2392 forEach(type.split(' '), function (type) { 2393 if (isUndefined(fn)) { 2394 removeEventListenerFn(element, type, events[type]); 2395 delete events[type]; 2396 } else { 2397 arrayRemove(events[type] || [], fn); 2398 } 2399 }); 2400 } 2401 } 2402 2403 function jqLiteRemoveData(element, name) { 2404 var expandoId = element[jqName], 2405 expandoStore = jqCache[expandoId]; 2406 2407 if (expandoStore) { 2408 if (name) { 2409 delete jqCache[expandoId].data[name]; 2410 return; 2411 } 2412 2413 if (expandoStore.handle) { 2414 expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); 2415 jqLiteOff(element); 2416 } 2417 delete jqCache[expandoId]; 2418 element[jqName] = undefined; // ie does not allow deletion of attributes on elements. 2419 } 2420 } 2421 2422 function jqLiteExpandoStore(element, key, value) { 2423 var expandoId = element[jqName], 2424 expandoStore = jqCache[expandoId || -1]; 2425 2426 if (isDefined(value)) { 2427 if (!expandoStore) { 2428 element[jqName] = expandoId = jqNextId(); 2429 expandoStore = jqCache[expandoId] = {}; 2430 } 2431 expandoStore[key] = value; 2432 } else { 2433 return expandoStore && expandoStore[key]; 2434 } 2435 } 2436 2437 function jqLiteData(element, key, value) { 2438 var data = jqLiteExpandoStore(element, 'data'), 2439 isSetter = isDefined(value), 2440 keyDefined = !isSetter && isDefined(key), 2441 isSimpleGetter = keyDefined && !isObject(key); 2442 2443 if (!data && !isSimpleGetter) { 2444 jqLiteExpandoStore(element, 'data', data = {}); 2445 } 2446 2447 if (isSetter) { 2448 data[key] = value; 2449 } else { 2450 if (keyDefined) { 2451 if (isSimpleGetter) { 2452 // don't create data in this case. 2453 return data && data[key]; 2454 } else { 2455 extend(data, key); 2456 } 2457 } else { 2458 return data; 2459 } 2460 } 2461 } 2462 2463 function jqLiteHasClass(element, selector) { 2464 if (!element.getAttribute) return false; 2465 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2466 indexOf(" " + selector + " ") > -1); 2467 } 2468 2469 function jqLiteRemoveClass(element, cssClasses) { 2470 if (cssClasses && element.setAttribute) { 2471 forEach(cssClasses.split(' '), function (cssClass) { 2472 element.setAttribute('class', trim( 2473 (" " + (element.getAttribute('class') || '') + " ") 2474 .replace(/[\n\t]/g, " ") 2475 .replace(" " + trim(cssClass) + " ", " ")) 2476 ); 2477 }); 2478 } 2479 } 2480 2481 function jqLiteAddClass(element, cssClasses) { 2482 if (cssClasses && element.setAttribute) { 2483 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 2484 .replace(/[\n\t]/g, " "); 2485 2486 forEach(cssClasses.split(' '), function (cssClass) { 2487 cssClass = trim(cssClass); 2488 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 2489 existingClasses += cssClass + ' '; 2490 } 2491 }); 2492 2493 element.setAttribute('class', trim(existingClasses)); 2494 } 2495 } 2496 2497 function jqLiteAddNodes(root, elements) { 2498 if (elements) { 2499 elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) 2500 ? elements 2501 : [ elements ]; 2502 for (var i = 0; i < elements.length; i++) { 2503 root.push(elements[i]); 2504 } 2505 } 2506 } 2507 2508 function jqLiteController(element, name) { 2509 return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); 2510 } 2511 2512 function jqLiteInheritedData(element, name, value) { 2513 element = jqLite(element); 2514 2515 // if element is the document object work with the html element instead 2516 // this makes $(document).scope() possible 2517 if (element[0].nodeType == 9) { 2518 element = element.find('html'); 2519 } 2520 var names = isArray(name) ? name : [name]; 2521 2522 while (element.length) { 2523 var node = element[0]; 2524 for (var i = 0, ii = names.length; i < ii; i++) { 2525 if ((value = element.data(names[i])) !== undefined) return value; 2526 } 2527 2528 // If dealing with a document fragment node with a host element, and no parent, use the host 2529 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 2530 // to lookup parent controllers. 2531 element = jqLite(node.parentNode || (node.nodeType === 11 && node.host)); 2532 } 2533 } 2534 2535 function jqLiteEmpty(element) { 2536 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2537 jqLiteDealoc(childNodes[i]); 2538 } 2539 while (element.firstChild) { 2540 element.removeChild(element.firstChild); 2541 } 2542 } 2543 2544////////////////////////////////////////// 2545// Functions which are declared directly. 2546////////////////////////////////////////// 2547 var JQLitePrototype = JQLite.prototype = { 2548 ready: function (fn) { 2549 var fired = false;
2550 2551 function trigger() { 2552 if (fired) return; 2553 fired = true; 2554 fn(); 2555 } 2556 2557 // check if document already is loaded 2558 if (document.readyState === 'complete') { 2559 setTimeout(trigger); 2560 } else { 2561 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 2562 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 2563 // jshint -W064 2564 JQLite(window).on('load', trigger); // fallback to window.onload for others 2565 // jshint +W064 2566 } 2567 }, 2568 toString: function () { 2569 var value = []; 2570 forEach(this, function (e) { 2571 value.push('' + e); 2572 }); 2573 return '[' + value.join(', ') + ']'; 2574 }, 2575 2576 eq: function (index) { 2577 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 2578 }, 2579 2580 length: 0, 2581 push: push, 2582 sort: [].sort, 2583 splice: [].splice 2584 }; 2585 2586////////////////////////////////////////// 2587// Functions iterating getter/setters. 2588// these functions return self on setter and 2589// value on get. 2590////////////////////////////////////////// 2591 var BOOLEAN_ATTR = {}; 2592 forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function (value) { 2593 BOOLEAN_ATTR[lowercase(value)] = value; 2594 }); 2595 var BOOLEAN_ELEMENTS = {}; 2596 forEach('input,select,option,textarea,button,form,details'.split(','), function (value) { 2597 BOOLEAN_ELEMENTS[uppercase(value)] = true; 2598 }); 2599 2600 function getBooleanAttrName(element, name) { 2601 // check dom last since we will most likely fail on name 2602 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 2603 2604 // booleanAttr is here twice to minimize DOM access 2605 return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; 2606 } 2607 2608 forEach({ 2609 data: jqLiteData, 2610 inheritedData: jqLiteInheritedData, 2611 2612 scope: function (element) { 2613 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2614 return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 2615 }, 2616 2617 isolateScope: function (element) { 2618 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2619 return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); 2620 }, 2621 2622 controller: jqLiteController, 2623 2624 injector: function (element) { 2625 return jqLiteInheritedData(element, '$injector'); 2626 }, 2627 2628 removeAttr: function (element, name) { 2629 element.removeAttribute(name); 2630 }, 2631 2632 hasClass: jqLiteHasClass, 2633 2634 css: function (element, name, value) { 2635 name = camelCase(name); 2636 2637 if (isDefined(value)) { 2638 element.style[name] = value; 2639 } else { 2640 var val; 2641 2642 if (msie <= 8) { 2643 // this is some IE specific weirdness that jQuery 1.6.4 does not sure why 2644 val = element.currentStyle && element.currentStyle[name]; 2645 if (val === '') val = 'auto'; 2646 } 2647 2648 val = val || element.style[name]; 2649 2650 if (msie <= 8) { 2651 // jquery weirdness :-/ 2652 val = (val === '') ? undefined : val; 2653 } 2654 2655 return val; 2656 } 2657 }, 2658 2659 attr: function (element, name, value) { 2660 var lowercasedName = lowercase(name); 2661 if (BOOLEAN_ATTR[lowercasedName]) { 2662 if (isDefined(value)) { 2663 if (!!value) { 2664 element[name] = true; 2665 element.setAttribute(name, lowercasedName); 2666 } else { 2667 element[name] = false; 2668 element.removeAttribute(lowercasedName); 2669 } 2670 } else { 2671 return (element[name] || 2672 (element.attributes.getNamedItem(name) || noop).specified) 2673 ? lowercasedName 2674 : undefined; 2675 } 2676 } else if (isDefined(value)) { 2677 element.setAttribute(name, value); 2678 } else if (element.getAttribute) { 2679 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 2680 // some elements (e.g. Document) don't have get attribute, so return undefined 2681 var ret = element.getAttribute(name, 2); 2682 // normalize non-existing attributes to undefined (as jQuery) 2683 return ret === null ? undefined : ret; 2684 } 2685 }, 2686 2687 prop: function (element, name, value) { 2688 if (isDefined(value)) { 2689 element[name] = value; 2690 } else { 2691 return element[name]; 2692 } 2693 }, 2694 2695 text: (function () { 2696 var NODE_TYPE_TEXT_PROPERTY = []; 2697 if (msie < 9) { 2698 NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; 2699 /** Element **/ 2700 NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; 2701 /** Text **/ 2702 } else { 2703 NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ 2704 NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; 2705 /** Text **/ 2706 } 2707 getText.$dv = ''; 2708 return getText; 2709 2710 function getText(element, value) { 2711 var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; 2712 if (isUndefined(value)) { 2713 return textProp ? element[textProp] : ''; 2714 } 2715 element[textProp] = value; 2716 } 2717 })(), 2718 2719 val: function (element, value) { 2720 if (isUndefined(value)) { 2721 if (nodeName_(element) === 'SELECT' && element.multiple) { 2722 var result = [];
2723 forEach(element.options, function (option) { 2724 if (option.selected) { 2725 result.push(option.value || option.text); 2726 } 2727 }); 2728 return result.length === 0 ? null : result; 2729 } 2730 return element.value; 2731 } 2732 element.value = value; 2733 }, 2734 2735 html: function (element, value) { 2736 if (isUndefined(value)) { 2737 return element.innerHTML; 2738 } 2739 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2740 jqLiteDealoc(childNodes[i]); 2741 } 2742 element.innerHTML = value; 2743 }, 2744 2745 empty: jqLiteEmpty 2746 }, function (fn, name) { 2747 /** 2748 * Properties: writes return selection, reads return first value 2749 */ 2750 JQLite.prototype[name] = function (arg1, arg2) { 2751 var i, key; 2752 2753 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 2754 // in a way that survives minification. 2755 // jqLiteEmpty takes no arguments but is a setter. 2756 if (fn !== jqLiteEmpty && 2757 (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { 2758 if (isObject(arg1)) { 2759 2760 // we are a write, but the object properties are the key/values 2761 for (i = 0; i < this.length; i++) { 2762 if (fn === jqLiteData) { 2763 // data() takes the whole object in jQuery 2764 fn(this[i], arg1); 2765 } else { 2766 for (key in arg1) { 2767 fn(this[i], key, arg1[key]); 2768 } 2769 } 2770 } 2771 // return self for chaining 2772 return this; 2773 } else { 2774 // we are a read, so read the first child. 2775 var value = fn.$dv; 2776 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 2777 var jj = (value === undefined) ? Math.min(this.length, 1) : this.length; 2778 for (var j = 0; j < jj; j++) { 2779 var nodeValue = fn(this[j], arg1, arg2); 2780 value = value ? value + nodeValue : nodeValue; 2781 } 2782 return value; 2783 } 2784 } else { 2785 // we are a write, so apply to all children 2786 for (i = 0; i < this.length; i++) { 2787 fn(this[i], arg1, arg2); 2788 } 2789 // return self for chaining 2790 return this; 2791 } 2792 }; 2793 }); 2794 2795 function createEventHandler(element, events) { 2796 var eventHandler = function (event, type) { 2797 if (!event.preventDefault) { 2798 event.preventDefault = function () { 2799 event.returnValue = false; //ie 2800 }; 2801 } 2802 2803 if (!event.stopPropagation) { 2804 event.stopPropagation = function () { 2805 event.cancelBubble = true; //ie 2806 }; 2807 } 2808 2809 if (!event.target) { 2810 event.target = event.srcElement || document; 2811 } 2812 2813 if (isUndefined(event.defaultPrevented)) { 2814 var prevent = event.preventDefault; 2815 event.preventDefault = function () { 2816 event.defaultPrevented = true; 2817 prevent.call(event); 2818 }; 2819 event.defaultPrevented = false; 2820 } 2821 2822 event.isDefaultPrevented = function () { 2823 return event.defaultPrevented || event.returnValue === false; 2824 }; 2825 2826 // Copy event handlers in case event handlers array is modified during execution. 2827 var eventHandlersCopy = shallowCopy(events[type || event.type] || []); 2828
2829 forEach(eventHandlersCopy, function (fn) { 2830 fn.call(element, event); 2831 }); 2832 2833 // Remove monkey-patched methods (IE), 2834 // as they would cause memory leaks in IE8. 2835 if (msie <= 8) { 2836 // IE7/8 does not allow to delete property on native object 2837 event.preventDefault = null; 2838 event.stopPropagation = null; 2839 event.isDefaultPrevented = null; 2840 } else { 2841 // It shouldn't affect normal browsers (native methods are defined on prototype). 2842 delete event.preventDefault; 2843 delete event.stopPropagation; 2844 delete event.isDefaultPrevented; 2845 } 2846 }; 2847 eventHandler.elem = element; 2848 return eventHandler; 2849 } 2850 2851////////////////////////////////////////// 2852// Functions iterating traversal. 2853// These functions chain results into a single 2854// selector. 2855////////////////////////////////////////// 2856 forEach({ 2857 removeData: jqLiteRemoveData, 2858 2859 dealoc: jqLiteDealoc, 2860 2861 on: function onFn(element, type, fn, unsupported) {
2862 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 2863 2864 var events = jqLiteExpandoStore(element, 'events'), 2865 handle = jqLiteExpandoStore(element, 'handle'); 2866 2867 if (!events) jqLiteExpandoStore(element, 'events', events = {}); 2868 if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); 2869 2870 forEach(type.split(' '), function (type) { 2871 var eventFns = events[type]; 2872 2873 if (!eventFns) { 2874 if (type == 'mouseenter' || type == 'mouseleave') { 2875 var contains = document.body.contains || document.body.compareDocumentPosition ? 2876 function (a, b) { 2877 // jshint bitwise: false 2878 var adown = a.nodeType === 9 ? a.documentElement : a, 2879 bup = b && b.parentNode; 2880 return a === bup || !!( bup && bup.nodeType === 1 && ( 2881 adown.contains ? 2882 adown.contains(bup) : 2883 a.compareDocumentPosition && a.compareDocumentPosition(bup) & 16 2884 )); 2885 } : 2886 function (a, b) { 2887 if (b) { 2888 while ((b = b.parentNode)) { 2889 if (b === a) { 2890 return true; 2891 } 2892 } 2893 } 2894 return false; 2895 }; 2896 2897 events[type] = []; 2898 2899 // Refer to jQuery's implementation of mouseenter & mouseleave 2900 // Read about mouseenter and mouseleave: 2901 // http://www.quirksmode.org/js/events_mouse.html#link8 2902 var eventmap = { mouseleave: "mouseout", mouseenter: "mouseover"}; 2903 2904 onFn(element, eventmap[type], function (event) { 2905 var target = this, related = event.relatedTarget; 2906 // For mousenter/leave call the handler if related is outside the target. 2907 // NB: No relatedTarget if the mouse left/entered the browser window 2908 if (!related || (related !== target && !contains(target, related))) { 2909 handle(event, type); 2910 } 2911 }); 2912 2913 } else { 2914 addEventListenerFn(element, type, handle); 2915 events[type] = []; 2916 } 2917 eventFns = events[type]; 2918 } 2919 eventFns.push(fn); 2920 }); 2921 }, 2922 2923 off: jqLiteOff, 2924 2925 one: function (element, type, fn) { 2926 element = jqLite(element); 2927 2928 //add the listener twice so that when it is called 2929 //you can remove the original function and still be 2930 //able to call element.off(ev, fn) normally 2931 element.on(type, function onFn() { 2932 element.off(type, fn); 2933 element.off(type, onFn); 2934 }); 2935 element.on(type, fn); 2936 }, 2937 2938 replaceWith: function (element, replaceNode) { 2939 var index, parent = element.parentNode; 2940 jqLiteDealoc(element); 2941 forEach(new JQLite(replaceNode), function (node) { 2942 if (index) { 2943 parent.insertBefore(node, index.nextSibling); 2944 } else { 2945 parent.replaceChild(node, element); 2946 } 2947 index = node; 2948 }); 2949 }, 2950 2951 children: function (element) { 2952 var children = [];
2953 forEach(element.childNodes, function (element) { 2954 if (element.nodeType === 1) 2955 children.push(element); 2956 }); 2957 return children; 2958 }, 2959 2960 contents: function (element) { 2961 return element.contentDocument || element.childNodes || []; 2962 }, 2963 2964 append: function (element, node) { 2965 forEach(new JQLite(node), function (child) { 2966 if (element.nodeType === 1 || element.nodeType === 11) { 2967 element.appendChild(child); 2968 } 2969 }); 2970 }, 2971 2972 prepend: function (element, node) { 2973 if (element.nodeType === 1) { 2974 var index = element.firstChild; 2975 forEach(new JQLite(node), function (child) { 2976 element.insertBefore(child, index); 2977 }); 2978 } 2979 }, 2980 2981 wrap: function (element, wrapNode) { 2982 wrapNode = jqLite(wrapNode)[0]; 2983 var parent = element.parentNode; 2984 if (parent) { 2985 parent.replaceChild(wrapNode, element); 2986 } 2987 wrapNode.appendChild(element); 2988 }, 2989 2990 remove: function (element) { 2991 jqLiteDealoc(element); 2992 var parent = element.parentNode; 2993 if (parent) parent.removeChild(element); 2994 }, 2995 2996 after: function (element, newElement) { 2997 var index = element, parent = element.parentNode; 2998 forEach(new JQLite(newElement), function (node) { 2999 parent.insertBefore(node, index.nextSibling); 3000 index = node; 3001 }); 3002 }, 3003 3004 addClass: jqLiteAddClass, 3005 removeClass: jqLiteRemoveClass, 3006 3007 toggleClass: function (element, selector, condition) { 3008 if (selector) { 3009 forEach(selector.split(' '), function (className) { 3010 var classCondition = condition; 3011 if (isUndefined(classCondition)) { 3012 classCondition = !jqLiteHasClass(element, className); 3013 } 3014 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 3015 }); 3016 } 3017 }, 3018 3019 parent: function (element) { 3020 var parent = element.parentNode; 3021 return parent && parent.nodeType !== 11 ? parent : null; 3022 }, 3023 3024 next: function (element) { 3025 if (element.nextElementSibling) { 3026 return element.nextElementSibling; 3027 } 3028 3029 // IE8 doesn't have nextElementSibling 3030 var elm = element.nextSibling; 3031 while (elm != null && elm.nodeType !== 1) { 3032 elm = elm.nextSibling; 3033 } 3034 return elm; 3035 }, 3036 3037 find: function (element, selector) { 3038 if (element.getElementsByTagName) { 3039 return element.getElementsByTagName(selector); 3040 } else { 3041 return []; 3042 } 3043 }, 3044 3045 clone: jqLiteClone, 3046 3047 triggerHandler: function (element, eventName, eventData) { 3048 var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; 3049 3050 eventData = eventData || []; 3051 3052 var event = [ 3053 { 3054 preventDefault: noop, 3055 stopPropagation: noop 3056 } 3057 ]; 3058 3059 forEach(eventFns, function (fn) { 3060 fn.apply(element, event.concat(eventData)); 3061 }); 3062 } 3063 }, function (fn, name) { 3064 /** 3065 * chaining functions 3066 */ 3067 JQLite.prototype[name] = function (arg1, arg2, arg3) { 3068 var value; 3069 for (var i = 0; i < this.length; i++) { 3070 if (isUndefined(value)) { 3071 value = fn(this[i], arg1, arg2, arg3); 3072 if (isDefined(value)) { 3073 // any function which returns a value needs to be wrapped 3074 value = jqLite(value); 3075 } 3076 } else { 3077 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3078 } 3079 } 3080 return isDefined(value) ? value : this; 3081 }; 3082 3083 // bind legacy bind/unbind to on/off 3084 JQLite.prototype.bind = JQLite.prototype.on; 3085 JQLite.prototype.unbind = JQLite.prototype.off; 3086 }); 3087 3088 /** 3089 * Computes a hash of an 'obj'. 3090 * Hash of a: 3091 * string is string 3092 * number is number as string 3093 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3094 * that is also assigned to the $$hashKey property of the object. 3095 * 3096 * @param obj 3097 * @returns {string} hash string such that the same input will have the same hash string. 3098 * The resulting string key is in 'type:hashKey' format. 3099 */ 3100 function hashKey(obj) { 3101 var objType = typeof obj, 3102 key; 3103 3104 if (objType == 'object' && obj !== null) { 3105 if (typeof (key = obj.$$hashKey) == 'function') { 3106 // must invoke on object to keep the right this 3107 key = obj.$$hashKey(); 3108 } else if (key === undefined) { 3109 key = obj.$$hashKey = nextUid(); 3110 } 3111 } else { 3112 key = obj; 3113 } 3114 3115 return objType + ':' + key; 3116 } 3117 3118 /** 3119 * HashMap which can use objects as keys 3120 */ 3121 function HashMap(array) {
3122 forEach(array, this.put, this); 3123 } 3124 3125 HashMap.prototype = { 3126 /** 3127 * Store key value pair 3128 * @param key key to store can be any type 3129 * @param value value to store can be any type 3130 */ 3131 put: function (key, value) { 3132 this[hashKey(key)] = value; 3133 }, 3134 3135 /** 3136 * @param key 3137 * @returns {Object} the value for the key 3138 */ 3139 get: function (key) { 3140 return this[hashKey(key)]; 3141 }, 3142 3143 /** 3144 * Remove the key/value pair 3145 * @param key 3146 */ 3147 remove: function (key) { 3148 var value = this[key = hashKey(key)]; 3149 delete this[key]; 3150 return value; 3151 } 3152 }; 3153 3154 /** 3155 * @ngdoc function 3156 * @module ng 3157 * @name angular.injector 3158 * @function 3159 * 3160 * @description 3161 * Creates an injector function that can be used for retrieving services as well as for 3162 * dependency injection (see {@link guide/di dependency injection}). 3163 * 3164 3165 * @param {Array.<string|Function>} modules A list of module functions or their aliases. See 3166 * {@link angular.module}. The `ng` module must be explicitly added. 3167 * @returns {function()} Injector function. See {@link auto.$injector $injector}. 3168 * 3169 * @example 3170 * Typical usage 3171 * ```js 3172 * // create an injector 3173 * var $injector = angular.injector(['ng']); 3174 * 3175 * // use the injector to kick off your application 3176 * // use the type inference to auto inject arguments, or use impl
3176icit injection 3177 * $injector.invoke(function($rootScope, $compile, $document){ 3178 * $compile($document)($rootScope); 3179 * $rootScope.$digest(); 3180 * }); 3181 * ``` 3182 * 3183 * Sometimes you want to get access to the injector of a currently running Angular app 3184 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3185 * application has been bootstrapped. You can do this using extra `injector()` added 3186 * to JQuery/jqLite elements. See {@link angular.element}. 3187 * 3188 * *This is fairly rare but could be the case if a third party library is injecting the 3189 * markup.* 3190 * 3191 * In the following example a new block of HTML containing a `ng-controller` 3192 * directive is added to the end of the document body by JQuery. We then compile and link 3193 * it into the current AngularJS scope. 3194 * 3195 * ```js 3196 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3197 * $(document.body).append($div); 3198 * 3199 * angular.element(document).injector().invoke(function($compile) { 3200 * var scope = angular.element($div).scope(); 3201 * $compile($div)(scope); 3202 * }); 3203 * ``` 3204 */ 3205 3206 3207 /** 3208 * @ngdoc module 3209 * @name auto 3210 * @description 3211 * 3212 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3213 */ 3214 3215 var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; 3216 var FN_ARG_SPLIT = /,/; 3217 var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3218 var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3219 var $injectorMinErr = minErr('$injector'); 3220 3221 function annotate(fn) { 3222 var $inject, 3223 fnText, 3224 argDecl, 3225 last; 3226 3227 if (typeof fn == 'function') { 3228 if (!($inject = fn.$inject)) { 3229 $inject = []; 3230 if (fn.length) { 3231 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3232 argDecl = fnText.match(FN_ARGS); 3233 forEach(argDecl[1].split(FN_ARG_SPLIT), function (arg) { 3234 arg.replace(FN_ARG, function (all, underscore, name) { 3235 $inject.push(name); 3236 }); 3237 }); 3238 } 3239 fn.$inject = $inject; 3240 } 3241 } else if (isArray(fn)) { 3242 last = fn.length - 1; 3243 assertArgFn(fn[last], 'fn'); 3244 $inject = fn.slice(0, last); 3245 } else { 3246 assertArgFn(fn, 'fn', true); 3247 } 3248 return $inject; 3249 } 3250 3251/////////////////////////////////////// 3252 3253 /** 3254 * @ngdoc service 3255 * @name $injector 3256 * @function 3257 * 3258 * @description 3259 * 3260 * `$injector` is used to retrieve object instances as defined by 3261 * {@link auto.$provide provider}, instantiate types, invoke methods, 3262 * and load modules. 3263 * 3264 * The following always holds true: 3265 * 3266 * ```js 3267 * var $injector = angular.injector(); 3268 * expect($injector.get('$injector')).toBe($injector); 3269 * expect($injector.invoke(function($injector){ 3270 * return $injector; 3271 * }).toBe($injector); 3272 * ``` 3273 * 3274 * # Injection Function Annotation 3275 * 3276 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3277 * following are all valid ways of annotating function with injection arguments and are equivalent. 3278 * 3279 * ```js 3280 * // inferred (only works if code not minified/obfuscated) 3281 * $injector.invoke(function(serviceA){}); 3282 * 3283 * // annotated 3284 * function explicit(serviceA) {}; 3285 * explicit.$inject = ['serviceA']; 3286 * $injector.invoke(explicit); 3287 * 3288 * // inline 3289 * $injector.invoke(['serviceA', function(serviceA){}]); 3290 * ``` 3291 * 3292 * ## Inference 3293 * 3294 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3295 * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with 3296 * minification, and obfuscation tools since these tools change the argument names. 3297 * 3298 * ## `$inject` Annotation 3299 * By adding a `$inject` property onto a function the injection parameters can be specified. 3300 * 3301 * ## Inline 3302 * As an array of injection names, where the last item in the array is the function to call. 3303 */ 3304 3305 /** 3306 * @ngdoc method 3307 * @name $injector#get 3308 *
3309 * @description 3310 * Return an instance of the service. 3311 * 3312 * @param {string} name The name of the instance to retrieve. 3313 * @return {*} The instance. 3314 */ 3315 3316 /** 3317 * @ngdoc method 3318 * @name $injector#invoke 3319 * 3320 * @description 3321 * Invoke the method and supply the method arguments from the `$injector`. 3322 * 3323 * @param {!Function} fn The function to invoke. Function parameters are injected according to the 3324 * {@link guide/di $inject Annotation} rules. 3325 * @param {Object=} self The `this` for the invoked method. 3326 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3327 * object first, before the `$injector` is consulted. 3328 * @returns {*} the value returned by the invoked `fn` function. 3329 */ 3330 3331 /** 3332 * @ngdoc method 3333 * @name $injector#has 3334 * 3335 * @description 3336 * Allows the user to query if the particular service exist. 3337 * 3338 * @param {string} Name of the service to query. 3339 * @returns {boolean} returns true if injector has given service. 3340 */ 3341 3342 /** 3343 * @ngdoc method 3344 * @name $injector#instantiate 3345 * @description 3346 * Create a new instance of JS type. The method takes a constructor function invokes the new 3347 * operator and supplies all of the arguments to the constructor function as specified by the 3348 * constructor annotation. 3349 * 3350 * @param {Function} Type Annotated constructor function. 3351 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3352 * object first, before the `$injector` is consulted. 3353 * @returns {Object} new instance of `Type`. 3354 */ 3355 3356 /** 3357 * @ngdoc method 3358 * @name $injector#annotate 3359 * 3360 * @description 3361 * Returns an array of service names which the function is requesting for injection. This API is 3362 * used by the injector to determine which services need to be injected into the function when the 3363 * function is invoked. There are three ways in which the function can be annotated with the needed 3364 * dependencies. 3365 * 3366 * # Argument names 3367 * 3368 * The simplest form is to extract the dependencies from the arguments of the function. This is done 3369 * by converting the function into a string using `toString()` method and extracting the argument 3370 * names. 3371 * ```js 3372 * // Given 3373 * function MyController($scope, $route) { 3374 * // ... 3375 * } 3376 * 3377 * // Then 3378 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3379 * ``` 3380 * 3381 * This method does not work with code minification / obfuscation. For this reason the following 3382 * annotation strategies are supported. 3383 * 3384 * # The `$inject` property 3385 * 3386 * If a function has an `$inject` property and its value is an array of strings, then the strings 3387 * represent names of services to be injected into the function. 3388 * ```js 3389 * // Given 3390 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 3391 * // ... 3392 * } 3393 * // Define function dependencies 3394 * MyController['$inject'] = ['$scope', '$route']; 3395 * 3396 * // Then 3397 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3398 * ``` 3399 * 3400 * # The array notation 3401 * 3402 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 3403 * is very inconvenient. In these situations using the array notation to specify the dependencies in 3404 * a way that survives minification is a better choice: 3405 * 3406 * ```js 3407 * // We wish to write this (not minification / obfuscation safe) 3408 * injector.invoke(function($compile, $rootScope) { 3409 * // ... 3410 * }); 3411 * 3412 * // We are forced to write break inlining 3413 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 3414 * // ... 3415 * }; 3416 * tmpFn.$inject = ['$compile', '$rootScope']; 3417 * injector.invoke(tmpFn); 3418 * 3419 * // To better support inline function the inline annotation is supported 3420 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 3421 * // ... 3422 * }]); 3423 * 3424 * // Therefore 3425 * expect(injector.annotate( 3426 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 3427 * ).toEqual(['$compile', '$rootScope']); 3428 * ``` 3429 * 3430 * @param {Function|Array.<string|Function>}
3430 fn Function for which dependent service names need to 3431 * be retrieved as described above. 3432 * 3433 * @returns {Array.<string>} The names of the services which the function requires. 3434 */ 3435 3436 3437 /** 3438 * @ngdoc object 3439 * @name $provide 3440 * 3441 * @description 3442 * 3443 * The {@link auto.$provide $provide} service has a number of methods for registering components 3444 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 3445 * {@link angular.Module}. 3446 * 3447 * An Angular **service** is a singleton object created by a **service factory**. These **service 3448 * factories** are functions which, in turn, are created by a **service provider**. 3449 * The **service providers** are constructor functions. When instantiated they must contain a 3450 * property called `$get`, which holds the **service factory** function. 3451 * 3452 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 3453 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 3454 * function to get the instance of the **service**. 3455 * 3456 * Often services have no configuration options and there is no need to add methods to the service 3457 * provider. The provider will be no more than a constructor function with a `$get` property. For 3458 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 3459 * services without specifying a provider. 3460 * 3461 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 3462 * {@link auto.$injector $injector} 3463 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 3464 * providers and services. 3465 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 3466 * services, not providers. 3467 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 3468 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 3469 * given factory function. 3470 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 3471 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 3472 * a new object using the given constructor function. 3473 * 3474 * See the individual methods for more information and examples. 3475 */ 3476 3477 /** 3478 * @ngdoc method 3479 * @name $provide#provider 3480 * @description 3481 * 3482 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 3483 * are constructor functions, whose instances are responsible for "providing" a factory for a 3484 * service. 3485 * 3486 * Service provider names start with the name of the service they provide followed by `Provider`. 3487 * For example, the {@link ng.$log $log} service has a provider called 3488 * {@link ng.$logProvider $logProvider}. 3489 * 3490 * Service provider objects can have additional methods which allow configuration of the provider 3491 * and its service. Importantly, you can configure what kind of service is created by the `$get` 3492 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 3493 * method {@link ng.$logProvider#debugEnabled debugEnabled} 3494 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 3495 * console or not. 3496 * 3497 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 3498 'Provider'` key. 3499 * @param {(Object|function())} provider If the provider is: 3500 * 3501 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 3502 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 3503 * - `Constructor`: a new instance of the provider will be created using 3504 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 3505 * 3506 * @returns {Object} registered provider instance 3507 3508 * @example 3509 * 3510 * The following example shows how to create a simple event tracking service and register it using 3511 * {@link auto.$provide#provider $provide.provider()}. 3512 * 3513 * ```js 3514 * // Define the eventTracker provider 3515 * function EventTrackerProvider() { 3516 * var trackingUrl = '/track'; 3517 * 3518 * // A provider method for configuring where the tracked events should been saved 3519 * this.setTrackingUrl = function(url) { 3520 * trackingUrl = url; 3521 * }; 3522 * 3523 * // The service factory function 3524 * this.$get = ['$http', function($http) { 3525 * var trackedEvents = {}; 3526 * return { 3527 * // Call this to track an event 3528 * event: function(event) { 3529 * var count = trackedEvents[event] || 0; 3530 * count += 1; 3531 * trackedEvents[event] = count; 3532 * return count; 3533 * }, 3534 * // Call this to save the tracked events to the trackingUrl 3535 * save: function() { 3536 * $http.post(trackingUrl, trackedEvents); 3537 * } 3538 * }; 3539 * }]; 3540 * } 3541 * 3542 * describe('eventTracker', function() { 3543 * var postSpy; 3544 * 3545 * beforeEach(module(function($provide) { 3546 * // Register the eventTracker provider 3547 * $provide.provider('eventTracker', EventTrackerProvider); 3548 * })); 3549 * 3550 * beforeEach(module(function(eventTrackerProvider) { 3551 * // Configure eventTracker provider 3552 * eventTrackerProvider.setTrackingUrl('/custom-track'); 3553 * })); 3554 * 3555 * it('tracks events', inject(function(eventTracker) { 3556 * expect(eventTracker.event('login')).toEqual(1); 3557 * expect(eventTracker.event('login')).toEqual(2); 3558 * })); 3559 * 3560 * it('saves to the tracking url', inject(function(eventTracker, $http) { 3561 * postSpy = spyOn($http, 'post'); 3562 * eventTracker.event('login'); 3563 * eventTracker.save(); 3564 * expect(postSpy).toHaveBeenCalled(); 3565 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 3566 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 3567 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 3568 * })); 3569 * }); 3570 * ``` 3571 */ 3572 3573 /** 3574 * @ngdoc method 3575 * @name $provide#factory
3576 * @description 3577 * 3578 * Register a **service factory**, which will be called to return the service instance. 3579 * This is short for registering a service where its provider consists of only a `$get` property, 3580 * which is the given service factory function. 3581 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 3582 * configure your service in a provider. 3583 * 3584 * @param {string} name The name of the instance. 3585 * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand 3586 * for `$provide.provider(name, {$get: $getFn})`. 3587 * @returns {Object} registered provider instance 3588 * 3589 * @example 3590 * Here is an example of registering a service 3591 * ```js 3592 * $provide.factory('ping', ['$http', function($http) { 3593 * return function ping() { 3594 * return $http.send('/ping'); 3595 * }; 3596 * }]); 3597 * ``` 3598 * You would then inject and use this service like this: 3599 * ```js 3600 * someModule.controller('Ctrl', ['ping', function(ping) { 3601 * ping(); 3602 * }]); 3603 * ``` 3604 */ 3605 3606 3607 /** 3608 * @ngdoc method 3609 * @name $provide#service 3610 * @description 3611 * 3612 * Register a **service constructor**, which will be invoked with `new` to create the service 3613 * instance. 3614 * This is short for registering a service where its provider's `$get` property is the service 3615 * constructor function that will be used to instantiate the service instance. 3616 * 3617 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 3618 * as a type/class. 3619 * 3620 * @param {string} name The name of the instance. 3621 * @param {Function} constructor A class (constructor function) that will be instantiated. 3622 * @returns {Object} registered provider instance 3623 * 3624 * @example 3625 * Here is an example of registering a service using 3626 * {@link auto.$provide#service $provide.service(class)}. 3627 * ```js 3628 * var Ping = function($http) { 3629 * this.$http = $http; 3630 * }; 3631 * 3632 * Ping.$inject = ['$http']; 3633 * 3634 * Ping.prototype.send = function() { 3635 * return this.$http.get('/ping'); 3636 * }; 3637 * $provide.service('ping', Ping); 3638 * ``` 3639 * You would then inject and use this service like this: 3640 * ```js 3641 * someModule.controller('Ctrl', ['ping', function(ping) { 3642 * ping.send(); 3643 * }]); 3644 * ``` 3645 */ 3646 3647 3648 /** 3649 * @ngdoc method 3650 * @name $provide#value 3651 * @description 3652 * 3653 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 3654 * number, an array, an object or a function. This is short for registering a service where its 3655 * provider's `$get` property is a factory function that takes no arguments and returns the **value 3656 * service**. 3657 * 3658 * Value services are similar to constant services, except that they cannot be injected into a 3659 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 3660 * an Angular 3661 * {@link auto.$provide#decorator decorator}. 3662 * 3663 * @param {string} name The name of the instance. 3664 * @param {*} value The value. 3665 * @returns {Object} registered provider instance 3666 * 3667 * @example 3668 * Here are some examples of creating value services. 3669 * ```js 3670 * $provide.value('ADMIN_USER', 'admin'); 3671 * 3672 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 3673 * 3674 * $provide.value('halfOf', function(value) { 3675 * return value / 2; 3676 * }); 3677 * ``` 3678 */ 3679 3680 3681 /** 3682 * @ngdoc method 3683 * @name $provide#constant 3684 * @description 3685 * 3686 * Register a **constant service**, such as a string, a number, an array, an object or a function, 3687 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be 3688 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 3689 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 3690 * 3691 * @param {string} name The name of the constant. 3692 * @param {*} value The constant value. 3693 * @returns {Object} registered instance 3694 * 3695 * @example 3696 * Here a some examples of creating constants: 3697 * ```js 3698 * $provide.constant('SHARD_HEIGHT', 306); 3699 * 3700 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 3701 * 3702 * $provide.constant('double', function(value) { 3703 * return value * 2; 3704 * }); 3705 * ``` 3706 */ 3707 3708 3709 /** 3710 * @ngdoc method 3711 * @name $provide#decorator
3712 * @description 3713 * 3714 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 3715 * intercepts the creation of a service, allowing it to override or modify the behaviour of the 3716 * service. The object returned by the decorator may be the original service, or a new service 3717 * object which replaces or wraps and delegates to the original service. 3718 * 3719 * @param {string} name The name of the service to decorate. 3720 * @param {function()} decorator This function will be invoked when the service needs to be 3721 * instantiated and should return the decorated service instance. The function is called using 3722 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 3723 * Local injection arguments: 3724 * 3725 * * `$delegate` - The original service instance, which can be monkey patched, configured, 3726 * decorated or delegated to. 3727 * 3728 * @example 3729 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 3730 * calls to {@link ng.$log#error $log.warn()}. 3731 * ```js 3732 * $provide.decorator('$log', ['$delegate', function($delegate) { 3733 * $delegate.warn = $delegate.error; 3734 * return $delegate; 3735 * }]); 3736 * ``` 3737 */ 3738 3739 3740 function createInjector(modulesToLoad) { 3741 var INSTANTIATING = {}, 3742 providerSuffix = 'Provider', 3743 path = [], 3744 loadedModules = new HashMap(), 3745 providerCache = { 3746 $provide: { 3747 provider: supportObject(provider), 3748 factory: supportObject(factory), 3749 service: supportObject(service), 3750 value: supportObject(value), 3751 constant: supportObject(constant), 3752 decorator: decorator 3753 } 3754 }, 3755 providerInjector = (providerCache.$injector = 3756 createInternalInjector(providerCache, function () { 3757 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 3758 })), 3759 instanceCache = {}, 3760 instanceInjector = (instanceCache.$injector = 3761 createInternalInjector(instanceCache, function (servicename) { 3762 var provider = providerInjector.get(servicename + providerSuffix); 3763 return instanceInjector.invoke(provider.$get, provider); 3764 })); 3765 3766 3767 forEach(loadModules(modulesToLoad), function (fn) { 3768 instanceInjector.invoke(fn || noop); 3769 }); 3770 3771 return instanceInjector; 3772
vendor: 1,915 bytes, lines 3773-3827
3773 //////////////////////////////////// 3774 // $provider 3775 //////////////////////////////////// 3776 3777 function supportObject(delegate) { 3778 return function (key, value) { 3779 if (isObject(key)) { 3780 forEach(key, reverseParams(delegate)); 3781 } else { 3782 return delegate(key, value); 3783 } 3784 }; 3785 } 3786 3787 function provider(name, provider_) { 3788 assertNotHasOwnProperty(name, 'service'); 3789 if (isFunction(provider_) || isArray(provider_)) { 3790 provider_ = providerInjector.instantiate(provider_); 3791 } 3792 if (!provider_.$get) { 3793 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 3794 } 3795 return providerCache[name + providerSuffix] = provider_; 3796 } 3797 3798 function factory(name, factoryFn) { 3799 return provider(name, { $get: factoryFn }); 3800 } 3801 3802 function service(name, constructor) { 3803 return factory(name, ['$injector', function ($injector) { 3804 return $injector.instantiate(constructor); 3805 }]); 3806 } 3807 3808 function value(name, val) { 3809 return factory(name, valueFn(val)); 3810 } 3811 3812 function constant(name, value) { 3813 assertNotHasOwnProperty(name, 'constant'); 3814 providerCache[name] = value; 3815 instanceCache[name] = value; 3816 } 3817 3818 function decorator(serviceName, decorFn) { 3819 var origProvider = providerInjector.get(serviceName + providerSuffix), 3820 orig$get = origProvider.$get; 3821 3822 origProvider.$get = function () { 3823 var origInstance = instanceInjector.invoke(orig$get, origProvider); 3824 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 3825 }; 3826 } 3827
3828 //////////////////////////////////// 3829 // Module Loading 3830 //////////////////////////////////// 3831 function loadModules(modulesToLoad) { 3832 var runBlocks = [], moduleFn, invokeQueue, i, ii; 3833 forEach(modulesToLoad, function (module) { 3834 if (loadedModules.get(module)) return; 3835 loadedModules.put(module, true); 3836 3837 try { 3838 if (isString(module)) { 3839 moduleFn = angularModule(module); 3840 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 3841 3842 for (invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { 3843 var invokeArgs = invokeQueue[i], 3844 provider = providerInjector.get(invokeArgs[0]); 3845 3846 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 3847 } 3848 } else if (isFunction(module)) { 3849 runBlocks.push(providerInjector.invoke(module)); 3850 } else if (isArray(module)) { 3851 runBlocks.push(providerInjector.invoke(module)); 3852 } else { 3853 assertArgFn(module, 'module'); 3854 } 3855 } catch (e) { 3856 if (isArray(module)) { 3857 module = module[module.length - 1]; 3858 } 3859 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 3860 // Safari & FF's stack traces don't contain error.message content 3861 // unlike those of Chrome and IE 3862 // So if stack doesn't contain message, we create a new string that contains both. 3863 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 3864 /* jshint -W022 */ 3865 e = e.message + '\n' + e.stack; 3866 } 3867 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 3868 module, e.stack || e.message || e); 3869 } 3870 }); 3871 return runBlocks; 3872 } 3873
3874 //////////////////////////////////// 3875 // internal Injector 3876 //////////////////////////////////// 3877 3878 function createInternalInjector(cache, factory) { 3879 3880 function getService(serviceName) { 3881 if (cache.hasOwnProperty(serviceName)) { 3882 if (cache[serviceName] === INSTANTIATING) { 3883 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- ')); 3884 } 3885 return cache[serviceName]; 3886 } else { 3887 try { 3888 path.unshift(serviceName); 3889 cache[serviceName] = INSTANTIATING; 3890 return cache[serviceName] = factory(serviceName); 3891 } catch (err) { 3892 if (cache[serviceName] === INSTANTIATING) { 3893 delete cache[serviceName]; 3894 } 3895 throw err; 3896 } finally { 3897 path.shift(); 3898 } 3899 } 3900 } 3901 3902 function invoke(fn, self, locals) { 3903 var args = [], 3904 $inject = annotate(fn), 3905 length, i, 3906 key; 3907 3908 for (i = 0, length = $inject.length; i < length; i++) { 3909 key = $inject[i]; 3910 if (typeof key !== 'string') { 3911 throw $injectorMinErr('itkn', 3912 'Incorrect injection token! Expected service name as string, got {0}', key); 3913 } 3914 args.push( 3915 locals && locals.hasOwnProperty(key) 3916 ? locals[key] 3917 : getService(key) 3918 ); 3919 } 3920 if (!fn.$inject) { 3921 // this means that we must be an array. 3922 fn = fn[length]; 3923 } 3924 3925 // http://jsperf.com/angularjs-invoke-apply-vs-switch 3926 // #5388 3927 return fn.apply(self, args); 3928 } 3929 3930 function instantiate(Type, locals) { 3931 var Constructor = function () { 3932 }, 3933 instance, returnedValue; 3934 3935 // Check if Type is annotated and use just the given function at n-1 as parameter 3936 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 3937 Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; 3938 instance = new Constructor(); 3939 returnedValue = invoke(Type, instance, locals); 3940 3941 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 3942 } 3943 3944 return { 3945 invoke: invoke, 3946 instantiate: instantiate, 3947 get: getService, 3948 annotate: annotate, 3949 has: function (name) { 3950 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 3951 } 3952 }; 3953 } 3954 } 3955 3956 /** 3957 * @ngdoc service 3958 * @name $anchorScroll 3959 * @kind function 3960 * @requires $window 3961 * @requires $location 3962 * @requires $rootScope 3963 * 3964 * @description 3965 * When called, it checks current value of `$location.hash()` and scroll to related element, 3966 * according to rules specified in 3967 * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). 3968 * 3969 * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. 3970 * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. 3971 * 3972 * @example 3973 <example> 3974 <file name="index.jsp"> 3975 <div id="scrollArea" ng-controller="ScrollCtrl"> 3976 <a ng-click="gotoBottom()">Go to bottom</a> 3977 <a id="bottom"></a> You're at the bottom! 3978 </div> 3979 </file> 3980 <file name="script.js"> 3981 function ScrollCtrl($scope, $location, $anchorScroll) { 3982 $scope.gotoBottom = function (){ 3983 // set the location.hash to the id of
3984 // the element you wish to scroll to. 3985 $location.hash('bottom'); 3986 3987 // call $anchorScroll() 3988 $anchorScroll(); 3989 }; 3990 } 3991 </file> 3992 <file name="style.css"> 3993 #scrollArea { 3994 height: 350px; 3995 overflow: auto; 3996 } 3997 3998 #bottom { 3999 display: block; 4000 margin-top: 2000px; 4001 } 4002 </file> 4003 </example> 4004 */ 4005 function $AnchorScrollProvider() { 4006 4007 var autoScrollingEnabled = true; 4008 4009 this.disableAutoScrolling = function () { 4010 autoScrollingEnabled = false; 4011 }; 4012 4013 this.$get = ['$window', '$location', '$rootScope', function ($window, $location, $rootScope) { 4014 var document = $window.document; 4015 4016 // helper function to get first anchor from a NodeList 4017 // can't use filter.filter, as it accepts only instances of Array 4018 // and IE can't convert NodeList to an array using [].slice 4019 // TODO(vojta): use filter if we change it to accept lists as well 4020 function getFirstAnchor(list) { 4021 var result = null; 4022 forEach(list, function (element) { 4023 if (!result && lowercase(element.nodeName) === 'a') result = element; 4024 }); 4025 return result; 4026 } 4027 4028 function scroll() { 4029 var hash = $location.hash(), elm; 4030 4031 // empty hash, scroll to the top of the page 4032 if (!hash) $window.scrollTo(0, 0); 4033 4034 // element with given id 4035 else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); 4036 4037 // first anchor with given name :-D 4038 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); 4039 4040 // no element and hash == 'top', scroll to the top of the page 4041 else if (hash === 'top') $window.scrollTo(0, 0); 4042 } 4043 4044 // does not scroll when user clicks on anchor link that is currently on 4045 // (no url change, no $location.hash() change), browser native does scroll 4046 if (autoScrollingEnabled) { 4047 $rootScope.$watch(function autoScrollWatch() { 4048 return $location.hash(); 4049 }, 4050 function autoScrollWatchAction() { 4051 $rootScope.$evalAsync(scroll); 4052 }); 4053 } 4054 4055 return scroll; 4056 }]; 4057 } 4058 4059 var $animateMinErr = minErr('$animate'); 4060 4061 /** 4062 * @ngdoc provider 4063 * @name $animateProvider 4064 * 4065 * @description 4066 * Default implementation of $animate that doesn't perform any animations, instead just 4067 * synchronously performs DOM 4068 * updates and calls done() callbacks. 4069 * 4070 * In order to enable animations the ngAnimate module has to be loaded. 4071 * 4072 * To see the functional implementation check out src/ngAnimate/animate.js 4073 */ 4074 var $AnimateProvider = ['$provide', function ($provide) { 4075 4076 4077 this.$$selectors = {}; 4078 4079 4080 /** 4081 * @ngdoc method 4082 * @name $animateProvider#register 4083 * 4084 * @description 4085 * Registers a new injectable animation factory function. The factory function produces the 4086 * animation object which contains callback functions for each event that is expected to be 4087 * animated. 4088 * 4089 * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` 4090 * must be called once the element animation is complete. If a function is returned then the 4091 * animation service will use this function to cancel the animation whenever a cancel event is 4092 * triggered. 4093 * 4094 * 4095 * ```js 4096 * return { 4097 * eventFn : function(element, done) { 4098 * //code to run the animation 4099 * //once complete, then run done() 4100 * return function cancellationFunction() { 4101 * //code to cancel the animation 4102 * } 4103 * } 4104 * } 4105 * ``` 4106 * 4107 * @param {string} name The name of the animation. 4108 * @param {Function} factory The factory function that will be executed to return the animation 4109 * object. 4110 */ 4111 this.register = function (name, factory) { 4112 var key = name + '-animation'; 4113 if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', 4114 "Expecting class selector starting with '.' got '{0}'.", name); 4115 this.$$selectors[name.substr(1)] = key; 4116 $provide.factory(key, factory); 4117 }; 4118 4119 /** 4120 * @ngdoc method 4121 * @name $animateProvider#classNameFilter 4122 *
4123 * @description 4124 * Sets and/or returns the CSS class regular expression that is checked when performing 4125 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 4126 * therefore enable $animate to attempt to perform an animation on any element. 4127 * When setting the classNameFilter value, animations will only be performed on elements 4128 * that successfully match the filter expression. This in turn can boost performance 4129 * for low-powered devices as well as applications containing a lot of structural operations. 4130 * @param {RegExp=} expression The className expression which will be checked against all animations 4131 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 4132 */ 4133 this.classNameFilter = function (expression) { 4134 if (arguments.length === 1) { 4135 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 4136 } 4137 return this.$$classNameFilter; 4138 }; 4139 4140 this.$get = ['$timeout', '$$asyncCallback', function ($timeout, $$asyncCallback) { 4141 4142 function async(fn) { 4143 fn && $$asyncCallback(fn); 4144 } 4145 4146 /** 4147 * 4148 * @ngdoc service 4149 * @name $animate 4150 * @description The $animate service provides rudimentary DOM manipulation functions to 4151 * insert, remove and move elements within the DOM, as well as adding and removing classes. 4152 * This service is the core service used by the ngAnimate $animator service which provides 4153 * high-level animation hooks for CSS and JavaScript. 4154 * 4155 * $animate is available in the AngularJS core, however, the ngAnimate module must be included 4156 * to enable full out animation support. Otherwise, $animate will only perform simple DOM 4157 * manipulation operations. 4158 * 4159 * To learn more about enabling animation support, click here to visit the {@link ngAnimate 4160 * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service 4161 * page}. 4162 */ 4163 return { 4164 4165 /** 4166 * 4167 * @ngdoc method 4168 * @name $animate#enter 4169 * @function 4170 * @description Inserts the element into the DOM either after the `after` element or within 4171 * the `parent` element. Once complete, the done() callback will be fired (if provided). 4172 * @param {DOMElement} element the element which will be inserted into the DOM 4173 * @param {DOMElement} parent the parent element which will append the element as 4174 * a child (if the after element is not present) 4175 * @param {DOMElement} after the sibling element which will append the element 4176 * after itself 4177 * @param {Function=} done callback function that will be called after the element has been 4178 * inserted into the DOM 4179 */ 4180 enter: function (element, parent, after, done) { 4181 if (after) { 4182 after.after(element); 4183 } else { 4184 if (!parent || !parent[0]) { 4185 parent = after.parent(); 4186 } 4187 parent.append(element); 4188 } 4189 async(done); 4190 }, 4191 4192 /** 4193 * 4194 * @ngdoc method 4195 * @name $animate#leave 4196 * @function 4197 * @description Removes the element from the DOM. Once complete, the done() callback will be 4198 * fired (if provided). 4199 * @param {DOMElement} element the element which will be removed from the DOM 4200 * @param {Function=} done callback function that will be called after the element has been 4201 * removed from the DOM 4202 */ 4203 leave: function (element, done) { 4204 element.remove(); 4205 async(done); 4206 }, 4207 4208 /** 4209 * 4210 * @ngdoc method 4211 * @name $animate#move 4212 * @function
4213 * @description Moves the position of the provided element within the DOM to be placed 4214 * either after the `after` element or inside of the `parent` element. Once complete, the 4215 * done() callback will be fired (if provided). 4216 * 4217 * @param {DOMElement} element the element which will be moved around within the 4218 * DOM 4219 * @param {DOMElement} parent the parent element where the element will be 4220 * inserted into (if the after element is not present) 4221 * @param {DOMElement} after the sibling element where the element will be 4222 * positioned next to 4223 * @param {Function=} done the callback function (if provided) that will be fired after the 4224 * element has been moved to its new position 4225 */ 4226 move: function (element, parent, after, done) { 4227 // Do not remove element before insert. Removing will cause data associated with the 4228 // element to be dropped. Insert will implicitly do the remove. 4229 this.enter(element, parent, after, done); 4230 }, 4231 4232 /** 4233 * 4234 * @ngdoc method 4235 * @name $animate#addClass 4236 * @function 4237 * @description Adds the provided className CSS class value to the provided element. Once 4238 * complete, the done() callback will be fired (if provided). 4239 * @param {DOMElement} element the element which will have the className value 4240 * added to it 4241 * @param {string} className the CSS class which will be added to the element 4242 * @param {Function=} done the callback function (if provided) that will be fired after the 4243 * className value has been added to the element 4244 */ 4245 addClass: function (element, className, done) { 4246 className = isString(className) ? 4247 className : 4248 isArray(className) ? className.join(' ') : ''; 4249 forEach(element, function (element) { 4250 jqLiteAddClass(element, className); 4251 }); 4252 async(done); 4253 }, 4254 4255 /** 4256 * 4257 * @ngdoc method 4258 * @name $animate#removeClass 4259 * @function 4260 * @description Removes the provided className CSS class value from the provided element. 4261 * Once complete, the done() callback will be fired (if provided). 4262 * @param {DOMElement} element the element which will have the className value 4263 * removed from it 4264 * @param {string} className the CSS class which will be removed from the element 4265 * @param {Function=} done the callback function (if provided) that will be fired after the 4266 * className value has been removed from the element 4267 */ 4268 removeClass: function (element, className, done) { 4269 className = isString(className) ? 4270 className : 4271 isArray(className) ? className.join(' ') : ''; 4272 forEach(element, function (element) { 4273 jqLiteRemoveClass(element, className); 4274 }); 4275 async(done); 4276 }, 4277 4278 /** 4279 * 4280 * @ngdoc method 4281 * @name $animate#setClass 4282 * @function 4283 * @description Adds and/or removes the given CSS classes to and from the element. 4284 * Once complete, the done() callback will be fired (if provided). 4285 * @param {DOMElement} element the element which will it's CSS classes changed 4286 * removed from it 4287 * @param {string} add the CSS classes which will be added to the element 4288 * @param {string} remove the CSS class which will be removed from the element 4289 * @param {Function=} done the callback function (if provided) that will be fired after the 4290 * CSS classes have been set on the element 4291 */ 4292 setClass: function (element, add, remove, done) {
4293 forEach(element, function (element) { 4294 jqLiteAddClass(element, add); 4295 jqLiteRemoveClass(element, remove); 4296 }); 4297 async(done); 4298 }, 4299 4300 enabled: noop 4301 }; 4302 }]; 4303 }]; 4304 4305 function $$AsyncCallbackProvider() { 4306 this.$get = ['$$rAF', '$timeout', function ($$rAF, $timeout) { 4307 return $$rAF.supported 4308 ? function (fn) { 4309 return $$rAF(fn); 4310 } 4311 : function (fn) { 4312 return $timeout(fn, 0, false); 4313 }; 4314 }]; 4315 } 4316 4317 /** 4318 * ! This is a private undocumented service ! 4319 * 4320 * @name $browser 4321 * @requires $log 4322 * @description 4323 * This object has two goals: 4324 * 4325 * - hide all the global state in the browser caused by the window object 4326 * - abstract away all the browser specific features and inconsistencies 4327 * 4328 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 4329 * service, which can be used for convenient testing of the application without the interaction with 4330 * the real browser apis. 4331 */ 4332 /** 4333 * @param {object} window The global window object. 4334 * @param {object} document jQuery wrapped document. 4335 * @param {function()} XHR XMLHttpRequest constructor. 4336 * @param {object} $log console.log or an object with the same interface. 4337 * @param {object} $sniffer $sniffer service 4338 */ 4339 function Browser(window, document, $log, $sniffer) { 4340 var self = this, 4341 rawDocument = document[0], 4342 location = window.location, 4343 history = window.history, 4344 setTimeout = window.setTimeout, 4345 clearTimeout = window.clearTimeout, 4346 pendingDeferIds = {}; 4347 4348 self.isMock = false; 4349 4350 var outstandingRequestCount = 0; 4351 var outstandingRequestCallbacks = []; 4352 4353 // TODO(vojta): remove this temporary api 4354 self.$$completeOutstandingRequest = completeOutstandingRequest; 4355 self.$$incOutstandingRequestCount = function () { 4356 outstandingRequestCount++; 4357 }; 4358 4359 /** 4360 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 4361 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 4362 */ 4363 function completeOutstandingRequest(fn) { 4364 try { 4365 fn.apply(null, sliceArgs(arguments, 1)); 4366 } finally { 4367 outstandingRequestCount--; 4368 if (outstandingRequestCount === 0) { 4369 while (outstandingRequestCallbacks.length) { 4370 try { 4371 outstandingRequestCallbacks.pop()(); 4372 } catch (e) { 4373 $log.error(e); 4374 } 4375 } 4376 } 4377 } 4378 } 4379 4380 /** 4381 * @private 4382 * Note: this method is used only by scenario runner 4383 * TODO(vojta): prefix this method with $$ ? 4384 * @param {function()} callback Function that will be called when no outstanding request 4385 */ 4386 self.notifyWhenNoOutstandingRequests = function (callback) { 4387 // force browser to execute all pollFns - this is needed so that cookies and other pollers fire 4388 // at some deterministic time in respect to the test runner's actions. Leaving things up to the 4389 // regular poller would result in flaky tests. 4390 forEach(pollFns, function (pollFn) { 4391 pollFn(); 4392 }); 4393 4394 if (outstandingRequestCount === 0) { 4395 callback(); 4396 } else { 4397 outstandingRequestCallbacks.push(callback); 4398 } 4399 }; 4400
4401 ////////////////////////////////////////////////////////////// 4402 // Poll Watcher API 4403 ////////////////////////////////////////////////////////////// 4404 var pollFns = [], 4405 pollTimeout; 4406 4407 /** 4408 * @name $browser#addPollFn 4409 * 4410 * @param {function()} fn Poll function to add 4411 * 4412 * @description 4413 * Adds a function to the list of functions that poller periodically executes, 4414 * and starts polling if not started yet. 4415 * 4416 * @returns {function()} the added function 4417 */ 4418 self.addPollFn = function (fn) { 4419 if (isUndefined(pollTimeout)) startPoller(100, setTimeout); 4420 pollFns.push(fn); 4421 return fn; 4422 }; 4423 4424 /** 4425 * @param {number} interval How often should browser call poll functions (ms) 4426 * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. 4427 * 4428 * @description 4429 * Configures the poller to run in the specified intervals, using the specified 4430 * setTimeout fn and kicks it off. 4431 */ 4432 function startPoller(interval, setTimeout) { 4433 (function check() { 4434 forEach(pollFns, function (pollFn) { 4435 pollFn(); 4436 }); 4437 pollTimeout = setTimeout(check, interval); 4438 })(); 4439 } 4440
4441 ////////////////////////////////////////////////////////////// 4442 // URL API 4443 ////////////////////////////////////////////////////////////// 4444 4445 var lastBrowserUrl = location.href, 4446 baseElement = document.find('base'), 4447 newLocation = null; 4448 4449 /** 4450 * @name $browser#url 4451 * 4452 * @description 4453 * GETTER: 4454 * Without any argument, this method just returns current value of location.href. 4455 * 4456 * SETTER: 4457 * With at least one argument, this method sets url to new value. 4458 * If html5 history api supported, pushState/replaceState is used, otherwise 4459 * location.href/location.replace is used. 4460 * Returns its own instance to allow chaining 4461 * 4462 * NOTE: this api is intended for use only by the $location service. Please use the 4463 * {@link ng.$location $location service} to change url. 4464 * 4465 * @param {string} url New url (when used as setter) 4466 * @param {boolean=} replace Should new url replace current history record ? 4467 */ 4468 self.url = function (url, replace) { 4469 // Android Browser BFCache causes location, history reference to become stale. 4470 if (location !== window.location) location = window.location; 4471 if (history !== window.history) history = window.history; 4472 4473 // setter 4474 if (url) { 4475 if (lastBrowserUrl == url) return; 4476 lastBrowserUrl = url; 4477 if ($sniffer.history) { 4478 if (replace) history.replaceState(null, '', url); 4479 else { 4480 history.pushState(null, '', url); 4481 // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 4482 baseElement.attr('href', baseElement.attr('href')); 4483 } 4484 } else { 4485 newLocation = url; 4486 if (replace) { 4487 location.replace(url); 4488 } else { 4489 location.href = url; 4490 } 4491 } 4492 return self; 4493 // getter 4494 } else { 4495 // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href 4496 // methods not updating location.href synchronously. 4497 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 4498 return newLocation || location.href.replace(/%27/g, "'"); 4499 } 4500 }; 4501 4502 var urlChangeListeners = [], 4503 urlChangeInit = false; 4504 4505 function fireUrlChange() { 4506 newLocation = null; 4507 if (lastBrowserUrl == self.url()) return; 4508 4509 lastBrowserUrl = self.url(); 4510 forEach(urlChangeListeners, function (listener) { 4511 listener(self.url()); 4512 }); 4513 } 4514 4515 /** 4516 * @name $browser#onUrlChange 4517 * 4518 * @description 4519 * Register callback function that will be called, when url changes. 4520 * 4521 * It's only called when the url is changed from outside of angular: 4522 * - user types different url into address bar 4523 * - user clicks on history (forward/back) button 4524 * - user clicks on a link 4525 * 4526 * It's not called when url is changed by $browser.url() method 4527 * 4528 * The listener gets called with new url as parameter. 4529 * 4530 * NOTE: this api is intended for use only by the $location service. Please use the 4531 * {@link ng.$location $location service} to monitor url changes in angular apps. 4532 * 4533 * @param {function(string)} listener Listener function to be called when url changes. 4534 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 4535 */ 4536 self.onUrlChange = function (callback) { 4537 // TODO(vojta): refactor to use node's syntax for events 4538 if (!urlChangeInit) { 4539 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 4540 // don't fire popstate when user change the address bar and don't fire hashchange when url 4541 // changed by push/replaceState 4542 4543 // html5 history api - popstate event 4544 if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); 4545 // hashchange event 4546 if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); 4547 // polling 4548 else self.addPollFn(fireUrlChange); 4549 4550 urlChangeInit = true; 4551 } 4552 4553 urlChangeListeners.push(callback); 4554 return callback; 4555 }; 4556
4557 ////////////////////////////////////////////////////////////// 4558 // Misc API 4559 ////////////////////////////////////////////////////////////// 4560 4561 /** 4562 * @name $browser#baseHref 4563 * 4564 * @description 4565 * Returns current <base href> 4566 * (always relative - without domain) 4567 * 4568 * @returns {string} The current base href 4569 */ 4570 self.baseHref = function () { 4571 var href = baseElement.attr('href'); 4572 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 4573 }; 4574 4575 ////////////////////////////////////////////////////////////// 4576 // Cookies API 4577 ////////////////////////////////////////////////////////////// 4578 var lastCookies = {}; 4579 var lastCookieString = ''; 4580 var cookiePath = self.baseHref(); 4581 4582 /** 4583 * @name $browser#cookies 4584 * 4585 * @param {string=} name Cookie name 4586 * @param {string=} value Cookie value 4587 * 4588 * @description 4589 * The cookies method provides a 'private' low level access to browser cookies. 4590 * It is not meant to be used directly, use the $cookie service instead. 4591 * 4592 * The return values vary depending on the arguments that the method was called with as follows: 4593 * 4594 * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify 4595 * it 4596 * - cookies(name, value) -> set name to value, if value is undefined delete the cookie 4597 * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that 4598 * way) 4599 * 4600 * @returns {Object} Hash of all cookies (if called without any parameter) 4601 */ 4602 self.cookies = function (name, value) { 4603 /* global escape: false, unescape: false */ 4604 var cookieLength, cookieArray, cookie, i, index; 4605 4606 if (name) { 4607 if (value === undefined) { 4608 rawDocument.cookie = escape(name) + "=;path=" + cookiePath + 4609 ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; 4610 } else { 4611 if (isString(value)) { 4612 cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + 4613 ';path=' + cookiePath).length + 1; 4614 4615 // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: 4616 // - 300 cookies 4617 // - 20 cookies per unique domain 4618 // - 4096 bytes per cookie 4619 if (cookieLength > 4096) { 4620 $log.warn("Cookie '" + name + 4621 "' possibly not set or overflowed because it was too large (" + 4622 cookieLength + " > 4096 bytes)!"); 4623 } 4624 } 4625 } 4626 } else { 4627 if (rawDocument.cookie !== lastCookieString) { 4628 lastCookieString = rawDocument.cookie; 4629 cookieArray = lastCookieString.split("; "); 4630 lastCookies = {}; 4631 4632 for (i = 0; i < cookieArray.length; i++) { 4633 cookie = cookieArray[i]; 4634 index = cookie.indexOf('='); 4635 if (index > 0) { //ignore nameless cookies 4636 name = unescape(cookie.substring(0, index)); 4637 // the first value that is seen for a cookie is the most 4638 // specific one. values for the same cookie name that 4639 // follow are for less specific paths. 4640 if (lastCookies[name] === undefined) { 4641 lastCookies[name] = unescape(cookie.substring(index + 1)); 4642 } 4643 } 4644 } 4645 } 4646 return lastCookies; 4647 } 4648 }; 4649 4650 4651 /** 4652 * @name $browser#defer 4653 * @param {function()} fn A function, who's execution should be deferred. 4654 * @param {number=} [delay=0] of milliseconds to defer the function execution. 4655 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 4656 *
4657 * @description 4658 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 4659 * 4660 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 4661 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 4662 * via `$browser.defer.flush()`. 4663 * 4664 */ 4665 self.defer = function (fn, delay) { 4666 var timeoutId; 4667 outstandingRequestCount++; 4668 timeoutId = setTimeout(function () { 4669 delete pendingDeferIds[timeoutId]; 4670 completeOutstandingRequest(fn); 4671 }, delay || 0); 4672 pendingDeferIds[timeoutId] = true; 4673 return timeoutId; 4674 }; 4675 4676 4677 /** 4678 * @name $browser#defer.cancel 4679 * 4680 * @description 4681 * Cancels a deferred task identified with `deferId`. 4682 * 4683 * @param {*} deferId Token returned by the `$browser.defer` function. 4684 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 4685 * canceled. 4686 */ 4687 self.defer.cancel = function (deferId) { 4688 if (pendingDeferIds[deferId]) { 4689 delete pendingDeferIds[deferId]; 4690 clearTimeout(deferId); 4691 completeOutstandingRequest(noop); 4692 return true; 4693 } 4694 return false; 4695 }; 4696 4697 } 4698 4699 function $BrowserProvider() { 4700 this.$get = ['$window', '$log', '$sniffer', '$document', 4701 function ($window, $log, $sniffer, $document) { 4702 return new Browser($window, $document, $log, $sniffer); 4703 }]; 4704 } 4705 4706 /** 4707 * @ngdoc service 4708 * @name $cacheFactory 4709 * 4710 * @description 4711 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 4712 * them. 4713 * 4714 * ```js 4715 * 4716 * var cache = $cacheFactory('cacheId'); 4717 * expect($cacheFactory.get('cacheId')).toBe(cache); 4718 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 4719 * 4720 * cache.put("key", "value"); 4721 * cache.put("another key", "another value"); 4722 * 4723 * // We've specified no options on creation 4724 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 4725 * 4726 * ``` 4727 * 4728 * 4729 * @param {string} cacheId Name or id of the newly created cache. 4730 * @param {object=} options Options object that specifies the cache behavior. Properties: 4731 * 4732 * - `{number=}` `capacity` â turns the cache into LRU cache. 4733 * 4734 * @returns {object} Newly created cache object with the following set of methods: 4735 * 4736 * - `{object}` `info()` â Returns id, size, and options of cache. 4737 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 4738 * it. 4739 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 4740 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 4741 * - `{void}` `removeAll()` â Removes all cached values. 4742 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 4743 * 4744 * @example 4745 <example module="cacheExampleApp"> 4746 <file name="index.jsp"> 4747 <div ng-controller="CacheController"> 4748 <input ng-model="newCacheKey" placeholder="Key"> 4749 <input ng-model="newCacheValue" placeholder="Value"> 4750 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 4751 4752 <p ng-if="keys.length">Cached Values</p> 4753 <div ng-repeat="key in keys"> 4754 <span ng-bind="key"></span> 4755 <span>: </span> 4756 <b ng-bind="cache.get(key)"></b> 4757 </div> 4758 4759 <p>Cache Info</p> 4760 <div ng-repeat="(key, value) in cache.info()"> 4761 <span ng-bind="key"></span> 4762 <span>: </span> 4763 <b ng-bind="value"></b> 4764 </div> 4765 </div> 4766 </file> 4767 <file name="script.js"> 4768 angular.module('cacheExampleApp', []). 4769 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 4770 $scope.keys = []; 4771 $scope.cache = $cacheFactory('cacheId'); 4772 $scope.put = function(key, value) { 4773 $scope.cache.put(key, value); 4774 $scope.keys.push(key); 4775 }; 4776 }]); 4777 </file> 4778 <file name="style.css"> 4779 p { 4780 margin: 10px 0 3px; 4781 } 4782 </file> 4783 </example> 4784 */
4785 function $CacheFactoryProvider() { 4786 4787 this.$get = function () { 4788 var caches = {}; 4789 4790 function cacheFactory(cacheId, options) { 4791 if (cacheId in caches) { 4792 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 4793 } 4794 4795 var size = 0, 4796 stats = extend({}, options, {id: cacheId}), 4797 data = {}, 4798 capacity = (options && options.capacity) || Number.MAX_VALUE, 4799 lruHash = {}, 4800 freshEnd = null, 4801 staleEnd = null; 4802 4803 /** 4804 * @ngdoc type 4805 * @name $cacheFactory.Cache 4806 * 4807 * @description 4808 * A cache object used to store and retrieve data, primarily used by 4809 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 4810 * templates and other data. 4811 * 4812 * ```js 4813 * angular.module('superCache') 4814 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 4815 * return $cacheFactory('super-cache'); 4816 * }]); 4817 * ``` 4818 * 4819 * Example test: 4820 * 4821 * ```js 4822 * it('should behave like a cache', inject(function(superCache) { 4823 * superCache.put('key', 'value'); 4824 * superCache.put('another key', 'another value'); 4825 * 4826 * expect(superCache.info()).toEqual({ 4827 * id: 'super-cache', 4828 * size: 2 4829 * }); 4830 * 4831 * superCache.remove('another key'); 4832 * expect(superCache.get('another key')).toBeUndefined(); 4833 * 4834 * superCache.removeAll(); 4835 * expect(superCache.info()).toEqual({ 4836 * id: 'super-cache', 4837 * size: 0 4838 * }); 4839 * })); 4840 * ``` 4841 */ 4842 return caches[cacheId] = { 4843 4844 /** 4845 * @ngdoc method 4846 * @name $cacheFactory.Cache#put 4847 * @function 4848 * 4849 * @description 4850 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 4851 * retrieved later, and incrementing the size of the cache if the key was not already 4852 * present in the cache. If behaving like an LRU cache, it will also remove stale 4853 * entries from the set. 4854 * 4855 * It will not insert undefined values into the cache. 4856 * 4857 * @param {string} key the key under which the cached data is stored. 4858 * @param {*} value the value to store alongside the key. If it is undefined, the key 4859 * will not be stored. 4860 * @returns {*} the value stored. 4861 */ 4862 put: function (key, value) { 4863 if (capacity < Number.MAX_VALUE) { 4864 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 4865 4866 refresh(lruEntry); 4867 } 4868 4869 if (isUndefined(value)) return; 4870 if (!(key in data)) size++; 4871 data[key] = value; 4872 4873 if (size > capacity) { 4874 this.remove(staleEnd.key); 4875 } 4876 4877 return value; 4878 }, 4879 4880 /** 4881 * @ngdoc method 4882 * @name $cacheFactory.Cache#get 4883 * @function 4884 * 4885 * @description 4886 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 4887 * 4888 * @param {string} key the key of the data to be retrieved 4889 * @returns {*} the value stored. 4890 */ 4891 get: function (key) { 4892 if (capacity < Number.MAX_VALUE) { 4893 var lruEntry = lruHash[key]; 4894 4895 if (!lruEntry) return; 4896 4897 refresh(lruEntry); 4898 } 4899 4900 return data[key]; 4901 }, 4902 4903 4904 /** 4905 * @ngdoc method 4906 * @name $cacheFactory.Cache#remove 4907 * @function 4908 *
4909 * @description 4910 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 4911 * 4912 * @param {string} key the key of the entry to be removed 4913 */ 4914 remove: function (key) { 4915 if (capacity < Number.MAX_VALUE) { 4916 var lruEntry = lruHash[key]; 4917 4918 if (!lruEntry) return; 4919 4920 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 4921 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 4922 link(lruEntry.n, lruEntry.p); 4923 4924 delete lruHash[key]; 4925 } 4926 4927 delete data[key]; 4928 size--; 4929 }, 4930 4931 4932 /** 4933 * @ngdoc method 4934 * @name $cacheFactory.Cache#removeAll 4935 * @function 4936 * 4937 * @description 4938 * Clears the cache object of any entries. 4939 */ 4940 removeAll: function () { 4941 data = {}; 4942 size = 0; 4943 lruHash = {}; 4944 freshEnd = staleEnd = null; 4945 }, 4946 4947 4948 /** 4949 * @ngdoc method 4950 * @name $cacheFactory.Cache#destroy 4951 * @function 4952 * 4953 * @description 4954 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 4955 * removing it from the {@link $cacheFactory $cacheFactory} set. 4956 */ 4957 destroy: function () { 4958 data = null; 4959 stats = null; 4960 lruHash = null; 4961 delete caches[cacheId]; 4962 }, 4963 4964 4965 /** 4966 * @ngdoc method 4967 * @name $cacheFactory.Cache#info 4968 * @function 4969 * 4970 * @description 4971 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 4972 * 4973 * @returns {object} an object with the following properties: 4974 * <ul> 4975 * <li>**id**: the id of the cache instance</li> 4976 * <li>**size**: the number of entries kept in the cache instance</li> 4977 * <li>**...**: any additional properties from the options object when creating the 4978 * cache.</li> 4979 * </ul> 4980 */ 4981 info: function () { 4982 return extend({}, stats, {size: size}); 4983 } 4984 }; 4985 4986 4987 /** 4988 * makes the `entry` the freshEnd of the LRU linked list 4989 */ 4990 function refresh(entry) { 4991 if (entry != freshEnd) { 4992 if (!staleEnd) { 4993 staleEnd = entry; 4994 } else if (staleEnd == entry) { 4995 staleEnd = entry.n; 4996 } 4997 4998 link(entry.n, entry.p); 4999 link(entry, freshEnd); 5000 freshEnd = entry; 5001 freshEnd.n = null; 5002 } 5003 } 5004 5005 5006 /** 5007 * bidirectionally links two entries of the LRU linked list 5008 */ 5009 function link(nextEntry, prevEntry) { 5010 if (nextEntry != prevEntry) { 5011 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 5012 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 5013 } 5014 } 5015 } 5016 5017 5018 /** 5019 * @ngdoc method 5020 * @name $cacheFactory#info 5021 *
5022 * @description 5023 * Get information about all the of the caches that have been created 5024 * 5025 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 5026 */ 5027 cacheFactory.info = function () { 5028 var info = {}; 5029 forEach(caches, function (cache, cacheId) { 5030 info[cacheId] = cache.info(); 5031 }); 5032 return info; 5033 }; 5034 5035 5036 /** 5037 * @ngdoc method 5038 * @name $cacheFactory#get 5039 * 5040 * @description 5041 * Get access to a cache object by the `cacheId` used when it was created. 5042 * 5043 * @param {string} cacheId Name or id of a cache to access. 5044 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 5045 */ 5046 cacheFactory.get = function (cacheId) { 5047 return caches[cacheId]; 5048 }; 5049 5050 5051 return cacheFactory; 5052 }; 5053 } 5054 5055 /** 5056 * @ngdoc service 5057 * @name $templateCache 5058 * 5059 * @description 5060 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 5061 * can load templates directly into the cache in a `script` tag, or by consuming the 5062 * `$templateCache` service directly. 5063 * 5064 * Adding via the `script` tag: 5065 * 5066 * ```html 5067 * <script type="text/ng-template" id="templateId.html"> 5068 * <p>This is the content of the template</p> 5069 * </script> 5070 * ``` 5071 * 5072 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 5073 * the document, but it must be below the `ng-app` definition. 5074 * 5075 * Adding via the $templateCache service: 5076 * 5077 * ```js 5078 * var myApp = angular.module('myApp', []); 5079 * myApp.run(function($templateCache) { 5080 * $templateCache.put('templateId.html', 'This is the content of the template'); 5081 * }); 5082 * ``` 5083 * 5084 * To retrieve the template later, simply use it in your HTML: 5085 * ```html 5086 * <div ng-include=" 'templateId.html' "></div> 5087 * ``` 5088 * 5089 * or get it via Javascript: 5090 * ```js 5091 * $templateCache.get('templateId.html') 5092 * ``` 5093 * 5094 * See {@link ng.$cacheFactory $cacheFactory}. 5095 * 5096 */ 5097 function $TemplateCacheProvider() { 5098 this.$get = ['$cacheFactory', function ($cacheFactory) { 5099 return $cacheFactory('templates'); 5100 }]; 5101 } 5102 5103 /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 5104 * 5105 * DOM-related variables: 5106 * 5107 * - "node" - DOM Node 5108 * - "element" - DOM Element or Node 5109 * - "$node" or "$element" - jqLite-wrapped node or element 5110 * 5111 * 5112 * Compiler related stuff: 5113 * 5114 * - "linkFn" - linking fn of a single directive 5115 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 5116 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 5117 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 5118 */ 5119 5120 5121 /** 5122 * @ngdoc service 5123 * @name $compile 5124 * @function 5125 * 5126 * @description 5127 * Compiles an HTML string or DOM into a template and produces a template function, which 5128 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 5129 * 5130 * The compilation is a process of walking the DOM tree and matching DOM elements to 5131 * {@link ng.$compileProvider#directive directives}. 5132 * 5133 * <div class="alert alert-warning"> 5134 * **Note:** This document is an in-depth reference of all directive options. 5135 * For a gentle introduction to directives with examples of common use cases, 5136 * see the {@link guide/directive directive guide}. 5137 * </div> 5138 * 5139 * ## Comprehensive Directive API 5140 * 5141 * There are many different options for a directive. 5142 * 5143 * The difference resides in the return value of the factory function. 5144 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 5145 * or just the `postLink` function (all other properties will have the default values). 5146 * 5147 * <div class="alert alert-success"> 5148 * **Best Practice:** It's recommended to use the "directive definition object" form. 5149 * </div> 5150 * 5151 * Here's an example directive declared with a Directive Definition Object: 5152 * 5153 * ```js 5154 * var myModule = angular.module(...); 5155 * 5156 * myModule.directive('directiveName', function factory(injectables) { 5157 * var directiveDefinitionObject = { 5158 * priority: 0, 5159 * template: '<div></div>
5159', // or // function(tElement, tAttrs) { ... }, 5160 * // or 5161 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 5162 * replace: false, 5163 * transclude: false, 5164 * restrict: 'A', 5165 * scope: false, 5166 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 5167 * controllerAs: 'stringAlias', 5168 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 5169 * compile: function compile(tElement, tAttrs, transclude) { 5170 * return { 5171 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 5172 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 5173 * } 5174 * // or 5175 * // return function postLink( ... ) { ... } 5176 * }, 5177 * // or 5178 * // link: { 5179 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 5180 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 5181 * // } 5182 * // or 5183 * // link: function postLink( ... ) { ... } 5184 * }; 5185 * return directiveDefinitionObject; 5186 * }); 5187 * ``` 5188 * 5189 * <div class="alert alert-warning"> 5190 * **Note:** Any unspecified options will use the default value. You can see the default values below. 5191 * </div> 5192 * 5193 * Therefore the above can be simplified as: 5194 * 5195 * ```js 5196 * var myModule = angular.module(...); 5197 * 5198 * myModule.directive('directiveName', function factory(injectables) { 5199 * var directiveDefinitionObject = { 5200 * link: function postLink(scope, iElement, iAttrs) { ... } 5201 * }; 5202 * return directiveDefinitionObject; 5203 * // or 5204 * // return function postLink(scope, iElement, iAttrs) { ... } 5205 * }); 5206 * ``` 5207 * 5208 * 5209 * 5210 * ### Directive Definition Object 5211 * 5212 * The directive definition object provides instructions to the {@link ng.$compile 5213 * compiler}. The attributes are: 5214 * 5215 * #### `priority` 5216 * When there are multiple directives defined on a single DOM element, sometimes it 5217 * is necessary to specify the order in which the directives are applied. The `priority` is used 5218 * to sort the directives before their `compile` functions get called. Priority is defined as a 5219 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 5220 * are also run in priority order, but post-link functions are run in reverse order. The order 5221 * of directives with the same priority is undefined. The default priority is `0`. 5222 * 5223 * #### `terminal` 5224 * If set to true then the current `priority` will be the last set of directives 5225 * which will execute (any directives at the current priority will still execute 5226 * as the order of execution on same `priority` is undefined). 5227 * 5228 * #### `scope` 5229 * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the 5230 * same element request a new scope, only one new scope is created. The new scope rule does not 5231 * apply for the root of the template since the root of the template always gets a new scope. 5232 * 5233 * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from 5234 * normal scope in that it does not prototypically inherit from the parent scope. This is useful 5235 * when creating reusable components, which should not accidentally read or modify data in the 5236 * parent scope. 5237 * 5238 * The 'isolate' scope takes an object hash which defines a set of local scope properties 5239 * derived from the parent scope. These local properties are useful for aliasing values for 5240 * templates. Locals definition is a hash of local scope property to its source: 5241 * 5242 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is 5243 * always a string since DOM attributes are strings. If no `attr` name is specified then the 5244 * attribute name is assumed to be the same as the local name. 5245 * Given `<widget my-attr="hello {{name}}">` and widget definition 5246 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 5247 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 5248 * `localName` property on the widget scope. The `name` is read from the parent scope (not 5249 * component scope). 5250 * 5251 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 5252 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 5253 * name is specified then the attribute name is assumed to be the same as the local name. 5254 * Given `<widget my-attr="parentModel">` and widget definition of 5255 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 5256 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 5257 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 5258 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 5259 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. 5260 * 5261 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 5262 * If no `attr` name is specified then the attribute name is assumed to be the same as the 5263 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 5264 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 5265 * a function wrapper for the `count = count + value` expression. Often it's desirable to 5266 * pass data from the isolated scope via an expression and to the parent scope, this can be 5267 * done by passing a map of local variable names and values into the expression wrapper fn. 5268 * For example, if the expression is `increment(amount)` then we can specify the amount value 5269 * by calling the `localFn` as `localFn({amount: 22})`. 5270 * 5271 * 5272 * 5273 * #### `controller` 5274 * Controller constructor function. The controller is instantiated before the 5275 * pre-linking phase and it is shared with other directives (see 5276 * `require` attribute). This allows the directives to communicate with each other and augment 5277 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 5278 * 5279 * * `$scope` - Current scope associated with the element 5280 * * `$element` - Current element 5281 * * `$attrs` - Current attributes object for the element 5282 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. 5283 * The scope can be overridden by an optional first argument. 5284 * `function([scope], cloneLinkingFn)`. 5285 * 5286 * 5287 * #### `require` 5288 * Require another directive and inject its controller as the fourth argument to the linking function. The
5289 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 5290 * injected argument will be an array in corresponding order. If no such directive can be 5291 * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: 5292 * 5293 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 5294 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 5295 * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found. 5296 * * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the 5297 * `link` fn if not found. 5298 * 5299 * 5300 * #### `controllerAs` 5301 * Controller alias at the directive scope. An alias for the controller so it 5302 * can be referenced at the directive template. The directive needs to define a scope for this 5303 * configuration to be used. Useful in the case when directive is used as component. 5304 * 5305 * 5306 * #### `restrict` 5307 * String of subset of `EACM` which restricts the directive to a specific directive 5308 * declaration style. If omitted, the default (attributes only) is used. 5309 * 5310 * * `E` - Element name: `<my-directive></my-directive>` 5311 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 5312 * * `C` - Class: `<div class="my-directive: exp;"></div>` 5313 * * `M` - Comment: `<!-- directive: my-directive exp -->` 5314 * 5315 * 5316 * #### `template` 5317 * replace the current element with the contents of the HTML. The replacement process 5318 * migrates all of the attributes / classes from the old element to the new one. See the 5319 * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive 5320 * Directives Guide} for an example. 5321 * 5322 * You can specify `template` as a string representing the template or as a function which takes 5323 * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and 5324 * returns a string value representing the template. 5325 * 5326 * 5327 * #### `templateUrl` 5328 * Same as `template` but the template is loaded from the specified URL. Because 5329 * the template loading is asynchronous the compilation/linking is suspended until the template 5330 * is loaded. 5331 * 5332 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 5333 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 5334 * a string value representing the url. In either case, the template URL is passed through {@link 5335 * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 5336 * 5337 * 5338 * #### `replace` 5339 * specify where the template should be inserted. Defaults to `false`. 5340 * 5341 * * `true` - the template will replace the current element. 5342 * * `false` - the template will replace the contents of the current element. 5343 * 5344 * 5345 * #### `transclude` 5346 * compile the content of the element and make it available to the directive. 5347 * Typically used with {@link ng.directive:ngTransclude 5348 * ngTransclude}. The advantage of transclusion is that the linking function receives a 5349 * transclusion function which is pre-bound to the correct scope. In a typical setup the widget 5350 * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` 5351 * scope. This makes it possible for the widget to have private state, and the transclusion to 5352 * be bound to the parent (pre-`isolate`) scope. 5353 * 5354 * * `true` - transclude the content of the directive. 5355 * * `'element'` - transclude the whole element including any directives defined at lower priority. 5356 * 5357 * 5358 * #### `compile` 5359 * 5360 * ```js 5361 * function compile(tElement, tAttrs, transclude) { ... } 5362 * ``` 5363 * 5364 * The compile function deals with transforming the template DOM. Since most directives do not do 5365 * template transformation, it is not used often. Examples that require compile functions are 5366 * directives that transform template DOM, such as {@link 5367 * api/ng.directive:ngRepeat ngRepeat}, or load the contents 5368 * asynchronously, such as {@link ngRoute.directive:ngView ngView}. The 5369 * compile function takes the following arguments. 5370 * 5371 * * `tElement` - template element - The element where the directive has been declared. It is 5372 * safe to do template transformation on the element and child elements only. 5373 * 5374 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 5375 * between all directive compile functions. 5376 * 5377 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 5378 * 5379 * <div class="alert alert-warning"> 5380 * **Note:** The template instance and the link instance may be different objects if the template has 5381 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 5382 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
5383 * should be done in a linking function rather than in a compile function. 5384 * </div> 5385 5386 * <div class="alert alert-warning"> 5387 * **Note:** The compile function cannot handle directives that recursively use themselves in their 5388 * own templates or compile functions. Compiling these directives results in an infinite loop and a 5389 * stack overflow errors. 5390 * 5391 * This can be avoided by manually using $compile in the postLink function to imperatively compile 5392 * a directive's template instead of relying on automatic template compilation via `template` or 5393 * `templateUrl` declaration or manual compilation inside the compile function. 5394 * </div> 5395 * 5396 * <div class="alert alert-error"> 5397 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 5398 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 5399 * to the link function instead. 5400 * </div> 5401 5402 * A compile function can have a return value which can be either a function or an object. 5403 * 5404 * * returning a (post-link) function - is equivalent to registering the linking function via the 5405 * `link` property of the config object when the compile function is empty. 5406 * 5407 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 5408 * control when a linking function should be called during the linking phase. See info about 5409 * pre-linking and post-linking functions below. 5410 * 5411 * 5412 * #### `link` 5413 * This property is used only if the `compile` property is not defined. 5414 * 5415 * ```js 5416 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 5417 * ``` 5418 * 5419 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 5420 * executed after the template has been cloned. This is where most of the directive logic will be 5421 * put. 5422 * 5423 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 5424 * directive for registering {@link ng.$rootScope.Scope#$watch watches}. 5425 * 5426 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 5427 * manipulate the children of the element only in `postLink` function since the children have 5428 * already been linked. 5429 * 5430 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 5431 * between all directive linking functions. 5432 * 5433 * * `controller` - a controller instance - A controller instance if at least one directive on the 5434 * element defines a controller. The controller is shared among all the directives, which allows 5435 * the directives to use the controllers as a communication channel. 5436 * 5437 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 5438 * The scope can be overridden by an optional first argument. This is the same as the `$transclude` 5439 * parameter of directive controllers. 5440 * `function([scope], cloneLinkingFn)`. 5441 * 5442 * 5443 * #### Pre-linking function 5444 * 5445 * Executed before the child elements are linked. Not safe to do DOM transformation since the 5446 * compiler linking function will fail to locate the correct elements for linking. 5447 * 5448 * #### Post-linking function 5449 * 5450 * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. 5451 * 5452 * <a name="Attributes"></a> 5453 * ### Attributes 5454 * 5455 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 5456 * `link()` or `compile()` functions. It has a variety of uses. 5457 * 5458 * accessing *Normalized attribute names:* 5459 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 5460 * the attributes object allows for normalized access to 5461 * the attributes. 5462 * 5463 * * *Directive inter-communication:* All directives share the same instance of the attributes 5464 * object which allows the directives to use the attributes object as inter directive 5465 * communication. 5466 * 5467 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 5468 * allowing other directives to read the interpolated value. 5469 * 5470 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 5471 * that contain interpolation (e.g. `src="{{bar}}
5471"`). Not only is this very efficient but it's also 5472 * the only way to easily get the actual value because during the linking phase the interpolation 5473 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 5474 * 5475 * ```js 5476 * function linkingFn(scope, elm, attrs, ctrl) { 5477 * // get the attribute value 5478 * console.log(attrs.ngModel); 5479 * 5480 * // change the attribute 5481 * attrs.$set('ngModel', 'new value'); 5482 * 5483 * // observe changes to interpolated attribute 5484 * attrs.$observe('ngModel', function(value) { 5485 * console.log('ngModel has changed value to ' + value); 5486 * }); 5487 * } 5488 * ``` 5489 * 5490 * Below is an example using `$compileProvider`. 5491 * 5492 * <div class="alert alert-warning"> 5493 * **Note**: Typically directives are registered with `module.directive`. The example below is 5494 * to illustrate how `$compile` works. 5495 * </div> 5496 * 5497 <example module="compile"> 5498 <file name="index.jsp"> 5499 <script> 5500 angular.module('compile', [], function($compileProvider) { 5501 // configure new 'compile' directive by passing a directive 5502 // factory function. The factory function injects the '$compile' 5503 $compileProvider.directive('compile', function($compile) { 5504 // directive factory creates a link function 5505 return function(scope, element, attrs) { 5506 scope.$watch( 5507 function(scope) { 5508 // watch the 'compile' expression for changes 5509 return scope.$eval(attrs.compile); 5510 }, 5511 function(value) { 5512 // when the 'compile' expression changes 5513 // assign it into the current DOM 5514 element.html(value); 5515 5516 // compile the new DOM and link it to the current 5517 // scope. 5518 // NOTE: we only compile .childNodes so that 5519 // we don't get into infinite loop compiling ourselves 5520 $compile(element.contents())(scope); 5521 } 5522 ); 5523 }; 5524 }) 5525 }); 5526 5527 function Ctrl($scope) { 5528 $scope.name = 'Angular'; 5529 $scope.html = 'Hello {{name}}'; 5530 } 5531 </script> 5532 <div ng-controller="Ctrl"> 5533 <input ng-model="name"> <br> 5534 <textarea ng-model="html"></textarea> <br> 5535 <div compile="html"></div> 5536 </div> 5537 </file> 5538 <file name="protractor.js" type="protractor"> 5539 it('should auto compile', function() { 5540 var textarea = $('textarea'); 5541 var output = $('div[compile]'); 5542 // The initial state reads 'Hello Angular'. 5543 expect(output.getText()).toBe('Hello Angular'); 5544 textarea.clear(); 5545 textarea.sendKeys('{{name}}!'); 5546 expect(output.getText()).toBe('Angular!'); 5547 }); 5548 </file> 5549 </example> 5550 5551 * 5552 * 5553 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 5554 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. 5555 * @param {number} maxPriority only apply directives lower than given priority (Only effects the 5556 * root element(s), not their children) 5557 * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template 5558 * (a DOM element/tree) to a scope. Where: 5559 * 5560 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 5561 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 5562 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 5563 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 5564 * called as: <br> `cloneAttachFn(clonedElement, scope)` where: 5565 * 5566 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 5567 * * `scope` - is the current scope with which the linking function is working with. 5568 * 5569 * Calling the linking function returns the element of the template. It is either the original 5570 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 5571 * 5572 * After linking the view is not updated until after a call to $digest which typically is done by 5573 * Angular automatically. 5574 * 5575 * If you need access to the bound view, there are two ways to do it: 5576 * 5577 * - If you are not asking the linking function to clone the template, create the DOM element(s) 5578 * before you send them to the compiler and keep this reference around. 5579 * ```js 5580 * var element = $compile('<p>{{total}}</p>')(scope); 5581 * ``` 5582 * 5583 * - if on the other hand, you need the element to be cloned, the view reference from the original 5584 * example would not point to the clone, but rather to the original template that was cloned. In 5585 * this case, you can access the clone via the cloneAttachFn: 5586 * ```js 5587 * var templateElement = angular.element('<p>{{total}}</p>'), 5588 * scope = ....; 5589 * 5590 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 5591 * //attach the clone to DOM document at the right place 5592 * }); 5593 * 5594 * //now we have reference to the cloned DOM via `clonedElement` 5595 * ``` 5596 * 5597 * 5598 * For information on how the compiler works, see the 5599 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 5600 */ 5601 5602 var $compileMinErr = minErr('$compile'); 5603 5604 /** 5605 * @ngdoc provider 5606 * @name $compileProvider 5607 * @function 5608 *
vendor: 4,137 bytes, lines 5609-5689
5609 * @description 5610 */ 5611 $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 5612 function $CompileProvider($provide, $$sanitizeUriProvider) { 5613 var hasDirectives = {}, 5614 Suffix = 'Directive', 5615 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, 5616 CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/; 5617 5618 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 5619 // The assumption is that future DOM event attribute names will begin with 5620 // 'on' and be composed of only English letters. 5621 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 5622 5623 /** 5624 * @ngdoc method 5625 * @name $compileProvider#directive 5626 * @function 5627 * 5628 * @description 5629 * Register a new directive with the compiler. 5630 * 5631 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 5632 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 5633 * names and the values are the factories. 5634 * @param {Function|Array} directiveFactory An injectable directive factory function. See 5635 * {@link guide/directive} for more info. 5636 * @returns {ng.$compileProvider} Self for chaining. 5637 */ 5638 this.directive = function registerDirective(name, directiveFactory) { 5639 assertNotHasOwnProperty(name, 'directive'); 5640 if (isString(name)) { 5641 assertArg(directiveFactory, 'directiveFactory'); 5642 if (!hasDirectives.hasOwnProperty(name)) { 5643 hasDirectives[name] = []; 5644 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 5645 function ($injector, $exceptionHandler) { 5646 var directives = []; 5647 forEach(hasDirectives[name], function (directiveFactory, index) { 5648 try { 5649 var directive = $injector.invoke(directiveFactory); 5650 if (isFunction(directive)) { 5651 directive = { compile: valueFn(directive) }; 5652 } else if (!directive.compile && directive.link) { 5653 directive.compile = valueFn(directive.link); 5654 } 5655 directive.priority = directive.priority || 0; 5656 directive.index = index; 5657 directive.name = directive.name || name; 5658 directive.require = directive.require || (directive.controller && directive.name); 5659 directive.restrict = directive.restrict || 'A'; 5660 directives.push(directive); 5661 } catch (e) { 5662 $exceptionHandler(e); 5663 } 5664 }); 5665 return directives; 5666 }]); 5667 } 5668 hasDirectives[name].push(directiveFactory); 5669 } else { 5670 forEach(name, reverseParams(registerDirective)); 5671 } 5672 return this; 5673 }; 5674 5675 5676 /** 5677 * @ngdoc method 5678 * @name $compileProvider#aHrefSanitizationWhitelist 5679 * @function 5680 * 5681 * @description 5682 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5683 * urls during a[href] sanitization. 5684 * 5685 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5686 * 5687 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 5688 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 5689 * regular expression. If a match is found, the original url is
5689written into the dom. Otherwise, 5690 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5691 * 5692 * @param {RegExp=} regexp New regexp to whitelist urls with. 5693 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5694 * chaining otherwise. 5695 */ 5696 this.aHrefSanitizationWhitelist = function (regexp) { 5697 if (isDefined(regexp)) { 5698 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 5699 return this; 5700 } else { 5701 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 5702 } 5703 }; 5704 5705 5706 /** 5707 * @ngdoc method 5708 * @name $compileProvider#imgSrcSanitizationWhitelist 5709 * @function 5710 * 5711 * @description 5712 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5713 * urls during img[src] sanitization. 5714 * 5715 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5716 * 5717 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 5718 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 5719 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 5720 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5721 * 5722 * @param {RegExp=} regexp New regexp to whitelist urls with. 5723 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5724 * chaining otherwise. 5725 */ 5726 this.imgSrcSanitizationWhitelist = function (regexp) { 5727 if (isDefined(regexp)) { 5728 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 5729 return this; 5730 } else { 5731 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 5732 } 5733 }; 5734 5735 this.$get = [ 5736 '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', 5737 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 5738 function ($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 5739 5740 var Attributes = function (element, attr) { 5741 this.$$element = element; 5742 this.$attr = attr || {}; 5743 }; 5744 5745 Attributes.prototype = { 5746 $normalize: directiveNormalize, 5747 5748 5749 /** 5750 * @ngdoc method 5751 * @name $compile.directive.Attributes#$addClass 5752 * @function 5753 * 5754 * @description 5755 * Adds the CSS class value specified by the classVal parameter to the element. If animations 5756 * are enabled then an animation will be triggered for the class addition. 5757 * 5758 * @param {string} classVal The className value that will be added to the element 5759 */ 5760 $addClass: function (classVal) { 5761 if (classVal && classVal.length > 0) { 5762 $animate.addClass(this.$$element, classVal); 5763 } 5764 }, 5765 5766 /** 5767 * @ngdoc method 5768 * @name $compile.directive.Attributes#$removeClass 5769 * @function 5770 * 5771 * @description 5772 * Removes the CSS class value specified by the classVal parameter from the element. If 5773 * animations are enabled then an animation will be triggered for the class removal. 5774 * 5775 * @param {string} classVal The className value that will be removed from the element 5776 */ 5777 $removeClass: function (classVal) { 5778 if (classVal && classVal.length > 0) { 5779 $animate.removeClass(this.$$element, classVal); 5780 } 5781 }, 5782 5783 /** 5784 * @ngdoc method 5785 * @name $compile.directive.Attributes#$updateClass 5786 * @function 5787 *
5788 * @description 5789 * Adds and removes the appropriate CSS class values to the element based on the difference 5790 * between the new and old CSS class values (specified as newClasses and oldClasses). 5791 * 5792 * @param {string} newClasses The current CSS className value 5793 * @param {string} oldClasses The former CSS className value 5794 */ 5795 $updateClass: function (newClasses, oldClasses) { 5796 var toAdd = tokenDifference(newClasses, oldClasses); 5797 var toRemove = tokenDifference(oldClasses, newClasses); 5798 5799 if (toAdd.length === 0) { 5800 $animate.removeClass(this.$$element, toRemove); 5801 } else if (toRemove.length === 0) { 5802 $animate.addClass(this.$$element, toAdd); 5803 } else { 5804 $animate.setClass(this.$$element, toAdd, toRemove); 5805 } 5806 }, 5807 5808 /** 5809 * Set a normalized attribute on the element in a way such that all directives 5810 * can share the attribute. This function properly handles boolean attributes. 5811 * @param {string} key Normalized key. (ie ngAttribute) 5812 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 5813 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 5814 * Defaults to true. 5815 * @param {string=} attrName Optional none normalized name. Defaults to key. 5816 */ 5817 $set: function (key, value, writeAttr, attrName) { 5818 // TODO: decide whether or not to throw an error if "class" 5819 //is set through this function since it may cause $updateClass to 5820 //become unstable. 5821 5822 var booleanKey = getBooleanAttrName(this.$$element[0], key), 5823 normalizedVal, 5824 nodeName; 5825 5826 if (booleanKey) { 5827 this.$$element.prop(key, value); 5828 attrName = booleanKey; 5829 } 5830 5831 this[key] = value; 5832 5833 // translate normalized key to actual key 5834 if (attrName) { 5835 this.$attr[key] = attrName; 5836 } else { 5837 attrName = this.$attr[key]; 5838 if (!attrName) { 5839 this.$attr[key] = attrName = snake_case(key, '-'); 5840 } 5841 } 5842 5843 nodeName = nodeName_(this.$$element); 5844 5845 // sanitize a[href] and img[src] values 5846 if ((nodeName === 'A' && key === 'href') || 5847 (nodeName === 'IMG' && key === 'src')) { 5848 this[key] = value = $$sanitizeUri(value, key === 'src'); 5849 } 5850 5851 if (writeAttr !== false) { 5852 if (value === null || value === undefined) { 5853 this.$$element.removeAttr(attrName); 5854 } else { 5855 this.$$element.attr(attrName, value); 5856 } 5857 } 5858 5859 // fire observers 5860 var $$observers = this.$$observers; 5861 $$observers && forEach($$observers[key], function (fn) { 5862 try { 5863 fn(value); 5864 } catch (e) { 5865 $exceptionHandler(e); 5866 } 5867 }); 5868 }, 5869 5870 5871 /** 5872 * @ngdoc method 5873 * @name $compile.directive.Attributes#$observe 5874 * @function 5875 *
5876 * @description 5877 * Observes an interpolated attribute. 5878 * 5879 * The observer function will be invoked once during the next `$digest` following 5880 * compilation. The observer is then invoked whenever the interpolated value 5881 * changes. 5882 * 5883 * @param {string} key Normalized key. (ie ngAttribute) . 5884 * @param {function(interpolatedValue)} fn Function that will be called whenever 5885 the interpolated value of the attribute changes. 5886 * See the {@link guide/directive#Attributes Directives} guide for more info. 5887 * @returns {function()} the `fn` parameter. 5888 */ 5889 $observe: function (key, fn) { 5890 var attrs = this, 5891 $$observers = (attrs.$$observers || (attrs.$$observers = {})), 5892 listeners = ($$observers[key] || ($$observers[key] = [])); 5893 5894 listeners.push(fn); 5895 $rootScope.$evalAsync(function () { 5896 if (!listeners.$$inter) { 5897 // no one registered attribute interpolation function, so lets call it manually 5898 fn(attrs[key]); 5899 } 5900 }); 5901 return fn; 5902 } 5903 }; 5904 5905 var startSymbol = $interpolate.startSymbol(), 5906 endSymbol = $interpolate.endSymbol(), 5907 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 5908 ? identity 5909 : function denormalizeTemplate(template) { 5910 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 5911 }, 5912 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 5913 5914 5915 return compile; 5916 5917 //================================ 5918 5919 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, previousCompileContext) { 5920 if (!($compileNodes instanceof jqLite)) { 5921 // jquery always rewraps, whereas we need to preserve the original selector so that we can 5922 // modify it. 5923 $compileNodes = jqLite($compileNodes); 5924 } 5925 // We can not compile top level text elements since text nodes can be merged and we will 5926 // not be able to attach scope data to them, so we will wrap them in <span> 5927 forEach($compileNodes, function (node, index) { 5928 if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */) { 5929 $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0]; 5930 } 5931 }); 5932 var compositeLinkFn = 5933 compileNodes($compileNodes, transcludeFn, $compileNodes, 5934 maxPriority, ignoreDirective, previousCompileContext); 5935 safeAddClass($compileNodes, 'ng-scope'); 5936 return function publicLinkFn(scope, cloneConnectFn, transcludeControllers) { 5937 assertArg(scope, 'scope'); 5938 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 5939 // and sometimes changes the structure of the DOM. 5940 var $linkNode = cloneConnectFn 5941 ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! 5942 : $compileNodes; 5943 5944 forEach(transcludeControllers, function (instance, name) { 5945 $linkNode.data('$' + name + 'Controller', instance); 5946 }); 5947 5948 // Attach scope only to non-text nodes. 5949 for (var i = 0, ii = $linkNode.length; i < ii; i++) { 5950 var node = $linkNode[i], 5951 nodeType = node.nodeType; 5952 if (nodeType === 1 /* element */ || nodeType === 9 /* document */) { 5953 $linkNode.eq(i).data('$scope', scope); 5954 } 5955 } 5956 5957 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 5958 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode); 5959 return $linkNode; 5960 }; 5961 } 5962 5963 function safeAddClass($element, className) { 5964 try { 5965 $element.addClass(className); 5966 } catch (e) { 5967 // ignore, since it means that we are trying to set class on 5968 // SVG element, where class name is read-only. 5969 } 5970 } 5971 5972 /** 5973 * Compile function matches each node in nodeList against the directives. Once all directives 5974 * for a particular node are collected their compile functions are executed. The compile 5975 * functions return values - the linking functions - are combined into a composite linking 5976 * function, which is the a linking function for the node. 5977 * 5978 * @param {NodeList} nodeList an array of nodes or NodeList to compile 5979 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 5980 * scope argument is auto-generated to the new child of the transcluded parent scope. 5981 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 5982 * the rootElement must be set the jqLite collection of the compile root. This is
5983 * needed so that the jqLite collection items can be replaced with widgets. 5984 * @param {number=} maxPriority Max directive priority. 5985 * @returns {Function} A composite linking function of all of the matched directives or null. 5986 */ 5987 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, previousCompileContext) { 5988 var linkFns = [], 5989 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound; 5990 5991 for (var i = 0; i < nodeList.length; i++) { 5992 attrs = new Attributes(); 5993 5994 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 5995 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 5996 ignoreDirective); 5997 5998 nodeLinkFn = (directives.length) 5999 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 6000 null, [], [], previousCompileContext) 6001 : null; 6002 6003 if (nodeLinkFn && nodeLinkFn.scope) { 6004 safeAddClass(jqLite(nodeList[i]), 'ng-scope'); 6005 } 6006 6007 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || !(childNodes = nodeList[i].childNodes) || !childNodes.length) 6008 ? null 6009 : compileNodes(childNodes, 6010 nodeLinkFn ? nodeLinkFn.transclude : transcludeFn); 6011 6012 linkFns.push(nodeLinkFn, childLinkFn); 6013 linkFnFound = linkFnFound || nodeLinkFn || childLinkFn; 6014 //use the previous context only for the first element in the virtual group 6015 previousCompileContext = null; 6016 } 6017 6018 // return a linking function if we have found anything, null otherwise 6019 return linkFnFound ? compositeLinkFn : null; 6020 6021 function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) { 6022 var nodeLinkFn, childLinkFn, node, $node, childScope, childTranscludeFn, i, ii, n; 6023 6024 // copy nodeList so that linking doesn't break due to live list updates. 6025 var nodeListLength = nodeList.length, 6026 stableNodeList = new Array(nodeListLength); 6027 for (i = 0; i < nodeListLength; i++) { 6028 stableNodeList[i] = nodeList[i]; 6029 } 6030 6031 for (i = 0, n = 0, ii = linkFns.length; i < ii; n++) { 6032 node = stableNodeList[n]; 6033 nodeLinkFn = linkFns[i++]; 6034 childLinkFn = linkFns[i++]; 6035 $node = jqLite(node); 6036 6037 if (nodeLinkFn) { 6038 if (nodeLinkFn.scope) { 6039 childScope = scope.$new(); 6040 $node.data('$scope', childScope); 6041 } else { 6042 childScope = scope; 6043 } 6044 childTranscludeFn = nodeLinkFn.transclude; 6045 if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) { 6046 nodeLinkFn(childLinkFn, childScope, node, $rootElement, 6047 createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn) 6048 ); 6049 } else { 6050 nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn); 6051 } 6052 } else if (childLinkFn) { 6053 childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn); 6054 } 6055 } 6056 } 6057 } 6058 6059 function createBoundTranscludeFn(scope, transcludeFn) { 6060 return function boundTranscludeFn(transcludedScope, cloneFn, controllers) { 6061 var scopeCreated = false;
6062 6063 if (!transcludedScope) { 6064 transcludedScope = scope.$new(); 6065 transcludedScope.$$transcluded = true; 6066 scopeCreated = true; 6067 } 6068 6069 var clone = transcludeFn(transcludedScope, cloneFn, controllers); 6070 if (scopeCreated) { 6071 clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy)); 6072 } 6073 return clone; 6074 }; 6075 } 6076 6077 /** 6078 * Looks for directives on the given node and adds them to the directive collection which is 6079 * sorted. 6080 * 6081 * @param node Node to search. 6082 * @param directives An array to which the directives are added to. This array is sorted before 6083 * the function returns. 6084 * @param attrs The shared attrs object which is used to populate the normalized attributes. 6085 * @param {number=} maxPriority Max directive priority. 6086 */ 6087 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 6088 var nodeType = node.nodeType, 6089 attrsMap = attrs.$attr, 6090 match, 6091 className; 6092 6093 switch (nodeType) { 6094 case 1: /* Element */ 6095 // use the node name: <directive> 6096 addDirective(directives, 6097 directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); 6098 6099 // iterate over the attributes 6100 for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes, 6101 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 6102 var attrStartName = false; 6103 var attrEndName = false; 6104 6105 attr = nAttrs[j]; 6106 if (!msie || msie >= 8 || attr.specified) { 6107 name = attr.name; 6108 // support ngAttr attribute binding 6109 ngAttrName = directiveNormalize(name); 6110 if (NG_ATTR_BINDING.test(ngAttrName)) { 6111 name = snake_case(ngAttrName.substr(6), '-'); 6112 } 6113 6114 var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); 6115 if (ngAttrName === directiveNName + 'Start') { 6116 attrStartName = name; 6117 attrEndName = name.substr(0, name.length - 5) + 'end'; 6118 name = name.substr(0, name.length - 6); 6119 } 6120 6121 nName = directiveNormalize(name.toLowerCase()); 6122 attrsMap[nName] = name; 6123 attrs[nName] = value = trim(attr.value); 6124 if (getBooleanAttrName(node, nName)) { 6125 attrs[nName] = true; // presence means true 6126 } 6127 addAttrInterpolateDirective(node, directives, value, nName); 6128 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 6129 attrEndName); 6130 } 6131 } 6132 6133 // use class as directive 6134 className = node.className; 6135 if (isString(className) && className !== '') { 6136 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 6137 nName = directiveNormalize(match[2]); 6138 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 6139 attrs[nName] = trim(match[3]); 6140 } 6141 className = className.substr(match.index + match[0].length); 6142 } 6143 } 6144 break; 6145 case 3: /* Text Node */ 6146 addTextInterpolateDirective(directives, node.nodeValue); 6147 break; 6148 case 8: /* Comment */ 6149 try { 6150 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 6151 if (match) { 6152 nName = directiveNormalize(match[1]); 6153 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 6154 attrs[nName] = trim(match[2]); 6155 } 6156 } 6157 } catch (e) { 6158 // turns out that under some circumstances IE9 throws errors when one attempts to read 6159 // comment's node value. 6160 // Just ignore it and continue. (Can't seem to reproduce in test case.) 6161 } 6162 break; 6163 } 6164 6165 directives.sort(byPriority); 6166 return directives; 6167 } 6168 6169 /**
6170 * Given a node with an directive-start it collects all of the siblings until it finds 6171 * directive-end. 6172 * @param node 6173 * @param attrStart 6174 * @param attrEnd 6175 * @returns {*} 6176 */ 6177 function groupScan(node, attrStart, attrEnd) { 6178 var nodes = []; 6179 var depth = 0; 6180 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 6181 var startNode = node; 6182 do { 6183 if (!node) { 6184 throw $compileMinErr('uterdir', 6185 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 6186 attrStart, attrEnd); 6187 } 6188 if (node.nodeType == 1 /** Element **/) { 6189 if (node.hasAttribute(attrStart)) depth++; 6190 if (node.hasAttribute(attrEnd)) depth--; 6191 } 6192 nodes.push(node); 6193 node = node.nextSibling; 6194 } while (depth > 0); 6195 } else { 6196 nodes.push(node); 6197 } 6198 6199 return jqLite(nodes); 6200 } 6201 6202 /** 6203 * Wrapper for linking function which converts normal linking function into a grouped 6204 * linking function. 6205 * @param linkFn 6206 * @param attrStart 6207 * @param attrEnd 6208 * @returns {Function} 6209 */ 6210 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 6211 return function (scope, element, attrs, controllers, transcludeFn) { 6212 element = groupScan(element[0], attrStart, attrEnd); 6213 return linkFn(scope, element, attrs, controllers, transcludeFn); 6214 }; 6215 } 6216 6217 /** 6218 * Once the directives have been collected, their compile functions are executed. This method 6219 * is responsible for inlining directive templates as well as terminating the application 6220 * of the directives if the terminal directive has been reached. 6221 * 6222 * @param {Array} directives Array of collected directives to execute their compile function. 6223 * this needs to be pre-sorted by priority order. 6224 * @param {Node} compileNode The raw DOM node to apply the compile functions to 6225 * @param {Object} templateAttrs The shared attribute function 6226 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 6227 * scope argument is auto-generated to the new 6228 * child of the transcluded parent scope. 6229 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 6230 * argument has the root jqLite array so that we can replace nodes 6231 * on it. 6232 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 6233 * compiling the transclusion. 6234 * @param {Array.<Function>} preLinkFns 6235 * @param {Array.<Function>} postLinkFns 6236 * @param {Object} previousCompileContext Context used for previous compilation of the current 6237 * node 6238 * @returns {Function} linkFn 6239 */ 6240 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, previousCompileContext) { 6241 previousCompileContext = previousCompileContext || {}; 6242 6243 var terminalPriority = -Number.MAX_VALUE, 6244 newScopeDirective, 6245 controllerDirectives = previousCompileContext.controllerDirectives, 6246 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 6247 templateDirective = previousCompileContext.templateDirective, 6248 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 6249 hasTranscludeDirective = false, 6250 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 6251 $compileNode = templateAttrs.$$element = jqLite(compileNode), 6252 directive, 6253 directiveName, 6254 $template, 6255 replaceDirective = originalReplaceDirective, 6256 childTranscludeFn = transcludeFn, 6257 linkFn, 6258 directiveValue; 6259 6260 // executes all directives on the current element 6261 for (var i = 0, ii = directives.length; i < ii; i++) { 6262 directive = directives[i]; 6263 var attrStart = directive.$$start; 6264 var attrEnd = directive.$$end; 6265 6266 // collect multiblock sections 6267 if (attrStart) { 6268 $compileNode = groupScan(compileNode, attrStart, attrEnd); 6269 } 6270 $template = undefined; 6271 6272 if (terminalPriority > directive.priority) { 6273 break; // prevent further processing of directives 6274 } 6275 6276 if (directiveValue = directive.scope) { 6277 newScopeDirective = newScopeDirective || directive; 6278 6279 // skip the check for directives with async templates, we'll check the derived sync 6280 // directive when the template arrives 6281 if (!directive.templateUrl) { 6282 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 6283 $compileNode); 6284 if (isObject(directiveValue)) { 6285 newIsolateScopeDirective = directive; 6286 } 6287 } 6288 } 6289 6290 directiveName = directive.name; 6291 6292 if (!directive.templateUrl && directive.controller) { 6293 directiveValue = directive.controller; 6294 controllerDirectives = controllerDirectives || {}; 6295 assertNoDuplicate("'" + directiveName + "' controller", 6296 controllerDirectives[directiveName], directive, $compileNode); 6297 controllerDirectives[directiveName] = directive; 6298 } 6299 6300 if (directiveValue = directive.transclude) { 6301 hasTranscludeDirective = true; 6302 6303 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 6304 // This option should only be used by directives that know how to safely handle element transclusion, 6305 // where the transcluded nodes are added or replaced after linking. 6306 if (!directive.$$tlb) { 6307 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 6308 nonTlbTranscludeDirective = directive; 6309 } 6310 6311 if (directiveValue == 'element') { 6312 hasElementTranscludeDirective = true; 6313 terminalPriority = directive.priority; 6314 $template = groupScan(compileNode, attrStart, attrEnd); 6315 $compileNode = templateAttrs.$$element = 6316 jqLite(document.createComment(' ' + directiveName + ': ' + 6317 templateAttrs[directiveName] + ' ')); 6318 compileNode = $compileNode[0]; 6319 replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); 6320 6321 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 6322 replaceDirective && replaceDirective.name, { 6323 // Don't pass in: 6324 // - controllerDirectives - otherwise we'll create duplicates controllers 6325 // - newIsolateScopeDirective or templateDirective - comb
6325ining templates with 6326 // element transclusion doesn't make sense. 6327 // 6328 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 6329 // on the same element more than once. 6330 nonTlbTranscludeDirective: nonTlbTranscludeDirective 6331 }); 6332 } else { 6333 $template = jqLite(jqLiteClone(compileNode)).contents(); 6334 $compileNode.empty(); // clear contents 6335 childTranscludeFn = compile($template, transcludeFn); 6336 } 6337 } 6338 6339 if (directive.template) { 6340 assertNoDuplicate('template', templateDirective, directive, $compileNode); 6341 templateDirective = directive; 6342 6343 directiveValue = (isFunction(directive.template)) 6344 ? directive.template($compileNode, templateAttrs) 6345 : directive.template; 6346 6347 directiveValue = denormalizeTemplate(directiveValue); 6348 6349 if (directive.replace) { 6350 replaceDirective = directive; 6351 if (jqLiteIsTextNode(directiveValue)) { 6352 $template = []; 6353 } else { 6354 $template = jqLite(directiveValue); 6355 } 6356 compileNode = $template[0]; 6357 6358 if ($template.length != 1 || compileNode.nodeType !== 1) { 6359 throw $compileMinErr('tplrt', 6360 "Template for directive '{0}' must have exactly one root element. {1}", 6361 directiveName, ''); 6362 } 6363 6364 replaceWith(jqCollection, $compileNode, compileNode); 6365 6366 var newTemplateAttrs = {$attr: {}}; 6367 6368 // combine directives from the original node and from the template: 6369 // - take the array of directives for this element 6370 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 6371 // - collect directives from the template and sort them by priority 6372 // - combine directives as: processed + template + unprocessed 6373 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 6374 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 6375 6376 if (newIsolateScopeDirective) { 6377 markDirectivesAsIsolate(templateDirectives); 6378 } 6379 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 6380 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 6381 6382 ii = directives.length; 6383 } else { 6384 $compileNode.html(directiveValue); 6385 } 6386 } 6387 6388 if (directive.templateUrl) { 6389 assertNoDuplicate('template', templateDirective, directive, $compileNode); 6390 templateDirective = directive; 6391 6392 if (directive.replace) { 6393 replaceDirective = directive; 6394 } 6395 6396 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 6397 templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, { 6398 controllerDirectives: controllerDirectives, 6399 newIsolateScopeDirective: newIsolateScopeDirective, 6400 templateDirective: templateDirective, 6401 nonTlbTranscludeDirective: nonTlbTranscludeDirective 6402 }); 6403 ii = directives.length; 6404 } else if (directive.compile) { 6405 try { 6406 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 6407 if (isFunction(linkFn)) { 6408 addLinkFns(null, linkFn, attrStart, attrEnd); 6409 } else if (linkFn) { 6410 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 6411 } 6412 } catch (e) { 6413 $exceptionHandler(e, startingTag($compileNode)); 6414 } 6415 } 6416 6417 if (directive.terminal) { 6418 nodeLinkFn.terminal = true; 6419 terminalPriority = Math.max(terminalPriority, directive.priority); 6420 } 6421 6422 } 6423 6424 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 6425 nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn; 6426 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 6427
6428 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 6429 return nodeLinkFn; 6430 6431 //////////////////// 6432 6433 function addLinkFns(pre, post, attrStart, attrEnd) { 6434 if (pre) { 6435 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 6436 pre.require = directive.require; 6437 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6438 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 6439 } 6440 preLinkFns.push(pre); 6441 } 6442 if (post) { 6443 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 6444 post.require = directive.require; 6445 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6446 post = cloneAndAnnotateFn(post, {isolateScope: true}); 6447 } 6448 postLinkFns.push(post); 6449 } 6450 } 6451 6452 6453 function getControllers(require, $element, elementControllers) { 6454 var value, retrievalMethod = 'data', optional = false;
6455 if (isString(require)) { 6456 while ((value = require.charAt(0)) == '^' || value == '?') { 6457 require = require.substr(1); 6458 if (value == '^') { 6459 retrievalMethod = 'inheritedData'; 6460 } 6461 optional = optional || value == '?'; 6462 } 6463 value = null; 6464 6465 if (elementControllers && retrievalMethod === 'data') { 6466 value = elementControllers[require]; 6467 } 6468 value = value || $element[retrievalMethod]('$' + require + 'Controller'); 6469 6470 if (!value && !optional) { 6471 throw $compileMinErr('ctreq', 6472 "Controller '{0}', required by directive '{1}', can't be found!", 6473 require, directiveName); 6474 } 6475 return value; 6476 } else if (isArray(require)) { 6477 value = []; 6478 forEach(require, function (require) { 6479 value.push(getControllers(require, $element, elementControllers)); 6480 }); 6481 } 6482 return value; 6483 } 6484 6485 6486 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 6487 var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; 6488 6489 if (compileNode === linkNode) { 6490 attrs = templateAttrs; 6491 } else { 6492 attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); 6493 } 6494 $element = attrs.$$element; 6495 6496 if (newIsolateScopeDirective) { 6497 var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; 6498 var $linkNode = jqLite(linkNode); 6499 6500 isolateScope = scope.$new(true); 6501 6502 if (templateDirective && (templateDirective === newIsolateScopeDirective.$$originalDirective)) { 6503 $linkNode.data('$isolateScope', isolateScope); 6504 } else { 6505 $linkNode.data('$isolateScopeNoTemplate', isolateScope); 6506 } 6507 6508 6509 safeAddClass($linkNode, 'ng-isolate-scope'); 6510 6511 forEach(newIsolateScopeDirective.scope, function (definition, scopeName) { 6512 var match = definition.match(LOCAL_REGEXP) || [], 6513 attrName = match[3] || scopeName, 6514 optional = (match[2] == '?'), 6515 mode = match[1], // @, =, or & 6516 lastValue, 6517 parentGet, parentSet, compare; 6518 6519 isolateScope.$$isolateBindings[scopeName] = mode + attrName; 6520 6521 switch (mode) { 6522 6523 case '@': 6524 attrs.$observe(attrName, function (value) { 6525 isolateScope[scopeName] = value; 6526 }); 6527 attrs.$$observers[attrName].$$scope = scope; 6528 if (attrs[attrName]) { 6529 // If the attribute has been provided then we trigger an interpolation to ensure 6530 // the value is there for use in the link fn 6531 isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); 6532 } 6533 break; 6534 6535 case '=': 6536 if (optional && !attrs[attrName]) { 6537 return; 6538 } 6539 parentGet = $parse(attrs[attrName]); 6540 if (parentGet.literal) { 6541 compare = equals; 6542 } else { 6543 compare = function (a, b) { 6544 return a === b; 6545 }; 6546 } 6547 parentSet = parentGet.assign || function () { 6548 // reset the change, or we will throw this exception on every $digest
6549 lastValue = isolateScope[scopeName] = parentGet(scope); 6550 throw $compileMinErr('nonassign', 6551 "Expression '{0}' used with directive '{1}' is non-assignable!", 6552 attrs[attrName], newIsolateScopeDirective.name); 6553 }; 6554 lastValue = isolateScope[scopeName] = parentGet(scope); 6555 isolateScope.$watch(function parentValueWatch() { 6556 var parentValue = parentGet(scope); 6557 if (!compare(parentValue, isolateScope[scopeName])) { 6558 // we are out of sync and need to copy 6559 if (!compare(parentValue, lastValue)) { 6560 // parent changed and it has precedence 6561 isolateScope[scopeName] = parentValue; 6562 } else { 6563 // if the parent can be assigned then do so 6564 parentSet(scope, parentValue = isolateScope[scopeName]); 6565 } 6566 } 6567 return lastValue = parentValue; 6568 }, null, parentGet.literal); 6569 break; 6570 6571 case '&': 6572 parentGet = $parse(attrs[attrName]); 6573 isolateScope[scopeName] = function (locals) { 6574 return parentGet(scope, locals); 6575 }; 6576 break; 6577 6578 default: 6579 throw $compileMinErr('iscp', 6580 "Invalid isolate scope definition for directive '{0}'." + 6581 " Definition: {... {1}: '{2}' ...}", 6582 newIsolateScopeDirective.name, scopeName, definition); 6583 } 6584 }); 6585 } 6586 transcludeFn = boundTranscludeFn && controllersBoundTransclude; 6587 if (controllerDirectives) { 6588 forEach(controllerDirectives, function (directive) { 6589 var locals = { 6590 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 6591 $element: $element, 6592 $attrs: attrs, 6593 $transclude: transcludeFn 6594 }, controllerInstance; 6595 6596 controller = directive.controller; 6597 if (controller == '@') { 6598 controller = attrs[directive.name]; 6599 } 6600 6601 controllerInstance = $controller(controller, locals); 6602 // For directives with element transclusion the element is a comment, 6603 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 6604 // clean up (http://bugs.jquery.com/ticket/8335). 6605 // Instead, we save the controllers for the element in a local hash and attach to .data 6606 // later, once we have the actual element. 6607 elementControllers[directive.name] = controllerInstance; 6608 if (!hasElementTranscludeDirective) { 6609 $element.data('$' + directive.name + 'Controller', controllerInstance); 6610 } 6611 6612 if (directive.controllerAs) { 6613 locals.$scope[directive.controllerAs] = controllerInstance; 6614 } 6615 }); 6616 } 6617 6618 // PRELINKING 6619 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 6620 try { 6621 linkFn = preLinkFns[i]; 6622 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6623 linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); 6624 } catch (e) { 6625 $exceptionHandler(e, startingTag($element)); 6626 } 6627 } 6628 6629 // RECURSION 6630 // We only pass the isolate scope, if the isolate directive has a template, 6631 // otherwise the child elements do not belong to the isolate directive. 6632 var scopeToChild = scope; 6633 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 6634 scopeToChild = isolateScope; 6635 } 6636 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 6637 6638 // POSTLINKING 6639 for (i = postLinkFns.length - 1; i >= 0; i--) { 6640 try { 6641 linkFn = postLinkFns[i]; 6642 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6643 linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); 6644 } catch (e) { 6645 $exceptionHandler(e, startingTag($element)); 6646 } 6647 } 6648 6649 // This is the function that is injected as `$transclude`. 6650 function controllersBoundTransclude(scope, cloneAttachFn) { 6651 var transcludeControllers; 6652 6653 // no scope passed 6654 if (arguments.length < 2) { 6655 cloneAttachFn = scope; 6656 scope = undefined; 6657 } 6658 6659 if (hasElementTranscludeDirective) { 6660 transcludeControllers = elementControllers; 6661 } 6662 6663 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); 6664 } 6665 } 6666 } 6667 6668 function markDirectivesAsIsolate(directives) { 6669 // mark all directives as needing isolate scope. 6670 for (var j = 0, jj = directives.length; j < jj; j++) { 6671 directives[j] = inherit(directives[j], {$$isolateScope: true}); 6672 } 6673 } 6674 6675 /** 6676 * looks up the directive and decorates it with exception handling and proper parameters. We 6677 * call this the boundDirective. 6678 * 6679 * @param {string} name name of the directive to look up. 6680 * @param {string} location The directive must be found in specific format. 6681 * String containing any of theses characters: 6682 * 6683 * * `E`: element name 6684 * * `A': attribute 6685 * * `C`: class 6686 * * `M`: comment 6687 * @returns {boolean} true if directive was added. 6688 */ 6689 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, endAttrName) { 6690 if (name === ignoreDirective) return null; 6691 var match = null; 6692 if (hasDirectives.hasOwnProperty(name)) { 6693 for (var directive, directives = $injector.get(name + Suffix), 6694 i = 0, ii = directives.length; i < ii; i++) { 6695 try { 6696 directive = directives[i]; 6697 if ((maxPriority === undefined || maxPriority > directive.priority) && 6698 directive.restrict.indexOf(location) != -1) { 6699 if (startAttrName) { 6700 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 6701 } 6702 tDirectives.push(directive); 6703 match = directive; 6704 } 6705 } catch (e) { 6706 $exceptionHandler(e); 6707 } 6708 } 6709 } 6710 return match; 6711 } 6712 6713 6714 /** 6715 * When the element is replaced with HTML template then the new attributes 6716 * on the template need to be merged with the existing attributes in the DOM. 6717 * The desired effect is to have both of the attributes present. 6718 * 6719 * @param {object} dst destination attributes (original DOM) 6720 * @param {object} src source attributes (from the directive template) 6721 */ 6722 function mergeTemplateAttributes(dst, src) { 6723 var srcAttr = src.$attr, 6724 dstAttr = dst.$attr, 6725 $element = dst.$$element; 6726 6727 // reapply the old attributes to the new element
6728 forEach(dst, function (value, key) { 6729 if (key.charAt(0) != '$') { 6730 if (src[key]) { 6731 value += (key === 'style' ? ';' : ' ') + src[key]; 6732 } 6733 dst.$set(key, value, true, srcAttr[key]); 6734 } 6735 }); 6736 6737 // copy the new attributes on the old attrs object 6738 forEach(src, function (value, key) { 6739 if (key == 'class') { 6740 safeAddClass($element, value); 6741 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 6742 } else if (key == 'style') { 6743 $element.attr('style', $element.attr('style') + ';' + value); 6744 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 6745 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 6746 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 6747 // have an attribute like "has-own-property" or "data-has-own-property", etc. 6748 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 6749 dst[key] = value; 6750 dstAttr[key] = srcAttr[key]; 6751 } 6752 }); 6753 } 6754 6755 6756 function compileTemplateUrl(directives, $compileNode, tAttrs, $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 6757 var linkQueue = [], 6758 afterTemplateNodeLinkFn, 6759 afterTemplateChildLinkFn, 6760 beforeTemplateCompileNode = $compileNode[0], 6761 origAsyncDirective = directives.shift(), 6762 // The fact that we have to copy and patch the directive seems wrong! 6763 derivedSyncDirective = extend({}, origAsyncDirective, { 6764 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 6765 }), 6766 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 6767 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 6768 : origAsyncDirective.templateUrl; 6769 6770 $compileNode.empty(); 6771 6772 $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). 6773 success(function (content) { 6774 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 6775 6776 content = denormalizeTemplate(content); 6777 6778 if (origAsyncDirective.replace) { 6779 if (jqLiteIsTextNode(content)) { 6780 $template = []; 6781 } else { 6782 $template = jqLite(content); 6783 } 6784 compileNode = $template[0]; 6785 6786 if ($template.length != 1 || compileNode.nodeType !== 1) { 6787 throw $compileMinErr('tplrt', 6788 "Template for directive '{0}' must have exactly one root element. {1}", 6789 origAsyncDirective.name, templateUrl); 6790 } 6791 6792 tempTemplateAttrs = {$attr: {}}; 6793 replaceWith($rootElement, $compileNode, compileNode); 6794 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 6795 6796 if (isObject(origAsyncDirective.scope)) { 6797 markDirectivesAsIsolate(templateDirectives); 6798 } 6799 directives = templateDirectives.concat(directives); 6800 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 6801 } else { 6802 compileNode = beforeTemplateCompileNode; 6803 $compileNode.html(content); 6804 } 6805 6806 directives.unshift(derivedSyncDirective); 6807 6808 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 6809 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 6810 previousCompileContext);
6811 forEach($rootElement, function (node, i) { 6812 if (node == compileNode) { 6813 $rootElement[i] = $compileNode[0]; 6814 } 6815 }); 6816 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 6817 6818 6819 while (linkQueue.length) { 6820 var scope = linkQueue.shift(), 6821 beforeTemplateLinkNode = linkQueue.shift(), 6822 linkRootElement = linkQueue.shift(), 6823 boundTranscludeFn = linkQueue.shift(), 6824 linkNode = $compileNode[0]; 6825 6826 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 6827 var oldClasses = beforeTemplateLinkNode.className; 6828 6829 if (!(previousCompileContext.hasElementTranscludeDirective && 6830 origAsyncDirective.replace)) { 6831 // it was cloned therefore we have to clone as well. 6832 linkNode = jqLiteClone(compileNode); 6833 } 6834 6835 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 6836 6837 // Copy in CSS classes from original node 6838 safeAddClass(jqLite(linkNode), oldClasses); 6839 } 6840 if (afterTemplateNodeLinkFn.transclude) { 6841 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude); 6842 } else { 6843 childBoundTranscludeFn = boundTranscludeFn; 6844 } 6845 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 6846 childBoundTranscludeFn); 6847 } 6848 linkQueue = null; 6849 }). 6850 error(function (response, code, headers, config) { 6851 throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); 6852 }); 6853 6854 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 6855 if (linkQueue) { 6856 linkQueue.push(scope); 6857 linkQueue.push(node); 6858 linkQueue.push(rootElement); 6859 linkQueue.push(boundTranscludeFn); 6860 } else { 6861 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn); 6862 } 6863 }; 6864 } 6865 6866 6867 /** 6868 * Sorting function for bound directives. 6869 */ 6870 function byPriority(a, b) { 6871 var diff = b.priority - a.priority; 6872 if (diff !== 0) return diff; 6873 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 6874 return a.index - b.index; 6875 } 6876 6877 6878 function assertNoDuplicate(what, previousDirective, directive, element) { 6879 if (previousDirective) { 6880 throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', 6881 previousDirective.name, directive.name, what, startingTag(element)); 6882 } 6883 } 6884 6885 6886 function addTextInterpolateDirective(directives, text) { 6887 var interpolateFn = $interpolate(text, true); 6888 if (interpolateFn) { 6889 directives.push({ 6890 priority: 0, 6891 compile: valueFn(function textInterpolateLinkFn(scope, node) { 6892 var parent = node.parent(), 6893 bindings = parent.data('$binding') || []; 6894 bindings.push(interpolateFn); 6895 safeAddClass(parent.data('$binding', bindings), 'ng-binding'); 6896 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 6897 node[0].nodeValue = value; 6898 }); 6899 }) 6900 }); 6901 } 6902 } 6903 6904 6905 function getTrustedContext(node, attrNormalizedName) { 6906 if (attrNormalizedName == "srcdoc") { 6907 return $sce.HTML; 6908 } 6909 var tag = nodeName_(node); 6910 // maction[xlink:href] can source SVG. It's not limited to <maction>. 6911 if (attrNormalizedName == "xlinkHref" || 6912 (tag == "FORM" && attrNormalizedName == "action") || 6913 (tag != "IMG" && (attrNormalizedName == "src" || 6914 attrNormalizedName == "ngSrc"))) { 6915 return $sce.RESOURCE_URL; 6916 } 6917 } 6918 6919 6920 function addAttrInterpolateDirective(node, directives, value, name) { 6921 var interpolateFn = $interpolate(value, true); 6922 6923 // no interpolation found -> ignore 6924 if (!interpolateFn) return; 6925 6926 6927 if (name === "multiple" && nodeName_(node) === "SELECT") { 6928 throw $compileMinErr("selmulti", 6929 "Binding to the 'multiple' attribute is not supported. Element: {0}", 6930 startingTag(node)); 6931 } 6932 6933 directives.push({ 6934 priority: 100, 6935 compile: function () { 6936 return { 6937 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 6938 var $$observers = (attr.$$observers || (attr.$$observers = {})); 6939 6940 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 6941 throw $compileMinErr('nodomevents', 6942 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 6943 "ng- versions (such as ng-click instead of onclick) instead."); 6944 } 6945 6946 // we need to interpolate again, in case the attribute value has been updated 6947 // (e.g. by another directive's compile function) 6948 interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); 6949 6950 // if attribute was updated so that there is no interpolation going on we don't want to 6951 // register any observers 6952 if (!interpolateFn) return; 6953 6954 // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the 6955 // actual attr value 6956 attr[name] = interpolateFn(scope); 6957 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 6958 (attr.$$observers && attr.$$observers[name].$$scope || scope). 6959 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 6960 //special case for class attribute addition + removal 6961 //so that class changes can tap into the animation 6962 //hooks provided by the $animate service. Be sure to 6963 //skip animations when the first digest occurs (when 6964 //both the new and the old values are the same) since 6965 //the CSS classes are the non-interpolated values 6966 if (name === 'class' && newValue != oldValue) { 6967 attr.$updateClass(newValue, oldValue); 6968 } else { 6969 attr.$set(name, newValue); 6970 } 6971 }); 6972 } 6973 }; 6974 } 6975 }); 6976 } 6977 6978 6979 /** 6980 * This is a special jqLite.replaceWith, which can replace items which 6981 * have no parents, provided that the containing jqLite collection is provided. 6982 * 6983 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 6984 * in the root of the tree. 6985 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 6986 * the shell, but replace its DOM node reference. 6987 * @param {Node} newNode The new DOM node. 6988 */ 6989 function replaceWith($rootElement, elementsToRemove, newNode) { 6990 var firstElementToRemove = elementsToRemove[0], 6991 removeCount = elementsToRemove.length, 6992 parent = firstElementToRemove.parentNode, 6993 i, ii; 6994 6995 if ($rootElement) { 6996 for (i = 0, ii = $rootElement.length; i < ii; i++) { 6997 if ($rootElement[i] == firstElementToRemove) { 6998 $rootElement[i++] = newNode; 6999 for (var j = i, j2 = j + removeCount - 1, 7000 jj = $rootElement.length; 7001 j < jj; j++, j2++) { 7002 if (j2 < jj) { 7003 $rootElement[j] = $rootElement[j2]; 7004 } else { 7005 delete $rootElement[j]; 7006 } 7007 } 7008 $rootElement.length -= removeCount - 1; 7009 break; 7010 } 7011 } 7012 } 7013 7014 if (parent) { 7015 parent.replaceChild(newNode, firstElementToRemove); 7016 } 7017 var fragment = document.createDocumentFragment(); 7018 fragment.appendChild(firstElementToRemove); 7019 newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; 7020 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 7021 var element = elementsToRemove[k]; 7022 jqLite(element).remove(); // must do this way to clean up expando 7023 fragment.appendChild(element); 7024 delete elementsToRemove[k]; 7025 } 7026 7027 elementsToRemove[0] = newNode; 7028 elementsToRemove.length = 1; 7029 } 7030 7031 7032 function cloneAndAnnotateFn(fn, annotation) { 7033 return extend(function () { 7034 return fn.apply(null, arguments); 7035 }, fn, annotation); 7036 } 7037 }]; 7038 } 7039 7040 var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; 7041 7042 /** 7043 * Converts all accepted directives format into proper directive name. 7044 * All of these will become 'myDirective': 7045 * my:Directive 7046 * my-directive 7047 * x-my-directive 7048 * data-my:directive 7049 * 7050 * Also there is special case for Moz prefix starting with upper case letter. 7051 * @param name Name to normalize 7052 */ 7053 function directiveNormalize(name) { 7054 return camelCase(name.replace(PREFIX_REGEXP, '')); 7055 } 7056 7057 /** 7058 * @ngdoc type 7059 * @name $compile.directive.Attributes 7060 *
7061 * @description 7062 * A shared object between directive compile / linking functions which contains normalized DOM 7063 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 7064 * needed since all of these are treated as equivalent in Angular: 7065 * 7066 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 7067 */ 7068 7069 /** 7070 * @ngdoc property 7071 * @name $compile.directive.Attributes#$attr 7072 * @returns {object} A map of DOM element attribute names to the normalized name. This is 7073 * needed to do reverse lookup from normalized name back to actual name. 7074 */ 7075 7076 7077 /** 7078 * @ngdoc method 7079 * @name $compile.directive.Attributes#$set 7080 * @function 7081 * 7082 * @description 7083 * Set DOM element attribute value. 7084 * 7085 * 7086 * @param {string} name Normalized element attribute name of the property to modify. The name is 7087 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 7088 * property to the original name. 7089 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 7090 */ 7091 7092 7093 /** 7094 * Closure compiler type information 7095 */ 7096 7097 function nodesetLinkingFn(/* angular.Scope */ scope, /* NodeList */ nodeList, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn) { 7098 } 7099 7100 function directiveLinkingFn(/* nodesetLinkingFn */ nodesetLinkingFn, /* angular.Scope */ scope, /* Node */ node, /* Element */ rootElement, /* function(Function) */ boundTranscludeFn) { 7101 } 7102 7103 function tokenDifference(str1, str2) { 7104 var values = '', 7105 tokens1 = str1.split(/\s+/), 7106 tokens2 = str2.split(/\s+/); 7107 7108 outer: 7109 for (var i = 0; i < tokens1.length; i++) { 7110 var token = tokens1[i]; 7111 for (var j = 0; j < tokens2.length; j++) { 7112 if (token == tokens2[j]) continue outer; 7113 } 7114 values += (values.length > 0 ? ' ' : '') + token; 7115 } 7116 return values; 7117 } 7118 7119 /** 7120 * @ngdoc provider 7121 * @name $controllerProvider 7122 * @description 7123 * The {@link ng.$controller $controller service} is used by Angular to create new 7124 * controllers. 7125 * 7126 * This provider allows controller registration via the 7127 * {@link ng.$controllerProvider#register register} method. 7128 */ 7129 function $ControllerProvider() { 7130 var controllers = {}, 7131 CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 7132 7133 7134 /** 7135 * @ngdoc method 7136 * @name $controllerProvider#register 7137 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 7138 * the names and the values are the constructors. 7139 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 7140 * annotations in the array notation). 7141 */ 7142 this.register = function (name, constructor) { 7143 assertNotHasOwnProperty(name, 'controller'); 7144 if (isObject(name)) { 7145 extend(controllers, name); 7146 } else { 7147 controllers[name] = constructor; 7148 } 7149 }; 7150 7151 7152 this.$get = ['$injector', '$window', function ($injector, $window) { 7153 7154 /** 7155 * @ngdoc service 7156 * @name $controller 7157 * @requires $injector 7158 * 7159 * @param {Function|string} constructor If called with a function then it's considered to be the 7160 * controller constructor function. Otherwise it's considered to be a string which is used 7161 * to retrieve the controller constructor using the following steps: 7162 * 7163 * * check if a controller with given name is registered via `$controllerProvider` 7164 * * check if evaluating the string on the current scope returns a constructor 7165 * * check `window[constructor]` on the global `window` object 7166 * 7167 * @param {Object} locals Injection locals for Controller. 7168 * @return {Object} Instance of given controller. 7169 *
7170 * @description 7171 * `$controller` service is responsible for instantiating controllers. 7172 * 7173 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 7174 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 7175 */ 7176 return function (expression, locals) { 7177 var instance, match, constructor, identifier; 7178 7179 if (isString(expression)) { 7180 match = expression.match(CNTRL_REG), 7181 constructor = match[1], 7182 identifier = match[3]; 7183 expression = controllers.hasOwnProperty(constructor) 7184 ? controllers[constructor] 7185 : getter(locals.$scope, constructor, true) || getter($window, constructor, true); 7186 7187 assertArgFn(expression, constructor, true); 7188 } 7189 7190 instance = $injector.instantiate(expression, locals); 7191 7192 if (identifier) { 7193 if (!(locals && typeof locals.$scope == 'object')) { 7194 throw minErr('$controller')('noscp', 7195 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 7196 constructor || expression.name, identifier); 7197 } 7198 7199 locals.$scope[identifier] = instance; 7200 } 7201 7202 return instance; 7203 }; 7204 }]; 7205 } 7206 7207 /** 7208 * @ngdoc service 7209 * @name $document 7210 * @requires $window 7211 * 7212 * @description 7213 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 7214 * 7215 * @example 7216 <example> 7217 <file name="index.jsp"> 7218 <div ng-controller="MainCtrl"> 7219 <p>$document title: <b ng-bind="title"></b></p> 7220 <p>window.document title: <b ng-bind="windowTitle"></b></p> 7221 </div> 7222 </file> 7223 <file name="script.js"> 7224 function MainCtrl($scope, $document) { 7225 $scope.title = $document[0].title; 7226 $scope.windowTitle = angular.element(window.document)[0].title; 7227 } 7228 </file> 7229 </example> 7230 */ 7231 function $DocumentProvider() { 7232 this.$get = ['$window', function (window) { 7233 return jqLite(window.document); 7234 }]; 7235 } 7236 7237 /** 7238 * @ngdoc service 7239 * @name $exceptionHandler 7240 * @requires ng.$log 7241 * 7242 * @description 7243 * Any uncaught exception in angular expressions is delegated to this service. 7244 * The default implementation simply delegates to `$log.error` which logs it into 7245 * the browser console. 7246 * 7247 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 7248 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 7249 * 7250 * ## Example: 7251 * 7252 * ```js 7253 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { 7254 * return function (exception, cause) { 7255 * exception.message += ' (caused by "' + cause + '")'; 7256 * throw exception; 7257 * }; 7258 * }); 7259 * ``` 7260 * 7261 * This example will override the normal action of `$exceptionHandler`, to make angular 7262 * exceptions fail hard when they happen, instead of just logging to the console. 7263 * 7264 * @param {Error} exception Exception associated with the error. 7265 * @param {string=} cause optional information about the context in which 7266 * the error was thrown. 7267 * 7268 */ 7269 function $ExceptionHandlerProvider() { 7270 this.$get = ['$log', function ($log) { 7271 return function (exception, cause) { 7272 $log.error.apply($log, arguments); 7273 }; 7274 }]; 7275 } 7276 7277 /** 7278 * Parse headers into key value object 7279 * 7280 * @param {string} headers Raw headers as a string 7281 * @returns {Object} Parsed headers as key value object 7282 */ 7283 function parseHeaders(headers) { 7284 var parsed = {}, key, val, i; 7285 7286 if (!headers) return parsed; 7287
7288 forEach(headers.split('\n'), function (line) { 7289 i = line.indexOf(':'); 7290 key = lowercase(trim(line.substr(0, i))); 7291 val = trim(line.substr(i + 1)); 7292 7293 if (key) { 7294 if (parsed[key]) { 7295 parsed[key] += ', ' + val; 7296 } else { 7297 parsed[key] = val; 7298 } 7299 } 7300 }); 7301 7302 return parsed; 7303 } 7304 7305 7306 /** 7307 * Returns a function that provides access to parsed headers. 7308 * 7309 * Headers are lazy parsed when first requested. 7310 * @see parseHeaders 7311 * 7312 * @param {(string|Object)} headers Headers to provide access to. 7313 * @returns {function(string=)} Returns a getter function which if called with: 7314 * 7315 * - if called with single an argument returns a single header value or null 7316 * - if called with no arguments returns an object containing all headers. 7317 */ 7318 function headersGetter(headers) { 7319 var headersObj = isObject(headers) ? headers : undefined; 7320 7321 return function (name) { 7322 if (!headersObj) headersObj = parseHeaders(headers); 7323 7324 if (name) { 7325 return headersObj[lowercase(name)] || null; 7326 } 7327 7328 return headersObj; 7329 }; 7330 } 7331 7332 7333 /** 7334 * Chain all given functions 7335 * 7336 * This function is used for both request and response transforming 7337 * 7338 * @param {*} data Data to transform. 7339 * @param {function(string=)} headers Http headers getter fn. 7340 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 7341 * @returns {*} Transformed data. 7342 */ 7343 function transformData(data, headers, fns) { 7344 if (isFunction(fns)) 7345 return fns(data, headers); 7346 7347 forEach(fns, function (fn) { 7348 data = fn(data, headers); 7349 }); 7350 7351 return data; 7352 } 7353 7354 7355 function isSuccess(status) { 7356 return 200 <= status && status < 300; 7357 } 7358 7359 7360 function $HttpProvider() { 7361 var JSON_START = /^\s*(\[|\{[^\{])/, 7362 JSON_END = /[\}\]]\s*$/, 7363 PROTECTION_PREFIX = /^\)\]\}',?\n/, 7364 CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; 7365 7366 var defaults = this.defaults = { 7367 // transform incoming response data 7368 transformResponse: [function (data) { 7369 if (isString(data)) { 7370 // strip json vulnerability protection prefix 7371 data = data.replace(PROTECTION_PREFIX, ''); 7372 if (JSON_START.test(data) && JSON_END.test(data)) 7373 data = fromJson(data); 7374 } 7375 return data; 7376 }], 7377 7378 // transform outgoing request data 7379 transformRequest: [function (d) { 7380 return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; 7381 }], 7382 7383 // default headers 7384 headers: { 7385 common: { 7386 'Accept': 'application/json, text/plain, */*' 7387 }, 7388 post: copy(CONTENT_TYPE_APPLICATION_JSON), 7389 put: copy(CONTENT_TYPE_APPLICATION_JSON), 7390 patch: copy(CONTENT_TYPE_APPLICATION_JSON) 7391 }, 7392 7393 xsrfCookieName: 'XSRF-TOKEN', 7394 xsrfHeaderName: 'X-XSRF-TOKEN' 7395 }; 7396 7397 /** 7398 * Are ordered by request, i.e. they are applied in the same order as the 7399 * array, on request, but reverse order, on response. 7400 */ 7401 var interceptorFactories = this.interceptors = []; 7402 7403 /** 7404 * For historical reasons, response interceptors are ordered by the order in which 7405 * they are applied to the response. (This is the opposite of interceptorFactories) 7406 */ 7407 var responseInterceptorFactories = this.responseInterceptors = []; 7408 7409 this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', 7410 function ($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { 7411 7412 var defaultCache = $cacheFactory('$http'); 7413 7414 /** 7415 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 7416 * The reversal is needed so that we can build up the interception chain around the 7417 * server request. 7418 */ 7419 var reversedInterceptors = []; 7420
7421 forEach(interceptorFactories, function (interceptorFactory) { 7422 reversedInterceptors.unshift(isString(interceptorFactory) 7423 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 7424 }); 7425 7426 forEach(responseInterceptorFactories, function (interceptorFactory, index) { 7427 var responseFn = isString(interceptorFactory) 7428 ? $injector.get(interceptorFactory) 7429 : $injector.invoke(interceptorFactory); 7430 7431 /** 7432 * Response interceptors go before "around" interceptors (no real reason, just 7433 * had to pick one.) But they are already reversed, so we can't use unshift, hence 7434 * the splice. 7435 */ 7436 reversedInterceptors.splice(index, 0, { 7437 response: function (response) { 7438 return responseFn($q.when(response)); 7439 }, 7440 responseError: function (response) { 7441 return responseFn($q.reject(response)); 7442 } 7443 }); 7444 }); 7445 7446 7447 /** 7448 * @ngdoc service 7449 * @kind function 7450 * @name $http 7451 * @requires ng.$httpBackend 7452 * @requires $cacheFactory 7453 * @requires $rootScope 7454 * @requires $q 7455 * @requires $injector 7456 * 7457 * @description 7458 * The `$http` service is a core Angular service that facilitates communication with the remote 7459 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 7460 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 7461 * 7462 * For unit testing applications that use `$http` service, see 7463 * {@link ngMock.$httpBackend $httpBackend mock}. 7464 * 7465 * For a higher level of abstraction, please check out the {@link ngResource.$resource 7466 * $resource} service. 7467 * 7468 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 7469 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 7470 * it is important to familiarize yourself with these APIs and the guarantees they provide. 7471 * 7472 * 7473 * # General usage 7474 * The `$http` service is a function which takes a single argument â a configuration object â 7475 * that is used to generate an HTTP request and returns a {@link ng.$q promise} 7476 * with two $http specific methods: `success` and `error`. 7477 * 7478 * ```js 7479 * $http({method: 'GET', url: '/someUrl'}). 7480 * success(function(data, status, headers, config) { 7481 * // this callback will be called asynchronously 7482 * // when the response is available 7483 * }). 7484 * error(function(data, status, headers, config) { 7485 * // called asynchronously if an error occurs 7486 * // or server returns response with an error status. 7487 * }); 7488 * ``` 7489 * 7490 * Since the returned value of calling the $http function is a `promise`, you can also use 7491 * the `then` method to register callbacks, and these callbacks will receive a single argument â 7492 * an object representing the response. See the API signature and type info below for more 7493 * details. 7494 * 7495 * A response status code between 200 and 299 is considered a success status and 7496 * will result in the success callback being called. Note that if the response is a redirect, 7497 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 7498 * called for such responses. 7499 * 7500 * # Writing Unit Tests that use $http 7501 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 7502 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 7503 * request using trained responses. 7504 * 7505 * ``` 7506 * $httpBackend.expectGET(...); 7507 * $http.get(...); 7508 * $httpBackend.flush(); 7509 * ``` 7510 * 7511 * # Shortcut methods 7512 * 7513 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 7514 * request data must be passed in for POST/PUT requests. 7515 * 7516 * ```js 7517 * $http.get('/someUrl').success(successCallback); 7518 * $http.post('/someUrl', data).success(successCallback); 7519 * ``` 7520 * 7521 * Complete list of shortcut methods: 7522 * 7523 * - {@link ng.$http#get $http.get} 7524 * - {@link ng.$http#head $http.head} 7525 * - {@link ng.$http#post $http.post} 7526 * - {@link ng.$http#put $http.put} 7527 * - {@link ng.$http#delete $http.delete} 7528 * - {@link ng.$http#jsonp $http.jsonp} 7529 * 7530 * 7531 * # Setting HTTP Headers 7532 * 7533 * The $http service will automatically add certain HTTP headers to all requests. These defaults 7534 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 7535 * object, which currently contains this default configuration: 7536 * 7537 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 7538 * - `Accept: application/json, text/plain, * / *` 7539 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 7540 * - `Content-Type: application/json` 7541 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 7542 * - `Content-Type: application/json` 7543 *
7544 * To add or overwrite these defaults, simply add or remove a property from these configuration 7545 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 7546 * with the lowercased HTTP method name as the key, e.g. 7547 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. 7548 * 7549 * The defaults can also be set at runtime via the `$http.defaults` object in the same 7550 * fashion. For example: 7551 * 7552 * ``` 7553 * module.run(function($http) { 7554 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' 7555 * }); 7556 * ``` 7557 * 7558 * In addition, you can supply a `headers` property in the config object passed when 7559 * calling `$http(config)`, which overrides the defaults without changing them globally. 7560 * 7561 * 7562 * # Transforming Requests and Responses 7563 * 7564 * Both requests and responses can be transformed using transform functions. By default, Angular 7565 * applies these transformations: 7566 * 7567 * Request transformations: 7568 * 7569 * - If the `data` property of the request configuration object contains an object, serialize it 7570 * into JSON format. 7571 * 7572 * Response transformations: 7573 * 7574 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 7575 * - If JSON response is detected, deserialize it using a JSON parser. 7576 * 7577 * To globally augment or override the default transforms, modify the 7578 * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` 7579 * properties. These properties are by default an array of transform functions, which allows you 7580 * to `push` or `unshift` a new transformation function into the transformation chain. You can 7581 * also decide to completely override any default transformations by assigning your 7582 * transformation functions to these properties directly without the array wrapper. These defaults 7583 * are again available on the $http factory at run-time, which may be useful if you have run-time 7584 * services you wish to be involved in your transformations. 7585 * 7586 * Similarly, to locally override the request/response transforms, augment the 7587 * `transformRequest` and/or `transformResponse` properties of the configuration object passed 7588 * into `$http`. 7589 * 7590 * 7591 * # Caching 7592 * 7593 * To enable caching, set the request configuration `cache` property to `true` (to use default 7594 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 7595 * When the cache is enabled, `$http` stores the response from the server in the specified 7596 * cache. The next time the same request is made, the response is served from the cache without 7597 * sending a request to the server. 7598 * 7599 * Note that even if the response is served from cache, delivery of the data is asynchronous in 7600 * the same way that real requests are. 7601 * 7602 * If there are multiple GET requests for the same URL that should be cached using the same 7603 * cache, but the cache is not populated yet, only one request to the server will be made and 7604 * the remaining requests will be fulfilled using the response from the first request. 7605 * 7606 * You can change the default cache to a new object (built with 7607 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 7608 * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set 7609 * their `cache` property to `true` will now use this cache object. 7610 * 7611 * If you set the default cache to `false` then only requests that specify their own custom 7612 * cache object will be cached. 7613 * 7614 * # Interceptors 7615 * 7616 * Before you start creating interceptors, be sure to understand the 7617 * {@link ng.$q $q and deferred/promise APIs}. 7618 * 7619 * For purposes of global error handling, authentication, or any kind of synchronous or 7620 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 7621 * able to intercept requests before they are handed to the server and 7622 * responses before they are handed over to the application code that 7623 * initiated these requests. The interceptors leverage the {@link ng.$q 7624 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 7625 * 7626 * The interceptors are service factories that are registered with the `$httpProvider` by 7627 * adding them to the `$httpProvider.interceptors` array. The factory is called and 7628 * injected with dependencies (if specified) and returns the interceptor. 7629 * 7630 * There are two kinds of interceptors (and two kinds of rejection interceptors): 7631 * 7632 * * `request`: interceptors get called with http `config` object. The function is free to 7633 * modify the `config` or create a new one. The function needs to return the `config` 7634 * directly or as a promise. 7635 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 7636 * resolved with a rejection. 7637 * * `response`: interceptors get called with http `response` object. The function is free to 7638 * modify the `response` or create a new one. The function needs to return the `response` 7639 * directly or as a promise. 7640 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 7641 * resolved with a rejection. 7642 * 7643 * 7644 * ```js 7645 * // register the interceptor as a service 7646 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7647 * return { 7648 * // optional method 7649 * 'request': function(config) {
7650 * // do something on success 7651 * return config || $q.when(config); 7652 * }, 7653 * 7654 * // optional method 7655 * 'requestError': function(rejection) { 7656 * // do something on error 7657 * if (canRecover(rejection)) { 7658 * return responseOrNewPromise 7659 * } 7660 * return $q.reject(rejection); 7661 * }, 7662 * 7663 * 7664 * 7665 * // optional method 7666 * 'response': function(response) { 7667 * // do something on success 7668 * return response || $q.when(response); 7669 * }, 7670 * 7671 * // optional method 7672 * 'responseError': function(rejection) { 7673 * // do something on error 7674 * if (canRecover(rejection)) { 7675 * return responseOrNewPromise 7676 * } 7677 * return $q.reject(rejection); 7678 * } 7679 * }; 7680 * }); 7681 * 7682 * $httpProvider.interceptors.push('myHttpInterceptor'); 7683 * 7684 * 7685 * // alternatively, register the interceptor via an anonymous factory 7686 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 7687 * return { 7688 * 'request': function(config) { 7689 * // same as above 7690 * }, 7691 * 7692 * 'response': function(response) { 7693 * // same as above 7694 * } 7695 * }; 7696 * }); 7697 * ``` 7698 * 7699 * # Response interceptors (DEPRECATED) 7700 * 7701 * Before you start creating interceptors, be sure to understand the 7702 * {@link ng.$q $q and deferred/promise APIs}. 7703 * 7704 * For purposes of global error handling, authentication or any kind of synchronous or 7705 * asynchronous preprocessing of received responses, it is desirable to be able to intercept 7706 * responses for http requests before they are handed over to the application code that 7707 * initiated these requests. The response interceptors leverage the {@link ng.$q 7708 * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. 7709 * 7710 * The interceptors are service factories that are registered with the $httpProvider by 7711 * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and 7712 * injected with dependencies (if specified) and returns the interceptor â a function that 7713 * takes a {@link ng.$q promise} and returns the original or a new promise. 7714 * 7715 * ```js 7716 * // register the interceptor as a service 7717 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7718 * return function(promise) { 7719 * return promise.then(function(response) { 7720 * // do something on success 7721 * return response; 7722 * }, function(response) { 7723 * // do something on error 7724 * if (canRecover(response)) { 7725 * return responseOrNewPromise 7726 * } 7727 * return $q.reject(response); 7728 * }); 7729 * } 7730 * }); 7731 * 7732 * $httpProvider.responseInterceptors.push('myHttpInterceptor'); 7733 * 7734 * 7735 * // register the interceptor via an anonymous factory 7736 * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) { 7737 * return function(promise) { 7738 * // same as above 7739 * } 7740 * }); 7741 * ``` 7742 * 7743 * 7744 * # Security Considerations 7745 * 7746 * When designing web applications, consider security threats from: 7747 * 7748 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 7749 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 7750 * 7751 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 7752 * pre-configured with strategies that address these issues, but for this to work backend server 7753 * cooperation is required. 7754 * 7755 * ## JSON Vulnerability Protection 7756 * 7757 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 7758 * allows third party website to turn your JSON resource URL into 7759 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 7760 * counter this your server can prefix all JSON requests with following str
7760ing `")]}',\n"`. 7761 * Angular will automatically strip the prefix before processing it as JSON. 7762 * 7763 * For example if your server needs to return: 7764 * ```js 7765 * ['one','two'] 7766 * ``` 7767 * 7768 * which is vulnerable to attack, your server can return: 7769 * ```js 7770 * )]}', 7771 * ['one','two'] 7772 * ``` 7773 * 7774 * Angular will strip the prefix, before processing the JSON. 7775 * 7776 * 7777 * ## Cross Site Request Forgery (XSRF) Protection 7778 * 7779 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which 7780 * an unauthorized site can gain your user's private data. Angular provides a mechanism 7781 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 7782 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 7783 * JavaScript that runs on your domain could read the cookie, your server can be assured that 7784 * the XHR came from JavaScript running on your domain. The header will not be set for 7785 * cross-domain requests. 7786 * 7787 * To take advantage of this, your server needs to set a token in a JavaScript readable session 7788 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 7789 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 7790 * that only JavaScript running on your domain could have sent the request. The token must be 7791 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 7792 * making up its own tokens). We recommend that the token is a digest of your site's 7793 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 7794 * for added security. 7795 * 7796 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 7797 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 7798 * or the per-request config object. 7799 * 7800 * 7801 * @param {object} config Object describing the request to be made and how it should be 7802 * processed. The object has following properties: 7803 * 7804 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 7805 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 7806 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be turned 7807 * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be 7808 * JSONified. 7809 * - **data** â `{string|Object}` â Data to be sent as the request message data. 7810 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 7811 * HTTP headers to send to the server. If the return value of a function is null, the 7812 * header will not be sent. 7813 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 7814 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 7815 * - **transformRequest** â 7816 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 7817 * transform function or an array of such functions. The transform function takes the http 7818 * request body and headers and returns its transformed (typically serialized) version. 7819 * - **transformResponse** â 7820 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>
7820}` â 7821 * transform function or an array of such functions. The transform function takes the http 7822 * response body and headers and returns its transformed (typically deserialized) version. 7823 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 7824 * GET request, otherwise if a cache instance built with 7825 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 7826 * caching. 7827 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 7828 * that should abort the request when resolved. 7829 * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the 7830 * XHR object. See [requests with credentials]https://developer.mozilla.org/en/http_access_control#section_5 7831 * for more information. 7832 * - **responseType** - `{string}` - see 7833 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). 7834 * 7835 * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the 7836 * standard `then` method and two http specific methods: `success` and `error`. The `then` 7837 * method takes two arguments a success and an error callback which will be called with a 7838 * response object. The `success` and `error` methods take a single argument - a function that 7839 * will be called when the request succeeds or fails respectively. The arguments passed into 7840 * these functions are destructured representation of the response object passed into the 7841 * `then` method. The response object has these properties: 7842 * 7843 * - **data** â `{string|Object}` â The response body transformed with the transform 7844 * functions. 7845 * - **status** â `{number}` â HTTP status code of the response. 7846 * - **headers** â `{function([headerName])}` â Header getter function. 7847 * - **config** â `{Object}` â The configuration object that was used to generate the request. 7848 * - **statusText** â `{string}` â HTTP status text of the response. 7849 * 7850 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 7851 * requests. This is primarily meant to be used for debugging purposes. 7852 * 7853 * 7854 * @example 7855 <example> 7856 <file name="index.jsp"> 7857 <div ng-controller="FetchCtrl"> 7858 <select ng-model="method"> 7859 <option>GET</option> 7860 <option>JSONP</option> 7861 </select> 7862 <input type="text" ng-model="url" size="80"/> 7863 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 7864 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 7865 <button id="samplejsonpbtn" 7866 ng-click="updateModel('JSONP', 7867 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 7868 Sample JSONP 7869 </button> 7870 <button id="invalidjsonpbtn" 7871 ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 7872 Invalid JSONP 7873 </button> 7874 <pre>http status code: {{status}}</pre> 7875 <pre>http response data: {{data}}</pre> 7876 </div> 7877 </file> 7878 <file name="script.js"> 7879 function FetchCtrl($scope, $http, $templateCache) { 7880 $scope.method = 'GET'; 7881 $scope.url = 'http-hello.html'; 7882 7883 $scope.fetch = function() { 7884 $scope.code = null; 7885 $scope.response = null; 7886 7887 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 7888 success(function(data, status) { 7889 $scope.status = status; 7890 $scope.data = data; 7891 }). 7892 error(function(data, status) { 7893 $scope.data = data || "Request failed"; 7894 $scope.status = status; 7895 }); 7896 }; 7897 7898 $scope.updateModel = function(method, url) { 7899 $scope.method = method; 7900 $scope.url = url; 7901 }; 7902 } 7903 </file> 7904 <file name="http-hello.html"> 7905 Hello, $http! 7906 </file> 7907 <file name="protractor.js" type="protractor"> 7908 var status = element(by.binding('status')); 7909 var data = element(by.binding('data')); 7910 var fetchBtn = element(by.id('fetchbtn')); 7911 var sampleGetBtn = element(by.id('samplegetbtn')); 7912 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 7913 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 7914 7915 it('should make an xhr GET request', function() { 7916 sampleGetBtn.click(); 7917 fetchBtn.click(); 7918 expect(status.getText()).toMatch('200'); 7919 expect(data.getText()).toMatch(/Hello, \$http!/); 7920 }); 7921 7922 it('should make a JSONP request to angularjs.org', function() { 7923 sampleJsonpBtn.click(); 7924 fetchBtn.click(); 7925 expect(status.getText()).toMatch('200'); 7926 expect(data.getText()).toMatch(/Super Hero!/); 7927 }); 7928
7929 it('should make JSONP request to invalid URL and invoke the error handler', 7930 function() { 7931 invalidJsonpBtn.click(); 7932 fetchBtn.click(); 7933 expect(status.getText()).toMatch('0'); 7934 expect(data.getText()).toMatch('Request failed'); 7935 }); 7936 </file> 7937 </example> 7938 */ 7939 function $http(requestConfig) { 7940 var config = { 7941 method: 'get', 7942 transformRequest: defaults.transformRequest, 7943 transformResponse: defaults.transformResponse 7944 }; 7945 var headers = mergeHeaders(requestConfig); 7946 7947 extend(config, requestConfig); 7948 config.headers = headers; 7949 config.method = uppercase(config.method); 7950 7951 var xsrfValue = urlIsSameOrigin(config.url) 7952 ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] 7953 : undefined; 7954 if (xsrfValue) { 7955 headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 7956 } 7957 7958 7959 var serverRequest = function (config) { 7960 headers = config.headers; 7961 var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); 7962 7963 // strip content-type if data is undefined 7964 if (isUndefined(config.data)) { 7965 forEach(headers, function (value, header) { 7966 if (lowercase(header) === 'content-type') { 7967 delete headers[header]; 7968 } 7969 }); 7970 } 7971 7972 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 7973 config.withCredentials = defaults.withCredentials; 7974 } 7975 7976 // send request 7977 return sendReq(config, reqData, headers).then(transformResponse, transformResponse); 7978 }; 7979 7980 var chain = [serverRequest, undefined]; 7981 var promise = $q.when(config); 7982 7983 // apply interceptors 7984 forEach(reversedInterceptors, function (interceptor) { 7985 if (interceptor.request || interceptor.requestError) { 7986 chain.unshift(interceptor.request, interceptor.requestError); 7987 } 7988 if (interceptor.response || interceptor.responseError) { 7989 chain.push(interceptor.response, interceptor.responseError); 7990 } 7991 }); 7992 7993 while (chain.length) { 7994 var thenFn = chain.shift(); 7995 var rejectFn = chain.shift(); 7996 7997 promise = promise.then(thenFn, rejectFn); 7998 } 7999 8000 promise.success = function (fn) { 8001 promise.then(function (response) { 8002 fn(response.data, response.status, response.headers, config); 8003 }); 8004 return promise; 8005 }; 8006 8007 promise.error = function (fn) { 8008 promise.then(null, function (response) { 8009 fn(response.data, response.status, response.headers, config); 8010 }); 8011 return promise; 8012 }; 8013 8014 return promise; 8015 8016 function transformResponse(response) { 8017 // make a copy since the response must be cacheable 8018 var resp = extend({}, response, { 8019 data: transformData(response.data, response.headers, config.transformResponse) 8020 }); 8021 return (isSuccess(response.status)) 8022 ? resp 8023 : $q.reject(resp); 8024 } 8025 8026 function mergeHeaders(config) { 8027 var defHeaders = defaults.headers, 8028 reqHeaders = extend({}, config.headers), 8029 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 8030 8031 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 8032 8033 // execute if header value is function 8034 execHeaders(defHeaders); 8035 execHeaders(reqHeaders); 8036
8037 // using for-in instead of forEach to avoid unecessary iteration after header has been found 8038 defaultHeadersIteration: 8039 for (defHeaderName in defHeaders) { 8040 lowercaseDefHeaderName = lowercase(defHeaderName); 8041 8042 for (reqHeaderName in reqHeaders) { 8043 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 8044 continue defaultHeadersIteration; 8045 } 8046 } 8047 8048 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 8049 } 8050 8051 return reqHeaders; 8052 8053 function execHeaders(headers) { 8054 var headerContent; 8055 8056 forEach(headers, function (headerFn, header) { 8057 if (isFunction(headerFn)) { 8058 headerContent = headerFn(); 8059 if (headerContent != null) { 8060 headers[header] = headerContent; 8061 } else { 8062 delete headers[header]; 8063 } 8064 } 8065 }); 8066 } 8067 } 8068 } 8069 8070 $http.pendingRequests = []; 8071 8072 /** 8073 * @ngdoc method 8074 * @name $http#get 8075 * 8076 * @description 8077 * Shortcut method to perform `GET` request. 8078 * 8079 * @param {string} url Relative or absolute URL specifying the destination of the request 8080 * @param {Object=} config Optional configuration object 8081 * @returns {HttpPromise} Future object 8082 */ 8083 8084 /** 8085 * @ngdoc method 8086 * @name $http#delete 8087 * 8088 * @description 8089 * Shortcut method to perform `DELETE` request. 8090 * 8091 * @param {string} url Relative or absolute URL specifying the destination of the request 8092 * @param {Object=} config Optional configuration object 8093 * @returns {HttpPromise} Future object 8094 */ 8095 8096 /** 8097 * @ngdoc method 8098 * @name $http#head 8099 * 8100 * @description 8101 * Shortcut method to perform `HEAD` request. 8102 * 8103 * @param {string} url Relative or absolute URL specifying the destination of the request 8104 * @param {Object=} config Optional configuration object 8105 * @returns {HttpPromise} Future object 8106 */ 8107 8108 /** 8109 * @ngdoc method 8110 * @name $http#jsonp 8111 * 8112 * @description 8113 * Shortcut method to perform `JSONP` request. 8114 * 8115 * @param {string} url Relative or absolute URL specifying the destination of the request. 8116 * Should contain `JSON_CALLBACK` string. 8117 * @param {Object=} config Optional configuration object 8118 * @returns {HttpPromise} Future object 8119 */ 8120 createShortMethods('get', 'delete', 'head', 'jsonp'); 8121 8122 /** 8123 * @ngdoc method 8124 * @name $http#post 8125 * 8126 * @description 8127 * Shortcut method to perform `POST` request. 8128 * 8129 * @param {string} url Relative or absolute URL specifying the destination of the request 8130 * @param {*} data Request content 8131 * @param {Object=} config Optional configuration object 8132 * @returns {HttpPromise} Future object 8133 */ 8134 8135 /** 8136 * @ngdoc method 8137 * @name $http#put 8138 *
8139 * @description 8140 * Shortcut method to perform `PUT` request. 8141 * 8142 * @param {string} url Relative or absolute URL specifying the destination of the request 8143 * @param {*} data Request content 8144 * @param {Object=} config Optional configuration object 8145 * @returns {HttpPromise} Future object 8146 */ 8147 createShortMethodsWithData('post', 'put'); 8148 8149 /** 8150 * @ngdoc property 8151 * @name $http#defaults 8152 * 8153 * @description 8154 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 8155 * default headers, withCredentials as well as request and response transformations. 8156 * 8157 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 8158 */ 8159 $http.defaults = defaults; 8160 8161 8162 return $http; 8163 8164 8165 function createShortMethods(names) { 8166 forEach(arguments, function (name) { 8167 $http[name] = function (url, config) { 8168 return $http(extend(config || {}, { 8169 method: name, 8170 url: url 8171 })); 8172 }; 8173 }); 8174 } 8175 8176 8177 function createShortMethodsWithData(name) { 8178 forEach(arguments, function (name) { 8179 $http[name] = function (url, data, config) { 8180 return $http(extend(config || {}, { 8181 method: name, 8182 url: url, 8183 data: data 8184 })); 8185 }; 8186 }); 8187 } 8188 8189 8190 /** 8191 * Makes the request. 8192 * 8193 * !!! ACCESSES CLOSURE VARS: 8194 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 8195 */ 8196 function sendReq(config, reqData, reqHeaders) { 8197 var deferred = $q.defer(), 8198 promise = deferred.promise, 8199 cache, 8200 cachedResp, 8201 url = buildUrl(config.url, config.params); 8202 8203 $http.pendingRequests.push(config); 8204 promise.then(removePendingReq, removePendingReq); 8205 8206 8207 if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') { 8208 cache = isObject(config.cache) ? config.cache 8209 : isObject(defaults.cache) ? defaults.cache 8210 : defaultCache; 8211 } 8212 8213 if (cache) { 8214 cachedResp = cache.get(url); 8215 if (isDefined(cachedResp)) { 8216 if (cachedResp.then) { 8217 // cached request has already been sent, but there is no response yet 8218 cachedResp.then(removePendingReq, removePendingReq); 8219 return cachedResp; 8220 } else { 8221 // serving from cache 8222 if (isArray(cachedResp)) { 8223 resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]), cachedResp[3]); 8224 } else { 8225 resolvePromise(cachedResp, 200, {}, 'OK'); 8226 } 8227 } 8228 } else { 8229 // put the promise for the non-transformed response into cache as a placeholder 8230 cache.put(url, promise); 8231 } 8232 } 8233 8234 // if we won't have the response in cache, send the request to the backend 8235 if (isUndefined(cachedResp)) { 8236 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 8237 config.withCredentials, config.responseType); 8238 } 8239 8240 return promise; 8241 8242 8243 /** 8244 * Callback registered to $httpBackend(): 8245 * - caches the response if desired 8246 * - resolves the raw $http promise 8247 * - calls $apply 8248 */ 8249 function done(status, response, headersString, statusText) { 8250 if (cache) { 8251 if (isSuccess(status)) { 8252 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 8253 } else { 8254 // remove promise from the cache 8255 cache.remove(url); 8256 } 8257 } 8258 8259 resolvePromise(response, status, headersString, statusText); 8260 if (!$rootScope.$$phase) $rootScope.$apply(); 8261 } 8262 8263 8264 /** 8265 * Resolves the raw $http promise. 8266 */ 8267 function resolvePromise(response, status, headers, statusText) { 8268 // normalize internal statuses to 0 8269 status = Math.max(status, 0); 8270 8271 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 8272 data: response, 8273 status: status, 8274 headers: headersGetter(headers), 8275 config: config, 8276 statusText: statusText 8277 }); 8278 } 8279 8280 8281 function removePendingReq() { 8282 var idx = indexOf($http.pendingRequests, config); 8283 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 8284 } 8285 } 8286 8287 8288 function buildUrl(url, params) { 8289 if (!params) return url; 8290 var parts = [];
8291 forEachSorted(params, function (value, key) { 8292 if (value === null || isUndefined(value)) return; 8293 if (!isArray(value)) value = [value]; 8294 8295 forEach(value, function (v) { 8296 if (isObject(v)) { 8297 v = toJson(v); 8298 } 8299 parts.push(encodeUriQuery(key) + '=' + 8300 encodeUriQuery(v)); 8301 }); 8302 }); 8303 if (parts.length > 0) { 8304 url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); 8305 } 8306 return url; 8307 } 8308 8309 8310 }]; 8311 } 8312 8313 function createXhr(method) { 8314 //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest 8315 //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest 8316 //if it is available 8317 if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) || !window.XMLHttpRequest)) { 8318 return new window.ActiveXObject("Microsoft.XMLHTTP"); 8319 } else if (window.XMLHttpRequest) { 8320 return new window.XMLHttpRequest(); 8321 } 8322 8323 throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); 8324 } 8325 8326 /** 8327 * @ngdoc service 8328 * @name $httpBackend 8329 * @requires $window 8330 * @requires $document 8331 * 8332 * @description 8333 * HTTP backend used by the {@link ng.$http service} that delegates to 8334 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 8335 * 8336 * You should never need to use this service directly, instead use the higher-level abstractions: 8337 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 8338 * 8339 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 8340 * $httpBackend} which can be trained with responses. 8341 */ 8342 function $HttpBackendProvider() { 8343 this.$get = ['$browser', '$window', '$document', function ($browser, $window, $document) { 8344 return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); 8345 }]; 8346 } 8347 8348 function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 8349 var ABORTED = -1; 8350 8351 // TODO(vojta): fix the signature 8352 return function (method, url, post, callback, headers, timeout, withCredentials, responseType) { 8353 var status; 8354 $browser.$$incOutstandingRequestCount(); 8355 url = url || $browser.url(); 8356 8357 if (lowercase(method) == 'jsonp') { 8358 var callbackId = '_' + (callbacks.counter++).toString(36); 8359 callbacks[callbackId] = function (data) { 8360 callbacks[callbackId].data = data; 8361 }; 8362 8363 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 8364 function () { 8365 if (callbacks[callbackId].data) { 8366 completeRequest(callback, 200, callbacks[callbackId].data); 8367 } else { 8368 completeRequest(callback, status || -2); 8369 } 8370 callbacks[callbackId] = angular.noop; 8371 }); 8372 } else { 8373 8374 var xhr = createXhr(method); 8375 8376 xhr.open(method, url, true); 8377 forEach(headers, function (value, key) { 8378 if (isDefined(value)) { 8379 xhr.setRequestHeader(key, value); 8380 } 8381 }); 8382 8383 // In IE6 and 7, this might be called synchronously when xhr.send below is called and the 8384 // response is in the cache. the promise api will ensure that to the app code the api is 8385 // always async 8386 xhr.onreadystatechange = function () { 8387 // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by 8388 // xhrs that are resolved while the app is in the background (see #5426). 8389 // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before 8390 // continuing 8391 // 8392 // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and 8393 // Safari respectively. 8394 if (xhr && xhr.readyState == 4) { 8395 var responseHeaders = null, 8396 response = null; 8397 8398 if (status !== ABORTED) { 8399 responseHeaders = xhr.getAllResponseHeaders(); 8400 8401 // responseText is the old-school way of retrieving response
8401(supported by IE8 & 9) 8402 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 8403 response = ('response' in xhr) ? xhr.response : xhr.responseText; 8404 } 8405 8406 completeRequest(callback, 8407 status || xhr.status, 8408 response, 8409 responseHeaders, 8410 xhr.statusText || ''); 8411 } 8412 }; 8413 8414 if (withCredentials) { 8415 xhr.withCredentials = true; 8416 } 8417 8418 if (responseType) { 8419 try { 8420 xhr.responseType = responseType; 8421 } catch (e) { 8422 // WebKit added support for the json responseType value on 09/03/2013 8423 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 8424 // known to throw when setting the value "json" as the response type. Other older 8425 // browsers implementing the responseType 8426 // 8427 // The json response type can be ignored if not supported, because JSON payloads are 8428 // parsed on the client-side regardless. 8429 if (responseType !== 'json') { 8430 throw e; 8431 } 8432 } 8433 } 8434 8435 xhr.send(post || null); 8436 } 8437 8438 if (timeout > 0) { 8439 var timeoutId = $browserDefer(timeoutRequest, timeout); 8440 } else if (timeout && timeout.then) { 8441 timeout.then(timeoutRequest); 8442 } 8443 8444 8445 function timeoutRequest() { 8446 status = ABORTED; 8447 jsonpDone && jsonpDone(); 8448 xhr && xhr.abort(); 8449 } 8450 8451 function completeRequest(callback, status, response, headersString, statusText) { 8452 // cancel timeout and subsequent timeout promise resolution 8453 timeoutId && $browserDefer.cancel(timeoutId); 8454 jsonpDone = xhr = null; 8455 8456 // fix status code when it is 0 (0 status is undocumented). 8457 // Occurs when accessing file resources or on Android 4.1 stock browser 8458 // while retrieving files from application cache. 8459 if (status === 0) { 8460 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 8461 } 8462 8463 // normalize IE bug (http://bugs.jquery.com/ticket/1450) 8464 status = status === 1223 ? 204 : status; 8465 statusText = statusText || ''; 8466 8467 callback(status, response, headersString, statusText); 8468 $browser.$$completeOutstandingRequest(noop); 8469 } 8470 }; 8471 8472 function jsonpReq(url, done) { 8473 // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: 8474 // - fetches local scripts via XHR and evals them 8475 // - adds and immediately removes script elements from the document 8476 var script = rawDocument.createElement('script'), 8477 doneWrapper = function () { 8478 script.onreadystatechange = script.onload = script.onerror = null; 8479 rawDocument.body.removeChild(script); 8480 if (done) done(); 8481 }; 8482 8483 script.type = 'text/javascript'; 8484 script.src = url; 8485 8486 if (msie && msie <= 8) { 8487 script.onreadystatechange = function () { 8488 if (/loaded|complete/.test(script.readyState)) { 8489 doneWrapper(); 8490 } 8491 }; 8492 } else { 8493 script.onload = script.onerror = function () { 8494 doneWrapper(); 8495 }; 8496 } 8497 8498 rawDocument.body.appendChild(script); 8499 return doneWrapper; 8500 } 8501 } 8502 8503 var $interpolateMinErr = minErr('$interpolate'); 8504 8505 /** 8506 * @ngdoc provider 8507 * @name $interpolateProvider 8508 * @function 8509 *
8510 * @description 8511 * 8512 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 8513 * 8514 * @example 8515 <example module="customInterpolationApp"> 8516 <file name="index.jsp"> 8517 <script> 8518 var customInterpolationApp = angular.module('customInterpolationApp', []); 8519 8520 customInterpolationApp.config(function($interpolateProvider) { 8521 $interpolateProvider.startSymbol('//'); 8522 $interpolateProvider.endSymbol('//'); 8523 }); 8524 8525 8526 customInterpolationApp.controller('DemoController', function DemoController() { 8527 this.label = "This binding is brought you by // interpolation symbols."; 8528 }); 8529 </script> 8530 <div ng-app="App" ng-controller="DemoController as demo"> 8531 //demo.label// 8532 </div> 8533 </file> 8534 <file name="protractor.js" type="protractor"> 8535 it('should interpolate binding with custom symbols', function() { 8536 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 8537 }); 8538 </file> 8539 </example> 8540 */ 8541 function $InterpolateProvider() { 8542 var startSymbol = '{{'; 8543 var endSymbol = '}}'; 8544 8545 /** 8546 * @ngdoc method 8547 * @name $interpolateProvider#startSymbol 8548 * @description 8549 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 8550 * 8551 * @param {string=} value new value to set the starting symbol to. 8552 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8553 */ 8554 this.startSymbol = function (value) { 8555 if (value) { 8556 startSymbol = value; 8557 return this; 8558 } else { 8559 return startSymbol; 8560 } 8561 }; 8562 8563 /** 8564 * @ngdoc method 8565 * @name $interpolateProvider#endSymbol 8566 * @description 8567 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8568 * 8569 * @param {string=} value new value to set the ending symbol to. 8570 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8571 */ 8572 this.endSymbol = function (value) { 8573 if (value) { 8574 endSymbol = value; 8575 return this; 8576 } else { 8577 return endSymbol; 8578 } 8579 }; 8580 8581 8582 this.$get = ['$parse', '$exceptionHandler', '$sce', function ($parse, $exceptionHandler, $sce) { 8583 var startSymbolLength = startSymbol.length, 8584 endSymbolLength = endSymbol.length; 8585 8586 /** 8587 * @ngdoc service 8588 * @name $interpolate 8589 * @function 8590 * 8591 * @requires $parse 8592 * @requires $sce 8593 * 8594 * @description 8595 * 8596 * Compiles a string with markup into an interpolation function. This service is used by the 8597 * HTML {@link ng.$compile $compile} service for data binding. See 8598 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 8599 * interpolation markup. 8600 * 8601 * 8602 * ```js 8603 * var $interpolate = ...; // injected 8604 * var exp = $interpolate('Hello {{name | uppercase}}!'); 8605 * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); 8606 * ``` 8607 * 8608 * 8609 * @param {string} text The text with markup to interpolate. 8610 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 8611 * embedded expression in order to return an interpolation function. Strings with no 8612 * embedded expression will return null for the interpolation function. 8613 * @param {string=} trustedContext when provided, the returned function passes the interpolated 8614 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 8615 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 8616 * provides Strict Contextual Escaping for details. 8617 * @returns {function(context)} an interpolation function which is used to compute the 8618 * interpolated string. The function has these parameters: 8619 * 8620 * * `context`: an object against which any expressions embedded in the strings are evaluated 8621 * against. 8622 * 8623 */ 8624 function $interpolate(text, mustHaveExpression, trustedContext) { 8625 var startIndex, 8626 endIndex, 8627 index = 0, 8628 parts = [], 8629 length = text.length, 8630 hasInterpolation = false, 8631 fn, 8632 exp, 8633 concat = []; 8634 8635 while (index < length) { 8636 if (((startIndex = text.indexOf(startSymbol, index)) != -1) && 8637 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { 8638 (index != startIndex) && parts.push(text.substring(index, startIndex)); 8639 parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); 8640 fn.exp = exp;
8641 index = endIndex + endSymbolLength; 8642 hasInterpolation = true; 8643 } else { 8644 // we did not find anything, so we have to add the remainder to the parts array 8645 (index != length) && parts.push(text.substring(index)); 8646 index = length; 8647 } 8648 } 8649 8650 if (!(length = parts.length)) { 8651 // we added, nothing, must have been an empty string. 8652 parts.push(''); 8653 length = 1; 8654 } 8655 8656 // Concatenating expressions makes it hard to reason about whether some combination of 8657 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 8658 // single expression be used for iframe[src], object[src], etc., we ensure that the value 8659 // that's used is assigned or constructed by some JS code somewhere that is more testable or 8660 // make it obvious that you bound the value to some user controlled value. This helps reduce 8661 // the load when auditing for XSS issues. 8662 if (trustedContext && parts.length > 1) { 8663 throw $interpolateMinErr('noconcat', 8664 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 8665 "interpolations that concatenate multiple expressions when a trusted value is " + 8666 "required. See http://docs.angularjs.org/api/ng.$sce", text); 8667 } 8668 8669 if (!mustHaveExpression || hasInterpolation) { 8670 concat.length = length; 8671 fn = function (context) { 8672 try { 8673 for (var i = 0, ii = length, part; i < ii; i++) { 8674 if (typeof (part = parts[i]) == 'function') { 8675 part = part(context); 8676 if (trustedContext) { 8677 part = $sce.getTrusted(trustedContext, part); 8678 } else { 8679 part = $sce.valueOf(part); 8680 } 8681 if (part === null || isUndefined(part)) { 8682 part = ''; 8683 } else if (typeof part != 'string') { 8684 part = toJson(part); 8685 } 8686 } 8687 concat[i] = part; 8688 } 8689 return concat.join(''); 8690 } 8691 catch (err) { 8692 var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, 8693 err.toString()); 8694 $exceptionHandler(newErr); 8695 } 8696 }; 8697 fn.exp = text; 8698 fn.parts = parts; 8699 return fn; 8700 } 8701 } 8702 8703 8704 /** 8705 * @ngdoc method 8706 * @name $interpolate#startSymbol 8707 * @description 8708 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 8709 * 8710 * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change 8711 * the symbol. 8712 * 8713 * @returns {string} start symbol. 8714 */ 8715 $interpolate.startSymbol = function () { 8716 return startSymbol; 8717 }; 8718 8719 8720 /** 8721 * @ngdoc method 8722 * @name $interpolate#endSymbol 8723 * @description 8724 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8725 * 8726 * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change 8727 * the symbol. 8728 * 8729 * @returns {string} end symbol. 8730 */ 8731 $interpolate.endSymbol = function () { 8732 return endSymbol; 8733 }; 8734 8735 return $interpolate; 8736 }]; 8737 } 8738 8739 function $IntervalProvider() { 8740 this.$get = ['$rootScope', '$window', '$q', 8741 function ($rootScope, $window, $q) { 8742 var intervals = {}; 8743 8744 8745 /** 8746 * @ngdoc service 8747 * @name $interval 8748 *
8749 * @description 8750 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 8751 * milliseconds. 8752 * 8753 * The return value of registering an interval function is a promise. This promise will be 8754 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 8755 * run indefinitely if `count` is not defined. The value of the notification will be the 8756 * number of iterations that have run. 8757 * To cancel an interval, call `$interval.cancel(promise)`. 8758 * 8759 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 8760 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 8761 * time. 8762 * 8763 * <div class="alert alert-warning"> 8764 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 8765 * with them. In particular they are not automatically destroyed when a controller's scope or a 8766 * directive's element are destroyed. 8767 * You should take this into consideration and make sure to always cancel the interval at the 8768 * appropriate moment. See the example below for more details on how and when to do this. 8769 * </div> 8770 * 8771 * @param {function()} fn A function that should be called repeatedly. 8772 * @param {number} delay Number of milliseconds between each function call. 8773 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 8774 * indefinitely. 8775 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 8776 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 8777 * @returns {promise} A promise which will be notified on each iteration. 8778 * 8779 * @example 8780 * <example module="time"> 8781 * <file name="index.jsp"> 8782 * <script> 8783 * function Ctrl2($scope,$interval) { 8784 * $scope.format = 'M/d/yy h:mm:ss a'; 8785 * $scope.blood_1 = 100; 8786 * $scope.blood_2 = 120; 8787 * 8788 * var stop; 8789 * $scope.fight = function() { 8790 * // Don't start a new fight if we are already fighting 8791 * if ( angular.isDefined(stop) ) return; 8792 * 8793 * stop = $interval(function() { 8794 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 8795 * $scope.blood_1 = $scope.blood_1 - 3; 8796 * $scope.blood_2 = $scope.blood_2 - 4; 8797 * } else { 8798 * $scope.stopFight(); 8799 * } 8800 * }, 100); 8801 * }; 8802 * 8803 * $scope.stopFight = function() { 8804 * if (angular.isDefined(stop)) { 8805 * $interval.cancel(stop); 8806 * stop = undefined; 8807 * } 8808 * }; 8809 * 8810 * $scope.resetFight = function() { 8811 * $scope.blood_1 = 100; 8812 * $scope.blood_2 = 120; 8813 * } 8814 * 8815 * $scope.$on('$destroy', function() { 8816 * // Make sure that the interval is destroyed too 8817 * $scope.stopFight(); 8818 * }); 8819 * } 8820 * 8821 * angular.module('time', []) 8822 * // Register the 'myCurrentTime' directive factory method. 8823 * // We inject $interval and dateFilter service since the factory method is DI. 8824 * .directive('myCurrentTime', function($interval, dateFilter) { 8825 * // return the directive link function. (compile function not needed) 8826 * return function(scope, element, attrs) { 8827 * var format, // date format 8828 * stopTime; // so that we can cancel the time updates 8829 * 8830 * // used to update the UI 8831 * function updateTime() { 8832 * element.text(dateFilter(new Date(), format)); 8833 * } 8834 * 8835 * // watch the expression, and update the UI on change. 8836 * scope.$watch(attrs.myCurrentTime, function(value) { 8837 * format = value; 8838 * updateTime(); 8839 * }); 8840 * 8841 * stopTime = $interval(updateTime, 1000); 8842 * 8843 * // listen on DOM destroy (removal) event, and cancel the next UI update 8844 * // to prevent updating time ofter the DOM element was removed. 8845 * element.bind('$destroy', function() { 8846 * $interval.cancel(stopTime); 8847 * }); 8848 * } 8849 * }); 8850 * </script> 8851 * 8852 * <div> 8853 * <div ng-controller="Ctrl2"> 8854 * Date format: <input ng-model="format"> <hr/> 8855 * Current time is: <span my-current-time="format"></span> 8856 * <hr/> 8857 * Blood 1 : <font color='red'>{{blood_1}}</font> 8858 * Blood 2 : <font color='red'>{{blood_2}}</font> 8859 * <button type="button" data-ng-click="fight()">Fight</button> 8860 * <button type="button" data-ng-click="stopFight()">StopFight</button> 8861 * <button type="button" data-ng-click="resetFight()">resetFight</button> 8862 * </div>
8863 * </div> 8864 * 8865 * </file> 8866 * </example> 8867 */ 8868 function interval(fn, delay, count, invokeApply) { 8869 var setInterval = $window.setInterval, 8870 clearInterval = $window.clearInterval, 8871 deferred = $q.defer(), 8872 promise = deferred.promise, 8873 iteration = 0, 8874 skipApply = (isDefined(invokeApply) && !invokeApply); 8875 8876 count = isDefined(count) ? count : 0; 8877 8878 promise.then(null, null, fn); 8879 8880 promise.$$intervalId = setInterval(function tick() { 8881 deferred.notify(iteration++); 8882 8883 if (count > 0 && iteration >= count) { 8884 deferred.resolve(iteration); 8885 clearInterval(promise.$$intervalId); 8886 delete intervals[promise.$$intervalId]; 8887 } 8888 8889 if (!skipApply) $rootScope.$apply(); 8890 8891 }, delay); 8892 8893 intervals[promise.$$intervalId] = deferred; 8894 8895 return promise; 8896 } 8897 8898 8899 /** 8900 * @ngdoc method 8901 * @name $interval#cancel 8902 * 8903 * @description 8904 * Cancels a task associated with the `promise`. 8905 * 8906 * @param {promise} promise returned by the `$interval` function. 8907 * @returns {boolean} Returns `true` if the task was successfully canceled. 8908 */ 8909 interval.cancel = function (promise) { 8910 if (promise && promise.$$intervalId in intervals) { 8911 intervals[promise.$$intervalId].reject('canceled'); 8912 clearInterval(promise.$$intervalId); 8913 delete intervals[promise.$$intervalId]; 8914 return true; 8915 } 8916 return false; 8917 }; 8918 8919 return interval; 8920 }]; 8921 } 8922 8923 /** 8924 * @ngdoc service 8925 * @name $locale 8926 * 8927 * @description 8928 * $locale service provides localization rules for various Angular components. As of right now the 8929 * only public api is: 8930 * 8931 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 8932 */ 8933 function $LocaleProvider() { 8934 this.$get = function () { 8935 return { 8936 id: 'en-us', 8937 8938 NUMBER_FORMATS: { 8939 DECIMAL_SEP: '.', 8940 GROUP_SEP: ',', 8941 PATTERNS: [ 8942 { // Decimal Pattern 8943 minInt: 1, 8944 minFrac: 0, 8945 maxFrac: 3, 8946 posPre: '', 8947 posSuf: '', 8948 negPre: '-', 8949 negSuf: '', 8950 gSize: 3, 8951 lgSize: 3 8952 }, 8953 { //Currency Pattern 8954 minInt: 1, 8955 minFrac: 2, 8956 maxFrac: 2, 8957 posPre: '\u00A4', 8958 posSuf: '', 8959 negPre: '(\u00A4', 8960 negSuf: ')', 8961 gSize: 3, 8962 lgSize: 3 8963 } 8964 ], 8965 CURRENCY_SYM: '$' 8966 }, 8967 8968 DATETIME_FORMATS: { 8969 MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December' 8970 .split(','), 8971 SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), 8972 DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), 8973 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), 8974 AMPMS: ['AM', 'PM'], 8975 medium: 'MMM d, y h:mm:ss a', 8976 short: 'M/d/yy h:mm a', 8977 fullDate: 'EEEE, MMMM d, y', 8978 longDate: 'MMMM d, y', 8979 mediumDate: 'MMM d, y', 8980 shortDate: 'M/d/yy', 8981 mediumTime: 'h:mm:ss a', 8982 shortTime: 'h:mm a' 8983 }, 8984 8985 pluralCat: function (num) { 8986 if (num === 1) { 8987 return 'one'; 8988 } 8989 return 'other'; 8990 } 8991 }; 8992 }; 8993 } 8994 8995 var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 8996 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 8997 var $locationMinErr = minErr('$location'); 8998 8999 9000 /** 9001 * Encode path using encodeUriSegment, ignoring forward slashes 9002 * 9003 * @param {string} path Path to encode 9004 * @returns {string} 9005 */ 9006 function encodePath(path) { 9007 var segments = path.split('/'), 9008 i = segments.length; 9009 9010 while (i--) { 9011 segments[i] = encodeUriSegment(segments[i]); 9012 } 9013 9014 return segments.join('/'); 9015 } 9016 9017 function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
9018 var parsedUrl = urlResolve(absoluteUrl, appBase); 9019 9020 locationObj.$$protocol = parsedUrl.protocol; 9021 locationObj.$$host = parsedUrl.hostname; 9022 locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 9023 } 9024 9025 9026 function parseAppUrl(relativeUrl, locationObj, appBase) { 9027 var prefixed = (relativeUrl.charAt(0) !== '/'); 9028 if (prefixed) { 9029 relativeUrl = '/' + relativeUrl; 9030 } 9031 var match = urlResolve(relativeUrl, appBase); 9032 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 9033 match.pathname.substring(1) : match.pathname); 9034 locationObj.$$search = parseKeyValue(match.search); 9035 locationObj.$$hash = decodeURIComponent(match.hash); 9036 9037 // make sure path starts with '/'; 9038 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 9039 locationObj.$$path = '/' + locationObj.$$path; 9040 } 9041 } 9042 9043 9044 /** 9045 * 9046 * @param {string} begin 9047 * @param {string} whole 9048 * @returns {string} returns text from whole after begin or undefined if it does not begin with 9049 * expected string. 9050 */ 9051 function beginsWith(begin, whole) { 9052 if (whole.indexOf(begin) === 0) { 9053 return whole.substr(begin.length); 9054 } 9055 } 9056 9057 9058 function stripHash(url) { 9059 var index = url.indexOf('#'); 9060 return index == -1 ? url : url.substr(0, index); 9061 } 9062 9063 9064 function stripFile(url) { 9065 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 9066 } 9067 9068 /* return the server only (scheme://host:port) */ 9069 function serverBase(url) { 9070 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 9071 } 9072 9073 9074 /** 9075 * LocationHtml5Url represents an url 9076 * This object is exposed as $location service when HTML5 mode is enabled and supported 9077 * 9078 * @constructor 9079 * @param {string} appBase application base URL 9080 * @param {string} basePrefix url path prefix 9081 */ 9082 function LocationHtml5Url(appBase, basePrefix) { 9083 this.$$html5 = true; 9084 basePrefix = basePrefix || ''; 9085 var appBaseNoFile = stripFile(appBase); 9086 parseAbsoluteUrl(appBase, this, appBase); 9087 9088 9089 /** 9090 * Parse given html5 (regular) url string into properties 9091 * @param {string} newAbsoluteUrl HTML5 url 9092 * @private 9093 */ 9094 this.$$parse = function (url) { 9095 var pathUrl = beginsWith(appBaseNoFile, url); 9096 if (!isString(pathUrl)) { 9097 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 9098 appBaseNoFile); 9099 } 9100 9101 parseAppUrl(pathUrl, this, appBase); 9102 9103 if (!this.$$path) { 9104 this.$$path = '/'; 9105 } 9106 9107 this.$$compose(); 9108 }; 9109 9110 /** 9111 * Compose url and update `absUrl` property 9112 * @private 9113 */ 9114 this.$$compose = function () { 9115 var search = toKeyValue(this.$$search), 9116 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9117 9118 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9119 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 9120 }; 9121 9122 this.$$rewrite = function (url) { 9123 var appUrl, prevAppUrl; 9124 9125 if ((appUrl = beginsWith(appBase, url)) !== undefined) { 9126 prevAppUrl = appUrl; 9127 if ((appUrl = beginsWith(basePrefix, appUrl)) !== undefined) { 9128 return appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 9129 } else { 9130 return appBase + prevAppUrl; 9131 } 9132 } else if ((appUrl = beginsWith(appBaseNoFile, url)) !== undefined) { 9133 return appBaseNoFile + appUrl; 9134 } else if (appBaseNoFile == url + '/') { 9135 return appBaseNoFile; 9136 } 9137 }; 9138 } 9139 9140 9141 /** 9142 * LocationHashbangUrl represents url 9143 * This object is exposed as $location service when developer doesn't opt into html5 mode. 9144 * It also serves as the base class for html5 mode fallback on legacy browsers. 9145 * 9146 * @constructor 9147 * @param {string} appBase application base URL 9148 * @param {string} hashPrefix hashbang prefix 9149 */ 9150 function LocationHashbangUrl(appBase, hashPrefix) { 9151 var appBaseNoFile = stripFile(appBase); 9152 9153 parseAbsoluteUrl(appBase, this, appBase); 9154 9155 9156 /** 9157 * Parse given hashbang url into properties 9158 * @param {string} url Hashbang url 9159 * @private 9160 */ 9161 this.$$parse = function (url) { 9162 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 9163 var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' 9164 ? beginsWith(hashPrefix, withoutBaseUrl) 9165 : (this.$$html5) 9166 ? withoutBaseUrl 9167 : ''; 9168 9169 if (!isString(withoutHashUrl)) { 9170 throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, 9171 hashPrefix); 9172 } 9173 parseAppUrl(withoutHashUrl, this, appBase); 9174 9175 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 9176 9177 this.$$compose(); 9178 9179 /* 9180 * In Windows, on an anchor node on documents loaded from 9181 * the filesystem, the browser will return a pathname
9182 * prefixed with the drive name ('/C:/path') when a 9183 * pathname without a drive is set: 9184 * * a.setAttribute('href', '/foo') 9185 * * a.pathname === '/C:/foo' //true 9186 * 9187 * Inside of Angular, we're always using pathnames that 9188 * do not include drive names for routing. 9189 */ 9190 function removeWindowsDriveName(path, url, base) { 9191 /* 9192 Matches paths for file protocol on windows, 9193 such as /C:/foo/bar, and captures only /foo/bar. 9194 */ 9195 var windowsFilePathExp = /^\/?.*?:(\/.*)/; 9196 9197 var firstPathSegmentMatch; 9198 9199 //Get the relative path from the input URL. 9200 if (url.indexOf(base) === 0) { 9201 url = url.replace(base, ''); 9202 } 9203 9204 /* 9205 * The input URL intentionally contains a 9206 * first path segment that ends with a colon. 9207 */ 9208 if (windowsFilePathExp.exec(url)) { 9209 return path; 9210 } 9211 9212 firstPathSegmentMatch = windowsFilePathExp.exec(path); 9213 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 9214 } 9215 }; 9216 9217 /** 9218 * Compose hashbang url and update `absUrl` property 9219 * @private 9220 */ 9221 this.$$compose = function () { 9222 var search = toKeyValue(this.$$search), 9223 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9224 9225 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9226 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 9227 }; 9228 9229 this.$$rewrite = function (url) { 9230 if (stripHash(appBase) == stripHash(url)) { 9231 return url; 9232 } 9233 }; 9234 } 9235 9236 9237 /** 9238 * LocationHashbangUrl represents url 9239 * This object is exposed as $location service when html5 history api is enabled but the browser 9240 * does not support it. 9241 * 9242 * @constructor 9243 * @param {string} appBase application base URL 9244 * @param {string} hashPrefix hashbang prefix 9245 */ 9246 function LocationHashbangInHtml5Url(appBase, hashPrefix) { 9247 this.$$html5 = true; 9248 LocationHashbangUrl.apply(this, arguments); 9249 9250 var appBaseNoFile = stripFile(appBase); 9251 9252 this.$$rewrite = function (url) { 9253 var appUrl; 9254 9255 if (appBase == stripHash(url)) { 9256 return url; 9257 } else if ((appUrl = beginsWith(appBaseNoFile, url))) { 9258 return appBase + hashPrefix + appUrl; 9259 } else if (appBaseNoFile === url + '/') { 9260 return appBaseNoFile; 9261 } 9262 }; 9263 } 9264 9265 9266 LocationHashbangInHtml5Url.prototype = 9267 LocationHashbangUrl.prototype = 9268 LocationHtml5Url.prototype = { 9269 9270 /** 9271 * Are we in html5 mode? 9272 * @private 9273 */ 9274 $$html5: false, 9275 9276 /** 9277 * Has any change been replacing ? 9278 * @private 9279 */ 9280 $$replace: false, 9281 9282 /** 9283 * @ngdoc method 9284 * @name $location#absUrl 9285 * 9286 * @description 9287 * This method is getter only. 9288 * 9289 * Return full url representation with all segments encoded according to rules specified in 9290 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 9291 * 9292 * @return {string} full url 9293 */ 9294 absUrl: locationGetter('$$absUrl'), 9295 9296 /** 9297 * @ngdoc method 9298 * @name $location#url 9299 * 9300 * @description 9301 * This method is getter / setter. 9302 * 9303 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 9304 * 9305 * Change path, search and hash, when called with parameter and return `$location`. 9306 * 9307 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 9308 * @param {string=} replace The path that will be changed 9309 * @return {string} url 9310 */ 9311 url: function (url, replace) { 9312 if (isUndefined(url)) 9313 return this.$$url; 9314 9315 var match = PATH_MATCH.exec(url); 9316 if (match[1]) this.path(decodeURIComponent(match[1])); 9317 if (match[2] || match[1]) this.search(match[3] || ''); 9318 this.hash(match[5] || '', replace); 9319 9320 return this; 9321 }, 9322 9323 /** 9324 * @ngdoc method 9325 * @name $location#protocol 9326 *
9327 * @description 9328 * This method is getter only. 9329 * 9330 * Return protocol of current url. 9331 * 9332 * @return {string} protocol of current url 9333 */ 9334 protocol: locationGetter('$$protocol'), 9335 9336 /** 9337 * @ngdoc method 9338 * @name $location#host 9339 * 9340 * @description 9341 * This method is getter only. 9342 * 9343 * Return host of current url. 9344 * 9345 * @return {string} host of current url. 9346 */ 9347 host: locationGetter('$$host'), 9348 9349 /** 9350 * @ngdoc method 9351 * @name $location#port 9352 * 9353 * @description 9354 * This method is getter only. 9355 * 9356 * Return port of current url. 9357 * 9358 * @return {Number} port 9359 */ 9360 port: locationGetter('$$port'), 9361 9362 /** 9363 * @ngdoc method 9364 * @name $location#path 9365 * 9366 * @description 9367 * This method is getter / setter. 9368 * 9369 * Return path of current url when called without any parameter. 9370 * 9371 * Change path when called with parameter and return `$location`. 9372 * 9373 * Note: Path should always begin with forward slash (/), this method will add the forward slash 9374 * if it is missing. 9375 * 9376 * @param {string=} path New path 9377 * @return {string} path 9378 */ 9379 path: locationGetterSetter('$$path', function (path) { 9380 return path.charAt(0) == '/' ? path : '/' + path; 9381 }), 9382 9383 /** 9384 * @ngdoc method 9385 * @name $location#search 9386 * 9387 * @description 9388 * This method is getter / setter. 9389 * 9390 * Return search part (as object) of current url when called without any parameter. 9391 * 9392 * Change search part when called with parameter and return `$location`. 9393 * 9394 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 9395 * hash object. Hash object may contain an array of values, which will be decoded as duplicates in 9396 * the url. 9397 * 9398 * @param {(string|Array<string>)=} paramValue If `search` is a string, then `paramValue` will override only a 9399 * single search parameter. If `paramValue` is an array, it will set the parameter as a 9400 * comma-separated value. If `paramValue` is `null`, the parameter will be deleted. 9401 * 9402 * @return {string} search 9403 */ 9404 search: function (search, paramValue) { 9405 switch (arguments.length) { 9406 case 0: 9407 return this.$$search; 9408 case 1: 9409 if (isString(search)) { 9410 this.$$search = parseKeyValue(search); 9411 } else if (isObject(search)) { 9412 this.$$search = search; 9413 } else { 9414 throw $locationMinErr('isrcharg', 9415 'The first argument of the `$location#search()` call must be a string or an object.'); 9416 } 9417 break; 9418 default: 9419 if (isUndefined(paramValue) || paramValue === null) { 9420 delete this.$$search[search]; 9421 } else { 9422 this.$$search[search] = paramValue; 9423 } 9424 } 9425 9426 this.$$compose(); 9427 return this; 9428 }, 9429 9430 /** 9431 * @ngdoc method 9432 * @name $location#hash 9433 *
9434 * @description 9435 * This method is getter / setter. 9436 * 9437 * Return hash fragment when called without any parameter. 9438 * 9439 * Change hash fragment when called with parameter and return `$location`. 9440 * 9441 * @param {string=} hash New hash fragment 9442 * @return {string} hash 9443 */ 9444 hash: locationGetterSetter('$$hash', identity), 9445 9446 /** 9447 * @ngdoc method 9448 * @name $location#replace 9449 * 9450 * @description 9451 * If called, all changes to $location during current `$digest` will be replacing current history 9452 * record, instead of adding new one. 9453 */ 9454 replace: function () { 9455 this.$$replace = true; 9456 return this; 9457 } 9458 }; 9459 9460 function locationGetter(property) { 9461 return function () { 9462 return this[property]; 9463 }; 9464 } 9465 9466 9467 function locationGetterSetter(property, preprocess) { 9468 return function (value) { 9469 if (isUndefined(value)) 9470 return this[property]; 9471 9472 this[property] = preprocess(value); 9473 this.$$compose(); 9474 9475 return this; 9476 }; 9477 } 9478 9479 9480 /** 9481 * @ngdoc service 9482 * @name $location 9483 * 9484 * @requires $rootElement 9485 * 9486 * @description 9487 * The $location service parses the URL in the browser address bar (based on the 9488 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 9489 * available to your application. Changes to the URL in the address bar are reflected into 9490 * $location service and changes to $location are reflected into the browser address bar. 9491 * 9492 * **The $location service:** 9493 * 9494 * - Exposes the current URL in the browser address bar, so you can 9495 * - Watch and observe the URL. 9496 * - Change the URL. 9497 * - Synchronizes the URL with the browser when the user 9498 * - Changes the address bar. 9499 * - Clicks the back or forward button (or clicks a History link). 9500 * - Clicks on a link. 9501 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 9502 * 9503 * For more information see {@link guide/$location Developer Guide: Using $location} 9504 */ 9505 9506 /** 9507 * @ngdoc provider 9508 * @name $locationProvider 9509 * @description 9510 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 9511 */ 9512 function $LocationProvider() { 9513 var hashPrefix = '', 9514 html5Mode = false; 9515 9516 /** 9517 * @ngdoc property 9518 * @name $locationProvider#hashPrefix 9519 * @description 9520 * @param {string=} prefix Prefix for hash part (containing path and search) 9521 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9522 */ 9523 this.hashPrefix = function (prefix) { 9524 if (isDefined(prefix)) { 9525 hashPrefix = prefix; 9526 return this; 9527 } else { 9528 return hashPrefix; 9529 } 9530 }; 9531 9532 /** 9533 * @ngdoc property 9534 * @name $locationProvider#html5Mode 9535 * @description 9536 * @param {boolean=} mode Use HTML5 strategy if available. 9537 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9538 */ 9539 this.html5Mode = function (mode) { 9540 if (isDefined(mode)) { 9541 html5Mode = mode; 9542 return this; 9543 } else { 9544 return html5Mode; 9545 } 9546 }; 9547 9548 /** 9549 * @ngdoc event 9550 * @name $location#$locationChangeStart 9551 * @eventType broadcast on root scope 9552 * @description 9553 * Broadcasted before a URL will change. This change can be prevented by calling 9554 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 9555 * details about event object. Upon successful change 9556 * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. 9557 * 9558 * @param {Object} angularEvent Synthetic event object. 9559 * @param {string} newUrl New URL 9560 * @param {string=} oldUrl URL that was before it was changed. 9561 */ 9562 9563 /** 9564 * @ngdoc event 9565 * @name $location#$locationChangeSuccess 9566 * @eventType broadcast on root scope
9567 * @description 9568 * Broadcasted after a URL was changed. 9569 * 9570 * @param {Object} angularEvent Synthetic event object. 9571 * @param {string} newUrl New URL 9572 * @param {string=} oldUrl URL that was before it was changed. 9573 */ 9574 9575 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', 9576 function ($rootScope, $browser, $sniffer, $rootElement) { 9577 var $location, 9578 LocationMode, 9579 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 9580 initialUrl = $browser.url(), 9581 appBase; 9582 9583 if (html5Mode) { 9584 appBase = serverBase(initialUrl) + (baseHref || '/'); 9585 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 9586 } else { 9587 appBase = stripHash(initialUrl); 9588 LocationMode = LocationHashbangUrl; 9589 } 9590 $location = new LocationMode(appBase, '#' + hashPrefix); 9591 $location.$$parse($location.$$rewrite(initialUrl)); 9592 9593 $rootElement.on('click', function (event) { 9594 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 9595 // currently we open nice url link and redirect then 9596 9597 if (event.ctrlKey || event.metaKey || event.which == 2) return; 9598 9599 var elm = jqLite(event.target); 9600 9601 // traverse the DOM up to find first A tag 9602 while (lowercase(elm[0].nodeName) !== 'a') { 9603 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 9604 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 9605 } 9606 9607 var absHref = elm.prop('href'); 9608 9609 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 9610 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 9611 // an animation. 9612 absHref = urlResolve(absHref.animVal).href; 9613 } 9614 9615 var rewrittenUrl = $location.$$rewrite(absHref); 9616 9617 if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) { 9618 event.preventDefault(); 9619 if (rewrittenUrl != $browser.url()) { 9620 // update location manually 9621 $location.$$parse(rewrittenUrl); 9622 $rootScope.$apply(); 9623 // hack to work around FF6 bug 684208 when scenario runner clicks on links 9624 window.angular['ff-684208-preventDefault'] = true; 9625 } 9626 } 9627 }); 9628 9629 9630 // rewrite hashbang url <> html5 url 9631 if ($location.absUrl() != initialUrl) { 9632 $browser.url($location.absUrl(), true); 9633 } 9634 9635 // update $location when $browser url changes 9636 $browser.onUrlChange(function (newUrl) { 9637 if ($location.absUrl() != newUrl) { 9638 $rootScope.$evalAsync(function () { 9639 var oldUrl = $location.absUrl(); 9640 9641 $location.$$parse(newUrl); 9642 if ($rootScope.$broadcast('$locationChangeStart', newUrl, 9643 oldUrl).defaultPrevented) { 9644 $location.$$parse(oldUrl); 9645 $browser.url(oldUrl); 9646 } else { 9647 afterLocationChange(oldUrl); 9648 } 9649 }); 9650 if (!$rootScope.$$phase) $rootScope.$digest(); 9651 } 9652 }); 9653 9654 // update browser 9655 var changeCounter = 0; 9656 $rootScope.$watch(function $locationWatch() { 9657 var oldUrl = $browser.url(); 9658 var currentReplace = $location.$$replace; 9659 9660 if (!changeCounter || oldUrl != $location.absUrl()) { 9661 changeCounter++; 9662 $rootScope.$evalAsync(function () { 9663 if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). 9664 defaultPrevented) { 9665 $location.$$parse(oldUrl); 9666 } else { 9667 $browser.url($location.absUrl(), currentReplace); 9668 afterLocationChange(oldUrl); 9669 } 9670 }); 9671 } 9672 $location.$$replace = false;
9673 9674 return changeCounter; 9675 }); 9676 9677 return $location; 9678 9679 function afterLocationChange(oldUrl) { 9680 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); 9681 } 9682 }]; 9683 } 9684 9685 /** 9686 * @ngdoc service 9687 * @name $log 9688 * @requires $window 9689 * 9690 * @description 9691 * Simple service for logging. Default implementation safely writes the message 9692 * into the browser's console (if present). 9693 * 9694 * The main purpose of this service is to simplify debugging and troubleshooting. 9695 * 9696 * The default is to log `debug` messages. You can use 9697 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 9698 * 9699 * @example 9700 <example> 9701 <file name="script.js"> 9702 function LogCtrl($scope, $log) { 9703 $scope.$log = $log; 9704 $scope.message = 'Hello World!'; 9705 } 9706 </file> 9707 <file name="index.jsp"> 9708 <div ng-controller="LogCtrl"> 9709 <p>Reload this page with open console, enter text and hit the log button...</p> 9710 Message: 9711 <input type="text" ng-model="message"/> 9712 <button ng-click="$log.log(message)">log</button> 9713 <button ng-click="$log.warn(message)">warn</button> 9714 <button ng-click="$log.info(message)">info</button> 9715 <button ng-click="$log.error(message)">error</button> 9716 </div> 9717 </file> 9718 </example> 9719 */ 9720 9721 /** 9722 * @ngdoc provider 9723 * @name $logProvider 9724 * @description 9725 * Use the `$logProvider` to configure how the application logs messages 9726 */ 9727 function $LogProvider() { 9728 var debug = true, 9729 self = this; 9730 9731 /** 9732 * @ngdoc property 9733 * @name $logProvider#debugEnabled 9734 * @description 9735 * @param {boolean=} flag enable or disable debug level messages 9736 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9737 */ 9738 this.debugEnabled = function (flag) { 9739 if (isDefined(flag)) { 9740 debug = flag; 9741 return this; 9742 } else { 9743 return debug; 9744 } 9745 }; 9746 9747 this.$get = ['$window', function ($window) { 9748 return { 9749 /** 9750 * @ngdoc method 9751 * @name $log#log 9752 * 9753 * @description 9754 * Write a log message 9755 */ 9756 log: consoleLog('log'), 9757 9758 /** 9759 * @ngdoc method 9760 * @name $log#info 9761 * 9762 * @description 9763 * Write an information message 9764 */ 9765 info: consoleLog('info'), 9766 9767 /** 9768 * @ngdoc method 9769 * @name $log#warn 9770 * 9771 * @description 9772 * Write a warning message 9773 */ 9774 warn: consoleLog('warn'), 9775 9776 /** 9777 * @ngdoc method 9778 * @name $log#error 9779 * 9780 * @description 9781 * Write an error message 9782 */ 9783 error: consoleLog('error'), 9784 9785 /** 9786 * @ngdoc method 9787 * @name $log#debug 9788 * 9789 * @description 9790 * Write a debug message 9791 */ 9792 debug: (function () { 9793 var fn = consoleLog('debug'); 9794 9795 return function () { 9796 if (debug) { 9797 fn.apply(self, arguments); 9798 } 9799 }; 9800 }()) 9801 }; 9802 9803 function formatError(arg) { 9804 if (arg instanceof Error) { 9805 if (arg.stack) { 9806 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 9807 ? 'Error: ' + arg.message + '\n' + arg.stack 9808 : arg.stack; 9809 } else if (arg.sourceURL) { 9810 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 9811 } 9812 } 9813 return arg; 9814 } 9815 9816 function consoleLog(type) { 9817 var console = $window.console || {}, 9818 logFn = console[type] || console.log || noop, 9819 hasApply = false;
9820 9821 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 9822 // The reason behind this is that console.log has type "object" in IE8... 9823 try { 9824 hasApply = !!logFn.apply; 9825 } catch (e) { 9826 } 9827 9828 if (hasApply) { 9829 return function () { 9830 var args = []; 9831 forEach(arguments, function (arg) { 9832 args.push(formatError(arg)); 9833 }); 9834 return logFn.apply(console, args); 9835 }; 9836 } 9837 9838 // we are IE which either doesn't have window.console => this is noop and we do nothing, 9839 // or we are IE where console.log doesn't have apply so we log at least first 2 args 9840 return function (arg1, arg2) { 9841 logFn(arg1, arg2 == null ? '' : arg2); 9842 }; 9843 } 9844 }]; 9845 } 9846 9847 var $parseMinErr = minErr('$parse'); 9848 var promiseWarningCache = {}; 9849 var promiseWarning; 9850 9851// Sandboxing Angular Expressions 9852// ------------------------------ 9853// Angular expressions are generally considered safe because these expressions only have direct 9854// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by 9855// obtaining a reference to native JS functions such as the Function constructor. 9856// 9857// As an example, consider the following Angular expression: 9858// 9859// {}.toString.constructor(alert("evil JS code")) 9860// 9861// We want to prevent this type of access. For the sake of performance, during the lexing phase we 9862// disallow any "dotted" access to any member named "constructor". 9863// 9864// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor 9865// while evaluating the expression, which is a stronger but more expensive test. Since reflective 9866// calls are expensive anyway, this is not such a big deal compared to static dereferencing. 9867// 9868// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 9869// against the expression language, but not to prevent exploits that were enabled by exposing 9870// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good 9871// practice and therefore we are not even trying to protect against interaction with an object 9872// explicitly exposed in this way. 9873// 9874// A developer could foil the name check by aliasing the Function constructor under a different 9875// name on the scope. 9876// 9877// In general, it is not possible to access a Window object from an angular expression unless a 9878// window or some DOM object that has a reference to window is published onto a Scope. 9879 9880 function ensureSafeMemberName(name, fullExpression) { 9881 if (name === "constructor") { 9882 throw $parseMinErr('isecfld', 9883 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', 9884 fullExpression); 9885 } 9886 return name; 9887 } 9888 9889 function ensureSafeObject(obj, fullExpression) { 9890 // nifty check if obj is Function that is fast and works across iframes and other contexts 9891 if (obj) { 9892 if (obj.constructor === obj) { 9893 throw $parseMinErr('isecfn', 9894 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 9895 fullExpression); 9896 } else if (// isWindow(obj) 9897 obj.document && obj.location && obj.alert && obj.setInterval) { 9898 throw $parseMinErr('isecwindow', 9899 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 9900 fullExpression); 9901 } else if (// isElement(obj) 9902 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 9903 throw $parseMinErr('isecdom', 9904 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 9905 fullExpression); 9906 } 9907 } 9908 return obj; 9909 } 9910 9911 var OPERATORS = { 9912 /* jshint bitwise : false */ 9913 'null': function () { 9914 return null; 9915 }, 9916 'true': function () { 9917 return true; 9918 }, 9919 'false': function () { 9920 return false;
9921 }, 9922 undefined: noop, 9923 '+': function (self, locals, a, b) { 9924 a = a(self, locals); 9925 b = b(self, locals); 9926 if (isDefined(a)) { 9927 if (isDefined(b)) { 9928 return a + b; 9929 } 9930 return a; 9931 } 9932 return isDefined(b) ? b : undefined; 9933 }, 9934 '-': function (self, locals, a, b) { 9935 a = a(self, locals); 9936 b = b(self, locals); 9937 return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0); 9938 }, 9939 '*': function (self, locals, a, b) { 9940 return a(self, locals) * b(self, locals); 9941 }, 9942 '/': function (self, locals, a, b) { 9943 return a(self, locals) / b(self, locals); 9944 }, 9945 '%': function (self, locals, a, b) { 9946 return a(self, locals) % b(self, locals); 9947 }, 9948 '^': function (self, locals, a, b) { 9949 return a(self, locals) ^ b(self, locals); 9950 }, 9951 '=': noop, 9952 '===': function (self, locals, a, b) { 9953 return a(self, locals) === b(self, locals); 9954 }, 9955 '!==': function (self, locals, a, b) { 9956 return a(self, locals) !== b(self, locals); 9957 }, 9958 '==': function (self, locals, a, b) { 9959 return a(self, locals) == b(self, locals); 9960 }, 9961 '!=': function (self, locals, a, b) { 9962 return a(self, locals) != b(self, locals); 9963 }, 9964 '<': function (self, locals, a, b) { 9965 return a(self, locals) < b(self, locals); 9966 }, 9967 '>': function (self, locals, a, b) { 9968 return a(self, locals) > b(self, locals); 9969 }, 9970 '<=': function (self, locals, a, b) { 9971 return a(self, locals) <= b(self, locals); 9972 }, 9973 '>=': function (self, locals, a, b) { 9974 return a(self, locals) >= b(self, locals); 9975 }, 9976 '&&': function (self, locals, a, b) { 9977 return a(self, locals) && b(self, locals); 9978 }, 9979 '||': function (self, locals, a, b) { 9980 return a(self, locals) || b(self, locals); 9981 }, 9982 '&': function (self, locals, a, b) { 9983 return a(self, locals) & b(self, locals); 9984 }, 9985// '|':function(self, locals, a,b){return a|b;}, 9986 '|': function (self, locals, a, b) { 9987 return b(self, locals)(self, locals, a(self, locals)); 9988 }, 9989 '!': function (self, locals, a) { 9990 return !a(self, locals); 9991 } 9992 }; 9993 /* jshint bitwise: true */ 9994 var ESCAPE = {"n": "\n", "f": "\f", "r": "\r", "t": "\t", "v": "\v", "'": "'", '"': '"'}; 9995 9996 9997///////////////////////////////////////// 9998 9999 10000 /** 10001 * @constructor 10002 */ 10003 var Lexer = function (options) { 10004 this.options = options; 10005 }; 10006 10007 Lexer.prototype = { 10008 constructor: Lexer, 10009 10010 lex: function (text) { 10011 this.text = text; 10012 10013 this.index = 0; 10014 this.ch = undefined; 10015 this.lastCh = ':'; // can start regexp 10016 10017 this.tokens = []; 10018 10019 var token; 10020 var json = []; 10021 10022 while (this.index < this.text.length) { 10023 this.ch = this.text.charAt(this.index); 10024 if (this.is('"\'')) { 10025 this.readString(this.ch); 10026 } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { 10027 this.readNumber(); 10028 } else if (this.isIdent(this.ch)) { 10029 this.readIdent(); 10030 // identifiers can only be if the preceding char was a { or , 10031 if (this.was('{,') && json[0] === '{' && 10032 (token = this.tokens[this.tokens.length - 1])) { 10033 token.json = token.text.indexOf('.') === -1; 10034 } 10035 } else if (this.is('(){}[].,;:?')) { 10036 this.tokens.push({ 10037 index: this.index, 10038 text: this.ch, 10039 json: (this.was(':[,') && this.is('{[')) || this.is('}]:,') 10040 }); 10041 if (this.is('{[')) json.unshift(this.ch); 10042 if (this.is('}
10042]')) json.shift(); 10043 this.index++; 10044 } else if (this.isWhitespace(this.ch)) { 10045 this.index++; 10046 continue; 10047 } else { 10048 var ch2 = this.ch + this.peek(); 10049 var ch3 = ch2 + this.peek(2); 10050 var fn = OPERATORS[this.ch]; 10051 var fn2 = OPERATORS[ch2]; 10052 var fn3 = OPERATORS[ch3]; 10053 if (fn3) { 10054 this.tokens.push({index: this.index, text: ch3, fn: fn3}); 10055 this.index += 3; 10056 } else if (fn2) { 10057 this.tokens.push({index: this.index, text: ch2, fn: fn2}); 10058 this.index += 2; 10059 } else if (fn) { 10060 this.tokens.push({ 10061 index: this.index, 10062 text: this.ch, 10063 fn: fn, 10064 json: (this.was('[,:') && this.is('+-')) 10065 }); 10066 this.index += 1; 10067 } else { 10068 this.throwError('Unexpected next character ', this.index, this.index + 1); 10069 } 10070 } 10071 this.lastCh = this.ch; 10072 } 10073 return this.tokens; 10074 }, 10075 10076 is: function (chars) { 10077 return chars.indexOf(this.ch) !== -1; 10078 }, 10079 10080 was: function (chars) { 10081 return chars.indexOf(this.lastCh) !== -1; 10082 }, 10083 10084 peek: function (i) { 10085 var num = i || 1; 10086 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 10087 }, 10088 10089 isNumber: function (ch) { 10090 return ('0' <= ch && ch <= '9'); 10091 }, 10092 10093 isWhitespace: function (ch) { 10094 // IE treats non-breaking space as \u00A0 10095 return (ch === ' ' || ch === '\r' || ch === '\t' || 10096 ch === '\n' || ch === '\v' || ch === '\u00A0'); 10097 }, 10098 10099 isIdent: function (ch) { 10100 return ('a' <= ch && ch <= 'z' || 10101 'A' <= ch && ch <= 'Z' || 10102 '_' === ch || ch === '$'); 10103 }, 10104 10105 isExpOperator: function (ch) { 10106 return (ch === '-' || ch === '+' || this.isNumber(ch)); 10107 }, 10108 10109 throwError: function (error, start, end) { 10110 end = end || this.index; 10111 var colStr = (isDefined(start) 10112 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 10113 : ' ' + end); 10114 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 10115 error, colStr, this.text); 10116 }, 10117 10118 readNumber: function () { 10119 var number = ''; 10120 var start = this.index; 10121 while (this.index < this.text.length) { 10122 var ch = lowercase(this.text.charAt(this.index)); 10123 if (ch == '.' || this.isNumber(ch)) { 10124 number += ch; 10125 } else { 10126 var peekCh = this.peek(); 10127 if (ch == 'e' && this.isExpOperator(peekCh)) { 10128 number += ch; 10129 } else if (this.isExpOperator(ch) && 10130 peekCh && this.isNumber(peekCh) && 10131 number.charAt(number.length - 1) == 'e') { 10132 number += ch; 10133 } else if (this.isExpOperator(ch) && 10134 (!peekCh || !this.isNumber(peekCh)) && 10135 number.charAt(number.length - 1) == 'e') { 10136 this.throwError('Invalid exponent'); 10137 } else { 10138 break; 10139 } 10140 } 10141 this.index++; 10142 } 10143 number = 1 * number; 10144 this.tokens.push({ 10145 index: start, 10146 text: number, 10147 json: true, 10148 fn: function () { 10149 return number; 10150 } 10151 }); 10152 }, 10153 10154 readIdent: function () { 10155 var parser = this; 10156 10157 var ident = ''; 10158 var start = this.index; 10159 10160 var lastDot, peekIndex, methodName, ch; 10161 10162 while (this.index < this.text.length) { 10163 ch = this.text.charAt(this.index); 10164 if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { 10165 if (ch === '.') lastDot = this.index; 10166 ident += ch; 10167 } else { 10168 break; 10169 } 10170 this.index++; 10171 } 10172 10173 //check if this is not a method invocation and if it is back out to last dot 10174 if (lastDot) { 10175 peekIndex = this.index; 10176 while (peekIndex < this.text.length) { 10177 ch = this.text.charAt(peekIndex); 10178 if (ch === '(') { 10179 methodName = ident.substr(lastDot - start + 1); 10180 ident = ident.substr(0, lastDot - start); 10181 this.index = peekIndex; 10182 break; 10183 } 10184 if (this.isWhitespace(ch)) { 10185 peekIndex++; 10186 } else { 10187 break; 10188 } 10189 } 10190 } 10191 10192 10193 var token = { 10194 index: start, 10195 text: ident 10196 }; 10197 10198 // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn 10199 if (OPERATORS.hasOwnProperty(ident)) { 10200 token.fn = OPERATORS[ident]; 10201 token.json = OPERATORS[ident]; 10202 } else { 10203 var getter = getterFn(ident, this.options, this.text); 10204 token.fn = extend(function (self, locals) { 10205 return (getter(self, locals)); 10206 }, { 10207 assign: function (self, value) { 10208 return setter(self, ident, value, parser.text, parser.options); 10209 } 10210 }); 10211 } 10212 10213 this.tokens.push(token); 10214 10215 if (methodName) { 10216 this.tokens.push({ 10217 index: lastDot, 10218 text: '.', 10219 json: false 10220 }); 10221 this.tokens.push({ 10222 index: lastDot + 1, 10223 text: methodName, 10224 json: false 10225 }); 10226 } 10227 }, 10228 10229 readString: function (quote) { 10230 var start = this.index; 10231 this.index++; 10232 var string = ''; 10233 var rawString = quote; 10234 var escape = false;
10235 while (this.index < this.text.length) { 10236 var ch = this.text.charAt(this.index); 10237 rawString += ch; 10238 if (escape) { 10239 if (ch === 'u') { 10240 var hex = this.text.substring(this.index + 1, this.index + 5); 10241 if (!hex.match(/[\da-f]{4}/i)) 10242 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 10243 this.index += 4; 10244 string += String.fromCharCode(parseInt(hex, 16)); 10245 } else { 10246 var rep = ESCAPE[ch]; 10247 if (rep) { 10248 string += rep; 10249 } else { 10250 string += ch; 10251 } 10252 } 10253 escape = false; 10254 } else if (ch === '\\') { 10255 escape = true; 10256 } else if (ch === quote) { 10257 this.index++; 10258 this.tokens.push({ 10259 index: start, 10260 text: rawString, 10261 string: string, 10262 json: true, 10263 fn: function () { 10264 return string; 10265 } 10266 }); 10267 return; 10268 } else { 10269 string += ch; 10270 } 10271 this.index++; 10272 } 10273 this.throwError('Unterminated quote', start); 10274 } 10275 }; 10276 10277 10278 /** 10279 * @constructor 10280 */ 10281 var Parser = function (lexer, $filter, options) { 10282 this.lexer = lexer; 10283 this.$filter = $filter; 10284 this.options = options; 10285 }; 10286 10287 Parser.ZERO = extend(function () { 10288 return 0; 10289 }, { 10290 constant: true 10291 }); 10292 10293 Parser.prototype = { 10294 constructor: Parser, 10295 10296 parse: function (text, json) { 10297 this.text = text; 10298 10299 //TODO(i): strip all the obsolte json stuff from this file 10300 this.json = json; 10301 10302 this.tokens = this.lexer.lex(text); 10303 10304 if (json) { 10305 // The extra level of aliasing is here, just in case the lexer misses something, so that 10306 // we prevent any accidental execution in JSON. 10307 this.assignment = this.logicalOR; 10308 10309 this.functionCall = 10310 this.fieldAccess = 10311 this.objectIndex = 10312 this.filterChain = function () { 10313 this.throwError('is not valid json', {text: text, index: 0}); 10314 }; 10315 } 10316 10317 var value = json ? this.primary() : this.statements(); 10318 10319 if (this.tokens.length !== 0) { 10320 this.throwError('is an unexpected token', this.tokens[0]); 10321 } 10322 10323 value.literal = !!value.literal; 10324 value.constant = !!value.constant; 10325 10326 return value; 10327 }, 10328 10329 primary: function () { 10330 var primary; 10331 if (this.expect('(')) { 10332 primary = this.filterChain(); 10333 this.consume(')'); 10334 } else if (this.expect('[')) { 10335 primary = this.arrayDeclaration(); 10336 } else if (this.expect('{')) { 10337 primary = this.object(); 10338 } else { 10339 var token = this.expect(); 10340 primary = token.fn; 10341 if (!primary) { 10342 this.throwError('not a primary expression', token); 10343 } 10344 if (token.json) { 10345 primary.constant = true; 10346 primary.literal = true; 10347 } 10348 } 10349 10350 var next, context; 10351 while ((next = this.expect('(', '[', '.'))) { 10352 if (next.text === '(') { 10353 primary = this.functionCall(primary, context); 10354 context = null; 10355 } else if (next.text === '[') { 10356 context = primary; 10357 primary = this.objectIndex(primary); 10358 } else if (next.text === '.') { 10359 context = primary; 10360 primary = this.fieldAccess(primary); 10361 } else { 10362 this.throwError('IMPOSSIBLE'); 10363 } 10364 } 10365 return primary; 10366 }, 10367 10368 throwError: function (msg, token) { 10369 throw $parseMinErr('syntax', 10370 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 10371 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 10372 }, 10373 10374 peekToken: function () { 10375 if (this.tokens.length === 0) 10376 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 10377 return this.tokens[0]; 10378 }, 10379 10380 peek: function (e1, e2, e3, e4) { 10381 if (this.tokens.length > 0) { 10382 var token = this.tokens[0]; 10383 var t = token.text; 10384 if (t === e1 || t === e2 || t === e3 || t === e4 || 10385 (!e1 && !e2 && !e3 && !e4)) { 10386 return token; 10387 } 10388 } 10389 return false;
10390 }, 10391 10392 expect: function (e1, e2, e3, e4) { 10393 var token = this.peek(e1, e2, e3, e4); 10394 if (token) { 10395 if (this.json && !token.json) { 10396 this.throwError('is not valid json', token); 10397 } 10398 this.tokens.shift(); 10399 return token; 10400 } 10401 return false; 10402 }, 10403 10404 consume: function (e1) { 10405 if (!this.expect(e1)) { 10406 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 10407 } 10408 }, 10409 10410 unaryFn: function (fn, right) { 10411 return extend(function (self, locals) { 10412 return fn(self, locals, right); 10413 }, { 10414 constant: right.constant 10415 }); 10416 }, 10417 10418 ternaryFn: function (left, middle, right) { 10419 return extend(function (self, locals) { 10420 return left(self, locals) ? middle(self, locals) : right(self, locals); 10421 }, { 10422 constant: left.constant && middle.constant && right.constant 10423 }); 10424 }, 10425 10426 binaryFn: function (left, fn, right) { 10427 return extend(function (self, locals) { 10428 return fn(self, locals, left, right); 10429 }, { 10430 constant: left.constant && right.constant 10431 }); 10432 }, 10433 10434 statements: function () { 10435 var statements = []; 10436 while (true) { 10437 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 10438 statements.push(this.filterChain()); 10439 if (!this.expect(';')) { 10440 // optimize for the common case where there is only one statement. 10441 // TODO(size): maybe we should not support multiple statements? 10442 return (statements.length === 1) 10443 ? statements[0] 10444 : function (self, locals) { 10445 var value; 10446 for (var i = 0; i < statements.length; i++) { 10447 var statement = statements[i]; 10448 if (statement) { 10449 value = statement(self, locals); 10450 } 10451 } 10452 return value; 10453 }; 10454 } 10455 } 10456 }, 10457 10458 filterChain: function () { 10459 var left = this.expression(); 10460 var token; 10461 while (true) { 10462 if ((token = this.expect('|'))) { 10463 left = this.binaryFn(left, token.fn, this.filter()); 10464 } else { 10465 return left; 10466 } 10467 } 10468 }, 10469 10470 filter: function () { 10471 var token = this.expect(); 10472 var fn = this.$filter(token.text); 10473 var argsFn = []; 10474 while (true) { 10475 if ((token = this.expect(':'))) { 10476 argsFn.push(this.expression()); 10477 } else { 10478 var fnInvoke = function (self, locals, input) { 10479 var args = [input]; 10480 for (var i = 0; i < argsFn.length; i++) { 10481 args.push(argsFn[i](self, locals)); 10482 } 10483 return fn.apply(self, args); 10484 }; 10485 return function () { 10486 return fnInvoke; 10487 }; 10488 } 10489 } 10490 }, 10491 10492 expression: function () { 10493 return this.assignment(); 10494 }, 10495 10496 assignment: function () { 10497 var left = this.ternary(); 10498 var right; 10499 var token; 10500 if ((token = this.expect('='))) { 10501 if (!left.assign) { 10502 this.throwError('implies assignment but [' + 10503 this.text.substring(0, token.index) + '] can not be assigned to', token); 10504 } 10505 right = this.ternary(); 10506 return function (scope, locals) { 10507 return left.assign(scope, right(scope, locals), locals); 10508 }; 10509 } 10510 return left; 10511 }, 10512 10513 ternary: function () { 10514 var left = this.logicalOR(); 10515 var middle; 10516 var token; 10517 if ((token = this.expect('?'))) { 10518 middle = this.ternary(); 10519 if ((token = this.expect(':'))) { 10520 return this.ternaryFn(left, middle, this.ternary()); 10521 } else { 10522 this.throwError('expected :', token); 10523 } 10524 } else { 10525 return left; 10526 } 10527 }, 10528 10529 logicalOR: function () { 10530 var left = this.logicalAND(); 10531 var token; 10532 while (true) { 10533 if ((token = this.expect('||'))) { 10534 left = this.binaryFn(left, token.fn, this.logicalAND()); 10535 } else { 10536 return left; 10537 } 10538 } 10539 }, 10540 10541 logicalAND: function () { 10542 var left = this.equality(); 10543 var token; 10544 if ((token = this.expect('&&'))) { 10545 left = this.binaryFn(left, token.fn, this.logicalAND()); 10546 } 10547 return left; 10548 }, 10549 10550 equality: function () { 10551 var left = this.relational(); 10552 var token; 10553 if ((token = this.expect('==', '!=', '===', '!=='))) { 10554 left = this.binaryFn(left, token.fn, this.equality()); 10555 } 10556 return left; 10557 }, 10558 10559 relational: function () { 10560 var left = this.additive(); 10561 var token; 10562 if ((token = this.expect('<', '>', '<=', '>='))) { 10563 left = this.binaryFn(left, token.fn, this.relational()); 10564 } 10565 return left; 10566 }, 10567 10568 additive: function () { 10569 var left = this.multiplicative(); 10570 var token; 10571 while ((token = this.expect('+', '-'))) { 10572 left = this.binaryFn(left, token.fn, this.multiplicative()); 10573 } 10574 return left; 10575 }, 10576 10577 multiplicative: function () { 10578 var left = this.unary(); 10579 var token; 10580 while ((token = this.expect('*', '/', '%'))) { 10581 left = this.binaryFn(left, token.fn, this.unary()); 10582 } 10583 return left; 10584 }, 10585 10586 unary: function () { 10587 var token; 10588 if (this.expect('+')) { 10589 return this.primary(); 10590 } else if ((token = this.expect('-'))) { 10591 return this.binaryFn(Parser.ZERO, token.fn, this.unary()); 10592 } else if ((token = this.expect('!'))) { 10593 return this.unaryFn(token.fn, this.unary()); 10594 } else { 10595 return this.primary(); 10596 } 10597 }, 10598 10599 fieldAccess: function (object) { 10600 var parser = this; 10601 var field = this.expect().text; 10602 var getter = getterFn(field, this.options, this.text); 10603 10604 return extend(function (scope, locals, self) { 10605 return getter(self || object(scope, locals)); 10606 }, { 10607 assign: function (scope, value, locals) { 10608 return setter(object(scope, locals), field, value, parser.text, parser.options); 10609 } 10610 }); 10611 }, 10612 10613 objectIndex: function (obj) { 10614 var parser = this; 10615 10616 var indexFn = this.expression(); 10617 this.consume(']'); 10618 10619 return extend(function (self, locals) { 10620 var o = obj(self, locals), 10621 i = indexFn(self, locals), 10622 v, p; 10623 10624 if (!o) return undefined; 10625 v = ensureSafeObject(o[i], parser.text); 10626 if (v && v.then && parser.options.unwrapPromises) { 10627 p = v; 10628 if (!('$$v' in v)) { 10629 p.$$v = undefined; 10630 p.then(function (val) { 10631 p.$$v = val; 10632 }); 10633 } 10634 v = v.$$v; 10635 } 10636 return v; 10637 }, { 10638 assign: function (self, value, locals) { 10639 var key = indexFn(self, locals);
10640 // prevent overwriting of Function.constructor which would break ensureSafeObject check 10641 var safe = ensureSafeObject(obj(self, locals), parser.text); 10642 return safe[key] = value; 10643 } 10644 }); 10645 }, 10646 10647 functionCall: function (fn, contextGetter) { 10648 var argsFn = []; 10649 if (this.peekToken().text !== ')') { 10650 do { 10651 argsFn.push(this.expression()); 10652 } while (this.expect(',')); 10653 } 10654 this.consume(')'); 10655 10656 var parser = this; 10657 10658 return function (scope, locals) { 10659 var args = []; 10660 var context = contextGetter ? contextGetter(scope, locals) : scope; 10661 10662 for (var i = 0; i < argsFn.length; i++) { 10663 args.push(argsFn[i](scope, locals)); 10664 } 10665 var fnPtr = fn(scope, locals, context) || noop; 10666 10667 ensureSafeObject(context, parser.text); 10668 ensureSafeObject(fnPtr, parser.text); 10669 10670 // IE stupidity! (IE doesn't have apply for some native functions) 10671 var v = fnPtr.apply 10672 ? fnPtr.apply(context, args) 10673 : fnPtr(args[0], args[1], args[2], args[3], args[4]); 10674 10675 return ensureSafeObject(v, parser.text); 10676 }; 10677 }, 10678 10679 // This is used with json array declaration 10680 arrayDeclaration: function () { 10681 var elementFns = []; 10682 var allConstant = true; 10683 if (this.peekToken().text !== ']') { 10684 do { 10685 if (this.peek(']')) { 10686 // Support trailing commas per ES5.1. 10687 break; 10688 } 10689 var elementFn = this.expression(); 10690 elementFns.push(elementFn); 10691 if (!elementFn.constant) { 10692 allConstant = false; 10693 } 10694 } while (this.expect(',')); 10695 } 10696 this.consume(']'); 10697 10698 return extend(function (self, locals) { 10699 var array = []; 10700 for (var i = 0; i < elementFns.length; i++) { 10701 array.push(elementFns[i](self, locals)); 10702 } 10703 return array; 10704 }, { 10705 literal: true, 10706 constant: allConstant 10707 }); 10708 }, 10709 10710 object: function () { 10711 var keyValues = []; 10712 var allConstant = true; 10713 if (this.peekToken().text !== '}') { 10714 do { 10715 if (this.peek('}')) { 10716 // Support trailing commas per ES5.1. 10717 break; 10718 } 10719 var token = this.expect(), 10720 key = token.string || token.text; 10721 this.consume(':'); 10722 var value = this.expression(); 10723 keyValues.push({key: key, value: value}); 10724 if (!value.constant) { 10725 allConstant = false; 10726 } 10727 } while (this.expect(',')); 10728 } 10729 this.consume('}'); 10730 10731 return extend(function (self, locals) { 10732 var object = {}; 10733 for (var i = 0; i < keyValues.length; i++) { 10734 var keyValue = keyValues[i]; 10735 object[keyValue.key] = keyValue.value(self, locals); 10736 } 10737 return object; 10738 }, { 10739 literal: true, 10740 constant: allConstant 10741 }); 10742 } 10743 }; 10744 10745
10746////////////////////////////////////////////////// 10747// Parser helper functions 10748////////////////////////////////////////////////// 10749 10750 function setter(obj, path, setValue, fullExp, options) { 10751 //needed? 10752 options = options || {}; 10753 10754 var element = path.split('.'), key; 10755 for (var i = 0; element.length > 1; i++) { 10756 key = ensureSafeMemberName(element.shift(), fullExp); 10757 var propertyObj = obj[key]; 10758 if (!propertyObj) { 10759 propertyObj = {}; 10760 obj[key] = propertyObj; 10761 } 10762 obj = propertyObj; 10763 if (obj.then && options.unwrapPromises) { 10764 promiseWarning(fullExp); 10765 if (!("$$v" in obj)) { 10766 (function (promise) { 10767 promise.then(function (val) { 10768 promise.$$v = val; 10769 }); 10770 })(obj); 10771 } 10772 if (obj.$$v === undefined) { 10773 obj.$$v = {}; 10774 } 10775 obj = obj.$$v; 10776 } 10777 } 10778 key = ensureSafeMemberName(element.shift(), fullExp); 10779 obj[key] = setValue; 10780 return setValue; 10781 } 10782 10783 var getterFnCache = {}; 10784 10785 /** 10786 * Implementation of the "Black Hole" variant from: 10787 * - http://jsperf.com/angularjs-parse-getter/4 10788 * - http://jsperf.com/path-evaluation-simplified/7 10789 */ 10790 function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { 10791 ensureSafeMemberName(key0, fullExp); 10792 ensureSafeMemberName(key1, fullExp); 10793 ensureSafeMemberName(key2, fullExp); 10794 ensureSafeMemberName(key3, fullExp); 10795 ensureSafeMemberName(key4, fullExp); 10796 10797 return !options.unwrapPromises 10798 ? function cspSafeGetter(scope, locals) { 10799 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; 10800 10801 if (pathVal == null) return pathVal; 10802 pathVal = pathVal[key0]; 10803 10804 if (!key1) return pathVal; 10805 if (pathVal == null) return undefined; 10806 pathVal = pathVal[key1]; 10807 10808 if (!key2) return pathVal; 10809 if (pathVal == null) return undefined; 10810 pathVal = pathVal[key2]; 10811 10812 if (!key3) return pathVal; 10813 if (pathVal == null) return undefined; 10814 pathVal = pathVal[key3]; 10815 10816 if (!key4) return pathVal; 10817 if (pathVal == null) return undefined; 10818 pathVal = pathVal[key4]; 10819 10820 return pathVal; 10821 } 10822 : function cspSafePromiseEnabledGetter(scope, locals) { 10823 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, 10824 promise; 10825 10826 if (pathVal == null) return pathVal; 10827 10828 pathVal = pathVal[key0]; 10829 if (pathVal && pathVal.then) { 10830 promiseWarning(fullExp); 10831 if (!("$$v" in pathVal)) { 10832 promise = pathVal; 10833 promise.$$v = undefined; 10834 promise.then(function (val) { 10835 promise.$$v = val; 10836 }); 10837 } 10838 pathVal = pathVal.$$v; 10839 } 10840 10841 if (!key1) return pathVal; 10842 if (pathVal == null) return undefined; 10843 pathVal = pathVal[key1]; 10844 if (pathVal && pathVal.then) { 10845 promiseWarning(fullExp); 10846 if (!("$$v" in pathVal)) { 10847 promise = pathVal; 10848 promise.$$v = undefined; 10849 promise.then(function (val) { 10850 promise.$$v = val; 10851 }); 10852 } 10853 pathVal = pathVal.$$v; 10854 } 10855 10856 if (!key2) return pathVal; 10857 if (pathVal == null) return undefined; 10858 pathVal = pathVal[key2]; 10859 if (pathVal && pathVal.then) { 10860 promiseWarning(fullExp); 10861 if (!("$$v" in pathVal)) { 10862 promise = pathVal; 10863 promise.$$v = undefined; 10864 promise.then(function (val) { 10865 promise.$$v = val; 10866 }); 10867 } 10868 pathVal = pathVal.$$v; 10869 } 10870 10871 if (!key3) return pathVal; 10872 if (pathVal == null) return undefined; 10873 pathVal = pathVal[key3]; 10874 if (pathVal && pathVal.then) { 10875 promiseWarning(fullExp); 10876 if (!("$$v" in pathVal)) { 10877 promise = pathVal; 10878 promise.$$v = undefined; 10879 promise.then(function (val) { 10880 promise.$$v = val; 10881 }); 10882 } 10883 pathVal = pathVal.$$v; 10884 } 10885 10886 if (!key4) return pathVal; 10887 if (pathVal == null) return undefined; 10888 pathVal = pathVal[key4]; 10889 if (pathVal && pathVal.then) { 10890 promiseWarning(fullExp); 10891 if (!("$$v" in pathVal)) { 10892 promise = pathVal; 10893 promise.$$v = undefined; 10894 promise.then(function (val) { 10895 promise.$$v = val; 10896 }); 10897 } 10898 pathVal = pathVal.$$v; 10899 } 10900 return pathVal; 10901 }; 10902 } 10903 10904 function simpleGetterFn1(key0, fullExp) { 10905 ensureSafeMemberName(key0, fullExp); 10906 10907 return function simpleGetterFn1(scope, locals) { 10908 if (scope == null) return undefined; 10909 return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; 10910 }; 10911 } 10912 10913 function simpleGetterFn2(key0, key1, fullExp) { 10914 ensureSafeMemberName(key0, fullExp); 10915 ensureSafeMemberName(key1, fullExp); 10916 10917 return function simpleGetterFn2(scope, locals) { 10918 if (scope == null) return undefined; 10919 scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; 10920 return scope == null ? undefined : scope[key1]; 10921 }; 10922 } 10923 10924 function getterFn(path, options, fullExp) { 10925 // Check whether the cache has this getter already. 10926 // We can use hasOwnProperty directly on the cache because we ensure, 10927 // see below, that the cache never stores a path called 'hasOwnProperty' 10928 if (getterFnCache.hasOwnProperty(path)) { 10929 return getterFnCache[path]; 10930 } 10931 10932 var pathKeys = path.split('.'), 10933 pathKeysLength = pathKeys.length, 10934 fn; 10935 10936 // When we have only 1 or 2 tokens, use optimized special case closures. 10937 // http://jsperf.com/angularjs-parse-getter/6 10938 if (!options.unwrapPromises && pathKeysLength === 1) { 10939 fn = simpleGetterFn1(pathKeys[0], fullExp); 10940 } else if (!options.unwrapPromises && pathKeysLength === 2) { 10941 fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp); 10942 } else if (options.csp) { 10943 if (pathKeysLength < 6) { 10944 fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, 10945 options); 10946 } else { 10947 fn = function (scope, locals) { 10948 var i = 0, val; 10949 do { 10950 val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], 10951 pathKeys[i++], fullExp, options)(scope, locals); 10952 10953 locals = undefined; // clear after first iteration 10954 scope = val; 10955 } while (i < pathKeysLength); 10956 return val; 10957 }; 10958 } 10959 } else { 10960 var code = 'var p;\n';
10961 forEach(pathKeys, function (key, index) { 10962 ensureSafeMemberName(key, fullExp); 10963 code += 'if(s == null) return undefined;\n' + 10964 's=' + (index 10965 // we simply dereference 's' on any .dot notation 10966 ? 's' 10967 // but if we are first then we check locals first, and if so read it first 10968 : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + 10969 (options.unwrapPromises 10970 ? 'if (s && s.then) {\n' + 10971 ' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' + 10972 ' if (!("$$v" in s)) {\n' + 10973 ' p=s;\n' + 10974 ' p.$$v = undefined;\n' + 10975 ' p.then(function(v) {p.$$v=v;});\n' + 10976 '}\n' + 10977 ' s=s.$$v\n' + 10978 '}\n' 10979 : ''); 10980 }); 10981 code += 'return s;'; 10982 10983 /* jshint -W054 */ 10984 var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning 10985 /* jshint +W054 */ 10986 evaledFnGetter.toString = valueFn(code); 10987 fn = options.unwrapPromises ? function (scope, locals) { 10988 return evaledFnGetter(scope, locals, promiseWarning); 10989 } : evaledFnGetter; 10990 } 10991 10992 // Only cache the value if it's not going to mess up the cache object 10993 // This is more performant that using Object.prototype.hasOwnProperty.call 10994 if (path !== 'hasOwnProperty') { 10995 getterFnCache[path] = fn; 10996 } 10997 return fn; 10998 } 10999 11000/////////////////////////////////// 11001 11002 /** 11003 * @ngdoc service 11004 * @name $parse 11005 * @kind function 11006 * 11007 * @description 11008 * 11009 * Converts Angular {@link guide/expression expression} into a function. 11010 * 11011 * ```js 11012 * var getter = $parse('user.name'); 11013 * var setter = getter.assign; 11014 * var context = {user:{name:'angular'}}; 11015 * var locals = {user:{name:'local'}}; 11016 * 11017 * expect(getter(context)).toEqual('angular'); 11018 * setter(context, 'newValue'); 11019 * expect(context.user.name).toEqual('newValue'); 11020 * expect(getter(context, locals)).toEqual('local'); 11021 * ``` 11022 * 11023 * 11024 * @param {string} expression String expression to compile. 11025 * @returns {function(context, locals)} a function which represents the compiled expression: 11026 * 11027 * * `context` â `{object}` â an object against which any expressions embedded in the strings 11028 * are evaluated against (typically a scope object). 11029 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 11030 * `context`. 11031 * 11032 * The returned function also has the following properties: 11033 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 11034 * literal. 11035 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 11036 * constant literals. 11037 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 11038 * set to a function to change its value on the given context. 11039 * 11040 */ 11041 11042 11043 /** 11044 * @ngdoc provider 11045 * @name $parseProvider 11046 * @function 11047 * 11048 * @description 11049 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 11050 * service. 11051 */ 11052 function $ParseProvider() { 11053 var cache = {}; 11054 11055 var $parseOptions = { 11056 csp: false, 11057 unwrapPromises: false, 11058 logPromiseWarnings: true 11059 }; 11060 11061 11062 /** 11063 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 11064 * 11065 * @ngdoc method 11066 * @name $parseProvider#unwrapPromises 11067 * @description 11068 * 11069 * **This feature is deprecated, see deprecation notes below for more info** 11070 * 11071 * If set to true (default is false), $parse will unwrap promises automatically when a promise is 11072 * found at any part of the expression. In other words, if set to true, the expression will always
11073 * result in a non-promise value. 11074 * 11075 * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, 11076 * the fulfillment value is used in place of the promise while evaluating the expression. 11077 * 11078 * **Deprecation notice** 11079 * 11080 * This is a feature that didn't prove to be wildly useful or popular, primarily because of the 11081 * dichotomy between data access in templates (accessed as raw values) and controller code 11082 * (accessed as promises). 11083 * 11084 * In most code we ended up resolving promises manually in controllers anyway and thus unifying 11085 * the model access there. 11086 * 11087 * Other downsides of automatic promise unwrapping: 11088 * 11089 * - when building components it's often desirable to receive the raw promises 11090 * - adds complexity and slows down expression evaluation 11091 * - makes expression code pre-generation unattractive due to the amount of code that needs to be 11092 * generated 11093 * - makes IDE auto-completion and tool support hard 11094 * 11095 * **Warning Logs** 11096 * 11097 * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a 11098 * promise (to reduce the noise, each expression is logged only once). To disable this logging use 11099 * `$parseProvider.logPromiseWarnings(false)` api. 11100 * 11101 * 11102 * @param {boolean=} value New value. 11103 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 11104 * setter. 11105 */ 11106 this.unwrapPromises = function (value) { 11107 if (isDefined(value)) { 11108 $parseOptions.unwrapPromises = !!value; 11109 return this; 11110 } else { 11111 return $parseOptions.unwrapPromises; 11112 } 11113 }; 11114 11115 11116 /** 11117 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 11118 * 11119 * @ngdoc method 11120 * @name $parseProvider#logPromiseWarnings 11121 * @description 11122 * 11123 * Controls whether Angular should log a warning on any encounter of a promise in an expression. 11124 * 11125 * The default is set to `true`. 11126 * 11127 * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. 11128 * 11129 * @param {boolean=} value New value. 11130 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 11131 * setter. 11132 */ 11133 this.logPromiseWarnings = function (value) { 11134 if (isDefined(value)) { 11135 $parseOptions.logPromiseWarnings = value; 11136 return this; 11137 } else { 11138 return $parseOptions.logPromiseWarnings; 11139 } 11140 }; 11141 11142 11143 this.$get = ['$filter', '$sniffer', '$log', function ($filter, $sniffer, $log) { 11144 $parseOptions.csp = $sniffer.csp; 11145 11146 promiseWarning = function promiseWarningFn(fullExp) { 11147 if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; 11148 promiseWarningCache[fullExp] = true; 11149 $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + 11150 'Automatic unwrapping of promises in Angular expressions is deprecated.'); 11151 }; 11152 11153 return function (exp) { 11154 var parsedExpression; 11155 11156 switch (typeof exp) { 11157 case 'string': 11158 11159 if (cache.hasOwnProperty(exp)) { 11160 return cache[exp]; 11161 } 11162 11163 var lexer = new Lexer($parseOptions); 11164 var parser = new Parser(lexer, $filter, $parseOptions); 11165 parsedExpression = parser.parse(exp, false); 11166 11167 if (exp !== 'hasOwnProperty') { 11168 // Only cache the value if it's not going to mess up the cache object 11169 // This is more performant that using Object.prototype.hasOwnProperty.call 11170 cache[exp] = parsedExpression; 11171 } 11172 11173 return parsedExpression; 11174 11175 case 'function': 11176 return exp; 11177 11178 default: 11179 return noop; 11180 } 11181 }; 11182 }]; 11183 } 11184 11185 /** 11186 * @ngdoc service 11187 * @name $q 11188 * @requires $rootScope 11189 *
11190 * @description 11191 * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). 11192 * 11193 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 11194 * interface for interacting with an object that represents the result of an action that is 11195 * performed asynchronously, and may or may not be finished at any given point in time. 11196 * 11197 * From the perspective of dealing with error handling, deferred and promise APIs are to 11198 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 11199 * 11200 * ```js 11201 * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` 11202 * // are available in the current lexical scope (they could have been injected or passed in). 11203 * 11204 * function asyncGreet(name) { 11205 * var deferred = $q.defer(); 11206 * 11207 * setTimeout(function() { 11208 * // since this fn executes async in a future turn of the event loop, we need to wrap 11209 * // our code into an $apply call so that the model changes are properly observed. 11210 * scope.$apply(function() { 11211 * deferred.notify('About to greet ' + name + '.'); 11212 * 11213 * if (okToGreet(name)) { 11214 * deferred.resolve('Hello, ' + name + '!'); 11215 * } else { 11216 * deferred.reject('Greeting ' + name + ' is not allowed.'); 11217 * } 11218 * }); 11219 * }, 1000); 11220 * 11221 * return deferred.promise; 11222 * } 11223 * 11224 * var promise = asyncGreet('Robin Hood'); 11225 * promise.then(function(greeting) { 11226 * alert('Success: ' + greeting); 11227 * }, function(reason) { 11228 * alert('Failed: ' + reason); 11229 * }, function(update) { 11230 * alert('Got notification: ' + update); 11231 * }); 11232 * ``` 11233 * 11234 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 11235 * comes in the way of guarantees that promise and deferred APIs make, see 11236 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 11237 * 11238 * Additionally the promise api allows for composition that is very hard to do with the 11239 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 11240 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 11241 * section on serial or parallel joining of promises. 11242 * 11243 * 11244 * # The Deferred API 11245 * 11246 * A new instance of deferred is constructed by calling `$q.defer()`. 11247 * 11248 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 11249 * that can be used for signaling the successful or unsuccessful completion, as well as the status 11250 * of the task. 11251 * 11252 * **Methods** 11253 * 11254 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 11255 * constructed via `$q.reject`, the promise will be rejected instead. 11256 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 11257 * resolving it with a rejection constructed via `$q.reject`. 11258 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 11259 * multiple times before the promise is either resolved or rejected. 11260 * 11261 * **Properties** 11262 * 11263 * - promise â `{Promise}` â promise object associated with this deferred. 11264 * 11265 * 11266 * # The Promise API 11267 * 11268 * A new promise instance is created when a deferred instance is created and can be retrieved by 11269 * calling `deferred.promise`. 11270 * 11271 * The purpose of the promise object is to allow for interested parties to get access to the result 11272 * of the deferred task when it completes. 11273 * 11274 * **Methods** 11275 * 11276 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 11277 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 11278 * as soon as the result is available. The callbacks are called with a single argument: the result 11279 * or rejection reason. Additionally, the notify callback may be called zero or more times to 11280 * provide a progress indication, before the promise is resolved or rejected. 11281 * 11282 * This method *returns a new promise* which is resolved or rejected via the return value of the 11283 * `successCallback`, `errorCallback`. It also notifies via the return value of the 11284 * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback 11285 * method. 11286 * 11287 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 11288 * 11289 * - `finally(callback)` â allows you to observe either the fulfillment or rejection of a promise, 11290 * but to do so without modifying the final value. This is useful to release resources or do some 11291 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 11292 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 11293 * more information. 11294 * 11295 * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as 11296 * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to
11297 * make your code IE8 and Android 2.x compatible. 11298 * 11299 * # Chaining promises 11300 * 11301 * Because calling the `then` method of a promise returns a new derived promise, it is easily 11302 * possible to create a chain of promises: 11303 * 11304 * ```js 11305 * promiseB = promiseA.then(function(result) { 11306 * return result + 1; 11307 * }); 11308 * 11309 * // promiseB will be resolved immediately after promiseA is resolved and its value 11310 * // will be the result of promiseA incremented by 1 11311 * ``` 11312 * 11313 * It is possible to create chains of any length and since a promise can be resolved with another 11314 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 11315 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 11316 * $http's response interceptors. 11317 * 11318 * 11319 * # Differences between Kris Kowal's Q and $q 11320 * 11321 * There are two main differences: 11322 * 11323 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 11324 * mechanism in angular, which means faster propagation of resolution or rejection into your 11325 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 11326 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
11327 * all the important functionality needed for common async tasks. 11328 * 11329 * # Testing 11330 * 11331 * ```js 11332 * it('should simulate promise', inject(function($q, $rootScope) { 11333 * var deferred = $q.defer(); 11334 * var promise = deferred.promise; 11335 * var resolvedValue; 11336 * 11337 * promise.then(function(value) { resolvedValue = value; }); 11338 * expect(resolvedValue).toBeUndefined(); 11339 * 11340 * // Simulate resolving of promise 11341 * deferred.resolve(123); 11342 * // Note that the 'then' function does not get called synchronously. 11343 * // This is because we want the promise API to always be async, whether or not 11344 * // it got called synchronously or asynchronously. 11345 * expect(resolvedValue).toBeUndefined(); 11346 * 11347 * // Propagate promise resolution to 'then' functions using $apply(). 11348 * $rootScope.$apply(); 11349 * expect(resolvedValue).toEqual(123); 11350 * })); 11351 * ``` 11352 */ 11353 function $QProvider() { 11354 11355 this.$get = ['$rootScope', '$exceptionHandler', function ($rootScope, $exceptionHandler) { 11356 return qFactory(function (callback) { 11357 $rootScope.$evalAsync(callback); 11358 }, $exceptionHandler); 11359 }]; 11360 } 11361 11362 11363 /** 11364 * Constructs a promise manager. 11365 * 11366 * @param {function(Function)} nextTick Function for executing functions in the next turn. 11367 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 11368 * debugging purposes. 11369 * @returns {object} Promise manager. 11370 */ 11371 function qFactory(nextTick, exceptionHandler) { 11372 11373 /** 11374 * @ngdoc method 11375 * @name $q#defer 11376 * @function 11377 * 11378 * @description 11379 * Creates a `Deferred` object which represents a task which will finish in the future. 11380 * 11381 * @returns {Deferred} Returns a new instance of deferred. 11382 */ 11383 var defer = function () { 11384 var pending = [], 11385 value, deferred; 11386 11387 deferred = { 11388 11389 resolve: function (val) { 11390 if (pending) { 11391 var callbacks = pending; 11392 pending = undefined; 11393 value = ref(val); 11394 11395 if (callbacks.length) { 11396 nextTick(function () { 11397 var callback; 11398 for (var i = 0, ii = callbacks.length; i < ii; i++) { 11399 callback = callbacks[i]; 11400 value.then(callback[0], callback[1], callback[2]); 11401 } 11402 }); 11403 } 11404 } 11405 }, 11406 11407 11408 reject: function (reason) { 11409 deferred.resolve(createInternalRejectedPromise(reason)); 11410 }, 11411 11412 11413 notify: function (progress) { 11414 if (pending) { 11415 var callbacks = pending; 11416 11417 if (pending.length) { 11418 nextTick(function () { 11419 var callback; 11420 for (var i = 0, ii = callbacks.length; i < ii; i++) { 11421 callback = callbacks[i]; 11422 callback[2](progress); 11423 } 11424 }); 11425 } 11426 } 11427 }, 11428 11429 11430 promise: { 11431 then: function (callback, errback, progressback) { 11432 var result = defer(); 11433 11434 var wrappedCallback = function (value) { 11435 try { 11436 result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); 11437 } catch (e) { 11438 result.reject(e); 11439 exceptionHandler(e); 11440 } 11441 }; 11442 11443 var wrappedErrback = function (reason) { 11444 try { 11445 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11446 } catch (e) { 11447 result.reject(e); 11448 exceptionHandler(e); 11449 } 11450 }; 11451 11452 var wrappedProgressback = function (progress) { 11453 try { 11454 result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); 11455 } catch (e) { 11456 exceptionHandler(e); 11457 } 11458 }; 11459 11460 if (pending) { 11461 pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); 11462 } else { 11463 value.then(wrappedCallback, wrappedErrback, wrappedProgressback); 11464 } 11465 11466 return result.promise; 11467 }, 11468 11469 "catch": function (callback) { 11470 return this.then(null, callback); 11471 }, 11472 11473 "finally": function (callback) { 11474 11475 function makePromise(value, resolved) { 11476 var result = defer(); 11477 if (resolved) { 11478 result.resolve(value); 11479 } else { 11480 result.reject(value); 11481 } 11482 return result.promise; 11483 } 11484 11485 function handleCallback(value, isResolved) { 11486 var callbackOutput = null; 11487 try { 11488 callbackOutput = (callback || defaultCallback)(); 11489 } catch (e) { 11490 return makePromise(e, false); 11491 } 11492 if (callbackOutput && isFunction(callbackOutput.then)) { 11493 return callbackOutput.then(function () { 11494 return makePromise(value, isResolved); 11495 }, function (error) { 11496 return makePromise(error, false); 11497 }); 11498 } else { 11499 return makePromise(value, isResolved); 11500 } 11501 } 11502 11503 return this.then(function (value) { 11504 return handleCallback(value, true); 11505 }, function (error) { 11506 return handleCallback(error, false); 11507 }); 11508 } 11509 } 11510 }; 11511 11512 return deferred; 11513 }; 11514 11515 11516 var ref = function (value) { 11517 if (value && isFunction(value.then)) return value; 11518 return { 11519 then: function (callback) { 11520 var result = defer(); 11521 nextTick(function () { 11522 result.resolve(callback(value)); 11523 }); 11524 return result.promise; 11525 } 11526 }; 11527 }; 11528 11529 11530 /** 11531 * @ngdoc method 11532 * @name $q#reject 11533 * @function 11534 *
11535 * @description 11536 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 11537 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 11538 * a promise chain, you don't need to worry about it. 11539 * 11540 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 11541 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 11542 * a promise error callback and you want to forward the error to the promise derived from the 11543 * current promise, you have to "rethrow" the error by returning a rejection constructed via 11544 * `reject`. 11545 * 11546 * ```js 11547 * promiseB = promiseA.then(function(result) { 11548 * // success: do something and resolve promiseB 11549 * // with the old or a new result 11550 * return result; 11551 * }, function(reason) { 11552 * // error: handle the error if possible and 11553 * // resolve promiseB with newPromiseOrValue, 11554 * // otherwise forward the rejection to promiseB 11555 * if (canHandle(reason)) { 11556 * // handle the error and recover 11557 * return newPromiseOrValue; 11558 * } 11559 * return $q.reject(reason); 11560 * }); 11561 * ``` 11562 * 11563 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 11564 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 11565 */ 11566 var reject = function (reason) { 11567 var result = defer(); 11568 result.reject(reason); 11569 return result.promise; 11570 }; 11571 11572 var createInternalRejectedPromise = function (reason) { 11573 return { 11574 then: function (callback, errback) { 11575 var result = defer(); 11576 nextTick(function () { 11577 try { 11578 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11579 } catch (e) { 11580 result.reject(e); 11581 exceptionHandler(e); 11582 } 11583 }); 11584 return result.promise; 11585 } 11586 }; 11587 }; 11588 11589 11590 /** 11591 * @ngdoc method 11592 * @name $q#when 11593 * @function 11594 * 11595 * @description 11596 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 11597 * This is useful when you are dealing with an object that might or might not be a promise, or if 11598 * the promise comes from a source that can't be trusted. 11599 * 11600 * @param {*} value Value or a promise 11601 * @returns {Promise} Returns a promise of the passed value or promise 11602 */ 11603 var when = function (value, callback, errback, progressback) { 11604 var result = defer(), 11605 done; 11606 11607 var wrappedCallback = function (value) { 11608 try { 11609 return (isFunction(callback) ? callback : defaultCallback)(value); 11610 } catch (e) { 11611 exceptionHandler(e); 11612 return reject(e); 11613 } 11614 }; 11615 11616 var wrappedErrback = function (reason) { 11617 try { 11618 return (isFunction(errback) ? errback : defaultErrback)(reason); 11619 } catch (e) { 11620 exceptionHandler(e); 11621 return reject(e); 11622 } 11623 }; 11624 11625 var wrappedProgressback = function (progress) { 11626 try { 11627 return (isFunction(progressback) ? progressback : defaultCallback)(progress); 11628 } catch (e) { 11629 exceptionHandler(e); 11630 } 11631 }; 11632 11633 nextTick(function () { 11634 ref(value).then(function (value) { 11635 if (done) return; 11636 done = true; 11637 result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); 11638 }, function (reason) { 11639 if (done) return; 11640 done = true; 11641 result.resolve(wrappedErrback(reason)); 11642 }, function (progress) { 11643 if (done) return; 11644 result.notify(wrappedProgressback(progress)); 11645 }); 11646 }); 11647 11648 return result.promise; 11649 }; 11650 11651 11652 function defaultCallback(value) { 11653 return value; 11654 } 11655 11656 11657 function defaultErrback(reason) { 11658 return reject(reason); 11659 } 11660 11661 11662 /** 11663 * @ngdoc method 11664 * @name $q#all 11665 * @function 11666 *
11667 * @description 11668 * Combines multiple promises into a single promise that is resolved when all of the input 11669 * promises are resolved. 11670 * 11671 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 11672 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 11673 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 11674 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 11675 * with the same rejection value. 11676 */ 11677 function all(promises) { 11678 var deferred = defer(), 11679 counter = 0, 11680 results = isArray(promises) ? [] : {}; 11681 11682 forEach(promises, function (promise, key) { 11683 counter++; 11684 ref(promise).then(function (value) { 11685 if (results.hasOwnProperty(key)) return; 11686 results[key] = value; 11687 if (!(--counter)) deferred.resolve(results); 11688 }, function (reason) { 11689 if (results.hasOwnProperty(key)) return; 11690 deferred.reject(reason); 11691 }); 11692 }); 11693 11694 if (counter === 0) { 11695 deferred.resolve(results); 11696 } 11697 11698 return deferred.promise; 11699 } 11700 11701 return { 11702 defer: defer, 11703 reject: reject, 11704 when: when, 11705 all: all 11706 }; 11707 } 11708 11709 function $$RAFProvider() { //rAF 11710 this.$get = ['$window', '$timeout', function ($window, $timeout) { 11711 var requestAnimationFrame = $window.requestAnimationFrame || 11712 $window.webkitRequestAnimationFrame || 11713 $window.mozRequestAnimationFrame; 11714 11715 var cancelAnimationFrame = $window.cancelAnimationFrame || 11716 $window.webkitCancelAnimationFrame || 11717 $window.mozCancelAnimationFrame || 11718 $window.webkitCancelRequestAnimationFrame; 11719 11720 var rafSupported = !!requestAnimationFrame; 11721 var raf = rafSupported 11722 ? function (fn) { 11723 var id = requestAnimationFrame(fn); 11724 return function () { 11725 cancelAnimationFrame(id); 11726 }; 11727 } 11728 : function (fn) { 11729 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 11730 return function () { 11731 $timeout.cancel(timer); 11732 }; 11733 }; 11734 11735 raf.supported = rafSupported; 11736 11737 return raf; 11738 }]; 11739 } 11740 11741 /** 11742 * DESIGN NOTES 11743 * 11744 * The design decisions behind the scope are heavily favored for speed and memory consumption. 11745 * 11746 * The typical use of scope is to watch the expressions, which most of the time return the same 11747 * value as last time so we optimize the operation. 11748 * 11749 * Closures construction is expensive in terms of speed as well as memory: 11750 * - No closures, instead use prototypical inheritance for API 11751 * - Internal state needs to be stored on scope directly, which means that private state is 11752 * exposed as $$____ properties 11753 * 11754 * Loop operations are optimized by using while(count--) { ... } 11755 * - this means that in order to keep the same order of execution as addition we have to add 11756 * items to the array at the beginning (shift) instead of at the end (push) 11757 * 11758 * Child scopes are created and removed often 11759 * - Using an array would be slow since inserts in middle are expensive so we use linked list 11760 * 11761 * There are few watches then a lot of observers. This is why you don't want the observer to be 11762 * implemented in the same way as watch. Watch requires return of initialization function which 11763 * are expensive to construct. 11764 */ 11765 11766 11767 /** 11768 * @ngdoc provider 11769 * @name $rootScopeProvider 11770 * @description 11771 * 11772 * Provider for the $rootScope service. 11773 */ 11774 11775 /** 11776 * @ngdoc method 11777 * @name $rootScopeProvider#digestTtl
11778 * @description 11779 * 11780 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 11781 * assuming that the model is unstable. 11782 * 11783 * The current default is 10 iterations. 11784 * 11785 * In complex applications it's possible that the dependencies between `$watch`s will result in 11786 * several digest iterations. However if an application needs more than the default 10 digest 11787 * iterations for its model to stabilize then you should investigate what is causing the model to 11788 * continuously change during the digest. 11789 * 11790 * Increasing the TTL could have performance implications, so you should not change it without 11791 * proper justification. 11792 * 11793 * @param {number} limit The number of digest iterations. 11794 */ 11795 11796 11797 /** 11798 * @ngdoc service 11799 * @name $rootScope 11800 * @description 11801 * 11802 * Every application has a single root {@link ng.$rootScope.Scope scope}. 11803 * All other scopes are descendant scopes of the root scope. Scopes provide separation 11804 * between the model and the view, via a mechanism for watching the model for changes. 11805 * They also provide an event emission/broadcast and subscription facility. See the 11806 * {@link guide/scope developer guide on scopes}. 11807 */ 11808 function $RootScopeProvider() { 11809 var TTL = 10; 11810 var $rootScopeMinErr = minErr('$rootScope'); 11811 var lastDirtyWatch = null; 11812 11813 this.digestTtl = function (value) { 11814 if (arguments.length) { 11815 TTL = value; 11816 } 11817 return TTL; 11818 }; 11819 11820 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 11821 function ($injector, $exceptionHandler, $parse, $browser) { 11822 11823 /** 11824 * @ngdoc type 11825 * @name $rootScope.Scope 11826 * 11827 * @description 11828 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 11829 * {@link auto.$injector $injector}. Child scopes are created using the 11830 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 11831 * compiled HTML template is executed.) 11832 * 11833 * Here is a simple scope snippet to show how you can interact with the scope. 11834 * ```html 11835 * <file src="./test/ng/rootScopeSpec.js" tag="docs1" /> 11836 * ``` 11837 * 11838 * # Inheritance 11839 * A scope can inherit from a parent scope, as in this example: 11840 * ```js 11841 var parent = $rootScope; 11842 var child = parent.$new(); 11843 11844 parent.salutation = "Hello"; 11845 child.name = "World"; 11846 expect(child.salutation).toEqual('Hello'); 11847 11848 child.salutation = "Welcome"; 11849 expect(child.salutation).toEqual('Welcome'); 11850 expect(parent.salutation).toEqual('Hello'); 11851 * ``` 11852 * 11853 * 11854 * @param {Object.<string, function()>=} providers Map of service factory which need to be 11855 * provided for the current scope. Defaults to {@link ng}. 11856 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 11857 * append/override services provided by `providers`. This is handy 11858 * when unit-testing and having the need to override a default 11859 * service. 11860 * @returns {Object} Newly created scope. 11861 * 11862 */ 11863 function Scope() { 11864 this.$id = nextUid(); 11865 this.$$phase = this.$parent = this.$$watchers = 11866 this.$$nextSibling = this.$$prevSibling = 11867 this.$$childHead = this.$$childTail = null; 11868 this['this'] = this.$root = this; 11869 this.$$destroyed = false;
11870 this.$$asyncQueue = []; 11871 this.$$postDigestQueue = []; 11872 this.$$listeners = {}; 11873 this.$$listenerCount = {}; 11874 this.$$isolateBindings = {}; 11875 } 11876 11877 /** 11878 * @ngdoc property 11879 * @name $rootScope.Scope#$id 11880 * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for 11881 * debugging. 11882 */ 11883 11884 11885 Scope.prototype = { 11886 constructor: Scope, 11887 /** 11888 * @ngdoc method 11889 * @name $rootScope.Scope#$new 11890 * @function 11891 * 11892 * @description 11893 * Creates a new child {@link ng.$rootScope.Scope scope}. 11894 * 11895 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and 11896 * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the 11897 * scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 11898 * 11899 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 11900 * desired for the scope and its child scopes to be permanently detached from the parent and 11901 * thus stop participating in model change detection and listener notification by invoking. 11902 * 11903 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 11904 * parent scope. The scope is isolated, as it can not see parent scope properties. 11905 * When creating widgets, it is useful for the widget to not accidentally read parent 11906 * state. 11907 * 11908 * @returns {Object} The newly created child scope. 11909 * 11910 */ 11911 $new: function (isolate) { 11912 var ChildScope, 11913 child; 11914 11915 if (isolate) { 11916 child = new Scope(); 11917 child.$root = this.$root; 11918 // ensure that there is just one async queue per $rootScope and its children 11919 child.$$asyncQueue = this.$$asyncQueue; 11920 child.$$postDigestQueue = this.$$postDigestQueue; 11921 } else { 11922 ChildScope = function () { 11923 }; // should be anonymous; This is so that when the minifier munges 11924 // the name it does not become random set of chars. This will then show up as class 11925 // name in the web inspector. 11926 ChildScope.prototype = this; 11927 child = new ChildScope(); 11928 child.$id = nextUid(); 11929 } 11930 child['this'] = child; 11931 child.$$listeners = {}; 11932 child.$$listenerCount = {}; 11933 child.$parent = this; 11934 child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null; 11935 child.$$prevSibling = this.$$childTail; 11936 if (this.$$childHead) { 11937 this.$$childTail.$$nextSibling = child; 11938 this.$$childTail = child; 11939 } else { 11940 this.$$childHead = this.$$childTail = child; 11941 } 11942 return child; 11943 }, 11944 11945 /** 11946 * @ngdoc method 11947 * @name $rootScope.Scope#$watch 11948 * @function 11949 *
11950 * @description 11951 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 11952 * 11953 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 11954 * $digest()} and should return the value that will be watched. (Since 11955 * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the 11956 * `watchExpression` can execute multiple times per 11957 * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) 11958 * - The `listener` is called only when the value from the current `watchExpression` and the 11959 * previous call to `watchExpression` are not equal (with the exception of the initial run, 11960 * see below). The inequality is determined according to 11961 * {@link angular.equals} function. To save the value of the object for later comparison, 11962 * the {@link angular.copy} function is used. It also means that watching complex options 11963 * will have adverse memory and performance implications. 11964 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 11965 * This is achieved by rerunning the watchers until no changes are detected. The rerun 11966 * iteration limit is 10 to prevent an infinite loop deadlock. 11967 * 11968 * 11969 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 11970 * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` 11971 * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a 11972 * change is detected, be prepared for multiple calls to your listener.) 11973 * 11974 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 11975 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 11976 * watcher. In rare cases, this is undesirable because the listener is called when the result 11977 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 11978 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 11979 * listener was called due to initialization. 11980 * 11981 * The example below contains an illustration of using a function as your $watch listener 11982 * 11983 * 11984 * # Example 11985 * ```js 11986 // let's assume that scope was dependency injected as the $rootScope 11987 var scope = $rootScope; 11988 scope.name = 'misko'; 11989 scope.counter = 0; 11990 11991 expect(scope.counter).toEqual(0); 11992 scope.$watch('name', function(newValue, oldValue) { 11993 scope.counter = scope.counter + 1; 11994 }); 11995 expect(scope.counter).toEqual(0); 11996 11997 scope.$digest(); 11998 // no variable change 11999 expect(scope.counter).toEqual(0); 12000 12001 scope.name = 'adam'; 12002 scope.$digest(); 12003 expect(scope.counter).toEqual(1); 12004 12005 12006 12007 // Using a listener function 12008 var food; 12009 scope.foodCounter = 0; 12010 expect(scope.foodCounter).toEqual(0); 12011 scope.$watch( 12012 // This is the listener function 12013 function() { return food; }, 12014 // This is the change handler 12015 function(newValue, oldValue) { 12016 if ( newValue !== oldValue ) { 12017 // Only increment the counter if the value changed 12018 scope.foodCounter = scope.foodCounter + 1; 12019 } 12020 } 12021 ); 12022 // No digest has been run so the counter will be zero 12023 expect(scope.foodCounter).toEqual(0); 12024 12025 // Run the digest but since food has not changed count will still be zero 12026 scope.$digest(); 12027 expect(scope.foodCounter).toEqual(0); 12028 12029 // Update food and run digest. Now the counter will increment 12030 food = 'cheeseburger'; 12031 scope.$digest(); 12032 expect(scope.foodCounter).toEqual(1); 12033 12034 * ``` 12035 * 12036 * 12037 * 12038 * @param {(function()|string)} watchExpression Expression that is evaluated on each 12039 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 12040 * a call to the `listener`. 12041 * 12042 * - `string`: Evaluated as {@link guide/expression expression} 12043 * - `function(scope)`: called with current `scope` as a parameter. 12044 * @param {(function()|string)=} listener Callback called whenever the return value of 12045 * the `watchExpression` changes. 12046 * 12047 * - `string`: Evaluated as {@link guide/expression expression} 12048 * - `function(newValue, oldValue, scope)`: called with current and previous values as 12049 * parameters. 12050 * 12051 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of 12052 * comparing for reference equality. 12053 * @returns {function()} Returns a deregistration function for this listener. 12054 */ 12055 $watch: function (watchExp, listener, objectEquality) { 12056 var scope = this, 12057 get = compileToFn(watchExp, 'watch'), 12058 array = scope.$$watchers, 12059 watcher = { 12060 fn: listener, 12061 last: initWatchVal, 12062 get: get, 12063 exp: watchExp, 12064 eq: !!objectEquality 12065 }; 12066 12067 lastDirtyWatch = null; 12068 12069 // in the case user pass string, we need to compile it, do we really need this ? 12070 if (!isFunction(listener)) { 12071 var listenFn = compileToFn(listener || noop, 'listener'); 12072 watcher.fn = function (newVal, oldVal, scope) { 12073 listenFn(scope); 12074 }; 12075 } 12076 12077 if (typeof watchExp == 'string' && get.constant) { 12078 var originalFn = watcher.fn; 12079 watcher.fn = function (newVal, oldVal, scope) { 12080 originalFn.call(this, newVal, oldVal, scope); 12081 arrayRemove(array, watcher); 12082 }; 12083 } 12084 12085 if (!array) { 12086 array = scope.$$watchers = []; 12087 } 12088 // we use unshift since we use a while loop in $digest for speed. 12089 // the while loop reads in reverse order. 12090 array.unshift(watcher); 12091 12092 return function () { 12093 arrayRemove(array, watcher); 12094 lastDirtyWatch = null; 12095 }; 12096 }, 12097 12098 12099 /** 12100 * @ngdoc method 12101 * @name $rootScope.Scope#$watchCollection 12102 * @function 12103 *
12104 * @description 12105 * Shallow watches the properties of an object and fires whenever any of the properties change 12106 * (for arrays, this implies watching the array items; for object maps, this implies watching 12107 * the properties). If a change is detected, the `listener` callback is fired. 12108 * 12109 * - The `obj` collection is observed via standard $watch operation and is examined on every 12110 * call to $digest() to see if any items have been added, removed, or moved. 12111 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 12112 * adding, removing, and moving items belonging to an object or array. 12113 * 12114 * 12115 * # Example 12116 * ```js 12117 $scope.names = ['igor', 'matias', 'misko', 'james']; 12118 $scope.dataCount = 4; 12119 12120 $scope.$watchCollection('names', function(newNames, oldNames) { 12121 $scope.dataCount = newNames.length; 12122 }); 12123 12124 expect($scope.dataCount).toEqual(4); 12125 $scope.$digest(); 12126 12127 //still at 4 ... no changes 12128 expect($scope.dataCount).toEqual(4); 12129 12130 $scope.names.pop(); 12131 $scope.$digest(); 12132 12133 //now there's been a change 12134 expect($scope.dataCount).toEqual(3); 12135 * ``` 12136 * 12137 * 12138 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 12139 * expression value should evaluate to an object or an array which is observed on each 12140 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 12141 * collection will trigger a call to the `listener`. 12142 * 12143 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 12144 * when a change is detected. 12145 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 12146 * - The `oldCollection` object is a copy of the former collection data. 12147 * Due to performance considerations, the`oldCollection` value is computed only if the 12148 * `listener` function declares two or more arguments. 12149 * - The `scope` argument refers to the current scope. 12150 * 12151 * @returns {function()} Returns a de-registration function for this listener. When the 12152 * de-registration function is executed, the internal watch operation is terminated. 12153 */ 12154 $watchCollection: function (obj, listener) { 12155 var self = this; 12156 // the current value, updated on each dirty-check run 12157 var newValue; 12158 // a shallow copy of the newValue from the last dirty-check run, 12159 // updated to match newValue during dirty-check run 12160 var oldValue; 12161 // a shallow copy of the newValue from when the last change happened 12162 var veryOldValue; 12163 // only track veryOldValue if the listener is asking for it 12164 var trackVeryOldValue = (listener.length > 1); 12165 var changeDetected = 0; 12166 var objGetter = $parse(obj); 12167 var internalArray = []; 12168 var internalObject = {}; 12169 var initRun = true; 12170 var oldLength = 0; 12171 12172 function $watchCollectionWatch() { 12173 newValue = objGetter(self); 12174 var newLength, key; 12175 12176 if (!isObject(newValue)) { // if primitive 12177 if (oldValue !== newValue) { 12178 oldValue = newValue; 12179 changeDetected++; 12180 } 12181 } else if (isArrayLike(newValue)) { 12182 if (oldValue !== internalArray) { 12183 // we are transitioning from something which was not an array into array.
12184 oldValue = internalArray; 12185 oldLength = oldValue.length = 0; 12186 changeDetected++; 12187 } 12188 12189 newLength = newValue.length; 12190 12191 if (oldLength !== newLength) { 12192 // if lengths do not match we need to trigger change notification 12193 changeDetected++; 12194 oldValue.length = oldLength = newLength; 12195 } 12196 // copy the items to oldValue and look for changes. 12197 for (var i = 0; i < newLength; i++) { 12198 var bothNaN = (oldValue[i] !== oldValue[i]) && 12199 (newValue[i] !== newValue[i]); 12200 if (!bothNaN && (oldValue[i] !== newValue[i])) { 12201 changeDetected++; 12202 oldValue[i] = newValue[i]; 12203 } 12204 } 12205 } else { 12206 if (oldValue !== internalObject) { 12207 // we are transitioning from something which was not an object into object. 12208 oldValue = internalObject = {}; 12209 oldLength = 0; 12210 changeDetected++; 12211 } 12212 // copy the items to oldValue and look for changes. 12213 newLength = 0; 12214 for (key in newValue) { 12215 if (newValue.hasOwnProperty(key)) { 12216 newLength++; 12217 if (oldValue.hasOwnProperty(key)) { 12218 if (oldValue[key] !== newValue[key]) { 12219 changeDetected++; 12220 oldValue[key] = newValue[key]; 12221 } 12222 } else { 12223 oldLength++; 12224 oldValue[key] = newValue[key]; 12225 changeDetected++; 12226 } 12227 } 12228 } 12229 if (oldLength > newLength) { 12230 // we used to have more keys, need to find them and destroy them. 12231 changeDetected++; 12232 for (key in oldValue) { 12233 if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { 12234 oldLength--; 12235 delete oldValue[key]; 12236 } 12237 } 12238 } 12239 } 12240 return changeDetected; 12241 } 12242 12243 function $watchCollectionAction() { 12244 if (initRun) { 12245 initRun = false; 12246 listener(newValue, newValue, self); 12247 } else { 12248 listener(newValue, veryOldValue, self); 12249 } 12250 12251 // make a copy for the next time a collection is changed 12252 if (trackVeryOldValue) { 12253 if (!isObject(newValue)) { 12254 //primitive 12255 veryOldValue = newValue; 12256 } else if (isArrayLike(newValue)) { 12257 veryOldValue = new Array(newValue.length); 12258 for (var i = 0; i < newValue.length; i++) { 12259 veryOldValue[i] = newValue[i]; 12260 } 12261 } else { // if object 12262 veryOldValue = {}; 12263 for (var key in newValue) { 12264 if (hasOwnProperty.call(newValue, key)) { 12265 veryOldValue[key] = newValue[key]; 12266 } 12267 } 12268 } 12269 } 12270 } 12271 12272 return this.$watch($watchCollectionWatch, $watchCollectionAction); 12273 }, 12274 12275 /** 12276 * @ngdoc method 12277 * @name $rootScope.Scope#$digest 12278 * @function 12279 *
12280 * @description 12281 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 12282 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 12283 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 12284 * until no more listeners are firing. This means that it is possible to get into an infinite 12285 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 12286 * iterations exceeds 10. 12287 * 12288 * Usually, you don't call `$digest()` directly in 12289 * {@link ng.directive:ngController controllers} or in 12290 * {@link ng.$compileProvider#directive directives}. 12291 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 12292 * a {@link ng.$compileProvider#directive directives}), which will force a `$digest()`. 12293 * 12294 * If you want to be notified whenever `$digest()` is called, 12295 * you can register a `watchExpression` function with 12296 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 12297 * 12298 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 12299 * 12300 * # Example 12301 * ```js 12302 var scope = ...; 12303 scope.name = 'misko'; 12304 scope.counter = 0; 12305 12306 expect(scope.counter).toEqual(0); 12307 scope.$watch('name', function(newValue, oldValue) { 12308 scope.counter = scope.counter + 1; 12309 }); 12310 expect(scope.counter).toEqual(0); 12311 12312 scope.$digest(); 12313 // no variable change 12314 expect(scope.counter).toEqual(0); 12315 12316 scope.name = 'adam'; 12317 scope.$digest(); 12318 expect(scope.counter).toEqual(1); 12319 * ``` 12320 * 12321 */ 12322 $digest: function () { 12323 var watch, value, last, 12324 watchers, 12325 asyncQueue = this.$$asyncQueue, 12326 postDigestQueue = this.$$postDigestQueue, 12327 length, 12328 dirty, ttl = TTL, 12329 next, current, target = this, 12330 watchLog = [], 12331 logIdx, logMsg, asyncTask; 12332 12333 beginPhase('$digest'); 12334 12335 lastDirtyWatch = null; 12336 12337 do { // "while dirty" loop 12338 dirty = false; 12339 current = target; 12340 12341 while (asyncQueue.length) { 12342 try { 12343 asyncTask = asyncQueue.shift(); 12344 asyncTask.scope.$eval(asyncTask.expression); 12345 } catch (e) { 12346 clearPhase(); 12347 $exceptionHandler(e); 12348 } 12349 lastDirtyWatch = null; 12350 } 12351 12352 traverseScopesLoop: 12353 do { // "traverse the scopes" loop 12354 if ((watchers = current.$$watchers)) { 12355 // process our watches 12356 length = watchers.length; 12357 while (length--) { 12358 try { 12359 watch = watchers[length]; 12360 // Most common watches are on primitives, in which case we can short 12361 // circuit it with === operator, only when === fail
12361s do we use .equals 12362 if (watch) { 12363 if ((value = watch.get(current)) !== (last = watch.last) && !(watch.eq 12364 ? equals(value, last) 12365 : (typeof value == 'number' && typeof last == 'number' 12366 && isNaN(value) && isNaN(last)))) { 12367 dirty = true; 12368 lastDirtyWatch = watch; 12369 watch.last = watch.eq ? copy(value) : value; 12370 watch.fn(value, ((last === initWatchVal) ? value : last), current); 12371 if (ttl < 5) { 12372 logIdx = 4 - ttl; 12373 if (!watchLog[logIdx]) watchLog[logIdx] = []; 12374 logMsg = (isFunction(watch.exp)) 12375 ? 'fn: ' + (watch.exp.name || watch.exp.toString()) 12376 : watch.exp; 12377 logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); 12378 watchLog[logIdx].push(logMsg); 12379 } 12380 } else if (watch === lastDirtyWatch) { 12381 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 12382 // have already been tested. 12383 dirty = false; 12384 break traverseScopesLoop; 12385 } 12386 } 12387 } catch (e) { 12388 clearPhase(); 12389 $exceptionHandler(e); 12390 } 12391 } 12392 } 12393 12394 // Insanity Warning: scope depth-first traversal 12395 // yes, this code is a bit crazy, but it works and we have tests to prove it! 12396 // this piece should be kept in sync with the traversal in $broadcast 12397 if (!(next = (current.$$childHead || 12398 (current !== target && current.$$nextSibling)))) { 12399 while (current !== target && !(next = current.$$nextSibling)) { 12400 current = current.$parent; 12401 } 12402 } 12403 } while ((current = next)); 12404 12405 // `break traverseScopesLoop;` takes us to here 12406 12407 if ((dirty || asyncQueue.length) && !(ttl--)) { 12408 clearPhase(); 12409 throw $rootScopeMinErr('infdig', 12410 '{0} $digest() iterations reached. Aborting!\n' + 12411 'Watchers fired in the last 5 iterations: {1}', 12412 TTL, toJson(watchLog)); 12413 } 12414 12415 } while (dirty || asyncQueue.length); 12416 12417 clearPhase(); 12418 12419 while (postDigestQueue.length) { 12420 try { 12421 postDigestQueue.shift()(); 12422 } catch (e) { 12423 $exceptionHandler(e); 12424 } 12425 } 12426 }, 12427 12428 12429 /** 12430 * @ngdoc event 12431 * @name $rootScope.Scope#$destroy 12432 * @eventType broadcast on scope being destroyed 12433 *
12434 * @description 12435 * Broadcasted when a scope and its children are being destroyed. 12436 * 12437 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 12438 * clean up DOM bindings before an element is removed from the DOM. 12439 */ 12440 12441 /** 12442 * @ngdoc method 12443 * @name $rootScope.Scope#$destroy 12444 * @function 12445 * 12446 * @description 12447 * Removes the current scope (and all of its children) from the parent scope. Removal implies 12448 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 12449 * propagate to the current scope and its children. Removal also implies that the current 12450 * scope is eligible for garbage collection. 12451 * 12452 * The `$destroy()` is usually used by directives such as 12453 * {@link ng.directive:ngRepeat ngRepeat} for managing the 12454 * unrolling of the loop. 12455 * 12456 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 12457 * Application code can register a `$destroy` event handler that will give it a chance to 12458 * perform any necessary cleanup. 12459 * 12460 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 12461 * clean up DOM bindings before an element is removed from the DOM. 12462 */ 12463 $destroy: function () { 12464 // we can't destroy the root scope or a scope that has been already destroyed 12465 if (this.$$destroyed) return; 12466 var parent = this.$parent; 12467 12468 this.$broadcast('$destroy'); 12469 this.$$destroyed = true; 12470 if (this === $rootScope) return; 12471 12472 forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); 12473 12474 // sever all the references to parent scopes (after this cleanup, the current scope should 12475 // not be retained by any of our references and should be eligible for garbage collection) 12476 if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 12477 if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 12478 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 12479 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 12480 12481 12482 // All of the code below is bogus code that works around V8's memory leak via optimized code 12483 // and inline caches. 12484 // 12485 // see: 12486 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 12487 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 12488 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 12489 12490 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 12491 this.$$childTail = this.$root = null; 12492 12493 // don't reset these to null in case some async task tries to register a listener/watch/task 12494 this.$$listeners = {}; 12495 this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = []; 12496 12497 // prevent NPEs since these methods have references to properties we nulled out 12498 this.$destroy = this.$digest = this.$apply = noop; 12499 this.$on = this.$watch = function () { 12500 return noop; 12501 }; 12502 }, 12503 12504 /** 12505 * @ngdoc method 12506 * @name $rootScope.Scope#$eval 12507 * @function 12508 *
12509 * @description 12510 * Executes the `expression` on the current scope and returns the result. Any exceptions in 12511 * the expression are propagated (uncaught). This is useful when evaluating Angular 12512 * expressions. 12513 * 12514 * # Example 12515 * ```js 12516 var scope = ng.$rootScope.Scope(); 12517 scope.a = 1; 12518 scope.b = 2; 12519 12520 expect(scope.$eval('a+b')).toEqual(3); 12521 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 12522 * ``` 12523 * 12524 * @param {(string|function())=} expression An angular expression to be executed. 12525 * 12526 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12527 * - `function(scope)`: execute the function with the current `scope` parameter. 12528 * 12529 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 12530 * @returns {*} The result of evaluating the expression. 12531 */ 12532 $eval: function (expr, locals) { 12533 return $parse(expr)(this, locals); 12534 }, 12535 12536 /** 12537 * @ngdoc method 12538 * @name $rootScope.Scope#$evalAsync 12539 * @function 12540 * 12541 * @description 12542 * Executes the expression on the current scope at a later point in time. 12543 * 12544 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 12545 * that: 12546 * 12547 * - it will execute after the function that scheduled the evaluation (preferably before DOM 12548 * rendering). 12549 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 12550 * `expression` execution. 12551 * 12552 * Any exceptions from the execution of the expression are forwarded to the 12553 * {@link ng.$exceptionHandler $exceptionHandler} service. 12554 * 12555 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 12556 * will be scheduled. However, it is encouraged to always call code that changes the model 12557 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 12558 * 12559 * @param {(string|function())=} expression An angular expression to be executed. 12560 * 12561 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12562 * - `function(scope)`: execute the function with the current `scope` parameter. 12563 * 12564 */ 12565 $evalAsync: function (expr) { 12566 // if we are outside of an $digest loop and this is the first time we are scheduling async 12567 // task also schedule async auto-flush 12568 if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { 12569 $browser.defer(function () { 12570 if ($rootScope.$$asyncQueue.length) { 12571 $rootScope.$digest(); 12572 } 12573 }); 12574 } 12575 12576 this.$$asyncQueue.push({scope: this, expression: expr}); 12577 }, 12578 12579 $$postDigest: function (fn) { 12580 this.$$postDigestQueue.push(fn); 12581 }, 12582 12583 /** 12584 * @ngdoc method 12585 * @name $rootScope.Scope#$apply 12586 * @function 12587 *
12588 * @description 12589 * `$apply()` is used to execute an expression in angular from outside of the angular 12590 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 12591 * Because we are calling into the angular framework we need to perform proper scope life 12592 * cycle of {@link ng.$exceptionHandler exception handling}, 12593 * {@link ng.$rootScope.Scope#$digest executing watches}. 12594 * 12595 * ## Life cycle 12596 * 12597 * # Pseudo-Code of `$apply()` 12598 * ```js 12599 function $apply(expr) { 12600 try { 12601 return $eval(expr); 12602 } catch (e) { 12603 $exceptionHandler(e); 12604 } finally { 12605 $root.$digest(); 12606 } 12607 } 12608 * ``` 12609 * 12610 * 12611 * Scope's `$apply()` method transitions through the following stages: 12612 * 12613 * 1. The {@link guide/expression expression} is executed using the 12614 * {@link ng.$rootScope.Scope#$eval $eval()} method. 12615 * 2. Any exceptions from the execution of the expression are forwarded to the 12616 * {@link ng.$exceptionHandler $exceptionHandler} service. 12617 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 12618 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 12619 * 12620 * 12621 * @param {(string|function())=} exp An angular expression to be executed. 12622 * 12623 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12624 * - `function(scope)`: execute the function with current `scope` parameter. 12625 * 12626 * @returns {*} The result of evaluating the expression. 12627 */ 12628 $apply: function (expr) { 12629 try { 12630 beginPhase('$apply'); 12631 return this.$eval(expr); 12632 } catch (e) { 12633 $exceptionHandler(e); 12634 } finally { 12635 clearPhase(); 12636 try { 12637 $rootScope.$digest(); 12638 } catch (e) { 12639 $exceptionHandler(e); 12640 throw e; 12641 } 12642 } 12643 }, 12644 12645 /** 12646 * @ngdoc method 12647 * @name $rootScope.Scope#$on 12648 * @function 12649 * 12650 * @description 12651 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 12652 * discussion of event life cycle. 12653 * 12654 * The event listener function format is: `function(event, args...)`. The `event` object 12655 * passed into the listener has the following attributes: 12656 * 12657 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 12658 * `$broadcast`-ed. 12659 * - `currentScope` - `{Scope}`: the current scope which is handling the event. 12660 * - `name` - `{string}`: name of the event. 12661 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 12662 * further event propagation (available only for events that were `$emit`-ed). 12663 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 12664 * to true. 12665 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 12666 * 12667 * @param {string} name Event name to listen on. 12668 * @param {function(event, ...args)} listener Function to call when the event is emitted. 12669 * @returns {function()} Returns a deregistration function for this listener. 12670 */ 12671 $on: function (name, listener) { 12672 var namedListeners = this.$$listeners[name]; 12673 if (!namedListeners) { 12674 this.$$listeners[name] = namedListeners = []; 12675 } 12676 namedListeners.push(listener); 12677 12678 var current = this; 12679 do { 12680 if (!current.$$listenerCount[name]) { 12681 current.$$listenerCount[name] = 0; 12682 } 12683 current.$$listenerCount[name]++; 12684 } while ((current = current.$parent)); 12685 12686 var self = this; 12687 return function () { 12688 namedListeners[indexOf(namedListeners, listener)] = null; 12689 decrementListenerCount(self, 1, name); 12690 }; 12691 }, 12692 12693 12694 /** 12695 * @ngdoc method 12696 * @name $rootScope.Scope#$emit 12697 * @function 12698 *
12699 * @description 12700 * Dispatches an event `name` upwards through the scope hierarchy notifying the 12701 * registered {@link ng.$rootScope.Scope#$on} listeners. 12702 * 12703 * The event life cycle starts at the scope on which `$emit` was called. All 12704 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 12705 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 12706 * registered listeners along the way. The event will stop propagating if one of the listeners 12707 * cancels it. 12708 * 12709 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 12710 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 12711 * 12712 * @param {string} name Event name to emit. 12713 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 12714 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 12715 */ 12716 $emit: function (name, args) { 12717 var empty = [], 12718 namedListeners, 12719 scope = this, 12720 stopPropagation = false, 12721 event = { 12722 name: name, 12723 targetScope: scope, 12724 stopPropagation: function () { 12725 stopPropagation = true; 12726 }, 12727 preventDefault: function () { 12728 event.defaultPrevented = true; 12729 }, 12730 defaultPrevented: false 12731 }, 12732 listenerArgs = concat([event], arguments, 1), 12733 i, length; 12734 12735 do { 12736 namedListeners = scope.$$listeners[name] || empty; 12737 event.currentScope = scope; 12738 for (i = 0, length = namedListeners.length; i < length; i++) { 12739 12740 // if listeners were deregistered, defragment the array 12741 if (!namedListeners[i]) { 12742 namedListeners.splice(i, 1); 12743 i--; 12744 length--; 12745 continue; 12746 } 12747 try { 12748 //allow all listeners attached to the current scope to run 12749 namedListeners[i].apply(null, listenerArgs); 12750 } catch (e) { 12751 $exceptionHandler(e); 12752 } 12753 } 12754 //if any listener on the current scope stops propagation, prevent bubbling 12755 if (stopPropagation) return event; 12756 //traverse upwards 12757 scope = scope.$parent; 12758 } while (scope); 12759 12760 return event; 12761 }, 12762 12763 12764 /** 12765 * @ngdoc method 12766 * @name $rootScope.Scope#$broadcast 12767 * @function 12768 * 12769 * @description 12770 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 12771 * registered {@link ng.$rootScope.Scope#$on} listeners. 12772 * 12773 * The event life cycle starts at the scope on which `$broadcast` was called. All 12774 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 12775 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 12776 * scope and calls all registered listeners along the way. The event cannot be canceled. 12777 * 12778 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 12779 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 12780 * 12781 * @param {string} name Event name to broadcast. 12782 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 12783 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 12784 */ 12785 $broadcast: function (name, args) { 12786 var target = this, 12787 current = target, 12788 next = target, 12789 event = { 12790 name: name, 12791 targetScope: target, 12792 preventDefault: function () { 12793 event.defaultPrevented = true; 12794 }, 12795 defaultPrevented: false 12796 }, 12797 listenerArgs = concat([event], arguments, 1), 12798 listeners, i, length; 12799 12800 //down while you can, then up and next sibling or up and next sibling until back at root 12801 while ((current = next)) { 12802 event.currentScope = current; 12803 listeners = current.$$listeners[name] || []; 12804 for (i = 0, length = listeners.length; i < length; i++) { 12805 // if listeners were deregistered, defragment the array 12806 if (!listeners[i]) { 12807 listeners.splice(i, 1); 12808 i--; 12809 length--; 12810 continue; 12811 } 12812 12813 try { 12814 listeners[i].apply(null, listenerArgs); 12815 } catch (e) { 12816 $exceptionHandler(e); 12817 } 12818 } 12819 12820 // Insanity Warning: scope depth-first traversal 12821 // yes, this code is a bit crazy, but it works and we have tests to prove it! 12822 // this piece should be kept in sync with the traversal in $digest 12823 // (though it differs due to having the extra check for $$listenerCount) 12824 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 12825 (current !== target && current.$$nextSibling)))) { 12826 while (current !== target && !(next = current.$$nextSibling)) { 12827 current = current.$parent; 12828 } 12829 } 12830 } 12831 12832 return event; 12833 } 12834 }; 12835 12836 var $rootScope = new Scope(); 12837 12838 return $rootScope; 12839 12840 12841 function beginPhase(phase) { 12842 if ($rootScope.$$phase) { 12843 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 12844 } 12845 12846 $rootScope.$$phase = phase; 12847 } 12848 12849 function clearPhase() { 12850 $rootScope.$$phase = null; 12851 } 12852 12853 function compileToFn(exp, name) { 12854 var fn = $parse(exp); 12855 assertArgFn(fn, name); 12856 return fn; 12857 } 12858 12859 function decrementListenerCount(current, count, name) { 12860 do { 12861 current.$$listenerCount[name] -= count; 12862 12863 if (current.$$listenerCount[name] === 0) { 12864 delete current.$$listenerCount[name]; 12865 } 12866 } while ((current = current.$parent)); 12867 } 12868 12869 /** 12870 * function used as an initial value for watchers. 12871 * because it's unique we can easily tell it apart from other values 12872 */ 12873 function initWatchVal() { 12874 } 12875 }]; 12876 } 12877 12878 /**
12879 * @description 12880 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 12881 */ 12882 function $$SanitizeUriProvider() { 12883 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 12884 imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//; 12885 12886 /** 12887 * @description 12888 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 12889 * urls during a[href] sanitization. 12890 * 12891 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 12892 * 12893 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 12894 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 12895 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 12896 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 12897 * 12898 * @param {RegExp=} regexp New regexp to whitelist urls with. 12899 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 12900 * chaining otherwise. 12901 */ 12902 this.aHrefSanitizationWhitelist = function (regexp) { 12903 if (isDefined(regexp)) { 12904 aHrefSanitizationWhitelist = regexp; 12905 return this; 12906 } 12907 return aHrefSanitizationWhitelist; 12908 }; 12909 12910 12911 /** 12912 * @description 12913 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 12914 * urls during img[src] sanitization. 12915 * 12916 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 12917 * 12918 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 12919 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 12920 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 12921 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 12922 * 12923 * @param {RegExp=} regexp New regexp to whitelist urls with. 12924 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 12925 * chaining otherwise. 12926 */ 12927 this.imgSrcSanitizationWhitelist = function (regexp) { 12928 if (isDefined(regexp)) { 12929 imgSrcSanitizationWhitelist = regexp; 12930 return this; 12931 } 12932 return imgSrcSanitizationWhitelist; 12933 }; 12934 12935 this.$get = function () { 12936 return function sanitizeUri(uri, isImage) { 12937 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 12938 var normalizedVal; 12939 // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case. 12940 if (!msie || msie >= 8) { 12941 normalizedVal = urlResolve(uri).href; 12942 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 12943 return 'unsafe:' + normalizedVal; 12944 } 12945 } 12946 return uri; 12947 }; 12948 }; 12949 } 12950 12951 var $sceMinErr = minErr('$sce'); 12952 12953 var SCE_CONTEXTS = { 12954 HTML: 'html', 12955 CSS: 'css', 12956 URL: 'url', 12957 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 12958 // url. (e.g. ng-include, script src, templateUrl) 12959 RESOURCE_URL: 'resourceUrl', 12960 JS: 'js' 12961 }; 12962 12963// Helper functions follow. 12964 12965// Copied from: 12966// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 12967// Prereq: s is a string. 12968 function escapeForRegexp(s) { 12969 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 12970 replace(/\x08/g, '\\x08'); 12971 } 12972 12973 12974 function adjustMatcher(matcher) { 12975 if (matcher === 'self') { 12976 return matcher; 12977 } else if (isString(matcher)) { 12978 // Strings match exactly except for 2 wildcards - '*' and '**'. 12979 // '*' matches any character except those from the set ':/.?&'. 12980 // '**' matches any character (like .* in a RegExp). 12981 // More than 2 *'s raises an error as it's ill defined. 12982 if (matcher.indexOf('***') > -1) { 12983 throw $sceMinErr('iwcard', 12984 'Illegal sequence *** in string matcher. String: {0}', matcher); 12985 } 12986 matcher = escapeForRegexp(matcher).
12987 replace('\\*\\*', '.*'). 12988 replace('\\*', '[^:/.?&;]*'); 12989 return new RegExp('^' + matcher + '$'); 12990 } else if (isRegExp(matcher)) { 12991 // The only other type of matcher allowed is a Regexp. 12992 // Match entire URL / disallow partial matches. 12993 // Flags are reset (i.e. no global, ignoreCase or multiline) 12994 return new RegExp('^' + matcher.source + '$'); 12995 } else { 12996 throw $sceMinErr('imatcher', 12997 'Matchers may only be "self", string patterns or RegExp objects'); 12998 } 12999 } 13000 13001 13002 function adjustMatchers(matchers) { 13003 var adjustedMatchers = []; 13004 if (isDefined(matchers)) { 13005 forEach(matchers, function (matcher) { 13006 adjustedMatchers.push(adjustMatcher(matcher)); 13007 }); 13008 } 13009 return adjustedMatchers; 13010 } 13011 13012 13013 /** 13014 * @ngdoc service 13015 * @name $sceDelegate 13016 * @function 13017 * 13018 * @description 13019 * 13020 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 13021 * Contextual Escaping (SCE)} services to AngularJS. 13022 * 13023 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 13024 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 13025 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 13026 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 13027 * work because `$sce` delegates to `$sceDelegate` for these operations. 13028 * 13029 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 13030 * 13031 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 13032 * can override it completely to change the behavior of `$sce`, the common case would 13033 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 13034 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 13035 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 13036 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 13037 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 13038 */ 13039 13040 /** 13041 * @ngdoc provider 13042 * @name $sceDelegateProvider 13043 * @description 13044 * 13045 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 13046 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 13047 * that the URLs used for sourcing Angular templates are safe. Refer {@link 13048 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 13049 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 13050 * 13051 * For the general details about this service in Angular, read the main page for {@link ng.$sce 13052 * Strict Contextual Escaping (SCE)}. 13053 * 13054 * **Example**: Consider the following case. <a name="example"></a> 13055 * 13056 * - your app is hosted at url `http://myapp.example.com/` 13057 * - but some of your templates are hosted on other domains you control such as 13058 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 13059 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 13060 * 13061 * Here is what a secure configuration for this scenario might look like: 13062 * 13063 * <pre class="prettyprint"> 13064 * angular.module('myApp', []).config(function($sceDelegateProvider) { 13065 * $sceDelegateProvider.resourceUrlWhitelist([ 13066 * // Allow same origin resource loads. 13067 * 'self', 13068 * // Allow loading from our assets domain. Notice the difference between * and **. 13069 * 'http://srv*.assets.example.com/**']); 13070 * 13071 * // The blacklist overrides the whitelist so the open redirect here is blocked. 13072 * $sceDelegateProvider.resourceUrlBlacklist([ 13073 * 'http://myapp.example.com/clickThru**']); 13074 * }); 13075 * </pre> 13076 */ 13077 13078 function $SceDelegateProvider() { 13079 this.SCE_CONTEXTS = SCE_CONTEXTS; 13080 13081 // Resource URLs can also be trusted by policy. 13082 var resourceUrlWhitelist = ['self'], 13083 resourceUrlBlacklist = []; 13084 13085 /** 13086 * @ngdoc method 13087 * @name $sceDelegateProvider#resourceUrlWhitelist 13088 * @function 13089 * 13090 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 13091 * provided. This must be an array or null. A snapshot of this array is used so further 13092 * changes to the array are ignored. 13093 * 13094 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 13095 * allowed in this array.
vendor: 5,113 bytes, lines 13096-13220
13096 * 13097 * Note: **an empty whitelist array will block all URLs**! 13098 * 13099 * @return {Array} the currently set whitelist array. 13100 * 13101 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 13102 * same origin resource requests. 13103 * 13104 * @description 13105 * Sets/Gets the whitelist of trusted resource URLs. 13106 */ 13107 this.resourceUrlWhitelist = function (value) { 13108 if (arguments.length) { 13109 resourceUrlWhitelist = adjustMatchers(value); 13110 } 13111 return resourceUrlWhitelist; 13112 }; 13113 13114 /** 13115 * @ngdoc method 13116 * @name $sceDelegateProvider#resourceUrlBlacklist 13117 * @function 13118 * 13119 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 13120 * provided. This must be an array or null. A snapshot of this array is used so further 13121 * changes to the array are ignored. 13122 * 13123 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 13124 * allowed in this array. 13125 * 13126 * The typical usage for the blacklist is to **block 13127 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 13128 * these would otherwise be trusted but actually return content from the redirected domain. 13129 * 13130 * Finally, **the blacklist overrides the whitelist** and has the final say. 13131 * 13132 * @return {Array} the currently set blacklist array. 13133 * 13134 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 13135 * is no blacklist.) 13136 * 13137 * @description 13138 * Sets/Gets the blacklist of trusted resource URLs. 13139 */ 13140 13141 this.resourceUrlBlacklist = function (value) { 13142 if (arguments.length) { 13143 resourceUrlBlacklist = adjustMatchers(value); 13144 } 13145 return resourceUrlBlacklist; 13146 }; 13147 13148 this.$get = ['$injector', function ($injector) { 13149 13150 var htmlSanitizer = function htmlSanitizer(html) { 13151 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 13152 }; 13153 13154 if ($injector.has('$sanitize')) { 13155 htmlSanitizer = $injector.get('$sanitize'); 13156 } 13157 13158 13159 function matchUrl(matcher, parsedUrl) { 13160 if (matcher === 'self') { 13161 return urlIsSameOrigin(parsedUrl); 13162 } else { 13163 // definitely a regex. See adjustMatchers() 13164 return !!matcher.exec(parsedUrl.href); 13165 } 13166 } 13167 13168 function isResourceUrlAllowedByPolicy(url) { 13169 var parsedUrl = urlResolve(url.toString()); 13170 var i, n, allowed = false; 13171 // Ensure that at least one item from the whitelist allows this url. 13172 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 13173 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 13174 allowed = true; 13175 break; 13176 } 13177 } 13178 if (allowed) { 13179 // Ensure that no item from the blacklist blocked this url. 13180 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 13181 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 13182 allowed = false; 13183 break; 13184 } 13185 } 13186 } 13187 return allowed; 13188 } 13189 13190 function generateHolderType(Base) { 13191 var holderType = function TrustedValueHolderType(trustedValue) { 13192 this.$$unwrapTrustedValue = function () { 13193 return trustedValue; 13194 }; 13195 }; 13196 if (Base) { 13197 holderType.prototype = new Base(); 13198 } 13199 holderType.prototype.valueOf = function sceValueOf() { 13200 return this.$$unwrapTrustedValue(); 13201 }; 13202 holderType.prototype.toString = function sceToString() { 13203 return this.$$unwrapTrustedValue().toString(); 13204 }; 13205 return holderType; 13206 } 13207 13208 var trustedValueHolderBase = generateHolderType(), 13209 byType = {}; 13210 13211 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 13212 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 13213 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 13214 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 13215 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 13216 13217 /** 13218 * @ngdoc method 13219 * @name $sceDelegate#trustAs 13220 *
13221 * @description 13222 * Returns an object that is trusted by angular for use in specified strict 13223 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 13224 * attribute interpolation, any dom event binding attribute interpolation 13225 * such as for onclick, etc.) that uses the provided value. 13226 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 13227 * 13228 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 13229 * resourceUrl, html, js and css. 13230 * @param {*} value The value that that should be considered trusted/safe. 13231 * @returns {*} A value that can be used to stand in for the provided `value` in places 13232 * where Angular expects a $sce.trustAs() return value. 13233 */ 13234 function trustAs(type, trustedValue) { 13235 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 13236 if (!Constructor) { 13237 throw $sceMinErr('icontext', 13238 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 13239 type, trustedValue); 13240 } 13241 if (trustedValue === null || trustedValue === undefined || trustedValue === '') { 13242 return trustedValue; 13243 } 13244 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 13245 // mutable objects, we ensure here that the value passed in is actually a string. 13246 if (typeof trustedValue !== 'string') { 13247 throw $sceMinErr('itype', 13248 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 13249 type); 13250 } 13251 return new Constructor(trustedValue); 13252 } 13253 13254 /** 13255 * @ngdoc method 13256 * @name $sceDelegate#valueOf 13257 * 13258 * @description 13259 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 13260 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 13261 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 13262 * 13263 * If the passed parameter is not a value that had been returned by {@link 13264 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 13265 * 13266 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 13267 * call or anything else. 13268 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 13269 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 13270 * `value` unchanged. 13271 */ 13272 function valueOf(maybeTrusted) { 13273 if (maybeTrusted instanceof trustedValueHolderBase) { 13274 return maybeTrusted.$$unwrapTrustedValue(); 13275 } else { 13276 return maybeTrusted; 13277 } 13278 } 13279 13280 /** 13281 * @ngdoc method 13282 * @name $sceDelegate#getTrusted 13283 * 13284 * @description 13285 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 13286 * returns the originally supplied value if the queried context type is a supertype of the 13287 * created type. If this condition isn't satisfied, throws an exception. 13288 * 13289 * @param {string} type The kind of context in which this value is to be used. 13290 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 13291 * `$sceDelegate.trustAs`} call. 13292 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 13293 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 13294 */ 13295 function getTrusted(type, maybeTrusted) { 13296 if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { 13297 return maybeTrusted; 13298 } 13299 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 13300 if (constructor && maybeTrusted instanceof constructor) { 13301 return maybeTrusted.$$unwrapTrustedValue(); 13302 } 13303 // If we get here, then we may only take one of two actions. 13304 // 1. sanitize the value for the requested type, or 13305 // 2. throw an exception. 13306 if (type === SCE_CONTEXTS.RESOURCE_URL) { 13307 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 13308 return maybeTrusted; 13309 } else { 13310 throw $sceMinErr('insecurl', 13311 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 13312 maybeTrusted.toString()); 13313 } 13314 } else if (type === SCE_CONTEXTS.HTML) { 13315 return htmlSanitizer(maybeTrusted); 13316 } 13317 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 13318 } 13319 13320 return { trustAs: trustAs, 13321 getTrusted: getTrusted,
13322 valueOf: valueOf }; 13323 }]; 13324 } 13325 13326 13327 /** 13328 * @ngdoc provider 13329 * @name $sceProvider 13330 * @description 13331 * 13332 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 13333 * - enable/disable Strict Contextual Escaping (SCE) in a module 13334 * - override the default implementation with a custom delegate 13335 * 13336 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 13337 */ 13338 13339 /* jshint maxlen: false*/ 13340 13341 /** 13342 * @ngdoc service 13343 * @name $sce 13344 * @function 13345 * 13346 * @description 13347 * 13348 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 13349 * 13350 * # Strict Contextual Escaping 13351 * 13352 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 13353 * contexts to result in a value that is marked as safe to use for that context. One example of 13354 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 13355 * to these contexts as privileged or SCE contexts. 13356 * 13357 * As of version 1.2, Angular ships with SCE enabled by default. 13358 * 13359 * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows 13360 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 13361 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
13362 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 13363 * to the top of your HTML document. 13364 * 13365 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 13366 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 13367 * 13368 * Here's an example of a binding in a privileged context: 13369 * 13370 * <pre class="prettyprint"> 13371 * <input ng-model="userHtml"> 13372 * <div ng-bind-html="userHtml"> 13373 * </pre> 13374 * 13375 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 13376 * disabled, this application allows the user to render arbitrary HTML into the DIV. 13377 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 13378 * bindings. (HTML is just one example of a context where rendering user controlled input creates 13379 * security vulnerabilities.) 13380 * 13381 * For the case of HTML, you might use a library, either on the client side, or on the server side, 13382 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 13383 * 13384 * How would you ensure that every place that used these types of bindings was bound to a value that 13385 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 13386 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 13387 * properties/fields and forgot to update the binding to the sanitized value? 13388 * 13389 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 13390 * determine that something explicitly says it's safe to use a value for binding in that
13391 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 13392 * for those values that you can easily tell are safe - because they were received from your server, 13393 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 13394 * allowing only the files in a specific directory to do this. Ensuring that the internal API 13395 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 13396 * 13397 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 13398 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 13399 * obtain values that will be accepted by SCE / privileged contexts. 13400 * 13401 * 13402 * ## How does it work? 13403 * 13404 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 13405 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 13406 * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 13407 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 13408 * 13409 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 13410 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 13411 * simplified): 13412 * 13413 * <pre class="prettyprint"> 13414 * var ngBindHtmlDirective = ['$sce', function($sce) { 13415 * return function(scope, element, attr) { 13416 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 13417 * element.html(value || ''); 13418 * }); 13419 * }; 13420 * }]; 13421 * </pre> 13422 * 13423 * ## Impact on loading templates 13424 * 13425 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 13426 * `templateUrl`'s specified by {@link guide/directive directives}. 13427 * 13428 * By default, Angular only loads templates from the same domain and protocol as the application 13429 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 13430 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 13431 * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 13432 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 13433 * 13434 * *Please note*: 13435 * The browser's 13436 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 13437 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 13438 * policy apply in addition to this and may further restrict whether the template is successfully 13439 * loaded. This means that without the right CORS policy, loading templates from a different domain 13440 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 13441 * browsers. 13442 * 13443 * ## This feels like too much overhead for the developer? 13444 * 13445 * It's important to remember that SCE only applies to interpolation expressions. 13446 * 13447 * If your expressions are constant literals, they're automatically trusted and you don't need to 13448 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 13449 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 13450 * 13451 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 13452 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 13453 * 13454 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 13455 * templates in `ng-include` from your application's domain without having to even know about SCE. 13456 * It blocks loading templates from other domains or loading templates over http from an https 13457 * served document. You can change these by setting your own custom {@link 13458 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 13459 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 13460 * 13461 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 13462 * application that's secure and can be audited to verify that with much more ease than bolting 13463 * security onto an application later. 13464 *
13465 * <a name="contexts"></a> 13466 * ## What trusted context types are supported? 13467 * 13468 * | Context | Notes | 13469 * |---------------------|----------------| 13470 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. | 13471 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 13472 * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`<a href=` and `<img src=` sanitize their urls and don't constitute an SCE context. | 13473 * | `$sce.RESOURCE_URL` | For URLs that are not only safe to follow as links, but whose contents are also safe to include in your application. Examples include `ng-include`, `src` / `ngSrc` bindings for tags other than `IMG` (e.g. `IFRAME`, `OBJECT`, etc.) <br><br>Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. | 13474 * | `$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. | 13475 * 13476 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 13477 * 13478 * Each element in these arrays must be one of the following: 13479 * 13480 * - **'self'** 13481 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 13482 * domain** as the application document using the **same protocol**. 13483 * - **String** (except the special value `'self'`) 13484 * - The string is matched against the full *normalized / absolute URL* of the resource 13485 * being tested (substring matches are not good enough.) 13486 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 13487 * match themselves. 13488 * - `*`: matches zero or more occurrences of any character other than one of the following 6 13489 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use 13490 * in a whitelist. 13491 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 13492 * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. 13493 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 13494 * not have been the intention.) It's usage at the very end of the path is ok. (e.g. 13495 * http://foo.example.com/templates/**). 13496 * - **RegExp** (*see caveat below*) 13497 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 13498 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 13499 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 13500 * have good test coverage.). For instance, the use of `.` in the regex is correct only in a 13501 * small number of cases. A `.` character in the regex used when matching the scheme or a 13502 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 13503 * is highly recommended to use the string patterns and only fall back to regular expressions 13504 * if they as a last resort. 13505 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 13506 * matched against the **entire** *normalized / absolute URL* of the resource being tested 13507 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 13508 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 13509 * - If you are generating your JavaScript from some other templating engine (not 13510 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 13511 * remember to escape your regular expression (and be aware that you might need more than 13512 * one level of escaping depending on your templating engine and the way you interpolated 13513 * the value.) Do make use of your platform's escaping mechanism as it might be good 13514 * enough before coding your own. e.g. Ruby has 13515 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 13516 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 13517 * Javascript lacks a similar built in function for escaping. Take a look at Google 13518 * Closure library's [goog.string.regExpEscape(s)]( 13519 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 13520 * 13521 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 13522 * 13523 * ## Show me an example using SCE. 13524 * 13525 * @example 13526 <example module="mySceApp" deps="angular-sanitize.js"> 13527 <file name="index.jsp"> 13528 <div ng-controller="myAppController as myCtrl"> 13529 <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 13530 <b>User comments</b><br> 13531 By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 13532 $sanitize is available. If $sanitize isn't available, this results in an error instead of an 13533 exploit. 13534 <div class="well"> 13535 <div ng-repeat="userComment in myCtrl.userComments"> 13536 <b>
13536{{userComment.name}}</b>: 13537 <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 13538 <br> 13539 </div> 13540 </div> 13541 </div> 13542 </file> 13543 13544 <file name="script.js"> 13545 var mySceApp = angular.module('mySceApp', ['ngSanitize']); 13546 13547 mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { 13548 var self = this; 13549 $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 13550 self.userComments = userComments; 13551 }); 13552 self.explicitlyTrustedHtml = $sce.trustAsHtml( 13553 '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 13554 'sanitization."">Hover over this text.</span>'); 13555 }); 13556 </file> 13557 13558 <file name="test_data.json"> 13559 [ 13560 { "name": "Alice", 13561 "htmlComment": 13562 "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 13563 }, 13564 { "name": "Bob", 13565 "htmlComment": "<i>Yes!</i> Am I the only other one?" 13566 } 13567 ] 13568 </file> 13569 13570 <file name="protractor.js" type="protractor"> 13571 describe('SCE doc demo', function() { 13572 it('should sanitize untrusted values', function() { 13573 expect(element(by.css('.htmlComment')).getInnerHtml()) 13574 .toBe('<span>Is <i>anyone</i> reading this?</span>'); 13575 }); 13576 13577 it('should NOT sanitize explicitly trusted values', function() { 13578 expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 13579 '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' +
13580 'sanitization."">Hover over this text.</span>'); 13581 }); 13582 }); 13583 </file> 13584 </example> 13585 * 13586 * 13587 * 13588 * ## Can I disable SCE completely? 13589 * 13590 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 13591 * for little coding overhead. It will be much harder to take an SCE disabled application and 13592 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 13593 * for cases where you have a lot of existing code that was written before SCE was introduced and 13594 * you're migrating them a module at a time. 13595 * 13596 * That said, here's how you can completely disable SCE: 13597 * 13598 * <pre class="prettyprint"> 13599 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 13600 * // Completely disable SCE. For demonstration purposes only! 13601 * // Do not use in new projects. 13602 * $sceProvider.enabled(false); 13603 * }); 13604 * </pre> 13605 * 13606 */ 13607 /* jshint maxlen: 100 */ 13608 13609 function $SceProvider() { 13610 var enabled = true; 13611 13612 /** 13613 * @ngdoc method 13614 * @name $sceProvider#enabled 13615 * @function 13616 * 13617 * @param {boolean=} value If provided, then enables/disables SCE. 13618 * @return {boolean} true if SCE is enabled, false otherwise. 13619 * 13620 * @description 13621 * Enables/disables SCE and returns the current value. 13622 */ 13623 this.enabled = function (value) { 13624 if (arguments.length) { 13625 enabled = !!value; 13626 } 13627 return enabled; 13628 }; 13629 13630 13631 /* Design notes on the default implementation for SCE. 13632 * 13633 * The API contract for the SCE delegate 13634 * ------------------------------------- 13635 * The SCE delegate object must provide the following 3 methods: 13636 * 13637 * - trustAs(contextEnum, value) 13638 * This method is used to tell the SCE service that the provided value is OK to use in the 13639 * contexts specified by contextEnum. It must return an object that will be accepted by 13640 * getTrusted() for a compatible contextEnum and return this value. 13641 * 13642 * - valueOf(value) 13643 * For values that were not produced by trustAs(), return them as is. For values that were 13644 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 13645 * trustAs is wrapping the given values into some type, this operation unwraps it when given 13646 * such a value. 13647 * 13648 * - getTrusted(contextEnum, value) 13649 * This function should return the a value that is safe to use in the context specified by 13650 * contextEnum or throw and exception otherwise. 13651 * 13652 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 13653 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 13654 * instance, an implementation could maintain a registry of all trusted objects by context. In 13655 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 13656 * return the same object passed in if it was found in the registry under a compatible context or 13657 * throw an exception otherwise. An implementation might only wrap values some of the time based 13658 * on some criteria. getTrusted() might return a value and not throw an exception for special 13659 * constants or objects even if not wrapped. All such implementations fulfill this contract. 13660 * 13661 * 13662 * A note on the inheritance model for SCE contexts 13663 * ------------------------------------------------ 13664 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 13665 * is purely an implementation details. 13666 * 13667 * The contract is simply this: 13668 * 13669 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 13670 * will also succeed. 13671 * 13672 * Inheritance happens to capture this in a natural way. In some future, we
13673 * may not use inheritance anymore. That is OK because no code outside of 13674 * sce.js and sceSpecs.js would need to be aware of this detail. 13675 */ 13676 13677 this.$get = ['$parse', '$sniffer', '$sceDelegate', function ($parse, $sniffer, $sceDelegate) { 13678 // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows 13679 // the "expression(javascript expression)" syntax which is insecure. 13680 if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { 13681 throw $sceMinErr('iequirks', 13682 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + 13683 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 13684 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 13685 } 13686 13687 var sce = copy(SCE_CONTEXTS); 13688 13689 /** 13690 * @ngdoc method 13691 * @name $sce#isEnabled 13692 * @function 13693 * 13694 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 13695 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 13696 * 13697 * @description 13698 * Returns a boolean indicating if SCE is enabled. 13699 */ 13700 sce.isEnabled = function () { 13701 return enabled; 13702 }; 13703 sce.trustAs = $sceDelegate.trustAs; 13704 sce.getTrusted = $sceDelegate.getTrusted; 13705 sce.valueOf = $sceDelegate.valueOf; 13706 13707 if (!enabled) { 13708 sce.trustAs = sce.getTrusted = function (type, value) { 13709 return value; 13710 }; 13711 sce.valueOf = identity; 13712 } 13713 13714 /** 13715 * @ngdoc method 13716 * @name $sce#parse 13717 * 13718 * @description 13719 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 13720 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 13721 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 13722 * *result*)} 13723 * 13724 * @param {string} type The kind of SCE context in which this result will be used. 13725 * @param {string} expression String expression to compile. 13726 * @returns {function(context, locals)} a function which represents the compiled expression: 13727 * 13728 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13729 * are evaluated against (typically a scope object). 13730 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13731 * `context`. 13732 */ 13733 sce.parseAs = function sceParseAs(type, expr) { 13734 var parsed = $parse(expr); 13735 if (parsed.literal && parsed.constant) { 13736 return parsed; 13737 } else { 13738 return function sceParseAsTrusted(self, locals) { 13739 return sce.getTrusted(type, parsed(self, locals)); 13740 }; 13741 } 13742 }; 13743 13744 /** 13745 * @ngdoc method 13746 * @name $sce#trustAs 13747 * 13748 * @description 13749 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 13750 * returns an object that is trusted by angular for use in specified strict contextual 13751 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 13752 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 13753 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 13754 * escaping. 13755 * 13756 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 13757 * resource_url, html, js and css. 13758 * @param {*} value The value that that should be considered trusted/safe. 13759 * @returns {*} A value that can be used to stand in for the provided `value` in places 13760 * where Angular expects a $sce.trustAs() return value. 13761 */ 13762 13763 /** 13764 * @ngdoc method 13765 * @name $sce#trustAsHtml 13766 *
13767 * @description 13768 * Shorthand method. `$sce.trustAsHtml(value)` â 13769 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 13770 * 13771 * @param {*} value The value to trustAs. 13772 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 13773 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 13774 * only accept expressions that are either literal constants or are the 13775 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 13776 */ 13777 13778 /** 13779 * @ngdoc method 13780 * @name $sce#trustAsUrl 13781 * 13782 * @description 13783 * Shorthand method. `$sce.trustAsUrl(value)` â 13784 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 13785 * 13786 * @param {*} value The value to trustAs. 13787 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 13788 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 13789 * only accept expressions that are either literal constants or are the 13790 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 13791 */ 13792 13793 /** 13794 * @ngdoc method 13795 * @name $sce#trustAsResourceUrl 13796 * 13797 * @description 13798 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 13799 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 13800 * 13801 * @param {*} value The value to trustAs. 13802 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 13803 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 13804 * only accept expressions that are either literal constants or are the return 13805 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 13806 */ 13807 13808 /** 13809 * @ngdoc method 13810 * @name $sce#trustAsJs 13811 * 13812 * @description 13813 * Shorthand method. `$sce.trustAsJs(value)` â 13814 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 13815 * 13816 * @param {*} value The value to trustAs. 13817 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 13818 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 13819 * only accept expressions that are either literal constants or are the 13820 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 13821 */ 13822 13823 /** 13824 * @ngdoc method 13825 * @name $sce#getTrusted 13826 * 13827 * @description 13828 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 13829 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 13830 * originally supplied value if the queried context type is a supertype of the created type. 13831 * If this condition isn't satisfied, throws an exception. 13832 * 13833 * @param {string} type The kind of context in which this value is to be used. 13834 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 13835 * call. 13836 * @returns {*} The value the was originally provided to 13837 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 13838 * Otherwise, throws an exception. 13839 */ 13840 13841 /** 13842 * @ngdoc method 13843 * @name $sce#getTrustedHtml 13844 * 13845 * @description 13846 * Shorthand method. `$sce.getTrustedHtml(value)` â 13847 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 13848 * 13849 * @param {*} value The value to pass to `$sce.getTrusted`. 13850 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 13851 */ 13852 13853 /** 13854 * @ngdoc method 13855 * @name $sce#getTrustedCss 13856 *
13857 * @description 13858 * Shorthand method. `$sce.getTrustedCss(value)` â 13859 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 13860 * 13861 * @param {*} value The value to pass to `$sce.getTrusted`. 13862 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 13863 */ 13864 13865 /** 13866 * @ngdoc method 13867 * @name $sce#getTrustedUrl 13868 * 13869 * @description 13870 * Shorthand method. `$sce.getTrustedUrl(value)` â 13871 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 13872 * 13873 * @param {*} value The value to pass to `$sce.getTrusted`. 13874 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 13875 */ 13876 13877 /** 13878 * @ngdoc method 13879 * @name $sce#getTrustedResourceUrl 13880 * 13881 * @description 13882 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 13883 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 13884 * 13885 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 13886 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 13887 */ 13888 13889 /** 13890 * @ngdoc method 13891 * @name $sce#getTrustedJs 13892 * 13893 * @description 13894 * Shorthand method. `$sce.getTrustedJs(value)` â 13895 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 13896 * 13897 * @param {*} value The value to pass to `$sce.getTrusted`. 13898 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 13899 */ 13900 13901 /** 13902 * @ngdoc method 13903 * @name $sce#parseAsHtml 13904 * 13905 * @description 13906 * Shorthand method. `$sce.parseAsHtml(expression string)` â 13907 * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} 13908 * 13909 * @param {string} expression String expression to compile. 13910 * @returns {function(context, locals)} a function which represents the compiled expression: 13911 * 13912 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13913 * are evaluated against (typically a scope object). 13914 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13915 * `context`. 13916 */ 13917 13918 /** 13919 * @ngdoc method 13920 * @name $sce#parseAsCss 13921 * 13922 * @description 13923 * Shorthand method. `$sce.parseAsCss(value)` â 13924 * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} 13925 * 13926 * @param {string} expression String expression to compile. 13927 * @returns {function(context, locals)} a function which represents the compiled expression: 13928 * 13929 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13930 * are evaluated against (typically a scope object). 13931 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13932 * `context`. 13933 */ 13934 13935 /** 13936 * @ngdoc method 13937 * @name $sce#parseAsUrl 13938 * 13939 * @description 13940 * Shorthand method. `$sce.parseAsUrl(value)` â 13941 * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} 13942 * 13943 * @param {string} expression String expression to compile. 13944 * @returns {function(context, locals)} a function which represents the compiled expression: 13945 * 13946 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13947 * are evaluated against (typically a scope object). 13948 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13949 * `context`. 13950 */ 13951 13952 /** 13953 * @ngdoc method 13954 * @name $sce#parseAsResourceUrl 13955 *
13956 * @description 13957 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 13958 * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} 13959 * 13960 * @param {string} expression String expression to compile. 13961 * @returns {function(context, locals)} a function which represents the compiled expression: 13962 * 13963 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13964 * are evaluated against (typically a scope object). 13965 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13966 * `context`. 13967 */ 13968 13969 /** 13970 * @ngdoc method 13971 * @name $sce#parseAsJs 13972 * 13973 * @description 13974 * Shorthand method. `$sce.parseAsJs(value)` â 13975 * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} 13976 * 13977 * @param {string} expression String expression to compile. 13978 * @returns {function(context, locals)} a function which represents the compiled expression: 13979 * 13980 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13981 * are evaluated against (typically a scope object). 13982 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13983 * `context`. 13984 */ 13985 13986 // Shorthand delegations. 13987 var parse = sce.parseAs, 13988 getTrusted = sce.getTrusted, 13989 trustAs = sce.trustAs; 13990 13991 forEach(SCE_CONTEXTS, function (enumValue, name) { 13992 var lName = lowercase(name); 13993 sce[camelCase("parse_as_" + lName)] = function (expr) { 13994 return parse(enumValue, expr); 13995 }; 13996 sce[camelCase("get_trusted_" + lName)] = function (value) { 13997 return getTrusted(enumValue, value); 13998 }; 13999 sce[camelCase("trust_as_" + lName)] = function (value) { 14000 return trustAs(enumValue, value); 14001 }; 14002 }); 14003 14004 return sce; 14005 }]; 14006 } 14007 14008 /** 14009 * !!! This is an undocumented "private" service !!! 14010 * 14011 * @name $sniffer 14012 * @requires $window 14013 * @requires $document 14014 * 14015 * @property {boolean} history Does the browser support html5 history api ? 14016 * @property {boolean} hashchange Does the browser support hashchange event ? 14017 * @property {boolean} transitions Does the browser support CSS transition events ? 14018 * @property {boolean} animations Does the browser support CSS animation events ? 14019 * 14020 * @description 14021 * This is very simple implementation of testing browser's features. 14022 */ 14023 function $SnifferProvider() { 14024 this.$get = ['$window', '$document', function ($window, $document) { 14025 var eventSupport = {}, 14026 android = 14027 int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 14028 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 14029 document = $document[0] || {}, 14030 documentMode = document.documentMode, 14031 vendorPrefix, 14032 vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, 14033 bodyStyle = document.body && document.body.style, 14034 transitions = false, 14035 animations = false, 14036 match; 14037 14038 if (bodyStyle) { 14039 for (var prop in bodyStyle) { 14040 if (match = vendorRegex.exec(prop)) { 14041 vendorPrefix = match[0]; 14042 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 14043 break; 14044 } 14045 } 14046 14047 if (!vendorPrefix) { 14048 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 14049 } 14050 14051 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 14052 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 14053 14054 if (android && (!transitions || !animations)) { 14055 transitions = isString(document.body.style.webkitTransition); 14056 animations = isString(document.body.style.webkitAnimation); 14057 } 14058 } 14059 14060 14061 return { 14062 // Android has history.pushState, but it does not update location correctly 14063 // so let's not use the history API at all. 14064 // http://code.google.com/p/android/issues/detail?id=17471 14065 // https://github.com/angular/angular.js/issues/904 14066 14067 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 14068 // so let's not use the history API also 14069 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 14070 // jshint -W018 14071 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 14072 // jshint +W018 14073 hashchange: 'onhashchange' in $window && 14074 // IE8 compatible mode lies 14075 (!documentMode || documentMode > 7), 14076 hasEvent: function (event) { 14077 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 14078 // it. In particular the event is not fired when backspace or delete key are pressed or 14079 // when cut operation is performed. 14080 if (event == 'input' && msie == 9) return false;
14081 14082 if (isUndefined(eventSupport[event])) { 14083 var divElm = document.createElement('div'); 14084 eventSupport[event] = 'on' + event in divElm; 14085 } 14086 14087 return eventSupport[event]; 14088 }, 14089 csp: csp(), 14090 vendorPrefix: vendorPrefix, 14091 transitions: transitions, 14092 animations: animations, 14093 android: android, 14094 msie: msie, 14095 msieDocumentMode: documentMode 14096 }; 14097 }]; 14098 } 14099 14100 function $TimeoutProvider() { 14101 this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', 14102 function ($rootScope, $browser, $q, $exceptionHandler) { 14103 var deferreds = {}; 14104 14105 14106 /** 14107 * @ngdoc service 14108 * @name $timeout 14109 * 14110 * @description 14111 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 14112 * block and delegates any exceptions to 14113 * {@link ng.$exceptionHandler $exceptionHandler} service. 14114 * 14115 * The return value of registering a timeout function is a promise, which will be resolved when 14116 * the timeout is reached and the timeout function is executed. 14117 * 14118 * To cancel a timeout request, call `$timeout.cancel(promise)`. 14119 * 14120 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 14121 * synchronously flush the queue of deferred functions. 14122 * 14123 * @param {function()} fn A function, whose execution should be delayed. 14124 * @param {number=} [delay=0] Delay in milliseconds. 14125 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 14126 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 14127 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 14128 * promise will be resolved with is the return value of the `fn` function. 14129 * 14130 */ 14131 function timeout(fn, delay, invokeApply) { 14132 var deferred = $q.defer(), 14133 promise = deferred.promise, 14134 skipApply = (isDefined(invokeApply) && !invokeApply), 14135 timeoutId; 14136 14137 timeoutId = $browser.defer(function () { 14138 try { 14139 deferred.resolve(fn()); 14140 } catch (e) { 14141 deferred.reject(e); 14142 $exceptionHandler(e); 14143 } 14144 finally { 14145 delete deferreds[promise.$$timeoutId]; 14146 } 14147 14148 if (!skipApply) $rootScope.$apply(); 14149 }, delay); 14150 14151 promise.$$timeoutId = timeoutId; 14152 deferreds[timeoutId] = deferred; 14153 14154 return promise; 14155 } 14156 14157 14158 /** 14159 * @ngdoc method 14160 * @name $timeout#cancel 14161 * 14162 * @description 14163 * Cancels a task associated with the `promise`. As a result of this, the promise will be 14164 * resolved with a rejection. 14165 * 14166 * @param {Promise=} promise Promise returned by the `$timeout` function. 14167 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 14168 * canceled. 14169 */ 14170 timeout.cancel = function (promise) { 14171 if (promise && promise.$$timeoutId in deferreds) { 14172 deferreds[promise.$$timeoutId].reject('canceled'); 14173 delete deferreds[promise.$$timeoutId]; 14174 return $browser.defer.cancel(promise.$$timeoutId); 14175 } 14176 return false;
vendor: 5,620 bytes, lines 14177-14289
14177 }; 14178 14179 return timeout; 14180 }]; 14181 } 14182 14183// NOTE: The usage of window and document instead of $window and $document here is 14184// deliberate. This service depends on the specific behavior of anchor nodes created by the 14185// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 14186// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 14187// doesn't know about mocked locations and resolves URLs to the real document - which is 14188// exactly the behavior needed here. There is little value is mocking these out for this 14189// service. 14190 var urlParsingNode = document.createElement("a"); 14191 var originUrl = urlResolve(window.location.href, true); 14192 14193 14194 /** 14195 * 14196 * Implementation Notes for non-IE browsers 14197 * ---------------------------------------- 14198 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 14199 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 14200 * URL will be resolved into an absolute URL in the context of the application document. 14201 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 14202 * properties are all populated to reflect the normalized URL. This approach has wide 14203 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 14204 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 14205 * 14206 * Implementation Notes for IE 14207 * --------------------------- 14208 * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other 14209 * browsers. However, the parsed components will not be set if the URL assigned did not specify 14210 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 14211 * work around that by performing the parsing in a 2nd step by taking a previously normalized 14212 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 14213 * properties such as protocol, hostname, port, etc. 14214 * 14215 * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one 14216 * uses the inner HTML approach to assign the URL as part of an HTML snippet - 14217 * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. 14218 * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. 14219 * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that 14220 * method and IE < 8 is unsupported. 14221 * 14222 * References: 14223 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 14224 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 14225 * http://url.spec.whatwg.org/#urlutils 14226 * https://github.com/angular/angular.js/pull/2902 14227 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 14228 * 14229 * @function 14230 * @param {string} url The URL to be parsed. 14231 * @description Normalizes and parses a URL. 14232 * @returns {object} Returns the normalized URL as a dictionary. 14233 * 14234 * | member name | Description | 14235 * |---------------|----------------| 14236 * | href | A normalized version of the provided URL if it was not an absolute URL | 14237 * | protocol | The protocol including the trailing colon | 14238 * | host | The host and port (if the port is non-default) of the normalizedUrl | 14239 * | search | The search params, minus the question mark | 14240 * | hash | The hash string, minus the hash symbol 14241 * | hostname | The hostname 14242 * | port | The port, without ":" 14243 * | pathname | The pathname, beginning with "/" 14244 * 14245 */ 14246 function urlResolve(url, base) { 14247 var href = url; 14248 14249 if (msie) { 14250 // Normalize before parse. Refer Implementation Notes on why this is 14251 // done in two steps on IE. 14252 urlParsingNode.setAttribute("href", href); 14253 href = urlParsingNode.href; 14254 } 14255 14256 urlParsingNode.setAttribute('href', href); 14257 14258 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 14259 return { 14260 href: urlParsingNode.href, 14261 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 14262 host: urlParsingNode.host, 14263 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 14264 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 14265 hostname: urlParsingNode.hostname, 14266 port: urlParsingNode.port, 14267 pathname: (urlParsingNode.pathname.charAt(0) === '/') 14268 ? urlParsingNode.pathname 14269 : '/' + urlParsingNode.pathname 14270 }; 14271 } 14272 14273 /** 14274 * Parse a request URL and determine whether this is a same-origin request as the application document. 14275 * 14276 * @param {string|object} requestUrl The url of the request as a string that will be resolved 14277 * or a parsed URL object. 14278 * @returns {boolean} Whether the request is for the same origin as the application document. 14279 */ 14280 function urlIsSameOrigin(requestUrl) { 14281 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 14282 return (parsed.protocol === originUrl.protocol && 14283 parsed.host === originUrl.host); 14284 } 14285 14286 /** 14287 * @ngdoc service 14288 * @name $window 14289 *
14290 * @description 14291 * A reference to the browser's `window` object. While `window` 14292 * is globally available in JavaScript, it causes testability problems, because 14293 * it is a global variable. In angular we always refer to it through the 14294 * `$window` service, so it may be overridden, removed or mocked for testing. 14295 * 14296 * Expressions, like the one defined for the `ngClick` directive in the example 14297 * below, are evaluated with respect to the current scope. Therefore, there is 14298 * no risk of inadvertently coding in a dependency on a global value in such an 14299 * expression. 14300 * 14301 * @example 14302 <example> 14303 <file name="index.jsp"> 14304 <script> 14305 function Ctrl($scope, $window) { 14306 $scope.greeting = 'Hello, World!'; 14307 $scope.doGreeting = function(greeting) { 14308 $window.alert(greeting); 14309 }; 14310 } 14311 </script> 14312 <div ng-controller="Ctrl"> 14313 <input type="text" ng-model="greeting" /> 14314 <button ng-click="doGreeting(greeting)">ALERT</button> 14315 </div> 14316 </file> 14317 <file name="protractor.js" type="protractor"> 14318 it('should display the greeting in the input box', function() { 14319 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 14320 // If we click the button it will block the test runner 14321 // element(':button').click(); 14322 }); 14323 </file> 14324 </example> 14325 */ 14326 function $WindowProvider() { 14327 this.$get = valueFn(window); 14328 } 14329 14330 /** 14331 * @ngdoc provider 14332 * @name $filterProvider 14333 * @description 14334 * 14335 * Filters are just functions which transform input to an output. However filters need to be 14336 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 14337 * annotated with dependencies and is responsible for creating a filter function. 14338 * 14339 * ```js 14340 * // Filter registration 14341 * function MyModule($provide, $filterProvider) { 14342 * // create a service to demonstrate injection (not always needed) 14343 * $provide.value('greet', function(name){ 14344 * return 'Hello ' + name + '!'; 14345 * }); 14346 * 14347 * // register a filter factory which uses the 14348 * // greet service to demonstrate DI. 14349 * $filterProvider.register('greet', function(greet){ 14350 * // return the filter function which uses the greet service 14351 * // to generate salutation 14352 * return function(text) { 14353 * // filters need to be forgiving so check input validity 14354 * return text && greet(text) || text; 14355 * }; 14356 * }); 14357 * } 14358 * ``` 14359 * 14360 * The filter function is registered with the `$injector` under the filter name suffix with 14361 * `Filter`. 14362 * 14363 * ```js 14364 * it('should be the same instance', inject( 14365 * function($filterProvider) { 14366 * $filterProvider.register('reverse', function(){ 14367 * return ...; 14368 * }); 14369 * }, 14370 * function($filter, reverseFilter) { 14371 * expect($filter('reverse')).toBe(reverseFilter); 14372 * }); 14373 * ``` 14374 * 14375 * 14376 * For more information about how angular filters work, and how to create your own filters, see 14377 * {@link guide/filter Filters} in the Angular Developer Guide. 14378 */ 14379 /** 14380 * @ngdoc method 14381 * @name $filterProvider#register 14382 * @description 14383 * Register filter factory function. 14384 * 14385 * @param {String} name Name of the filter. 14386 * @param {Function} fn The filter factory function which is injectable. 14387 */ 14388 14389 14390 /** 14391 * @ngdoc service 14392 * @name $filter 14393 * @function 14394 * @description 14395 * Filters are used for formatting data displayed to the user. 14396 * 14397 * The general syntax in templates is as follows: 14398 * 14399 * {{ expression [| filter_name[:parameter_value] ... ] }} 14400 * 14401 * @param {String} name Name of the filter function to retrieve 14402 * @return {Function} the filter function 14403 */ 14404 $FilterProvider.$inject = ['$provide']; 14405 function $FilterProvider($provide) { 14406 var suffix = 'Filter'; 14407 14408 /** 14409 * @ngdoc method 14410 * @name $controllerProvider#register 14411 * @param {string|Object} name Name of the filter function, or an object map of filters where 14412 * the keys are the filter names and the values are the filter factories. 14413 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 14414 * of the registered filter instances. 14415 */ 14416 function register(name, factory) { 14417 if (isObject(name)) { 14418 var filters = {};
14419 forEach(name, function (filter, key) { 14420 filters[key] = register(key, filter); 14421 }); 14422 return filters; 14423 } else { 14424 return $provide.factory(name + suffix, factory); 14425 } 14426 } 14427 14428 this.register = register; 14429 14430 this.$get = ['$injector', function ($injector) { 14431 return function (name) { 14432 return $injector.get(name + suffix); 14433 }; 14434 }]; 14435 14436 //////////////////////////////////////// 14437 14438 /* global 14439 currencyFilter: false, 14440 dateFilter: false, 14441 filterFilter: false, 14442 jsonFilter: false, 14443 limitToFilter: false, 14444 lowercaseFilter: false, 14445 numberFilter: false, 14446 orderByFilter: false, 14447 uppercaseFilter: false, 14448 */ 14449 14450 register('currency', currencyFilter); 14451 register('date', dateFilter); 14452 register('filter', filterFilter); 14453 register('json', jsonFilter); 14454 register('limitTo', limitToFilter); 14455 register('lowercase', lowercaseFilter); 14456 register('number', numberFilter); 14457 register('orderBy', orderByFilter); 14458 register('uppercase', uppercaseFilter); 14459 } 14460 14461 /** 14462 * @ngdoc filter 14463 * @name filter 14464 * @function 14465 * 14466 * @description 14467 * Selects a subset of items from `array` and returns it as a new array. 14468 * 14469 * @param {Array} array The source array. 14470 * @param {string|Object|function()} expression The predicate to be used for selecting items from 14471 * `array`. 14472 * 14473 * Can be one of: 14474 * 14475 * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against 14476 * the contents of the `array`. All strings or objects with string properties in `array` that contain this string 14477 * will be returned. The predicate can be negated by prefixing the string with `!`. 14478 * 14479 * - `Object`: A pattern object can be used to filter specific properties on objects contained 14480 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 14481 * which have property `name` containing "M" and property `phone` containing "1". A special 14482 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 14483 * property of the object. That's equivalent to the simple substring match with a `string` 14484 * as described above. 14485 * 14486 * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is 14487 * called for each element of `array`. The final result is an array of those elements that 14488 * the predicate returned true for. 14489 * 14490 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 14491 * determining if the expected value (from the filter expression) and actual value (from 14492 * the object in the array) should be considered a match. 14493 * 14494 * Can be one of: 14495 * 14496 * - `function(actual, expected)`: 14497 * The function will be given the object value and the predicate value to compare and 14498 * should return true if the item should be included in filtered result. 14499 * 14500 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. 14501 * this is essentially strict comparison of expected and actual. 14502 * 14503 * - `false|undefined`: A short hand for a function which will look for a substring match in case 14504 * insensitive way. 14505 * 14506 * @example 14507 <example> 14508 <file name="index.jsp"> 14509 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 14510 {name:'Mary', phone:'800-BIG-MARY'}, 14511 {name:'Mike', phone:'555-4321'}, 14512 {name:'Adam', phone:'555-5678'}, 14513 {name:'Julie', phone:'555-8765'}, 14514 {name:'Juliette', phone:'555-5678'}]"></div> 14515 14516 Search: <input ng-model="searchText"> 14517 <table id="searchTextResults"> 14518 <tr><th>Name</th><th>Phone</th></tr> 14519 <tr ng-repeat="friend in friends | filter:searchText"> 14520 <td>{{friend.name}}</td> 14521 <td>{{friend.phone}}</td> 14522 </tr> 14523 </table> 14524 <hr> 14525 Any: <input ng-model="search.$"> <br> 14526 Name only <input ng-model="search.name"><br> 14527 Phone only <input ng-model="search.phone"><br> 14528 Equality <input type="checkbox" ng-model="strict"><br> 14529 <table id="searchObjResults"> 14530 <tr><th>
14530Name</th><th>Phone</th></tr> 14531 <tr ng-repeat="friendObj in friends | filter:search:strict"> 14532 <td>{{friendObj.name}}</td> 14533 <td>{{friendObj.phone}}</td> 14534 </tr> 14535 </table> 14536 </file> 14537 <file name="protractor.js" type="protractor"> 14538 var expectFriendNames = function(expectedNames, key) { 14539 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 14540 arr.forEach(function(wd, i) { 14541 expect(wd.getText()).toMatch(expectedNames[i]); 14542 }); 14543 }); 14544 }; 14545 14546 it('should search across all fields when filtering with a string', function() { 14547 var searchText = element(by.model('searchText')); 14548 searchText.clear(); 14549 searchText.sendKeys('m'); 14550 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 14551 14552 searchText.clear(); 14553 searchText.sendKeys('76'); 14554 expectFriendNames(['John', 'Julie'], 'friend'); 14555 }); 14556 14557 it('should search in specific fields when filtering with a predicate object', function() { 14558 var searchAny = element(by.model('search.$')); 14559 searchAny.clear(); 14560 searchAny.sendKeys('i'); 14561 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 14562 }); 14563 it('should use a equal comparison when comparator is true', function() { 14564 var searchName = element(by.model('search.name')); 14565 var strict = element(by.model('strict')); 14566 searchName.clear(); 14567 searchName.sendKeys('Julie'); 14568 strict.click(); 14569 expectFriendNames(['Julie'], 'friendObj'); 14570 }); 14571 </file> 14572 </example> 14573 */ 14574 function filterFilter() { 14575 return function (array, expression, comparator) { 14576 if (!isArray(array)) return array; 14577 14578 var comparatorType = typeof(comparator), 14579 predicates = []; 14580 14581 predicates.check = function (value) { 14582 for (var j = 0; j < predicates.length; j++) { 14583 if (!predicates[j](value)) { 14584 return false; 14585 } 14586 } 14587 return true; 14588 }; 14589 14590 if (comparatorType !== 'function') { 14591 if (comparatorType === 'boolean' && comparator) { 14592 comparator = function (obj, text) { 14593 return angular.equals(obj, text); 14594 }; 14595 } else { 14596 comparator = function (obj, text) { 14597 if (obj && text && typeof obj === 'object' && typeof text === 'object') { 14598 for (var objKey in obj) { 14599 if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) && 14600 comparator(obj[objKey], text[objKey])) { 14601 return true; 14602 } 14603 } 14604 return false; 14605 } 14606 text = ('' + text).toLowerCase(); 14607 return ('' + obj).toLowerCase().indexOf(text) > -1; 14608 }; 14609 } 14610 } 14611 14612 var search = function (obj, text) { 14613 if (typeof text == 'string' && text.charAt(0) === '!') { 14614 return !search(obj, text.substr(1)); 14615 } 14616 switch (typeof obj) { 14617 case "boolean": 14618 case "number": 14619 case "string": 14620 return comparator(obj, text); 14621 case "object": 14622 switch (typeof text) { 14623 case "object": 14624 return comparator(obj, text); 14625 default: 14626 for (var objKey in obj) { 14627 if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { 14628 return true; 14629 } 14630 } 14631 break; 14632 } 14633 return false;
14634 case "array": 14635 for (var i = 0; i < obj.length; i++) { 14636 if (search(obj[i], text)) { 14637 return true; 14638 } 14639 } 14640 return false; 14641 default: 14642 return false; 14643 } 14644 }; 14645 switch (typeof expression) { 14646 case "boolean": 14647 case "number": 14648 case "string": 14649 // Set up expression object and fall through 14650 expression = {$: expression}; 14651 // jshint -W086 14652 case "object": 14653 // jshint +W086 14654 for (var key in expression) { 14655 (function (path) { 14656 if (typeof expression[path] == 'undefined') return; 14657 predicates.push(function (value) { 14658 return search(path == '$' ? value : (value && value[path]), expression[path]); 14659 }); 14660 })(key); 14661 } 14662 break; 14663 case 'function': 14664 predicates.push(expression); 14665 break; 14666 default: 14667 return array; 14668 } 14669 var filtered = []; 14670 for (var j = 0; j < array.length; j++) { 14671 var value = array[j]; 14672 if (predicates.check(value)) { 14673 filtered.push(value); 14674 } 14675 } 14676 return filtered; 14677 }; 14678 } 14679 14680 /** 14681 * @ngdoc filter 14682 * @name currency 14683 * @function 14684 * 14685 * @description 14686 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 14687 * symbol for current locale is used. 14688 * 14689 * @param {number} amount Input to filter. 14690 * @param {string=} symbol Currency symbol or identifier to be displayed. 14691 * @returns {string} Formatted number. 14692 * 14693 * 14694 * @example 14695 <example> 14696 <file name="index.jsp"> 14697 <script> 14698 function Ctrl($scope) { 14699 $scope.amount = 1234.56; 14700 } 14701 </script> 14702 <div ng-controller="Ctrl"> 14703 <input type="number" ng-model="amount"> <br> 14704 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 14705 custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span> 14706 </div> 14707 </file> 14708 <file name="protractor.js" type="protractor"> 14709 it('should init with 1234.56', function() { 14710 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 14711 expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56'); 14712 }); 14713 it('should update', function() { 14714 if (browser.params.browser == 'safari') { 14715 // Safari does not understand the minus key. See 14716 // https://github.com/angular/protractor/issues/481 14717 return; 14718 } 14719 element(by.model('amount')).clear(); 14720 element(by.model('amount')).sendKeys('-1234'); 14721 expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); 14722 expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)'); 14723 }); 14724 </file> 14725 </example> 14726 */ 14727 currencyFilter.$inject = ['$locale']; 14728 function currencyFilter($locale) { 14729 var formats = $locale.NUMBER_FORMATS; 14730 return function (amount, currencySymbol) { 14731 if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; 14732 return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). 14733 replace(/\u00A4/g, currencySymbol); 14734 }; 14735 } 14736 14737 /** 14738 * @ngdoc filter 14739 * @name number 14740 * @function 14741 * 14742 * @description 14743 * Formats a number as text. 14744 * 14745 * If the input is not a number an empty string is returned. 14746 * 14747 * @param {number|string} number Number to format. 14748 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 14749 * If this is not provided then the fraction size is computed from the current locale's number 14750 * formatting pattern. In the case of the default locale, it will be 3. 14751 * @returns {string} Number rounded to decimalPlaces and places a â,â after each third digit. 14752 * 14753 * @example 14754 <example> 14755 <file name="index.jsp"> 14756 <script> 14757 function Ctrl($scope) { 14758 $scope.val = 1234.56789; 14759 } 14760 </script> 14761 <div ng-controller="Ctrl"> 14762 Enter number: <input ng-model='val'><br> 14763 Default formatting: <span id='number-default'>{{val | number}}</span><br> 14764 No fractions: <span>{{val | number:0}}</span><br> 14765 Negative number: <span>{{-val | number:4}}</span>
14766 </div> 14767 </file> 14768 <file name="protractor.js" type="protractor"> 14769 it('should format numbers', function() { 14770 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 14771 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 14772 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 14773 }); 14774 14775 it('should update', function() { 14776 element(by.model('val')).clear(); 14777 element(by.model('val')).sendKeys('3374.333'); 14778 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 14779 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 14780 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 14781 }); 14782 </file> 14783 </example> 14784 */ 14785 14786 14787 numberFilter.$inject = ['$locale']; 14788 function numberFilter($locale) { 14789 var formats = $locale.NUMBER_FORMATS; 14790 return function (number, fractionSize) { 14791 return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 14792 fractionSize); 14793 }; 14794 } 14795 14796 var DECIMAL_SEP = '.'; 14797 14798 function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 14799 if (number == null || !isFinite(number) || isObject(number)) return ''; 14800 14801 var isNegative = number < 0; 14802 number = Math.abs(number); 14803 var numStr = number + '', 14804 formatedText = '', 14805 parts = []; 14806 14807 var hasExponent = false; 14808 if (numStr.indexOf('e') !== -1) { 14809 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 14810 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 14811 numStr = '0'; 14812 } else { 14813 formatedText = numStr; 14814 hasExponent = true; 14815 } 14816 } 14817 14818 if (!hasExponent) { 14819 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 14820 14821 // determine fractionSize if it is not specified 14822 if (isUndefined(fractionSize)) { 14823 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 14824 } 14825 14826 var pow = Math.pow(10, fractionSize); 14827 number = Math.round(number * pow) / pow; 14828 var fraction = ('' + number).split(DECIMAL_SEP); 14829 var whole = fraction[0]; 14830 fraction = fraction[1] || ''; 14831 14832 var i, pos = 0, 14833 lgroup = pattern.lgSize, 14834 group = pattern.gSize; 14835 14836 if (whole.length >= (lgroup + group)) { 14837 pos = whole.length - lgroup; 14838 for (i = 0; i < pos; i++) { 14839 if ((pos - i) % group === 0 && i !== 0) { 14840 formatedText += groupSep; 14841 } 14842 formatedText += whole.charAt(i); 14843 } 14844 } 14845 14846 for (i = pos; i < whole.length; i++) { 14847 if ((whole.length - i) % lgroup === 0 && i !== 0) { 14848 formatedText += groupSep; 14849 } 14850 formatedText += whole.charAt(i); 14851 } 14852 14853 // format fraction part. 14854 while (fraction.length < fractionSize) { 14855 fraction += '0'; 14856 } 14857 14858 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 14859 } else { 14860 14861 if (fractionSize > 0 && number > -1 && number < 1) { 14862 formatedText = number.toFixed(fractionSize); 14863 } 14864 } 14865 14866 parts.push(isNegative ? pattern.negPre : pattern.posPre); 14867 parts.push(formatedText); 14868 parts.push(isNegative ? pattern.negSuf : pattern.posSuf); 14869 return parts.join(''); 14870 } 14871 14872 function padNumber(num, digits, trim) { 14873 var neg = ''; 14874 if (num < 0) { 14875 neg = '-'; 14876 num = -num; 14877 } 14878 num = '' + num; 14879 while (num.length < digits) num = '0' + num; 14880 if (trim) 14881 num = num.substr(num.length - digits); 14882 return neg + num; 14883 } 14884 14885 14886 function dateGetter(name, size, offset, trim) { 14887 offset = offset || 0; 14888 return function (date) { 14889 var value = date['get' + name](); 14890 if (offset > 0 || value > -offset) 14891 value += offset; 14892 if (value === 0 && offset == -12) value = 12; 14893 return padNumber(value, size, trim); 14894 }; 14895 } 14896 14897 function dateStrGetter(name, shortForm) { 14898 return function (date, formats) { 14899 var value = date['get' + name](); 14900 var get = uppercase(shortForm ? ('SHORT' + name) : name); 14901 14902 return formats[get][value]; 14903 }; 14904 } 14905 14906 function timeZoneGetter(date) { 14907 var zone = -1 * date.getTimezoneOffset(); 14908 var paddedZone = (zone >= 0) ? "+" : ""; 14909 14910 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 14911 padNumber(Math.abs(zone % 60), 2); 14912 14913 return paddedZone; 14914 } 14915 14916 function ampmGetter(date, formats) { 14917 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 14918 } 14919 14920 var DATE_FORMATS = { 14921 yyyy: dateGetter('FullYear', 4), 14922 yy: dateGetter('FullYear', 2, 0, true), 14923 y: dateGetter('FullYear', 1), 14924 MMMM: dateStrGetter('Month'),
vendor: 7,605 bytes, lines 14925-15095
14925 MMM: dateStrGetter('Month', true), 14926 MM: dateGetter('Month', 2, 1), 14927 M: dateGetter('Month', 1, 1), 14928 dd: dateGetter('Date', 2), 14929 d: dateGetter('Date', 1), 14930 HH: dateGetter('Hours', 2), 14931 H: dateGetter('Hours', 1), 14932 hh: dateGetter('Hours', 2, -12), 14933 h: dateGetter('Hours', 1, -12), 14934 mm: dateGetter('Minutes', 2), 14935 m: dateGetter('Minutes', 1), 14936 ss: dateGetter('Seconds', 2), 14937 s: dateGetter('Seconds', 1), 14938 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 14939 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 14940 sss: dateGetter('Milliseconds', 3), 14941 EEEE: dateStrGetter('Day'), 14942 EEE: dateStrGetter('Day', true), 14943 a: ampmGetter, 14944 Z: timeZoneGetter 14945 }; 14946 14947 var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, 14948 NUMBER_STRING = /^\-?\d+$/; 14949 14950 /** 14951 * @ngdoc filter 14952 * @name date 14953 * @function 14954 * 14955 * @description 14956 * Formats `date` to a string based on the requested `format`. 14957 * 14958 * `format` string can be composed of the following elements: 14959 * 14960 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 14961 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 14962 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 14963 * * `'MMMM'`: Month in year (January-December) 14964 * * `'MMM'`: Month in year (Jan-Dec) 14965 * * `'MM'`: Month in year, padded (01-12) 14966 * * `'M'`: Month in year (1-12) 14967 * * `'dd'`: Day in month, padded (01-31) 14968 * * `'d'`: Day in month (1-31) 14969 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 14970 * * `'EEE'`: Day in Week, (Sun-Sat) 14971 * * `'HH'`: Hour in day, padded (00-23) 14972 * * `'H'`: Hour in day (0-23) 14973 * * `'hh'`: Hour in am/pm, padded (01-12) 14974 * * `'h'`: Hour in am/pm, (1-12) 14975 * * `'mm'`: Minute in hour, padded (00-59) 14976 * * `'m'`: Minute in hour (0-59) 14977 * * `'ss'`: Second in minute, padded (00-59) 14978 * * `'s'`: Second in minute (0-59) 14979 * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) 14980 * * `'a'`: am/pm marker 14981 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 14982 * 14983 * `format` string can also be one of the following predefined 14984 * {@link guide/i18n localizable formats}: 14985 * 14986 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 14987 * (e.g. Sep 3, 2010 12:05:08 pm) 14988 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) 14989 * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale 14990 * (e.g. Friday, September 3, 2010) 14991 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 14992 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 14993 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 14994 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) 14995 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) 14996 * 14997 * `format` string can contain literal values. These need to be quoted with single quotes (e.g. 14998 * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence 14999 * (e.g. `"h 'o''clock'"`). 15000 * 15001 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 15002 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its 15003 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 15004 * specified in the string input, the time is considered to be in the local timezone. 15005 * @param {string=} format Formatting rules (see Description). If not specified, 15006 * `mediumDate` is used. 15007 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 15008 * 15009 * @example 15010 <example> 15011 <file name="index.jsp"> 15012 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 15013 <span>{{1288323623006 | date:'medium'}}</span><br> 15014 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 15015 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 15016 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 15017 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 15018 </file> 15019 <file name="protractor.js" type="protractor"> 15020 it('should format date', function() { 15021 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 15022 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 15023 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 15024 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 15025 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 15026 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 15027 }); 15028 </file> 15029 </example> 15030 */ 15031 dateFilter.$inject = ['$locale']; 15032 function dateFilter($locale) { 15033 15034 15035 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 15036 // 1 2 3 4 5 6 7 8 9 10 11 15037 function jsonStringToDate(string) { 15038 var match; 15039 if (match = string.match(R_ISO8601_STR)) { 15040 var date = new Date(0), 15041 tzHour = 0, 15042 tzMin = 0, 15043 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 15044 timeSetter = match[8] ? date.setUTCHours : date.setHours; 15045 15046 if (match[9]) { 15047 tzHour = int(match[9] + match[10]); 15048 tzMin = int(match[9] + match[11]); 15049 } 15050 dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); 15051 var h = int(match[4] || 0) - tzHour; 15052 var m = int(match[5] || 0) - tzMin; 15053 var s = int(match[6] || 0); 15054 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 15055 timeSetter.call(date, h, m, s, ms); 15056 return date; 15057 } 15058 return string; 15059 } 15060 15061 15062 return function (date, format) { 15063 var text = '', 15064 parts = [], 15065 fn, match; 15066 15067 format = format || 'mediumDate'; 15068 format = $locale.DATETIME_FORMATS[format] || format; 15069 if (isString(date)) { 15070 if (NUMBER_STRING.test(date)) { 15071 date = int(date); 15072 } else { 15073 date = jsonStringToDate(date); 15074 } 15075 } 15076 15077 if (isNumber(date)) { 15078 date = new Date(date); 15079 } 15080 15081 if (!isDate(date)) { 15082 return date; 15083 } 15084 15085 while (format) { 15086 match = DATE_FORMATS_SPLIT.exec(format); 15087 if (match) { 15088 parts = concat(parts, match, 1); 15089 format = parts.pop(); 15090 } else { 15091 parts.push(format); 15092 format = null; 15093 } 15094 } 15095
15096 forEach(parts, function (value) { 15097 fn = DATE_FORMATS[value]; 15098 text += fn ? fn(date, $locale.DATETIME_FORMATS) 15099 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 15100 }); 15101 15102 return text; 15103 }; 15104 } 15105 15106 15107 /** 15108 * @ngdoc filter 15109 * @name json 15110 * @function 15111 * 15112 * @description 15113 * Allows you to convert a JavaScript object into JSON string. 15114 * 15115 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 15116 * the binding is automatically converted to JSON. 15117 * 15118 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 15119 * @returns {string} JSON string. 15120 * 15121 * 15122 * @example 15123 <example> 15124 <file name="index.jsp"> 15125 <pre>{{ {'name':'value'} | json }}</pre> 15126 </file> 15127 <file name="protractor.js" type="protractor"> 15128 it('should jsonify filtered objects', function() { 15129 expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/); 15130 }); 15131 </file> 15132 </example> 15133 * 15134 */ 15135 function jsonFilter() { 15136 return function (object) { 15137 return toJson(object, true); 15138 }; 15139 } 15140 15141 15142 /** 15143 * @ngdoc filter 15144 * @name lowercase 15145 * @function 15146 * @description 15147 * Converts string to lowercase. 15148 * @see angular.lowercase 15149 */ 15150 var lowercaseFilter = valueFn(lowercase); 15151 15152 15153 /** 15154 * @ngdoc filter 15155 * @name uppercase 15156 * @function 15157 * @description 15158 * Converts string to uppercase. 15159 * @see angular.uppercase 15160 */ 15161 var uppercaseFilter = valueFn(uppercase); 15162 15163 /** 15164 * @ngdoc filter 15165 * @name limitTo 15166 * @function 15167 * 15168 * @description 15169 * Creates a new array or string containing only a specified number of elements. The elements 15170 * are taken from either the beginning or the end of the source array or string, as specified by 15171 * the value and sign (positive or negative) of `limit`. 15172 * 15173 * @param {Array|string} input Source array or string to be limited. 15174 * @param {string|number} limit The length of the returned array or string. If the `limit` number 15175 * is positive, `limit` number of items from the beginning of the source array/string are copied. 15176 * If the number is negative, `limit` number of items from the end of the source array/string 15177 * are copied. The `limit` will be trimmed if it exceeds `array.length` 15178 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 15179 * had less than `limit` elements. 15180 * 15181 * @example 15182 <example> 15183 <file name="index.jsp"> 15184 <script> 15185 function Ctrl($scope) { 15186 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 15187 $scope.letters = "abcdefghi"; 15188 $scope.numLimit = 3; 15189 $scope.letterLimit = 3; 15190 } 15191 </script> 15192 <div ng-controller="Ctrl"> 15193 Limit {{numbers}} to: <input type="integer" ng-model="numLimit"> 15194 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 15195 Limit {{letters}} to: <input type="integer" ng-model="letterLimit"> 15196 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 15197 </div> 15198 </file> 15199 <file name="protractor.js" type="protractor"> 15200 var numLimitInput = element(by.model('numLimit')); 15201 var letterLimitInput = element(by.model('letterLimit')); 15202 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 15203 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 15204 15205 it('should limit the number array to first three items', function() { 15206 expect(numLimitInput.getAttribute('value')).toBe('3'); 15207 expect(letterLimitInput.getAttribute('value')).toBe('3'); 15208 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 15209 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 15210 }); 15211 15212 it('should update the output when -3 is entered', function() { 15213 numLimitInput.clear(); 15214 numLimitInput.sendKeys('-3'); 15215 letterLimitInput.clear(); 15216 letterLimitInput.sendKeys('-3'); 15217 expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 15218 expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 15219 }); 15220 15221 it('should not exceed the maximum size of input array', function() { 15222 numLimitInput.clear(); 15223 numLimitInput.sendKeys('100'); 15224 letterLimitInput.clear(); 15225 letterLimitInput.sendKeys('100'); 15226 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 15227 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 15228 }); 15229 </file> 15230 </example> 15231 */ 15232 function limitToFilter() { 15233 return function (input, limit) { 15234 if (!isArray(input) && !isString(input)) return input; 15235 15236 limit = int(limit); 15237 15238 if (isString(input)) { 15239 //NaN check on limit 15240 if (limit) { 15241 return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); 15242 } else { 15243 return ""; 15244 } 15245 } 15246 15247 var out = [], 15248 i, n; 15249 15250 // if abs(limit) exceeds maximum length, trim it 15251 if (limit > input.length) 15252 limit = input.length; 15253 else if (limit < -input.length) 15254 limit = -input.length; 15255 15256 if (limit > 0) { 15257 i = 0; 15258 n = limit; 15259 } else { 15260 i = input.length + limit; 15261 n = input.length; 15262 } 15263 15264 for (; i < n; i++) { 15265 out.push(input[i]); 15266 } 15267 15268 return out; 15269 }; 15270 } 15271 15272 /** 15273 * @ngdoc filter 15274 * @name orderBy 15275 * @function 15276 *
15277 * @description 15278 * Orders a specified `array` by the `expression` predicate. 15279 * 15280 * @param {Array} array The array to sort. 15281 * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be 15282 * used by the comparator to determine the order of elements. 15283 * 15284 * Can be one of: 15285 * 15286 * - `function`: Getter function. The result of this function will be sorted using the 15287 * `<`, `=`, `>` operator. 15288 * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' 15289 * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control 15290 * ascending or descending sort order (for example, +name or -name). 15291 * - `Array`: An array of function or string predicates. The first predicate in the array 15292 * is used for sorting, but when two items are equivalent, the next predicate is used. 15293 * 15294 * @param {boolean=} reverse Reverse the order of the array. 15295 * @returns {Array} Sorted copy of the source array. 15296 * 15297 * @example 15298 <example> 15299 <file name="index.jsp"> 15300 <script> 15301 function Ctrl($scope) { 15302 $scope.friends = 15303 [{name:'John', phone:'555-1212', age:10}, 15304 {name:'Mary', phone:'555-9876', age:19}, 15305 {name:'Mike', phone:'555-4321', age:21}, 15306 {name:'Adam', phone:'555-5678', age:35}, 15307 {name:'Julie', phone:'555-8765', age:29}] 15308 $scope.predicate = '-age'; 15309 } 15310 </script> 15311 <div ng-controller="Ctrl"> 15312 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 15313 <hr/> 15314 [ <a href="" ng-click="predicate=''">unsorted</a> ] 15315 <table class="friend"> 15316 <tr> 15317 <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a> 15318 (<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th> 15319 <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> 15320 <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th> 15321 </tr> 15322 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 15323 <td>{{friend.name}}</td> 15324 <td>{{friend.phone}}</td> 15325 <td>{{friend.age}}</td> 15326 </tr> 15327 </table>
15328 </div> 15329 </file> 15330 </example> 15331 */ 15332 orderByFilter.$inject = ['$parse']; 15333 function orderByFilter($parse) { 15334 return function (array, sortPredicate, reverseOrder) { 15335 if (!isArray(array)) return array; 15336 if (!sortPredicate) return array; 15337 sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate]; 15338 sortPredicate = map(sortPredicate, function (predicate) { 15339 var descending = false, get = predicate || identity; 15340 if (isString(predicate)) { 15341 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 15342 descending = predicate.charAt(0) == '-'; 15343 predicate = predicate.substring(1); 15344 } 15345 get = $parse(predicate); 15346 if (get.constant) { 15347 var key = get(); 15348 return reverseComparator(function (a, b) { 15349 return compare(a[key], b[key]); 15350 }, descending); 15351 } 15352 } 15353 return reverseComparator(function (a, b) { 15354 return compare(get(a), get(b)); 15355 }, descending); 15356 }); 15357 var arrayCopy = []; 15358 for (var i = 0; i < array.length; i++) { 15359 arrayCopy.push(array[i]); 15360 } 15361 return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); 15362 15363 function comparator(o1, o2) { 15364 for (var i = 0; i < sortPredicate.length; i++) { 15365 var comp = sortPredicate[i](o1, o2); 15366 if (comp !== 0) return comp; 15367 } 15368 return 0; 15369 } 15370 15371 function reverseComparator(comp, descending) { 15372 return toBoolean(descending) 15373 ? function (a, b) { 15374 return comp(b, a); 15375 } 15376 : comp; 15377 } 15378 15379 function compare(v1, v2) { 15380 var t1 = typeof v1; 15381 var t2 = typeof v2; 15382 if (t1 == t2) { 15383 if (t1 == "string") { 15384 v1 = v1.toLowerCase(); 15385 v2 = v2.toLowerCase(); 15386 } 15387 if (v1 === v2) return 0; 15388 return v1 < v2 ? -1 : 1; 15389 } else { 15390 return t1 < t2 ? -1 : 1; 15391 } 15392 } 15393 }; 15394 } 15395 15396 function ngDirective(directive) { 15397 if (isFunction(directive)) { 15398 directive = { 15399 link: directive 15400 }; 15401 } 15402 directive.restrict = directive.restrict || 'AC'; 15403 return valueFn(directive); 15404 } 15405 15406 /** 15407 * @ngdoc directive 15408 * @name a 15409 * @restrict E 15410 * 15411 * @description 15412 * Modifies the default behavior of the html A tag so that the default action is prevented when 15413 * the href attribute is empty. 15414 * 15415 * This change permits the easy creation of action links with the `ngClick` directive 15416 * without changing the location or causing page reloads, e.g.: 15417 * `<a href="" ng-click="list.addItem()">Add Item</a>` 15418 */ 15419 var htmlAnchorDirective = valueFn({ 15420 restrict: 'E', 15421 compile: function (element, attr) { 15422 15423 if (msie <= 8) { 15424 15425 // turn <a href ng-click="..">link</a> into a stylable link in IE 15426 // but only if it doesn't have name attribute, in which case it's an anchor 15427 if (!attr.href && !attr.name) { 15428 attr.$set('href', ''); 15429 } 15430 15431 // add a comment node to anchors to workaround IE bug that causes element content to be reset 15432 // to new attribute content if attribute is updated with value containing @ and element also 15433 // contains value with @ 15434 // see issue #1949 15435 element.append(document.createComment('IE fix')); 15436 } 15437 15438 if (!attr.href && !attr.xlinkHref && !attr.name) { 15439 return function (scope, element) { 15440 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 15441 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 15442 'xlink:href' : 'href'; 15443 element.on('click', function (event) { 15444 // if we have no href url, then don't navigate anywhere. 15445 if (!element.attr(href)) { 15446 event.preventDefault(); 15447 } 15448 }); 15449 }; 15450 } 15451 } 15452 }); 15453 15454 /** 15455 * @ngdoc directive 15456 * @name ngHref 15457 * @restrict A 15458 * @priority 99 15459 *
15460 * @description 15461 * Using Angular markup like `{{hash}}` in an href attribute will 15462 * make the link go to the wrong URL if the user clicks it before 15463 * Angular has a chance to replace the `{{hash}}` markup with its 15464 * value. Until Angular replaces the markup the link will be broken 15465 * and will most likely return a 404 error. 15466 * 15467 * The `ngHref` directive solves this problem. 15468 * 15469 * The wrong way to write it: 15470 * ```html 15471 * <a href="http://www.gravatar.com/avatar/{{hash}}"/> 15472 * ``` 15473 * 15474 * The correct way to write it: 15475 * ```html 15476 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/> 15477 * ``` 15478 * 15479 * @element A 15480 * @param {template} ngHref any string which can contain `{{}}` markup. 15481 * 15482 * @example 15483 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 15484 * in links and their different behaviors: 15485 <example> 15486 <file name="index.jsp"> 15487 <input ng-model="value" /><br /> 15488 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 15489 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 15490 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 15491 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 15492 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 15493 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 15494 </file> 15495 <file name="protractor.js" type="protractor"> 15496 it('should execute ng-click but not reload when href without value', function() { 15497 element(by.id('link-1')).click(); 15498 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 15499 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 15500 }); 15501 15502 it('should execute ng-click but not reload when href empty string', function() { 15503 element(by.id('link-2')).click(); 15504 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 15505 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 15506 }); 15507 15508 it('should execute ng-click and change url when ng-href specified', function() { 15509 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 15510 15511 element(by.id('link-3')).click(); 15512 15513 // At this point, we navigate away from an Angular page, so we need 15514 // to use browser.driver to get the base webdriver. 15515 15516 browser.wait(function() { 15517 return browser.driver.getCurrentUrl().then(function(url) { 15518 return url.match(/\/123$/); 15519 }); 15520 }, 1000, 'page should navigate to /123'); 15521 }); 15522 15523 xit('should execute ng-click but not reload when href empty string and name specified', function() { 15524 element(by.id('link-4')).click(); 15525 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 15526 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 15527 }); 15528 15529 it('should execute ng-click but not reload when no href but name specified', function() { 15530 element(by.id('link-5')).click(); 15531 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 15532 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 15533 }); 15534 15535 it('should only change url when only ng-href', function() { 15536 element(by.model('value')).clear(); 15537 element(by.model('value')).sendKeys('6'); 15538 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 15539 15540 element(by.id('link-6')).click(); 15541 15542 // At this point, we navigate away from an Angular page, so we need 15543 // to use browser.driver to get the base webdriver. 15544 browser.wait(function() { 15545 return browser.driver.getCurrentUrl().then(function(url) { 15546 return url.match(/\/6$/); 15547 }); 15548 }, 1000, 'page should navigate to /6'); 15549 }); 15550 </file> 15551 </example> 15552 */ 15553 15554 /** 15555 * @ngdoc directive 15556 * @name ngSrc 15557 * @restrict A 15558 * @priority 99 15559 *
15560 * @description 15561 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 15562 * work right: The browser will fetch from the URL with the literal 15563 * text `{{hash}}` until Angular replaces the expression inside 15564 * `{{hash}}`. The `ngSrc` directive solves this problem. 15565 * 15566 * The buggy way to write it: 15567 * ```html 15568 * <img src="http://www.gravatar.com/avatar/{{hash}}"/> 15569 * ``` 15570 * 15571 * The correct way to write it: 15572 * ```html 15573 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/> 15574 * ``` 15575 * 15576 * @element IMG 15577 * @param {template} ngSrc any string which can contain `{{}}` markup. 15578 */ 15579 15580 /** 15581 * @ngdoc directive 15582 * @name ngSrcset 15583 * @restrict A 15584 * @priority 99 15585 * 15586 * @description 15587 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 15588 * work right: The browser will fetch from the URL with the literal 15589 * text `{{hash}}` until Angular replaces the expression inside 15590 * `{{hash}}`. The `ngSrcset` directive solves this problem. 15591 * 15592 * The buggy way to write it: 15593 * ```html 15594 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 15595 * ``` 15596 * 15597 * The correct way to write it: 15598 * ```html 15599 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 15600 * ``` 15601 * 15602 * @element IMG 15603 * @param {template} ngSrcset any string which can contain `{{}}` markup. 15604 */ 15605 15606 /** 15607 * @ngdoc directive 15608 * @name ngDisabled 15609 * @restrict A 15610 * @priority 100 15611 * 15612 * @description 15613 * 15614 * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: 15615 * ```html 15616 * <div ng-init="scope = { isDisabled: false }"> 15617 * <button disabled="{{scope.isDisabled}}">Disabled</button> 15618 * </div> 15619 * ``` 15620 * 15621 * The HTML specification does not require browsers to preserve the values of boolean attributes 15622 * such as disabled. (Their presence means true and their absence means false.) 15623 * If we put an Angular interpolation expression into such an attribute then the 15624 * binding information would be lost when the browser removes the attribute. 15625 * The `ngDisabled` directive solves this problem for the `disabled` attribute. 15626 * This complementary directive is not removed by the browser and so provides 15627 * a permanent reliable place to store the binding information. 15628 * 15629 * @example 15630 <example> 15631 <file name="index.jsp"> 15632 Click me to toggle: <input type="checkbox" ng-model="checked"><br/> 15633 <button ng-model="button" ng-disabled="checked">Button</button> 15634 </file> 15635 <file name="protractor.js" type="protractor"> 15636 it('should toggle button', function() { 15637 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy(); 15638 element(by.model('checked')).click(); 15639 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 15640 }); 15641 </file> 15642 </example> 15643 * 15644 * @element INPUT 15645 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 15646 * then special attribute "disabled" will be set on the element 15647 */ 15648 15649 15650 /** 15651 * @ngdoc directive 15652 * @name ngChecked 15653 * @restrict A 15654 * @priority 100 15655 * 15656 * @description 15657 * The HTML specification does not require browsers to preserve the values of boolean attributes 15658 * such as checked. (Their presence means true and their absence means false.) 15659 * If we put an Angular interpolation expression into such an attribute then the 15660 * binding information would be lost when the browser removes the attribute. 15661 * The `ngChecked` directive solves this problem for the `checked` attribute. 15662 * This complementary directive is not removed by the browser and so provides 15663 * a permanent reliable place to store the binding information. 15664 * @example 15665 <example> 15666 <file name="index.jsp"> 15667 Check me to check both: <input type="checkbox" ng-model="master"><br/> 15668 <input id="checkSlave" type="checkbox" ng-checked="master"> 15669 </file> 15670 <file name="protractor.js" type="protractor"> 15671 it('should check both checkBoxes', function() { 15672 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
15673 element(by.model('master')).click(); 15674 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 15675 }); 15676 </file> 15677 </example> 15678 * 15679 * @element INPUT 15680 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 15681 * then special attribute "checked" will be set on the element 15682 */ 15683 15684 15685 /** 15686 * @ngdoc directive 15687 * @name ngReadonly 15688 * @restrict A 15689 * @priority 100 15690 * 15691 * @description 15692 * The HTML specification does not require browsers to preserve the values of boolean attributes 15693 * such as readonly. (Their presence means true and their absence means false.) 15694 * If we put an Angular interpolation expression into such an attribute then the 15695 * binding information would be lost when the browser removes the attribute. 15696 * The `ngReadonly` directive solves this problem for the `readonly` attribute. 15697 * This complementary directive is not removed by the browser and so provides 15698 * a permanent reliable place to store the binding information. 15699 * @example 15700 <example> 15701 <file name="index.jsp"> 15702 Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/> 15703 <input type="text" ng-readonly="checked" value="I'm Angular"/> 15704 </file> 15705 <file name="protractor.js" type="protractor"> 15706 it('should toggle readonly attr', function() { 15707 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 15708 element(by.model('checked')).click(); 15709 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 15710 }); 15711 </file> 15712 </example> 15713 * 15714 * @element INPUT 15715 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 15716 * then special attribute "readonly" will be set on the element 15717 */ 15718 15719 15720 /** 15721 * @ngdoc directive 15722 * @name ngSelected 15723 * @restrict A 15724 * @priority 100 15725 * 15726 * @description 15727 * The HTML specification does not require browsers to preserve the values of boolean attributes 15728 * such as selected. (Their presence means true and their absence means false.) 15729 * If we put an Angular interpolation expression into such an attribute then the 15730 * binding information would be lost when the browser removes the attribute. 15731 * The `ngSelected` directive solves this problem for the `selected` attribute. 15732 * This complementary directive is not removed by the browser and so provides 15733 * a permanent reliable place to store the binding information. 15734 * 15735 * @example 15736 <example> 15737 <file name="index.jsp"> 15738 Check me to select: <input type="checkbox" ng-model="selected"><br/> 15739 <select> 15740 <option>Hello!</option> 15741 <option id="greet" ng-selected="selected">Greetings!</option> 15742 </select> 15743 </file> 15744 <file name="protractor.js" type="protractor"> 15745 it('should select Greetings!', function() { 15746 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy(); 15747 element(by.model('selected')).click(); 15748 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 15749 }); 15750 </file> 15751 </example> 15752 * 15753 * @element OPTION 15754 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 15755 * then special attribute "selected" will be set on the element 15756 */ 15757 15758 /** 15759 * @ngdoc directive 15760 * @name ngOpen 15761 * @restrict A 15762 * @priority 100 15763 * 15764 * @description 15765 * The HTML specification does not require browsers to preserve the values of boolean attributes 15766 * such as open. (Their presence means true and their absence means false.) 15767 * If we put an Angular interpolation expression into such an attribute then the 15768 * binding information would be lost when the browser removes the attribute. 15769 * The `ngOpen` directive solves this problem for the `open` attribute. 15770 * This complementary directive is not removed by the browser and so provides 15771 * a permanent reliable place to store the binding information. 15772 * @example 15773 <example> 15774 <file name="index.jsp"> 15775 Check me check multiple: <input type="checkbox" ng-model="open"><br/> 15776 <details id="details" ng-open="open"> 15777 <summary>Show/Hide me</summary>
15778 </details> 15779 </file> 15780 <file name="protractor.js" type="protractor"> 15781 it('should toggle open', function() { 15782 expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); 15783 element(by.model('open')).click(); 15784 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 15785 }); 15786 </file> 15787 </example> 15788 * 15789 * @element DETAILS 15790 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 15791 * then special attribute "open" will be set on the element 15792 */ 15793 15794 var ngAttributeAliasDirectives = {}; 15795 15796 15797// boolean attrs are evaluated 15798 forEach(BOOLEAN_ATTR, function (propName, attrName) { 15799 // binding to multiple is not supported 15800 if (propName == "multiple") return; 15801 15802 var normalized = directiveNormalize('ng-' + attrName); 15803 ngAttributeAliasDirectives[normalized] = function () { 15804 return { 15805 priority: 100, 15806 link: function (scope, element, attr) { 15807 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 15808 attr.$set(attrName, !!value); 15809 }); 15810 } 15811 }; 15812 }; 15813 }); 15814 15815 15816// ng-src, ng-srcset, ng-href are interpolated 15817 forEach(['src', 'srcset', 'href'], function (attrName) { 15818 var normalized = directiveNormalize('ng-' + attrName); 15819 ngAttributeAliasDirectives[normalized] = function () { 15820 return { 15821 priority: 99, // it needs to run after the attributes are interpolated 15822 link: function (scope, element, attr) { 15823 var propName = attrName, 15824 name = attrName; 15825 15826 if (attrName === 'href' && 15827 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 15828 name = 'xlinkHref'; 15829 attr.$attr[name] = 'xlink:href'; 15830 propName = null; 15831 } 15832 15833 attr.$observe(normalized, function (value) { 15834 if (!value) 15835 return; 15836 15837 attr.$set(name, value); 15838 15839 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 15840 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 15841 // to set the property as well to achieve the desired effect. 15842 // we use attr[attrName] value since $set can sanitize the url. 15843 if (msie && propName) element.prop(propName, attr[name]); 15844 }); 15845 } 15846 }; 15847 }; 15848 }); 15849 15850 /* global -nullFormCtrl */ 15851 var nullFormCtrl = { 15852 $addControl: noop, 15853 $removeControl: noop, 15854 $setValidity: noop, 15855 $setDirty: noop, 15856 $setPristine: noop 15857 }; 15858 15859 /** 15860 * @ngdoc type 15861 * @name form.FormController 15862 * 15863 * @property {boolean} $pristine True if user has not interacted with the form yet. 15864 * @property {boolean} $dirty True if user has already interacted with the form. 15865 * @property {boolean} $valid True if all of the containing forms and controls are valid. 15866 * @property {boolean} $invalid True if at least one containing control or form is invalid. 15867 * 15868 * @property {Object} $error Is an object hash, containing references to all invalid controls or 15869 * forms, where: 15870 * 15871 * - keys are validation tokens (error names), 15872 * - values are arrays of controls or forms that are invalid for given error name. 15873 * 15874 * 15875 * Built-in validation tokens: 15876 * 15877 * - `email` 15878 * - `max` 15879 * - `maxlength` 15880 * - `min` 15881 * - `minlength` 15882 * - `number` 15883 * - `pattern` 15884 * - `required` 15885 * - `url` 15886 * 15887 * @description 15888 * `FormController` keeps track of all its controls and nested forms as well as state of them, 15889 * such as being valid/invalid or dirty/pristine. 15890 * 15891 * Each {@link ng.directive:form form} directive creates an instance 15892 * of `FormController`. 15893 * 15894 */ 15895//asks for $scope to fool the BC controller module
15896 FormController.$inject = ['$element', '$attrs', '$scope', '$animate']; 15897 function FormController(element, attrs, $scope, $animate) { 15898 var form = this, 15899 parentForm = element.parent().controller('form') || nullFormCtrl, 15900 invalidCount = 0, // used to easily determine if we are valid 15901 errors = form.$error = {}, 15902 controls = []; 15903 15904 // init state 15905 form.$name = attrs.name || attrs.ngForm; 15906 form.$dirty = false; 15907 form.$pristine = true; 15908 form.$valid = true; 15909 form.$invalid = false; 15910 15911 parentForm.$addControl(form); 15912 15913 // Setup initial state of the control 15914 element.addClass(PRISTINE_CLASS); 15915 toggleValidCss(true); 15916 15917 // convenience method for easy toggling of classes 15918 function toggleValidCss(isValid, validationErrorKey) { 15919 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 15920 $animate.removeClass(element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); 15921 $animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 15922 } 15923 15924 /** 15925 * @ngdoc method 15926 * @name form.FormController#$addControl 15927 * 15928 * @description 15929 * Register a control with the form. 15930 * 15931 * Input elements using ngModelController do this automatically when they are linked. 15932 */ 15933 form.$addControl = function (control) { 15934 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 15935 // and not added to the scope. Now we throw an error. 15936 assertNotHasOwnProperty(control.$name, 'input'); 15937 controls.push(control); 15938 15939 if (control.$name) { 15940 form[control.$name] = control; 15941 } 15942 }; 15943 15944 /** 15945 * @ngdoc method 15946 * @name form.FormController#$removeControl 15947 * 15948 * @description 15949 * Deregister a control from the form. 15950 * 15951 * Input elements using ngModelController do this automatically when they are destroyed. 15952 */ 15953 form.$removeControl = function (control) { 15954 if (control.$name && form[control.$name] === control) { 15955 delete form[control.$name]; 15956 } 15957 forEach(errors, function (queue, validationToken) { 15958 form.$setValidity(validationToken, true, control); 15959 }); 15960 15961 arrayRemove(controls, control); 15962 }; 15963 15964 /** 15965 * @ngdoc method 15966 * @name form.FormController#$setValidity 15967 * 15968 * @description 15969 * Sets the validity of a form control. 15970 * 15971 * This method will also propagate to parent forms. 15972 */ 15973 form.$setValidity = function (validationToken, isValid, control) { 15974 var queue = errors[validationToken]; 15975 15976 if (isValid) { 15977 if (queue) { 15978 arrayRemove(queue, control); 15979 if (!queue.length) { 15980 invalidCount--; 15981 if (!invalidCount) { 15982 toggleValidCss(isValid); 15983 form.$valid = true; 15984 form.$invalid = false; 15985 } 15986 errors[validationToken] = false; 15987 toggleValidCss(true, validationToken); 15988 parentForm.$setValidity(validationToken, true, form); 15989 } 15990 } 15991 15992 } else { 15993 if (!invalidCount) { 15994 toggleValidCss(isValid); 15995 } 15996 if (queue) { 15997 if (includes(queue, control)) return; 15998 } else { 15999 errors[validationToken] = queue = []; 16000 invalidCount++; 16001 toggleValidCss(false, validationToken); 16002 parentForm.$setValidity(validationToken, false, form); 16003 } 16004 queue.push(control); 16005 16006 form.$valid = false;
16007 form.$invalid = true; 16008 } 16009 }; 16010 16011 /** 16012 * @ngdoc method 16013 * @name form.FormController#$setDirty 16014 * 16015 * @description 16016 * Sets the form to a dirty state. 16017 * 16018 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 16019 * state (ng-dirty class). This method will also propagate to parent forms. 16020 */ 16021 form.$setDirty = function () { 16022 $animate.removeClass(element, PRISTINE_CLASS); 16023 $animate.addClass(element, DIRTY_CLASS); 16024 form.$dirty = true; 16025 form.$pristine = false; 16026 parentForm.$setDirty(); 16027 }; 16028 16029 /** 16030 * @ngdoc method 16031 * @name form.FormController#$setPristine 16032 * 16033 * @description 16034 * Sets the form to its pristine state. 16035 * 16036 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 16037 * state (ng-pristine class). This method will also propagate to all the controls contained 16038 * in this form. 16039 * 16040 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 16041 * saving or resetting it. 16042 */ 16043 form.$setPristine = function () { 16044 $animate.removeClass(element, DIRTY_CLASS); 16045 $animate.addClass(element, PRISTINE_CLASS); 16046 form.$dirty = false; 16047 form.$pristine = true; 16048 forEach(controls, function (control) { 16049 control.$setPristine(); 16050 }); 16051 }; 16052 } 16053 16054 16055 /** 16056 * @ngdoc directive 16057 * @name ngForm 16058 * @restrict EAC 16059 * 16060 * @description 16061 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 16062 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 16063 * sub-group of controls needs to be determined. 16064 * 16065 * Note: the purpose of `ngForm` is to group controls, 16066 * but not to be a replacement for the `<form>` tag with all of its capabilities 16067 * (e.g. posting to the server, ...). 16068 * 16069 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 16070 * related scope, under this name. 16071 * 16072 */ 16073 16074 /** 16075 * @ngdoc directive 16076 * @name form 16077 * @restrict E 16078 * 16079 * @description 16080 * Directive that instantiates 16081 * {@link form.FormController FormController}. 16082 * 16083 * If the `name` attribute is specified, the form controller is published onto the current scope under 16084 * this name. 16085 * 16086 * # Alias: {@link ng.directive:ngForm `ngForm`} 16087 * 16088 * In Angular forms can be nested. This means that the outer form is valid when all of the child 16089 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 16090 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to 16091 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when 16092 * using Angular validation directives in forms that are dynamically generated using the 16093 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` 16094 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an 16095 * `ngForm` directive and nest these in an outer `form` element. 16096 * 16097 * 16098 * # CSS classes 16099 * - `ng-valid` is set if the form is valid. 16100 * - `ng-invalid` is set if the form is invalid. 16101 * - `ng-pristine` is set if the form is pristine. 16102 * - `ng-dirty` is set if the form is dirty. 16103 * 16104 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 16105 * 16106 * 16107 * # Submitting a form and preventing the default action 16108 * 16109 * Since the role of forms in client-side Angular applications is different than in classical 16110 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 16111 * page reload that sends the data to the server. Instead some javascript logic should be triggered 16112 * to handle the form submission in an application-specific way. 16113 * 16114 * For this reason, Angular prevents the default action (form submission to the server) unless the 16115 * `<form>` element has an `action` attribute specified. 16116 * 16117 * You can use one of the following two ways to specify what javascript method should be called when 16118 * a form is submitted: 16119 * 16120 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 16121 * - {@link ng.directive:ngClick ngClick}
16121 directive on the first 16122 * button or input field of type submit (input[type=submit]) 16123 * 16124 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 16125 * or {@link ng.directive:ngClick ngClick} directives. 16126 * This is because of the following form submission rules in the HTML specification: 16127 * 16128 * - If a form has only one input field then hitting enter in this field triggers form submit 16129 * (`ngSubmit`) 16130 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 16131 * doesn't trigger submit 16132 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 16133 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 16134 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 16135 * 16136 * @param {string=} name Name of the form. If specified, the form controller will be published into 16137 * related scope, under this name. 16138 * 16139 * ## Animation Hooks 16140 * 16141 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 16142 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 16143 * other validations that are performed within the form. Animations in ngForm are similar to how 16144 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 16145 * as JS animations. 16146 * 16147 * The following example shows a simple way to utilize CSS transitions to style a form element 16148 * that has been rendered as invalid after it has been validated: 16149 * 16150 * <pre> 16151 * //be sure to include ngAnimate as a module to hook into more 16152 * //advanced animations 16153 * .my-form { 16154 * transition:0.5s linear all; 16155 * background: white; 16156 * } 16157 * .my-form.ng-invalid { 16158 * background: red; 16159 * color:white; 16160 * } 16161 * </pre> 16162 * 16163 * @example 16164 <example deps="angular-animate.js" animations="true" fixBase="true"> 16165 <file name="index.jsp"> 16166 <script> 16167 function Ctrl($scope) { 16168 $scope.userType = 'guest'; 16169 } 16170 </script> 16171 <style> 16172 .my-form { 16173 -webkit-transition:all linear 0.5s; 16174 transition:all linear 0.5s; 16175 background: transparent; 16176 } 16177 .my-form.ng-invalid { 16178 background: red; 16179 } 16180 </style> 16181 <form name="myForm" ng-controller="Ctrl" class="my-form"> 16182 userType: <input name="input" ng-model="userType" required> 16183 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 16184 <tt>userType = {{userType}}</tt><br> 16185 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br> 16186 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br> 16187 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 16188 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 16189 </form> 16190 </file> 16191 <file name="protractor.js" type="protractor"> 16192 it('should initialize to model', function() { 16193 var userType = element(by.binding('userType')); 16194 var valid = element(by.binding('myForm.input.$valid')); 16195 16196 expect(userType.getText()).toContain('guest'); 16197 expect(valid.getText()).toContain('true'); 16198 }); 16199 16200 it('should be invalid if empty', function() { 16201 var userType = element(by.binding('userType')); 16202 var valid = element(by.binding('myForm.input.$valid')); 16203 var userInput = element(by.model('userType')); 16204 16205 userInput.clear(); 16206 userInput.sendKeys(''); 16207 16208 expect(userType.getText()).toEqual('userType ='); 16209 expect(valid.getText()).toContain('false'); 16210 }); 16211 </file> 16212 </example> 16213 * 16214 */ 16215 var formDirectiveFactory = function (isNgForm) { 16216 return ['$timeout', function ($timeout) { 16217 var formDirective = { 16218 name: 'form', 16219 restrict: isNgForm ? 'EAC' : 'E', 16220 controller: FormController, 16221 compile: function () { 16222 return { 16223 pre: function (scope, formElement, attr, controller) { 16224 if (!attr.action) { 16225 // we can't use jq events because if a form is destroyed during submission the default 16226 // action is not prevented. see #1238 16227 // 16228 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 16229 // page reload if the form was destroyed by submission of the form via a click handler 16230 // on a button in the form. Looks like an IE9 specific bug. 16231 var preventDefaultListener = function (event) { 16232 event.preventDefault 16233 ? event.preventDefault() 16234 : event.returnValue = false; // IE 16235 }; 16236 16237 addEventListenerFn(formElement[0], 'submit', preventDefaultListener); 16238 16239 // unregister the preventDefault listener so that we don't not leak memory but in a 16240 // way that will achieve the prevention of the default action. 16241 formElement.on('$destroy', function () { 16242 $timeout(function () { 16243 removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); 16244 }, 0, false); 16245 }); 16246 } 16247 16248 var parentFormCtrl = formElement.parent().controller('form'), 16249 alias = attr.name || attr.ngForm; 16250 16251 if (alias) { 16252 setter(scope, alias, controller, alias); 16253 } 16254 if (parentFormCtrl) { 16255 formElement.on('$destroy', function () { 16256 parentFormCtrl.$removeControl(controller); 16257 if (alias) { 16258 setter(scope, alias, undefined, alias); 16259 } 16260 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 16261 }); 16262 } 16263 } 16264 }; 16265 } 16266 }; 16267 16268 return formDirective; 16269 }]; 16270 }; 16271 16272 var formDirective = formDirectiveFactory(); 16273 var ngFormDirective = formDirectiveFactory(true); 16274 16275 /* global 16276 16277 -VALID_CLASS, 16278 -INVALID_CLASS, 16279 -PRISTINE_CLASS,
16280 -DIRTY_CLASS 16281 */ 16282 16283 var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 16284 var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i; 16285 var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; 16286 16287 var inputType = { 16288 16289 /** 16290 * @ngdoc input 16291 * @name input[text] 16292 * 16293 * @description 16294 * Standard HTML text input with angular data binding. 16295 * 16296 * @param {string} ngModel Assignable angular expression to data-bind to. 16297 * @param {string=} name Property name of the form under which the control is published. 16298 * @param {string=} required Adds `required` validation error key if the value is not entered. 16299 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16300 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16301 * `required` when you want to data-bind to the `required` attribute. 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 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 16312 * 16313 * @example 16314 <example name="text-input-directive"> 16315 <file name="index.jsp"> 16316 <script> 16317 function Ctrl($scope) { 16318 $scope.text = 'guest'; 16319 $scope.word = /^\s*\w*\s*$/; 16320 } 16321 </script> 16322 <form name="myForm" ng-controller="Ctrl"> 16323 Single word: <input type="text" name="input" ng-model="text" 16324 ng-pattern="word" required ng-trim="false"> 16325 <span class="error" ng-show="myForm.input.$error.required"> 16326 Required!</span> 16327 <span class="error" ng-show="myForm.input.$error.pattern"> 16328 Single word only!</span> 16329 16330 <tt>text = {{text}}</tt><br/> 16331 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16332 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16333 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16334 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16335 </form> 16336 </file> 16337 <file name="protractor.js" type="protractor"> 16338 var text = element(by.binding('text')); 16339 var valid = element(by.binding('myForm.input.$valid')); 16340 var input = element(by.model('text')); 16341 16342 it('should initialize to model', function() { 16343 expect(text.getText()).toContain('guest'); 16344 expect(valid.getText()).toContain('true'); 16345 }); 16346 16347 it('should be invalid if empty', function() { 16348 input.clear(); 16349 input.sendKeys(''); 16350 16351 expect(text.getText()).toEqual('text ='); 16352 expect(valid.getText()).toContain('false'); 16353 }); 16354 16355 it('should be invalid if multi word', function() { 16356 input.clear(); 16357 input.sendKeys('hello world'); 16358 16359 expect(valid.getText()).toContain('false'); 16360 }); 16361 </file> 16362 </example> 16363 */ 16364 'text': textInputType, 16365 16366 16367 /** 16368 * @ngdoc input 16369 * @name input[number] 16370 * 16371 * @description 16372 * Text input with number validation and transformation. Sets the `number` validation 16373 * error if not a valid number. 16374 * 16375 * @param {string} ngModel Assignable angular expression to data-bind to. 16376 * @param {string=} name Property name of the form under which the control is published. 16377 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 16378 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 16379 * @param {string=} required Sets `required` validation error key if the value is not entered. 16380 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16381 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16382 * `required` when you want to data-bind to the `required` attribute. 16383 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16384 * minlength. 16385 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16386 * maxlength. 16387 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16388 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16389 * patterns defined as scope expressions. 16390 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16391 * interaction with the input element. 16392 * 16393 * @example 16394 <example name="number-input-directive"> 16395 <file name="index.jsp"> 16396 <script> 16397 function Ctrl($scope) { 16398 $scope.value = 12; 16399 } 16400 </script> 16401 <form name="myForm" ng-controller="Ctrl"> 16402 Number: <input type="number" name="input" ng-model="value" 16403 min="0" max="99" required>
16404 <span class="error" ng-show="myForm.input.$error.required"> 16405 Required!</span> 16406 <span class="error" ng-show="myForm.input.$error.number"> 16407 Not valid number!</span> 16408 <tt>value = {{value}}</tt><br/> 16409 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16410 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16411 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16412 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16413 </form> 16414 </file> 16415 <file name="protractor.js" type="protractor"> 16416 var value = element(by.binding('value')); 16417 var valid = element(by.binding('myForm.input.$valid')); 16418 var input = element(by.model('value')); 16419 16420 it('should initialize to model', function() { 16421 expect(value.getText()).toContain('12'); 16422 expect(valid.getText()).toContain('true'); 16423 }); 16424 16425 it('should be invalid if empty', function() { 16426 input.clear(); 16427 input.sendKeys(''); 16428 expect(value.getText()).toEqual('value ='); 16429 expect(valid.getText()).toContain('false'); 16430 }); 16431 16432 it('should be invalid if over max', function() { 16433 input.clear(); 16434 input.sendKeys('123'); 16435 expect(value.getText()).toEqual('value ='); 16436 expect(valid.getText()).toContain('false'); 16437 }); 16438 </file> 16439 </example> 16440 */ 16441 'number': numberInputType, 16442 16443 16444 /** 16445 * @ngdoc input 16446 * @name input[url] 16447 * 16448 * @description 16449 * Text input with URL validation. Sets the `url` validation error key if the content is not a 16450 * valid URL. 16451 * 16452 * @param {string} ngModel Assignable angular expression to data-bind to. 16453 * @param {string=} name Property name of the form under which the control is published. 16454 * @param {string=} required Sets `required` validation error key if the value is not entered. 16455 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16456 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16457 * `required` when you want to data-bind to the `required` attribute. 16458 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16459 * minlength. 16460 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16461 * maxlength. 16462 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16463 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16464 * patterns defined as scope expressions. 16465 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16466 * interaction with the input element. 16467 * 16468 * @example 16469 <example name="url-input-directive"> 16470 <file name="index.jsp"> 16471 <script> 16472 function Ctrl($scope) { 16473 $scope.text = 'http://google.com'; 16474 } 16475 </script> 16476 <form name="myForm" ng-controller="Ctrl"> 16477 URL: <input type="url" name="input" ng-model="text" required> 16478 <span class="error" ng-show="myForm.input.$error.required"> 16479 Required!</span> 16480 <span class="error" ng-show="myForm.input.$error.url"> 16481 Not valid url!</span> 16482 <tt>text = {{text}}</tt><br/> 16483 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16484 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16485 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16486 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16487 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 16488 </form> 16489 </file> 16490 <file name="protractor.js" type="protractor"> 16491 var text = element(by.binding('text')); 16492 var valid = element(by.binding('myForm.input.$valid')); 16493 var input = element(by.model('text')); 16494 16495 it('should initialize to model', function() { 16496 expect(text.getText()).toContain('http://google.com'); 16497 expect(valid.getText()).toContain('true'); 16498 }); 16499 16500 it('should be invalid if empty', function() { 16501 input.clear(); 16502 input.sendKeys(''); 16503 16504 expect(text.getText()).toEqual('text ='); 16505 expect(valid.getText()).toContain('false'); 16506 }); 16507 16508 it('should be invalid if not url', function() { 16509 input.clear(); 16510 input.sendKeys('box'); 16511 16512 expect(valid.getText()).toContain('false'); 16513 }); 16514 </file> 16515 </example> 16516 */ 16517 'url': urlInputType, 16518 16519 16520 /** 16521 * @ngdoc input 16522 * @name input[email] 16523 *
16524 * @description 16525 * Text input with email validation. Sets the `email` validation error key if not a valid email 16526 * address. 16527 * 16528 * @param {string} ngModel Assignable angular expression to data-bind to. 16529 * @param {string=} name Property name of the form under which the control is published. 16530 * @param {string=} required Sets `required` validation error key if the value is not entered. 16531 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16532 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16533 * `required` when you want to data-bind to the `required` attribute. 16534 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16535 * minlength. 16536 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16537 * maxlength. 16538 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16539 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16540 * patterns defined as scope expressions. 16541 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16542 * interaction with the input element. 16543 * 16544 * @example 16545 <example name="email-input-directive"> 16546 <file name="index.jsp"> 16547 <script> 16548 function Ctrl($scope) { 16549 $scope.text = '[email protected]'; 16550 } 16551 </script> 16552 <form name="myForm" ng-controller="Ctrl"> 16553 Email: <input type="email" name="input" ng-model="text" required> 16554 <span class="error" ng-show="myForm.input.$error.required"> 16555 Required!</span> 16556 <span class="error" ng-show="myForm.input.$error.email"> 16557 Not valid email!</span> 16558 <tt>text = {{text}}</tt><br/> 16559 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16560 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16561 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16562 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16563 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 16564 </form> 16565 </file> 16566 <file name="protractor.js" type="protractor"> 16567 var text = element(by.binding('text')); 16568 var valid = element(by.binding('myForm.input.$valid')); 16569 var input = element(by.model('text')); 16570 16571 it('should initialize to model', function() { 16572 expect(text.getText()).toContain('[email protected]'); 16573 expect(valid.getText()).toContain('true'); 16574 }); 16575 16576 it('should be invalid if empty', function() { 16577 input.clear(); 16578 input.sendKeys(''); 16579 expect(text.getText()).toEqual('text ='); 16580 expect(valid.getText()).toContain('false'); 16581 }); 16582 16583 it('should be invalid if not email', function() { 16584 input.clear(); 16585 input.sendKeys('xxx'); 16586 16587 expect(valid.getText()).toContain('false'); 16588 }); 16589 </file> 16590 </example> 16591 */ 16592 'email': emailInputType, 16593 16594 16595 /** 16596 * @ngdoc input 16597 * @name input[radio] 16598 * 16599 * @description 16600 * HTML radio button. 16601 * 16602 * @param {string} ngModel Assignable angular expression to data-bind to. 16603 * @param {string} value The value to which the expression should be set when selected. 16604 * @param {string=} name Property name of the form under which the control is published. 16605 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16606 * interaction with the input element. 16607 * @param {string} ngValue Angular expression which sets the value to which the expression should 16608 * be set when selected. 16609 * 16610 * @example 16611 <example name="radio-input-directive"> 16612 <file name="index.jsp"> 16613 <script> 16614 function Ctrl($scope) { 16615 $scope.color = 'blue'; 16616 $scope.specialValue = { 16617 "id": "12345", 16618 "value": "green" 16619 }; 16620 } 16621 </script> 16622 <form name="myForm" ng-controller="Ctrl"> 16623 <input type="radio" ng-model="color" value="red"> Red <br/> 16624 <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/> 16625 <input type="radio" ng-model="color" value="blue"> Blue <br/> 16626 <tt>color = {{color | json}}</tt><br/> 16627 </form> 16628 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 16629 </file> 16630 <file name="protractor.js" type="protractor"> 16631 it('should change state', function() { 16632 var color = element(by.binding('color')); 16633 16634 expect(color.getText()).toContain('blue'); 16635 16636 element.all(by.model('color')).get(0).click(); 16637 16638 expect(color.getText()).toContain('red'); 16639 }); 16640 </file> 16641 </example> 16642 */ 16643 'radio': radioInputType, 16644 16645 16646 /** 16647 * @ngdoc input 16648 * @name input[checkbox] 16649 *
16650 * @description 16651 * HTML checkbox. 16652 * 16653 * @param {string} ngModel Assignable angular expression to data-bind to. 16654 * @param {string=} name Property name of the form under which the control is published. 16655 * @param {string=} ngTrueValue The value to which the expression should be set when selected. 16656 * @param {string=} ngFalseValue The value to which the expression should be set when not selected. 16657 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16658 * interaction with the input element. 16659 * 16660 * @example 16661 <example name="checkbox-input-directive"> 16662 <file name="index.jsp"> 16663 <script> 16664 function Ctrl($scope) { 16665 $scope.value1 = true; 16666 $scope.value2 = 'YES' 16667 } 16668 </script> 16669 <form name="myForm" ng-controller="Ctrl"> 16670 Value1: <input type="checkbox" ng-model="value1"> <br/> 16671 Value2: <input type="checkbox" ng-model="value2" 16672 ng-true-value="YES" ng-false-value="NO"> <br/> 16673 <tt>value1 = {{value1}}</tt><br/> 16674 <tt>value2 = {{value2}}</tt><br/> 16675 </form> 16676 </file> 16677 <file name="protractor.js" type="protractor"> 16678 it('should change state', function() { 16679 var value1 = element(by.binding('value1')); 16680 var value2 = element(by.binding('value2')); 16681 16682 expect(value1.getText()).toContain('true'); 16683 expect(value2.getText()).toContain('YES'); 16684 16685 element(by.model('value1')).click(); 16686 element(by.model('value2')).click(); 16687 16688 expect(value1.getText()).toContain('false'); 16689 expect(value2.getText()).toContain('NO'); 16690 }); 16691 </file> 16692 </example> 16693 */ 16694 'checkbox': checkboxInputType, 16695 16696 'hidden': noop, 16697 'button': noop, 16698 'submit': noop, 16699 'reset': noop, 16700 'file': noop 16701 }; 16702 16703// A helper function to call $setValidity and return the value / undefined, 16704// a pattern that is repeated a lot in the input validation logic. 16705 function validate(ctrl, validatorName, validity, value) { 16706 ctrl.$setValidity(validatorName, validity); 16707 return validity ? value : undefined; 16708 } 16709 16710 16711 function addNativeHtml5Validators(ctrl, validatorName, element) { 16712 var validity = element.prop('validity'); 16713 if (isObject(validity)) { 16714 var validator = function (value) { 16715 // Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can 16716 // perform the required validation) 16717 if (!ctrl.$error[validatorName] && (validity.badInput || validity.customError || 16718 validity.typeMismatch) && !validity.valueMissing) { 16719 ctrl.$setValidity(validatorName, false); 16720 return; 16721 } 16722 return value; 16723 }; 16724 ctrl.$parsers.push(validator); 16725 } 16726 } 16727 16728 function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16729 var validity = element.prop('validity'); 16730 // In composition mode, users are still inputing intermediate text buffer, 16731 // hold the listener until composition is done. 16732 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 16733 if (!$sniffer.android) { 16734 var composing = false; 16735 16736 element.on('compositionstart', function (data) { 16737 composing = true; 16738 }); 16739 16740 element.on('compositionend', function () { 16741 composing = false; 16742 listener(); 16743 }); 16744 } 16745 16746 var listener = function () { 16747 if (composing) return; 16748 var value = element.val(); 16749 16750 // By default we will trim the value 16751 // If the attribute ng-trim exists we will avoid trimming 16752 // e.g. <input ng-model="foo" ng-trim="false"> 16753 if (toBoolean(attr.ngTrim || 'T')) { 16754 value = trim(value); 16755 } 16756 16757 if (ctrl.$viewValue !== value || 16758 // If the value is still empty/falsy, and there is no `required` error, run validators 16759 // again. This enables HTML5 constraint validation errors to affect Angular val
16759idation 16760 // even when the first character entered causes an error. 16761 (validity && value === '' && !validity.valueMissing)) { 16762 if (scope.$$phase) { 16763 ctrl.$setViewValue(value); 16764 } else { 16765 scope.$apply(function () { 16766 ctrl.$setViewValue(value); 16767 }); 16768 } 16769 } 16770 }; 16771 16772 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 16773 // input event on backspace, delete or cut 16774 if ($sniffer.hasEvent('input')) { 16775 element.on('input', listener); 16776 } else { 16777 var timeout; 16778 16779 var deferListener = function () { 16780 if (!timeout) { 16781 timeout = $browser.defer(function () { 16782 listener(); 16783 timeout = null; 16784 }); 16785 } 16786 }; 16787 16788 element.on('keydown', function (event) { 16789 var key = event.keyCode; 16790 16791 // ignore 16792 // command modifiers arrows 16793 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 16794 16795 deferListener(); 16796 }); 16797 16798 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 16799 if ($sniffer.hasEvent('paste')) { 16800 element.on('paste cut', deferListener); 16801 } 16802 } 16803 16804 // if user paste into input using mouse on older browser 16805 // or form autocomplete on newer browser, we need "change" event to catch it 16806 element.on('change', listener); 16807 16808 ctrl.$render = function () { 16809 element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); 16810 }; 16811 16812 // pattern validator 16813 var pattern = attr.ngPattern, 16814 patternValidator, 16815 match; 16816 16817 if (pattern) { 16818 var validateRegex = function (regexp, value) { 16819 return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value); 16820 }; 16821 match = pattern.match(/^\/(.*)\/([gim]*)$/); 16822 if (match) { 16823 pattern = new RegExp(match[1], match[2]); 16824 patternValidator = function (value) { 16825 return validateRegex(pattern, value); 16826 }; 16827 } else { 16828 patternValidator = function (value) { 16829 var patternObj = scope.$eval(pattern); 16830 16831 if (!patternObj || !patternObj.test) { 16832 throw minErr('ngPattern')('noregexp', 16833 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, 16834 patternObj, startingTag(element)); 16835 } 16836 return validateRegex(patternObj, value); 16837 }; 16838 } 16839 16840 ctrl.$formatters.push(patternValidator); 16841 ctrl.$parsers.push(patternValidator); 16842 } 16843 16844 // min length validator 16845 if (attr.ngMinlength) { 16846 var minlength = int(attr.ngMinlength); 16847 var minLengthValidator = function (value) { 16848 return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value); 16849 }; 16850 16851 ctrl.$parsers.push(minLengthValidator); 16852 ctrl.$formatters.push(minLengthValidator); 16853 } 16854 16855 // max length validator 16856 if (attr.ngMaxlength) { 16857 var maxlength = int(attr.ngMaxlength); 16858 var maxLengthValidator = function (value) { 16859 return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value); 16860 }; 16861 16862 ctrl.$parsers.push(maxLengthValidator); 16863 ctrl.$formatters.push(maxLengthValidator); 16864 } 16865 } 16866 16867 function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16868 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 16869 16870 ctrl.$parsers.push(function (value) { 16871 var empty = ctrl.$isEmpty(value); 16872 if (empty || NUMBER_REGEXP.test(value)) { 16873 ctrl.$setValidity('number', true); 16874 return value === '' ? null : (empty ? value : parseFloat(value)); 16875 } else { 16876 ctrl.$setValidity('number', false); 16877 return undefined; 16878 } 16879 }); 16880 16881 addNativeHtml5Validators(ctrl, 'number', element); 16882 16883 ctrl.$formatters.push(function (value) { 16884 return ctrl.$isEmpty(value) ? '' : '' + value; 16885 }); 16886 16887 if (attr.min) { 16888 var minValidator = function (value) { 16889 var min = parseFloat(attr.min); 16890 return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); 16891 }; 16892 16893 ctrl.$parsers.push(minValidator); 16894 ctrl.$formatters.push(minValidator); 16895 } 16896 16897 if (attr.max) { 16898 var maxValidator = function (value) { 16899 var max = parseFloat(attr.max); 16900 return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); 16901 }; 16902 16903 ctrl.$parsers.push(maxValidator); 16904 ctrl.$formatters.push(maxValidator); 16905 } 16906 16907 ctrl.$formatters.push(function (value) { 16908 return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); 16909 }); 16910 } 16911 16912 function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16913 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 16914 16915 var urlValidator = function (value) { 16916 return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); 16917 }; 16918 16919 ctrl.$formatters.push(urlValidator); 16920 ctrl.$parsers.push(urlValidator); 16921 } 16922 16923 function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16924 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 16925 16926 var emailValidator = function (value) { 16927 return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); 16928 }; 16929 16930 ctrl.$formatters.push(emailValidator); 16931 ctrl.$parsers.push(emailValidator); 16932 } 16933 16934 function radioInputType(scope, element, attr, ctrl) { 16935 // make the name unique, if not defined 16936 if (isUndefined(attr.name)) { 16937 element.attr('name', nextUid()); 16938 } 16939 16940 element.on('click', function () { 16941 if (element[0].checked) { 16942 scope.$apply(function () { 16943 ctrl.$setViewValue(attr.value); 16944 }); 16945 } 16946 }); 16947 16948 ctrl.$render = function () { 16949 var value = attr.value; 16950 element[0].checked = (value == ctrl.$viewValue); 16951 }; 16952 16953 attr.$observe('value', ctrl.$render); 16954 } 16955 16956 function checkboxInputType(scope, element, attr, ctrl) { 16957 var trueValue = attr.ngTrueValue, 16958 falseValue = attr.ngFalseValue; 16959 16960 if (!isString(trueValue)) trueValue = true; 16961 if (!isString(falseValue)) falseValue = false;
16962 16963 element.on('click', function () { 16964 scope.$apply(function () { 16965 ctrl.$setViewValue(element[0].checked); 16966 }); 16967 }); 16968 16969 ctrl.$render = function () { 16970 element[0].checked = ctrl.$viewValue; 16971 }; 16972 16973 // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. 16974 ctrl.$isEmpty = function (value) { 16975 return value !== trueValue; 16976 }; 16977 16978 ctrl.$formatters.push(function (value) { 16979 return value === trueValue; 16980 }); 16981 16982 ctrl.$parsers.push(function (value) { 16983 return value ? trueValue : falseValue; 16984 }); 16985 } 16986 16987 16988 /** 16989 * @ngdoc directive 16990 * @name textarea 16991 * @restrict E 16992 * 16993 * @description 16994 * HTML textarea element control with angular data-binding. The data-binding and validation 16995 * properties of this element are exactly the same as those of the 16996 * {@link ng.directive:input input element}. 16997 * 16998 * @param {string} ngModel Assignable angular expression to data-bind to. 16999 * @param {string=} name Property name of the form under which the control is published. 17000 * @param {string=} required Sets `required` validation error key if the value is not entered. 17001 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 17002 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 17003 * `required` when you want to data-bind to the `required` attribute. 17004 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 17005 * minlength. 17006 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 17007 * maxlength. 17008 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 17009 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 17010 * patterns defined as scope expressions. 17011 * @param {string=} ngChange Angular expression to be executed when input changes due to user 17012 * interaction with the input element. 17013 */ 17014 17015 17016 /** 17017 * @ngdoc directive 17018 * @name input 17019 * @restrict E 17020 * 17021 * @description 17022 * HTML input element control with angular data-binding. Input control follows HTML5 input types 17023 * and polyfills the HTML5 validation behavior for older browsers. 17024 * 17025 * @param {string} ngModel Assignable angular expression to data-bind to. 17026 * @param {string=} name Property name of the form under which the control is published. 17027 * @param {string=} required Sets `required` validation error key if the value is not entered. 17028 * @param {boolean=} ngRequired Sets `required` attribute if set to true 17029 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 17030 * minlength. 17031 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 17032 * maxlength. 17033 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 17034 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 17035 * patterns defined as scope expressions. 17036 * @param {string=} ngChange Angular expression to be executed when input changes due to user 17037 * interaction with the input element. 17038 * 17039 * @example 17040 <example name="input-directive"> 17041 <file name="index.jsp"> 17042 <script> 17043 function Ctrl($scope) { 17044 $scope.user = {name: 'guest', last: 'visitor'}; 17045 } 17046 </script> 17047 <div ng-controller="Ctrl"> 17048 <form name="myForm"> 17049 User name: <input type="text" name="userName" ng-model="user.name" required> 17050 <span class="error" ng-show="myForm.userName.$error.required"> 17051 Required!</span><br> 17052 Last name: <input type="text" name="lastName" ng-model="user.last" 17053 ng-minlength="3" ng-maxlength="10"> 17054 <span class="error" ng-show="myForm.lastName.$error.minlength"> 17055 Too short!</span> 17056 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 17057 Too long!</span><br> 17058 </form> 17059 <hr> 17060 <tt>user = {{user}}</tt><br/> 17061 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br> 17062 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br> 17063 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br> 17064 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br> 17065 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 17066 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 17067 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br> 17068 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br>
17069 </div> 17070 </file> 17071 <file name="protractor.js" type="protractor"> 17072 var user = element(by.binding('{{user}}')); 17073 var userNameValid = element(by.binding('myForm.userName.$valid')); 17074 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 17075 var lastNameError = element(by.binding('myForm.lastName.$error')); 17076 var formValid = element(by.binding('myForm.$valid')); 17077 var userNameInput = element(by.model('user.name')); 17078 var userLastInput = element(by.model('user.last')); 17079 17080 it('should initialize to model', function() { 17081 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 17082 expect(userNameValid.getText()).toContain('true'); 17083 expect(formValid.getText()).toContain('true'); 17084 }); 17085 17086 it('should be invalid if empty when required', function() { 17087 userNameInput.clear(); 17088 userNameInput.sendKeys(''); 17089 17090 expect(user.getText()).toContain('{"last":"visitor"}'); 17091 expect(userNameValid.getText()).toContain('false'); 17092 expect(formValid.getText()).toContain('false'); 17093 }); 17094 17095 it('should be valid if empty when min length is set', function() { 17096 userLastInput.clear(); 17097 userLastInput.sendKeys(''); 17098
17099 expect(user.getText()).toContain('{"name":"guest","last":""}'); 17100 expect(lastNameValid.getText()).toContain('true'); 17101 expect(formValid.getText()).toContain('true'); 17102 }); 17103 17104 it('should be invalid if less than required min length', function() { 17105 userLastInput.clear(); 17106 userLastInput.sendKeys('xx'); 17107 17108 expect(user.getText()).toContain('{"name":"guest"}'); 17109 expect(lastNameValid.getText()).toContain('false'); 17110 expect(lastNameError.getText()).toContain('minlength'); 17111 expect(formValid.getText()).toContain('false'); 17112 }); 17113 17114 it('should be invalid if longer than max length', function() { 17115 userLastInput.clear(); 17116 userLastInput.sendKeys('some ridiculously long name'); 17117 17118 expect(user.getText()).toContain('{"name":"guest"}'); 17119 expect(lastNameValid.getText()).toContain('false'); 17120 expect(lastNameError.getText()).toContain('maxlength'); 17121 expect(formValid.getText()).toContain('false'); 17122 }); 17123 </file> 17124 </example> 17125 */ 17126 var inputDirective = ['$browser', '$sniffer', function ($browser, $sniffer) { 17127 return { 17128 restrict: 'E', 17129 require: '?ngModel', 17130 link: function (scope, element, attr, ctrl) { 17131 if (ctrl) { 17132 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, 17133 $browser); 17134 } 17135 } 17136 }; 17137 }]; 17138 17139 var VALID_CLASS = 'ng-valid', 17140 INVALID_CLASS = 'ng-invalid', 17141 PRISTINE_CLASS = 'ng-pristine', 17142 DIRTY_CLASS = 'ng-dirty'; 17143 17144 /** 17145 * @ngdoc type 17146 * @name ngModel.NgModelController 17147 * 17148 * @property {string} $viewValue Actual string value in the view. 17149 * @property {*} $modelValue The value in the model, that the control is bound to. 17150 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 17151 the control reads value from the DOM. Each function is called, in turn, passing the value 17152 through to the next. The last return value is used to populate the model. 17153 Used to sanitize / convert the value as well as validation. For validation, 17154 the parsers should update the validity state using 17155 {@link ngModel.NgModelController#$setValidity $setValidity()}, 17156 and return `undefined` for invalid values. 17157 17158 * 17159 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 17160 the model value changes. Each function is called, in turn, passing the value through to the 17161 next. Used to format / convert values for display in the control and validation. 17162 * ```js 17163 * function formatter(value) { 17164 * if (value) { 17165 * return value.toUpperCase(); 17166 * } 17167 * } 17168 * ngModel.$formatters.push(formatter); 17169 * ``` 17170 * 17171 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 17172 * view value has changed. It is called with no arguments, and its return value is ignored. 17173 * This can be used in place of additional $watches against the model value. 17174 * 17175 * @property {Object} $error An object hash with all errors as keys. 17176 * 17177 * @property {boolean} $pristine True if user has not interacted with the control yet. 17178 * @property {boolean} $dirty True if user has already interacted with the control. 17179 * @property {boolean} $valid True if there is no error. 17180 * @property {boolean} $invalid True if at least one error on the control. 17181 * 17182 * @description 17183 * 17184 * `NgModelController` provides API for the `ng-model` directive. The controller contains 17185 * services for data-binding, validation, CSS updates, and value formatting and parsing. It 17186 * purposefully does not contain any logic which deals with DOM rendering or listening to 17187 * DOM events. Such DOM related logic should be provided by other directives which make use of 17188 * `NgModelController` for data-binding. 17189 * 17190 * ## Custom Control Example 17191 * This example shows how to use `NgModelController` with a custom control to achieve 17192 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 17193 * collaborate together to achieve the desired result. 17194 * 17195 * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element 17196 * contents be edited in place by the user. This will not work on older browsers. 17197 * 17198 * <example name="NgModelController" module="customControl"> 17199 <file name="style.css"> 17200 [contenteditable] { 17201 border: 1px solid black; 17202 background-color: white; 17203 min-height: 20px; 17204 } 17205 17206 .ng-invalid { 17207 border: 1px solid red; 17208 } 17209 17210 </file> 17211 <file name="script.js">
17212 angular.module('customControl', []). 17213 directive('contenteditable', function() { 17214 return { 17215 restrict: 'A', // only activate on element attribute 17216 require: '?ngModel', // get a hold of NgModelController 17217 link: function(scope, element, attrs, ngModel) { 17218 if(!ngModel) return; // do nothing if no ng-model 17219 17220 // Specify how UI should be updated 17221 ngModel.$render = function() { 17222 element.html(ngModel.$viewValue || ''); 17223 }; 17224 17225 // Listen for change events to enable binding 17226 element.on('blur keyup change', function() { 17227 scope.$apply(read); 17228 }); 17229 read(); // initialize 17230 17231 // Write data to the model 17232 function read() { 17233 var html = element.html(); 17234 // When we clear the content editable the browser leaves a <br> behind 17235 // If strip-br attribute is provided then we strip this out 17236 if( attrs.stripBr && html == '<br>' ) { 17237 html = ''; 17238 } 17239 ngModel.$setViewValue(html); 17240 } 17241 } 17242 }; 17243 }); 17244 </file> 17245 <file name="index.jsp"> 17246 <form name="myForm"> 17247 <div contenteditable 17248 name="myWidget" ng-model="userContent" 17249 strip-br="true" 17250 required>Change me!</div> 17251 <span ng-show="myForm.myWidget.$error.required">Required!</span> 17252 <hr> 17253 <textarea ng-model="userContent"></textarea> 17254 </form> 17255 </file> 17256 <file name="protractor.js" type="protractor"> 17257 it('should data-bind and become invalid', function() { 17258 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 17259 // SafariDriver can't handle contenteditable 17260 // and Firefox driver can't clear contenteditables very well 17261 return; 17262 } 17263 var contentEditable = element(by.css('[contenteditable]')); 17264 var content = 'Change me!'; 17265 17266 expect(contentEditable.getText()).toEqual(content); 17267 17268 contentEditable.clear(); 17269 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 17270 expect(contentEditable.getText()).toEqual(''); 17271 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 17272 }); 17273 </file> 17274 * </example> 17275 * 17276 * 17277 */ 17278 var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', 17279 function ($scope, $exceptionHandler, $attr, $element, $parse, $animate) { 17280 this.$viewValue = Number.NaN; 17281 this.$modelValue = Number.NaN; 17282 this.$parsers = []; 17283 this.$formatters = []; 17284 this.$viewChangeListeners = []; 17285 this.$pristine = true; 17286 this.$dirty = false; 17287 this.$valid = true; 17288 this.$invalid = false; 17289 this.$name = $attr.name; 17290 17291 var ngModelGet = $parse($attr.ngModel), 17292 ngModelSet = ngModelGet.assign; 17293 17294 if (!ngModelSet) { 17295 throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 17296 $attr.ngModel, startingTag($element)); 17297 } 17298 17299 /** 17300 * @ngdoc method 17301 * @name ngModel.NgModelController#$render 17302 * 17303 * @description 17304 * Called when the view needs to be updated. It is expected that the user of the ng-model 17305 * directive will implement this method. 17306 */ 17307 this.$render = noop; 17308 17309 /** 17310 * @ngdoc method 17311 * @name ngModel.NgModelController#$isEmpty 17312 * 17313 * @description 17314 * This is called when we need to determine if the value of the input is empty. 17315 * 17316 * For instance, the required directive does this to work out if the input has data or not. 17317 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 17318 * 17319 * You can override this for input directives whose concept of being empty is different to the 17320 * default. The `checkboxInputType` directive does this because in
17320its case a value of `false` 17321 * implies empty. 17322 * 17323 * @param {*} value Reference to check. 17324 * @returns {boolean} True if `value` is empty. 17325 */ 17326 this.$isEmpty = function (value) { 17327 return isUndefined(value) || value === '' || value === null || value !== value; 17328 }; 17329 17330 var parentForm = $element.inheritedData('$formController') || nullFormCtrl, 17331 invalidCount = 0, // used to easily determine if we are valid 17332 $error = this.$error = {}; // keep invalid keys here 17333 17334 17335 // Setup initial state of the control 17336 $element.addClass(PRISTINE_CLASS); 17337 toggleValidCss(true); 17338 17339 // convenience method for easy toggling of classes 17340 function toggleValidCss(isValid, validationErrorKey) { 17341 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 17342 $animate.removeClass($element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); 17343 $animate.addClass($element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 17344 } 17345 17346 /** 17347 * @ngdoc method 17348 * @name ngModel.NgModelController#$setValidity 17349 * 17350 * @description 17351 * Change the validity state, and notifies the form when the control changes validity. (i.e. it 17352 * does not notify form if given validator is already marked as invalid). 17353 * 17354 * This method should be called by validators - i.e. the parser or formatter functions. 17355 * 17356 * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign 17357 * to `$error[validationErrorKey]=isValid` so that it is available for data-binding. 17358 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 17359 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 17360 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 17361 * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). 17362 */ 17363 this.$setValidity = function (validationErrorKey, isValid) { 17364 // Purposeful use of ! here to cast isValid to boolean in case it is undefined 17365 // jshint -W018 17366 if ($error[validationErrorKey] === !isValid) return; 17367 // jshint +W018 17368 17369 if (isValid) { 17370 if ($error[validationErrorKey]) invalidCount--; 17371 if (!invalidCount) { 17372 toggleValidCss(true); 17373 this.$valid = true; 17374 this.$invalid = false; 17375 } 17376 } else { 17377 toggleValidCss(false); 17378 this.$invalid = true; 17379 this.$valid = false; 17380 invalidCount++; 17381 } 17382 17383 $error[validationErrorKey] = !isValid; 17384 toggleValidCss(isValid, validationErrorKey); 17385 17386 parentForm.$setValidity(validationErrorKey, isValid, this); 17387 }; 17388 17389 /** 17390 * @ngdoc method 17391 * @name ngModel.NgModelController#$setPristine 17392 * 17393 * @description 17394 * Sets the control to its pristine state. 17395 * 17396 * This method can be called to remove the 'ng-dirty' class and set the control to its pristine 17397 * state (ng-pristine class). 17398 */ 17399 this.$setPristine = function () { 17400 this.$dirty = false; 17401 this.$pristine = true; 17402 $animate.removeClass($element, DIRTY_CLASS); 17403 $animate.addClass($element, PRISTINE_CLASS); 17404 }; 17405 17406 /** 17407 * @ngdoc method 17408 * @name ngModel.NgModelController#$setViewValue 17409 *
17410 * @description 17411 * Update the view value. 17412 * 17413 * This method should be called when the view value changes, typically from within a DOM event handler. 17414 * For example {@link ng.directive:input input} and 17415 * {@link ng.directive:select select} directives call it. 17416 * 17417 * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, 17418 * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to 17419 * `$modelValue` and the **expression** specified in the `ng-model` attribute. 17420 * 17421 * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. 17422 * 17423 * Note that calling this function does not trigger a `$digest`. 17424 * 17425 * @param {string} value Value from the view. 17426 */ 17427 this.$setViewValue = function (value) { 17428 this.$viewValue = value; 17429 17430 // change to dirty 17431 if (this.$pristine) { 17432 this.$dirty = true; 17433 this.$pristine = false; 17434 $animate.removeClass($element, PRISTINE_CLASS); 17435 $animate.addClass($element, DIRTY_CLASS); 17436 parentForm.$setDirty(); 17437 } 17438 17439 forEach(this.$parsers, function (fn) { 17440 value = fn(value); 17441 }); 17442 17443 if (this.$modelValue !== value) { 17444 this.$modelValue = value; 17445 ngModelSet($scope, value); 17446 forEach(this.$viewChangeListeners, function (listener) { 17447 try { 17448 listener(); 17449 } catch (e) { 17450 $exceptionHandler(e); 17451 } 17452 }); 17453 } 17454 }; 17455 17456 // model -> value 17457 var ctrl = this; 17458 17459 $scope.$watch(function ngModelWatch() { 17460 var value = ngModelGet($scope); 17461 17462 // if scope model value and ngModel value are out of sync 17463 if (ctrl.$modelValue !== value) { 17464 17465 var formatters = ctrl.$formatters, 17466 idx = formatters.length; 17467 17468 ctrl.$modelValue = value; 17469 while (idx--) { 17470 value = formatters[idx](value); 17471 } 17472 17473 if (ctrl.$viewValue !== value) { 17474 ctrl.$viewValue = value; 17475 ctrl.$render(); 17476 } 17477 } 17478 17479 return value; 17480 }); 17481 }]; 17482 17483 17484 /** 17485 * @ngdoc directive 17486 * @name ngModel 17487 * 17488 * @element input 17489 * 17490 * @description 17491 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 17492 * property on the scope using {@link ngModel.NgModelController NgModelController}, 17493 * which is created and exposed by this directive. 17494 * 17495 * `ngModel` is responsible for: 17496 * 17497 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 17498 * require. 17499 * - Providing validation behavior (i.e. required, number, email, url). 17500 * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). 17501 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`) including animations. 17502 * - Registering the control with its parent {@link ng.directive:form form}. 17503 * 17504 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 17505 * current scope. If the property doesn't already exist on this scope, it will be created 17506 * implicitly and added to the scope. 17507 * 17508 * For best practices on using `ngModel`, see: 17509 * 17510 * - [https://github.com/angular/angular.js/wiki/Understanding-Scopes] 17511 * 17512 * For basic examples, how to use `ngModel`, see: 17513 * 17514 * - {@link ng.directive:input input} 17515 * - {@link input[text] text} 17516 * - {@link input[checkbox] checkbox} 17517 * - {@link input[radio] radio} 17518 * - {@link input[number] number} 17519 * - {@link input[email] email} 17520 * - {@link input[url] url} 17521 * - {@link ng.directive:select select} 17522 * - {@link ng.directive:textarea textarea} 17523 * 17524 * # CSS classes 17525 * The following CSS classes are added and removed on the associated input/select/textarea element 17526 * depending on the validity of the model. 17527 * 17528 * - `ng-valid` is set if the model is valid.
17529 * - `ng-invalid` is set if the model is invalid. 17530 * - `ng-pristine` is set if the model is pristine. 17531 * - `ng-dirty` is set if the model is dirty. 17532 * 17533 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 17534 * 17535 * ## Animation Hooks 17536 * 17537 * Animations within models are triggered when any of the associated CSS classes are added and removed 17538 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, 17539 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 17540 * The animations that are triggered within ngModel are similar to how they work in ngClass and 17541 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 17542 * 17543 * The following example shows a simple way to utilize CSS transitions to style an input element 17544 * that has been rendered as invalid after it has been validated: 17545 * 17546 * <pre> 17547 * //be sure to include ngAnimate as a module to hook into more 17548 * //advanced animations 17549 * .my-input { 17550 * transition:0.5s linear all; 17551 * background: white; 17552 * } 17553 * .my-input.ng-invalid { 17554 * background: red; 17555 * color:white; 17556 * } 17557 * </pre> 17558 * 17559 * @example 17560 * <example deps="angular-animate.js" animations="true" fixBase="true"> 17561 <file name="index.jsp"> 17562 <script> 17563 function Ctrl($scope) { 17564 $scope.val = '1'; 17565 } 17566 </script> 17567 <style> 17568 .my-input { 17569 -webkit-transition:all linear 0.5s; 17570 transition:all linear 0.5s; 17571 background: transparent; 17572 } 17573 .my-input.ng-invalid { 17574 color:white; 17575 background: red; 17576 } 17577 </style> 17578 Update input to see transitions when valid/invalid. 17579 Integer is a valid value. 17580 <form name="testForm" ng-controller="Ctrl"> 17581 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" /> 17582 </form> 17583 </file> 17584 * </example> 17585 */ 17586 var ngModelDirective = function () { 17587 return { 17588 require: ['ngModel', '^?form'], 17589 controller: NgModelController, 17590 link: function (scope, element, attr, ctrls) { 17591 // notify others, especially parent forms 17592 17593 var modelCtrl = ctrls[0], 17594 formCtrl = ctrls[1] || nullFormCtrl; 17595 17596 formCtrl.$addControl(modelCtrl); 17597 17598 scope.$on('$destroy', function () { 17599 formCtrl.$removeControl(modelCtrl); 17600 }); 17601 } 17602 }; 17603 }; 17604 17605 17606 /** 17607 * @ngdoc directive 17608 * @name ngChange 17609 * 17610 * @description 17611 * Evaluate the given expression when the user changes the input. 17612 * The expression is evaluated immediately, unlike the JavaScript onchange event 17613 * which only triggers at the end of a change (usually, when the user leaves the 17614 * form element or presses the return key). 17615 * The expression is not evaluated when the value change is coming from the model. 17616 * 17617 * Note, this directive requires `ngModel` to be present. 17618 * 17619 * @element input 17620 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 17621 * in input value. 17622 * 17623 * @example 17624 * <example name="ngChange-directive"> 17625 * <file name="index.jsp"> 17626 * <script> 17627 * function Controller($scope) { 17628 * $scope.counter = 0; 17629 * $scope.change = function() { 17630 * $scope.counter++; 17631 * }; 17632 * } 17633 * </script> 17634 * <div ng-controller="Controller"> 17635 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 17636 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 17637 * <label for="ng-change-example2">Confirmed</label><br /> 17638 * <tt>debug = {{confirmed}}</tt><br/> 17639 * <tt>counter = {{counter}}</tt><br/> 17640 * </div> 17641 * </file> 17642 * <file name="protractor.js" type="protractor"> 17643 * var counter = element(by.binding('counter')); 17644 * var debug = element(by.binding('confirmed')); 17645 * 17646 * it('should evaluate the expression if changing from view', function() { 17647 * expect(counter.getText()).toContain('0'); 17648 *
17649 * element(by.id('ng-change-example1')).click(); 17650 * 17651 * expect(counter.getText()).toContain('1'); 17652 * expect(debug.getText()).toContain('true'); 17653 * }); 17654 * 17655 * it('should not evaluate the expression if changing from model', function() { 17656 * element(by.id('ng-change-example2')).click(); 17657 17658 * expect(counter.getText()).toContain('0'); 17659 * expect(debug.getText()).toContain('true'); 17660 * }); 17661 * </file> 17662 * </example> 17663 */ 17664 var ngChangeDirective = valueFn({ 17665 require: 'ngModel', 17666 link: function (scope, element, attr, ctrl) { 17667 ctrl.$viewChangeListeners.push(function () { 17668 scope.$eval(attr.ngChange); 17669 }); 17670 } 17671 }); 17672 17673 17674 var requiredDirective = function () { 17675 return { 17676 require: '?ngModel', 17677 link: function (scope, elm, attr, ctrl) { 17678 if (!ctrl) return; 17679 attr.required = true; // force truthy in case we are on non input element 17680 17681 var validator = function (value) { 17682 if (attr.required && ctrl.$isEmpty(value)) { 17683 ctrl.$setValidity('required', false); 17684 return; 17685 } else { 17686 ctrl.$setValidity('required', true); 17687 return value; 17688 } 17689 }; 17690 17691 ctrl.$formatters.push(validator); 17692 ctrl.$parsers.unshift(validator); 17693 17694 attr.$observe('required', function () { 17695 validator(ctrl.$viewValue); 17696 }); 17697 } 17698 }; 17699 }; 17700 17701 17702 /** 17703 * @ngdoc directive 17704 * @name ngList 17705 * 17706 * @description 17707 * Text input that converts between a delimited string and an array of strings. The delimiter 17708 * can be a fixed string (by default a comma) or a regular expression. 17709 * 17710 * @element input 17711 * @param {string=} ngList optional delimiter that should be used to split the value. If 17712 * specified in form `/something/` then the value will be converted into a regular expression. 17713 * 17714 * @example 17715 <example name="ngList-directive"> 17716 <file name="index.jsp"> 17717 <script> 17718 function Ctrl($scope) { 17719 $scope.names = ['igor', 'misko', 'vojta']; 17720 } 17721 </script> 17722 <form name="myForm" ng-controller="Ctrl"> 17723 List: <input name="namesInput" ng-model="names" ng-list required> 17724 <span class="error" ng-show="myForm.namesInput.$error.required"> 17725 Required!</span> 17726 <br> 17727 <tt>names = {{names}}</tt><br/> 17728 <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 17729 <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 17730 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 17731 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 17732 </form> 17733 </file> 17734 <file name="protractor.js" type="protractor"> 17735 var listInput = element(by.model('names')); 17736 var names = element(by.binding('{{names}}')); 17737 var valid = element(by.binding('myForm.namesInput.$valid')); 17738 var error = element(by.css('span.error')); 17739 17740 it('should initialize to model', function() { 17741 expect(names.getText()).toContain('["igor","misko","vojta"]'); 17742 expect(valid.getText()).toContain('true'); 17743 expect(error.getCssValue('display')).toBe('none'); 17744 }); 17745 17746 it('should be invalid if empty', function() { 17747 listInput.clear(); 17748 listInput.sendKeys(''); 17749 17750 expect(names.getText()).toContain(''); 17751 expect(valid.getText()).toContain('false'); 17752 expect(error.getCssValue('display')).not.toBe('none'); }); 17753 </file> 17754 </example> 17755 */ 17756 var ngListDirective = function () { 17757 return { 17758 require: 'ngModel', 17759 link: function (scope, element, attr, ctrl) { 17760 var match = /\/(.*)\//.exec(attr.ngList), 17761 separator = match && new RegExp(match[1]) || attr.ngList || ','; 17762 17763 var parse = function (viewValue) {
vendor: 4,267 bytes, lines 17764-17884
17764 // If the viewValue is invalid (say required but empty) it will be `undefined` 17765 if (isUndefined(viewValue)) return; 17766 17767 var list = []; 17768 17769 if (viewValue) { 17770 forEach(viewValue.split(separator), function (value) { 17771 if (value) list.push(trim(value)); 17772 }); 17773 } 17774 17775 return list; 17776 }; 17777 17778 ctrl.$parsers.push(parse); 17779 ctrl.$formatters.push(function (value) { 17780 if (isArray(value)) { 17781 return value.join(', '); 17782 } 17783 17784 return undefined; 17785 }); 17786 17787 // Override the standard $isEmpty because an empty array means the input is empty. 17788 ctrl.$isEmpty = function (value) { 17789 return !value || !value.length; 17790 }; 17791 } 17792 }; 17793 }; 17794 17795 17796 var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 17797 /** 17798 * @ngdoc directive 17799 * @name ngValue 17800 * 17801 * @description 17802 * Binds the given expression to the value of `input[select]` or `input[radio]`, so 17803 * that when the element is selected, the `ngModel` of that element is set to the 17804 * bound value. 17805 * 17806 * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as 17807 * shown below. 17808 * 17809 * @element input 17810 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 17811 * of the `input` element 17812 * 17813 * @example 17814 <example name="ngValue-directive"> 17815 <file name="index.jsp"> 17816 <script> 17817 function Ctrl($scope) { 17818 $scope.names = ['pizza', 'unicorns', 'robots']; 17819 $scope.my = { favorite: 'unicorns' }; 17820 } 17821 </script> 17822 <form ng-controller="Ctrl"> 17823 <h2>Which is your favorite?</h2> 17824 <label ng-repeat="name in names" for="{{name}}"> 17825 {{name}} 17826 <input type="radio" 17827 ng-model="my.favorite" 17828 ng-value="name" 17829 id="{{name}}" 17830 name="favorite"> 17831 </label> 17832 <div>You chose {{my.favorite}}</div> 17833 </form> 17834 </file> 17835 <file name="protractor.js" type="protractor"> 17836 var favorite = element(by.binding('my.favorite')); 17837 17838 it('should initialize to model', function() { 17839 expect(favorite.getText()).toContain('unicorns'); 17840 }); 17841 it('should bind the values to the inputs', function() { 17842 element.all(by.model('my.favorite')).get(0).click(); 17843 expect(favorite.getText()).toContain('pizza'); 17844 }); 17845 </file> 17846 </example> 17847 */ 17848 var ngValueDirective = function () { 17849 return { 17850 priority: 100, 17851 compile: function (tpl, tplAttr) { 17852 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 17853 return function ngValueConstantLink(scope, elm, attr) { 17854 attr.$set('value', scope.$eval(attr.ngValue)); 17855 }; 17856 } else { 17857 return function ngValueLink(scope, elm, attr) { 17858 scope.$watch(attr.ngValue, function valueWatchAction(value) { 17859 attr.$set('value', value); 17860 }); 17861 }; 17862 } 17863 } 17864 }; 17865 }; 17866 17867 /** 17868 * @ngdoc directive 17869 * @name ngBind 17870 * @restrict AC 17871 * 17872 * @description 17873 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 17874 * with the value of a given expression, and to update the text content when the value of that 17875 * expression changes. 17876 * 17877 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 17878 * `{{ expression }}` which is similar but less verbose. 17879 * 17880 * It is preferable to use `ngBind` instead of `{{ expression }}` when a template is momentarily 17881 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 17882 * element attribute, it makes the bindings invisible to the user while the page is loading. 17883 * 17884 * An alternative solution to this problem would be using the
17885 * {@link ng.directive:ngCloak ngCloak} directive. 17886 * 17887 * 17888 * @element ANY 17889 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 17890 * 17891 * @example 17892 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 17893 <example> 17894 <file name="index.jsp"> 17895 <script> 17896 function Ctrl($scope) { 17897 $scope.name = 'Whirled'; 17898 } 17899 </script> 17900 <div ng-controller="Ctrl"> 17901 Enter name: <input type="text" ng-model="name"><br> 17902 Hello <span ng-bind="name"></span>! 17903 </div> 17904 </file> 17905 <file name="protractor.js" type="protractor"> 17906 it('should check ng-bind', function() { 17907 var nameInput = element(by.model('name')); 17908 17909 expect(element(by.binding('name')).getText()).toBe('Whirled'); 17910 nameInput.clear(); 17911 nameInput.sendKeys('world'); 17912 expect(element(by.binding('name')).getText()).toBe('world'); 17913 }); 17914 </file> 17915 </example> 17916 */ 17917 var ngBindDirective = ngDirective(function (scope, element, attr) { 17918 element.addClass('ng-binding').data('$binding', attr.ngBind); 17919 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 17920 // We are purposefully using == here rather than === because we want to 17921 // catch when value is "null or undefined" 17922 // jshint -W041 17923 element.text(value == undefined ? '' : value); 17924 }); 17925 }); 17926 17927 17928 /** 17929 * @ngdoc directive 17930 * @name ngBindTemplate 17931 * 17932 * @description 17933 * The `ngBindTemplate` directive specifies that the element 17934 * text content should be replaced with the interpolation of the template 17935 * in the `ngBindTemplate` attribute. 17936 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 17937 * expressions. This directive is needed since some HTML elements 17938 * (such as TITLE and OPTION) cannot contain SPAN elements. 17939 * 17940 * @element ANY 17941 * @param {string} ngBindTemplate template of form 17942 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 17943 * 17944 * @example 17945 * Try it here: enter text in text box and watch the greeting change. 17946 <example> 17947 <file name="index.jsp"> 17948 <script> 17949 function Ctrl($scope) { 17950 $scope.salutation = 'Hello'; 17951 $scope.name = 'World'; 17952 } 17953 </script> 17954 <div ng-controller="Ctrl"> 17955 Salutation: <input type="text" ng-model="salutation"><br> 17956 Name: <input type="text" ng-model="name"><br> 17957 <pre ng-bind-template="{{salutation}} {{name}}!"></pre>
17958 </div> 17959 </file> 17960 <file name="protractor.js" type="protractor"> 17961 it('should check ng-bind', function() { 17962 var salutationElem = element(by.binding('salutation')); 17963 var salutationInput = element(by.model('salutation')); 17964 var nameInput = element(by.model('name')); 17965 17966 expect(salutationElem.getText()).toBe('Hello World!'); 17967 17968 salutationInput.clear(); 17969 salutationInput.sendKeys('Greetings'); 17970 nameInput.clear(); 17971 nameInput.sendKeys('user'); 17972 17973 expect(salutationElem.getText()).toBe('Greetings user!'); 17974 }); 17975 </file> 17976 </example> 17977 */ 17978 var ngBindTemplateDirective = ['$interpolate', function ($interpolate) { 17979 return function (scope, element, attr) { 17980 // TODO: move this to scenario runner 17981 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 17982 element.addClass('ng-binding').data('$binding', interpolateFn); 17983 attr.$observe('ngBindTemplate', function (value) { 17984 element.text(value); 17985 }); 17986 }; 17987 }]; 17988 17989 17990 /** 17991 * @ngdoc directive 17992 * @name ngBindHtml 17993 * 17994 * @description 17995 * Creates a binding that will innerHTML the result of evaluating the `expression` into the current 17996 * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link 17997 * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` 17998 * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in 17999 * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to 18000 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 18001 * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. 18002 * 18003 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 18004 * will have an exception (instead of an exploit.) 18005 * 18006 * @element ANY 18007 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 18008 * 18009 * @example 18010 Try it here: enter text in text box and watch the greeting change. 18011 18012 <example module="ngBindHtmlExample" deps="angular-sanitize.js"> 18013 <file name="index.jsp"> 18014 <div ng-controller="ngBindHtmlCtrl"> 18015 <p ng-bind-html="myHTML"></p> 18016 </div> 18017 </file> 18018 18019 <file name="script.js"> 18020 angular.module('ngBindHtmlExample', ['ngSanitize']) 18021 18022 .controller('ngBindHtmlCtrl', ['$scope', function ngBindHtmlCtrl($scope) { 18023 $scope.myHTML = 18024 'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>'; 18025 }]); 18026 </file> 18027 18028 <file name="protractor.js" type="protractor"> 18029 it('should check ng-bind-html', function() { 18030 expect(element(by.binding('myHTML')).getText()).toBe( 18031 'I am an HTMLstring with links! and other stuff'); 18032 }); 18033 </file> 18034 </example> 18035 */ 18036 var ngBindHtmlDirective = ['$sce', '$parse', function ($sce, $parse) { 18037 return function (scope, element, attr) { 18038 element.addClass('ng-binding').data('$binding', attr.ngBindHtml); 18039 18040 var parsed = $parse(attr.ngBindHtml); 18041 18042 function getStringValue() { 18043 return (parsed(scope) || '').toString(); 18044 } 18045 18046 scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { 18047 element.html($sce.getTrustedHtml(parsed(scope)) || ''); 18048 }); 18049 }; 18050 }]; 18051 18052 function classDirective(name, selector) { 18053 name = 'ngClass' + name; 18054 return ['$animate', function ($animate) { 18055 return { 18056 restrict: 'AC', 18057 link: function (scope, element, attr) { 18058 var oldVal; 18059 18060 scope.$watch(attr[name], ngClassWatchAction, true); 18061 18062 attr.$observe('class', function (value) { 18063 ngClassWatchAction(scope.$eval(attr[name])); 18064 }); 18065 18066 18067 if (name !== 'ngClass') { 18068 scope.$watch('$index', function ($index, old$index) { 18069 // jshint bitwise: false 18070 var mod = $index & 1; 18071 if (mod !== old$index & 1) {
18072 var classes = arrayClasses(scope.$eval(attr[name])); 18073 mod === selector ? 18074 addClasses(classes) : 18075 removeClasses(classes); 18076 } 18077 }); 18078 } 18079 18080 function addClasses(classes) { 18081 var newClasses = digestClassCounts(classes, 1); 18082 attr.$addClass(newClasses); 18083 } 18084 18085 function removeClasses(classes) { 18086 var newClasses = digestClassCounts(classes, -1); 18087 attr.$removeClass(newClasses); 18088 } 18089 18090 function digestClassCounts(classes, count) { 18091 var classCounts = element.data('$classCounts') || {}; 18092 var classesToUpdate = []; 18093 forEach(classes, function (className) { 18094 if (count > 0 || classCounts[className]) { 18095 classCounts[className] = (classCounts[className] || 0) + count; 18096 if (classCounts[className] === +(count > 0)) { 18097 classesToUpdate.push(className); 18098 } 18099 } 18100 }); 18101 element.data('$classCounts', classCounts); 18102 return classesToUpdate.join(' '); 18103 } 18104 18105 function updateClasses(oldClasses, newClasses) { 18106 var toAdd = arrayDifference(newClasses, oldClasses); 18107 var toRemove = arrayDifference(oldClasses, newClasses); 18108 toRemove = digestClassCounts(toRemove, -1); 18109 toAdd = digestClassCounts(toAdd, 1); 18110 18111 if (toAdd.length === 0) { 18112 $animate.removeClass(element, toRemove); 18113 } else if (toRemove.length === 0) { 18114 $animate.addClass(element, toAdd); 18115 } else { 18116 $animate.setClass(element, toAdd, toRemove); 18117 } 18118 } 18119 18120 function ngClassWatchAction(newVal) { 18121 if (selector === true || scope.$index % 2 === selector) { 18122 var newClasses = arrayClasses(newVal || []); 18123 if (!oldVal) { 18124 addClasses(newClasses); 18125 } else if (!equals(newVal, oldVal)) { 18126 var oldClasses = arrayClasses(oldVal); 18127 updateClasses(oldClasses, newClasses); 18128 } 18129 } 18130 oldVal = copy(newVal); 18131 } 18132 } 18133 }; 18134 18135 function arrayDifference(tokens1, tokens2) { 18136 var values = []; 18137 18138 outer: 18139 for (var i = 0; i < tokens1.length; i++) { 18140 var token = tokens1[i]; 18141 for (var j = 0; j < tokens2.length; j++) { 18142 if (token == tokens2[j]) continue outer; 18143 } 18144 values.push(token); 18145 } 18146 return values; 18147 } 18148 18149 function arrayClasses(classVal) { 18150 if (isArray(classVal)) { 18151 return classVal; 18152 } else if (isString(classVal)) { 18153 return classVal.split(' '); 18154 } else if (isObject(classVal)) { 18155 var classes = [], i = 0; 18156 forEach(classVal, function (v, k) { 18157 if (v) { 18158 classes.push(k); 18159 } 18160 }); 18161 return classes; 18162 } 18163 return classVal; 18164 } 18165 }]; 18166 } 18167 18168 /** 18169 * @ngdoc directive 18170 * @name ngClass 18171 * @restrict AC 18172 *
18173 * @description 18174 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 18175 * an expression that represents all classes to be added. 18176 * 18177 * The directive operates in three different ways, depending on which of three types the expression 18178 * evaluates to: 18179 * 18180 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 18181 * names. 18182 * 18183 * 2. If the expression evaluates to an array, each element of the array should be a string that is 18184 * one or more space-delimited class names. 18185 * 18186 * 3. If the expression evaluates to an object, then for each key-value pair of the 18187 * object with a truthy value the corresponding key is used as a class name. 18188 * 18189 * The directive won't add duplicate classes if a particular class was already set. 18190 * 18191 * When the expression changes, the previously added classes are removed and only then the 18192 * new classes are added. 18193 * 18194 * @animations 18195 * add - happens just before the class is applied to the element 18196 * remove - happens just before the class is removed from the element 18197 * 18198 * @element ANY 18199 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 18200 * of the evaluation can be a string representing space delimited class 18201 * names, an array, or a map of class names to boolean values. In the case of a map, the 18202 * names of the properties whose values are truthy will be added as css classes to the 18203 * element. 18204 * 18205 * @example Example that demonstrates basic bindings via ngClass directive. 18206 <example> 18207 <file name="index.jsp"> 18208 <p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p> 18209 <input type="checkbox" ng-model="deleted"> deleted (apply "strike" class)<br> 18210 <input type="checkbox" ng-model="important"> important (apply "bold" class)<br> 18211 <input type="checkbox" ng-model="error"> error (apply "red" class) 18212 <hr> 18213 <p ng-class="style">Using String Syntax</p> 18214 <input type="text" ng-model="style" placeholder="Type: bold strike red"> 18215 <hr> 18216 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 18217 <input ng-model="style1" placeholder="Type: bold, strike or red"><br> 18218 <input ng-model="style2" placeholder="Type: bold, strike or red"><br> 18219 <input ng-model="style3" placeholder="Type: bold, strike or red"><br> 18220 </file> 18221 <file name="style.css"> 18222 .strike { 18223 text-decoration: line-through; 18224 } 18225 .bold { 18226 font-weight: bold; 18227 } 18228 .red { 18229 color: red; 18230 } 18231 </file> 18232 <file name="protractor.js" type="protractor"> 18233 var ps = element.all(by.css('p')); 18234 18235 it('should let you toggle the class', function() { 18236 18237 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 18238 expect(ps.first().getAttribute('class')).not.toMatch(/red/); 18239 18240 element(by.model('important')).click(); 18241 expect(ps.first().getAttribute('class')).toMatch(/bold/); 18242 18243 element(by.model('error')).click(); 18244 expect(ps.first().getAttribute('class')).toMatch(/red/); 18245 }); 18246 18247 it('should let you toggle string example', function() { 18248 expect(ps.get(1).getAttribute('class')).toBe(''); 18249 element(by.model('style')).clear(); 18250 element(by.model('style')).sendKeys('red'); 18251 expect(ps.get(1).getAttribute('class')).toBe('red'); 18252 }); 18253 18254 it('array example should have 3 classes', function() { 18255 expect(ps.last().getAttribute('class')).toBe(''); 18256 element(by.model('style1')).sendKeys('bold'); 18257 element(by.model('style2')).sendKeys('strike'); 18258 element(by.model('style3')).sendKeys('red'); 18259 expect(ps.last().getAttribute('class')).toBe('bold strike red'); 18260 }); 18261 </file> 18262 </example> 18263 18264 ## Animations 18265 18266 The example below demonstrates how to perform animations using ngClass. 18267 18268 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 18269 <file name="index.jsp"> 18270 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 18271 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 18272 <br> 18273 <span class="base-class" ng-class="myVar">Sample Text</span> 18274 </file> 18275 <file name="style.css"> 18276 .base-class { 18277 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18278 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18279 } 18280 18281 .base-class.my-class { 18282 color: red; 18283 font-size:3em; 18284 } 18285 </file> 18286 <file name="protractor.js" type="protractor"> 18287 it('should check ng-class', function() { 18288 expect(element(by.css('.base-class')).getAttribute('class')).not. 18289 toMatch(/my-class/); 18290
18291 element(by.id('setbtn')).click(); 18292 18293 expect(element(by.css('.base-class')).getAttribute('class')). 18294 toMatch(/my-class/); 18295 18296 element(by.id('clearbtn')).click(); 18297 18298 expect(element(by.css('.base-class')).getAttribute('class')).not. 18299 toMatch(/my-class/); 18300 }); 18301 </file> 18302 </example> 18303 18304 18305 ## ngClass and pre-existing CSS3 Transitions/Animations 18306 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 18307 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 18308 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 18309 to view the step by step details of {@link ngAnimate.$animate#addclass $animate.addClass} and 18310 {@link ngAnimate.$animate#removeclass $animate.removeClass}. 18311 */ 18312 var ngClassDirective = classDirective('', true); 18313 18314 /** 18315 * @ngdoc directive 18316 * @name ngClassOdd 18317 * @restrict AC 18318 * 18319 * @description 18320 * The `ngClassOdd` and `ngClassEven` directives work exactly as 18321 * {@link ng.directive:ngClass ngClass}, except they work in 18322 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 18323 * 18324 * This directive can be applied only within the scope of an 18325 * {@link ng.directive:ngRepeat ngRepeat}. 18326 * 18327 * @element ANY 18328 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 18329 * of the evaluation can be a string representing space delimited class names or an array. 18330 * 18331 * @example 18332 <example> 18333 <file name="index.jsp"> 18334 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 18335 <li ng-repeat="name in names"> 18336 <span ng-class-odd="'odd'" ng-class-even="'even'"> 18337 {{name}} 18338 </span> 18339 </li> 18340 </ol> 18341 </file> 18342 <file name="style.css"> 18343 .odd { 18344 color: red; 18345 } 18346 .even { 18347 color: blue; 18348 } 18349 </file> 18350 <file name="protractor.js" type="protractor"> 18351 it('should check ng-class-odd and ng-class-even', function() { 18352 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 18353 toMatch(/odd/); 18354 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 18355 toMatch(/even/); 18356 }); 18357 </file> 18358 </example> 18359 */ 18360 var ngClassOddDirective = classDirective('Odd', 0); 18361 18362 /** 18363 * @ngdoc directive 18364 * @name ngClassEven 18365 * @restrict AC 18366 * 18367 * @description 18368 * The `ngClassOdd` and `ngClassEven` directives work exactly as 18369 * {@link ng.directive:ngClass ngClass}, except they work in 18370 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 18371 * 18372 * This directive can be applied only within the scope of an 18373 * {@link ng.directive:ngRepeat ngRepeat}. 18374 * 18375 * @element ANY 18376 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 18377 * result of the evaluation can be a string representing space delimited class names or an array. 18378 * 18379 * @example 18380 <example> 18381 <file name="index.jsp"> 18382 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 18383 <li ng-repeat="name in names"> 18384 <span ng-class-odd="'odd'" ng-class-even="'even'"> 18385 {{name}} 18386 </span> 18387 </li> 18388 </ol> 18389 </file> 18390 <file name="style.css"> 18391 .odd { 18392 color: red; 18393 } 18394 .even { 18395 color: blue; 18396 } 18397 </file> 18398 <file name="protractor.js" type="protractor"> 18399 it('should check ng-class-odd and ng-class-even', function() { 18400 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 18401 toMatch(/odd/); 18402 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 18403 toMatch(/even/); 18404 }); 18405 </file> 18406 </example> 18407 */ 18408 var ngClassEvenDirective = classDirective('Even', 1); 18409 18410 /** 18411 * @ngdoc directive 18412 * @name ngCloak 18413 * @restrict AC 18414 *
18415 * @description 18416 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 18417 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 18418 * directive to avoid the undesirable flicker effect caused by the html template display. 18419 * 18420 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 18421 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 18422 * of the browser view. 18423 * 18424 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 18425 * `angular.min.js`. 18426 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 18427 * 18428 * ```css 18429 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 18430 * display: none !important; 18431 * } 18432 * ``` 18433 * 18434 * When this css rule is loaded by the browser, all html elements (including their children) that 18435 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 18436 * during the compilation of the template it deletes the `ngCloak` element attribute, making 18437 * the compiled element visible. 18438 * 18439 * For the best result, the `angular.js` script must be loaded in the head section of the html 18440 * document; alternatively, the css rule above must be included in the external stylesheet of the 18441 * application. 18442 * 18443 * Legacy browsers, like IE7, do not provide attribute selector support (a
18443dded in CSS 2.1) so they 18444 * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css 18445 * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. 18446 * 18447 * @element ANY 18448 * 18449 * @example 18450 <example> 18451 <file name="index.jsp"> 18452 <div id="template1" ng-cloak>{{ 'hello' }}</div> 18453 <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div> 18454 </file> 18455 <file name="protractor.js" type="protractor"> 18456 it('should remove the template directive and css class', function() { 18457 expect($('#template1').getAttribute('ng-cloak')). 18458 toBeNull(); 18459 expect($('#template2').getAttribute('ng-cloak')). 18460 toBeNull(); 18461 }); 18462 </file> 18463 </example> 18464 * 18465 */ 18466 var ngCloakDirective = ngDirective({ 18467 compile: function (element, attr) { 18468 attr.$set('ngCloak', undefined); 18469 element.removeClass('ng-cloak'); 18470 } 18471 }); 18472 18473 /** 18474 * @ngdoc directive 18475 * @name ngController 18476 * 18477 * @description 18478 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 18479 * supports the principles behind the Model-View-Controller design pattern. 18480 * 18481 * MVC components in angular: 18482 * 18483 * * Model â The Model is scope properties; scopes are attached to the DOM where scope properties 18484 * are accessed through bindings. 18485 * * View â The template (HTML with data bindings) that is rendered into the View. 18486 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 18487 * logic behind the application to decorate the scope with functions and values 18488 * 18489 * Note that you can also attach controllers to the DOM by declaring it in a route definition 18490 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 18491 * again using `ng-controller` in the template itself. This will cause the controller to be attached 18492 * and executed twice. 18493 * 18494 * @element ANY 18495 * @scope 18496 * @param {expression} ngController Name of a globally accessible constructor function or an 18497 * {@link guide/expression expression} that on the current scope evaluates to a 18498 * constructor function. The controller instance can be published into a scope property 18499 * by specifying `as propertyName`. 18500 * 18501 * @example 18502 * Here is a simple form for editing user contact information. Adding, removing, clearing, and 18503 * greeting are methods declared on the controller (see source tab). These methods can 18504 * easily be called from the angular markup. Notice that the scope becomes the `this` for the 18505 * controller's instance. This allows for easy access to the view data from the controller. Also 18506 * notice that any changes to the data are automatically reflected in the View without the need 18507 * for a manual update. The example is shown in two different declaration styles you may use 18508 * according to preference. 18509 <example> 18510 <file name="index.jsp"> 18511 <script> 18512 function SettingsController1() { 18513 this.name = "John Smith"; 18514 this.contacts = [ 18515 {type: 'phone', value: '408 555 1212'}, 18516 {type: 'email', value: '[email protected]'} ]; 18517 }; 18518 18519 SettingsController1.prototype.greet = function() { 18520 alert(this.name); 18521 }; 18522 18523 SettingsController1.prototype.addContact = function() { 18524 this.contacts.push({type: 'email', value: '[email protected]'}); 18525 }; 18526 18527 SettingsController1.prototype.removeContact = function(contactToRemove) { 18528 var index = this.contacts.indexOf(contactToRemove); 18529 this.contacts.splice(index, 1); 18530 }; 18531 18532 SettingsController1.prototype.clearContact = function(contact) { 18533 contact.type = 'phone'; 18534 contact.value = ''; 18535 }; 18536 </script> 18537 <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 18538 Name: <input type="text" ng-model="settings.name"/> 18539 [ <a href="" ng-click="settings.greet()">greet</a> ]<br/> 18540 Contact: 18541 <ul> 18542 <li ng-repeat="contact in settings.contacts"> 18543 <select ng-model="contact.type"> 18544 <option>phone</option> 18545 <option>email</option> 18546 </select> 18547 <input type="text" ng-model="contact.value"/> 18548 [ <a href="" ng-click="settings.clearContact(contact)">clear</a> 18549 | <a href="" ng-click="settings.removeContact(contact)">X</a> ] 18550 </li> 18551 <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li> 18552 </ul>
18553 </div> 18554 </file> 18555 <file name="protractor.js" type="protractor"> 18556 it('should check controller as', function() { 18557 var container = element(by.id('ctrl-as-exmpl')); 18558 18559 expect(container.findElement(by.model('settings.name')) 18560 .getAttribute('value')).toBe('John Smith'); 18561 18562 var firstRepeat = 18563 container.findElement(by.repeater('contact in settings.contacts').row(0)); 18564 var secondRepeat = 18565 container.findElement(by.repeater('contact in settings.contacts').row(1)); 18566 18567 expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18568 .toBe('408 555 1212'); 18569 expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18570 .toBe('[email protected]'); 18571 18572 firstRepeat.findElement(by.linkText('clear')).click(); 18573 18574 expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18575 .toBe(''); 18576 18577 container.findElement(by.linkText('add')).click(); 18578 18579 expect(container.findElement(by.repeater('contact in settings.contacts').row(2)) 18580 .findElement(by.model('contact.value')) 18581 .getAttribute('value')) 18582 .toBe('[email protected]'); 18583 }); 18584 </file> 18585 </example> 18586 <example> 18587 <file name="index.jsp"> 18588 <script> 18589 function SettingsController2($scope) { 18590 $scope.name = "John Smith"; 18591 $scope.contacts = [ 18592 {type:'phone', value:'408 555 1212'}, 18593 {type:'email', value:'[email protected]'} ]; 18594 18595 $scope.greet = function() { 18596 alert(this.name); 18597 }; 18598 18599 $scope.addContact = function() { 18600 this.contacts.push({type:'email', value:'[email protected]'}); 18601 }; 18602 18603 $scope.removeContact = function(contactToRemove) { 18604 var index = this.contacts.indexOf(contactToRemove); 18605 this.contacts.splice(index, 1); 18606 }; 18607 18608 $scope.clearContact = function(contact) { 18609 contact.type = 'phone'; 18610 contact.value = ''; 18611 }; 18612 } 18613 </script> 18614 <div id="ctrl-exmpl" ng-controller="SettingsController2"> 18615 Name: <input type="text" ng-model="name"/> 18616 [ <a href="" ng-click="greet()">greet</a> ]<br/> 18617 Contact: 18618 <ul> 18619 <li ng-repeat="contact in contacts"> 18620 <select ng-model="contact.type"> 18621 <option>phone</option> 18622 <option>email</option> 18623 </select> 18624 <input type="text" ng-model="contact.value"/> 18625 [ <a href="" ng-click="clearContact(contact)">clear</a> 18626 | <a href="" ng-click="removeContact(contact)">X</a> ] 18627 </li> 18628 <li>[ <a href="" ng-click="addContact()">add</a> ]</li> 18629 </ul> 18630 </div> 18631 </file> 18632 <file name="protractor.js" type="protractor"> 18633 it('should check controller', function() { 18634 var container = element(by.id('ctrl-exmpl')); 18635 18636 expect(container.findElement(by.model('name')) 18637 .getAttribute('value')).toBe('John Smith'); 18638 18639 var firstRepeat = 18640 container.findElement(by.repeater('contact in contacts').row(0)); 18641 var secondRepeat = 18642 container.findElement(by.repeater('contact in contacts').row(1)); 18643 18644 expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18645 .toBe('408 555 1212'); 18646 expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18647 .toBe('[email protected]'); 18648 18649 firstRepeat.findElement(by.linkText('clear')).click(); 18650 18651 expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18652 .toBe(''); 18653 18654 container.findElement(by.linkText('add')).click(); 18655 18656 expect(container.findElement(by.repeater('contact in contacts').row(2)) 18657 .findElement(by.model('contact.value')) 18658 .getAttribute('value')) 18659 .toBe('[email protected]'); 18660 }); 18661 </file> 18662 </example> 18663 18664 */ 18665 var ngControllerDirective = [function () { 18666 return { 18667 scope: true, 18668 controller: '@', 18669 priority: 500 18670 }; 18671 }]; 18672 18673 /** 18674 * @ngdoc directive 18675 * @name ngCsp 18676 * 18677 * @element html
18678 * @description 18679 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. 18680 * 18681 * This is necessary when developing things like Google Chrome Extensions. 18682 * 18683 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). 18684 * For us to be compatible, we just need to implement the "getterFn" in $parse without violating 18685 * any of these restrictions. 18686 * 18687 * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` 18688 * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will 18689 * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will 18690 * be raised. 18691 * 18692 * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically 18693 * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). 18694 * To make those directives work in CSP mode, include the `angular-csp.css` manually. 18695 * 18696 * In order to use this feature put the `ngCsp` directive on the root element of the application. 18697 * 18698 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 18699 * 18700 * @example 18701 * This example shows how to apply the `ngCsp` directive to the `html` tag. 18702 ```html 18703 <!doctype html> 18704 <html ng-app ng-csp> 18705 ... 18706 ... 18707 </html> 18708 ``` 18709 */ 18710 18711// ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap 18712// the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute 18713// anywhere in the current doc 18714 18715 /** 18716 * @ngdoc directive 18717 * @name ngClick 18718 * 18719 * @description 18720 * The ngClick directive allows you to specify custom behavior when 18721 * an element is clicked. 18722 * 18723 * @element ANY 18724 * @priority 0 18725 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 18726 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 18727 * 18728 * @example 18729 <example> 18730 <file name="index.jsp"> 18731 <button ng-click="count = count + 1" ng-init="count=0"> 18732 Increment 18733 </button> 18734 count: {{count}} 18735 </file> 18736 <file name="protractor.js" type="protractor"> 18737 it('should check ng-click', function() { 18738 expect(element(by.binding('count')).getText()).toMatch('0'); 18739 element(by.css('button')).click(); 18740 expect(element(by.binding('count')).getText()).toMatch('1'); 18741 }); 18742 </file> 18743 </example> 18744 */ 18745 /* 18746 * A directive that allows creation of custom onclick handlers that are defined as angular 18747 * expressions and are compiled and executed within the current scope. 18748 * 18749 * Events that are handled via these handler are always configured not to propagate further. 18750 */ 18751 var ngEventDirectives = {}; 18752 forEach( 18753 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 18754 function (name) { 18755 var directiveName = directiveNormalize('ng-' + name); 18756 ngEventDirectives[directiveName] = ['$parse', function ($parse) { 18757 return { 18758 compile: function ($element, attr) { 18759 var fn = $parse(attr[directiveName]); 18760 return function (scope, element, attr) { 18761 element.on(lowercase(name), function (event) { 18762 scope.$apply(function () { 18763 fn(scope, {$event: event}); 18764 }); 18765 }); 18766 }; 18767 } 18768 }; 18769 }]; 18770 } 18771 ); 18772 18773 /** 18774 * @ngdoc directive 18775 * @name ngDblclick 18776 * 18777 * @description 18778 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 18779 * 18780 * @element ANY 18781 * @priority 0 18782 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 18783 * a dblclick. (The Event object is available as `$event`) 18784 * 18785 * @example 18786 <example> 18787 <file name="index.jsp"> 18788 <button ng-dblclick="count = count + 1" ng-init="count=0"> 18789 Increment (on double click) 18790 </button> 18791 count: {{count}} 18792 </file> 18793 </example> 18794 */ 18795 18796 18797 /** 18798 * @ngdoc directive 18799 * @name ngMousedown 18800 *
18801 * @description 18802 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 18803 * 18804 * @element ANY 18805 * @priority 0 18806 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 18807 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 18808 * 18809 * @example 18810 <example> 18811 <file name="index.jsp"> 18812 <button ng-mousedown="count = count + 1" ng-init="count=0"> 18813 Increment (on mouse down) 18814 </button> 18815 count: {{count}} 18816 </file> 18817 </example> 18818 */ 18819 18820 18821 /** 18822 * @ngdoc directive 18823 * @name ngMouseup 18824 * 18825 * @description 18826 * Specify custom behavior on mouseup event. 18827 * 18828 * @element ANY 18829 * @priority 0 18830 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 18831 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 18832 * 18833 * @example 18834 <example> 18835 <file name="index.jsp"> 18836 <button ng-mouseup="count = count + 1" ng-init="count=0"> 18837 Increment (on mouse up) 18838 </button> 18839 count: {{count}} 18840 </file> 18841 </example> 18842 */ 18843 18844 /** 18845 * @ngdoc directive 18846 * @name ngMouseover 18847 * 18848 * @description 18849 * Specify custom behavior on mouseover event. 18850 * 18851 * @element ANY 18852 * @priority 0 18853 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 18854 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 18855 * 18856 * @example 18857 <example> 18858 <file name="index.jsp"> 18859 <button ng-mouseover="count = count + 1" ng-init="count=0"> 18860 Increment (when mouse is over) 18861 </button> 18862 count: {{count}} 18863 </file> 18864 </example> 18865 */ 18866 18867 18868 /** 18869 * @ngdoc directive 18870 * @name ngMouseenter 18871 * 18872 * @description 18873 * Specify custom behavior on mouseenter event. 18874 * 18875 * @element ANY 18876 * @priority 0 18877 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 18878 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 18879 * 18880 * @example 18881 <example> 18882 <file name="index.jsp"> 18883 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 18884 Increment (when mouse enters) 18885 </button> 18886 count: {{count}} 18887 </file> 18888 </example> 18889 */ 18890 18891 18892 /** 18893 * @ngdoc directive 18894 * @name ngMouseleave 18895 * 18896 * @description 18897 * Specify custom behavior on mouseleave event. 18898 * 18899 * @element ANY 18900 * @priority 0 18901 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 18902 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 18903 * 18904 * @example 18905 <example> 18906 <file name="index.jsp"> 18907 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 18908 Increment (when mouse leaves) 18909 </button> 18910 count: {{count}} 18911 </file> 18912 </example> 18913 */ 18914 18915 18916 /** 18917 * @ngdoc directive 18918 * @name ngMousemove 18919 * 18920 * @description 18921 * Specify custom behavior on mousemove event. 18922 * 18923 * @element ANY 18924 * @priority 0 18925 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 18926 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 18927 * 18928 * @example 18929 <example> 18930 <file name="index.jsp"> 18931 <button ng-mousemove="count = count + 1" ng-init="count=0"> 18932 Increment (when mouse moves) 18933 </button> 18934 count: {{count}} 18935 </file> 18936 </example> 18937 */ 18938 18939 18940 /** 18941 * @ngdoc directive 18942 * @name ngKeydown 18943 * 18944 * @description 18945 * Specify custom behavior on keydown event. 18946 * 18947 * @element ANY 18948 * @priority 0 18949 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 18950 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 18951 * 18952 * @example 18953 <example> 18954 <file name="index.jsp"> 18955 <input ng-keydown="count = count + 1" ng-init="count=0"> 18956 key down count: {{count}} 18957 </file> 18958 </example> 18959 */ 18960 18961 18962 /** 18963 * @ngdoc directive 18964 * @name ngKeyup 18965 *
18966 * @description 18967 * Specify custom behavior on keyup event. 18968 * 18969 * @element ANY 18970 * @priority 0 18971 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 18972 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 18973 * 18974 * @example 18975 <example> 18976 <file name="index.jsp"> 18977 <input ng-keyup="count = count + 1" ng-init="count=0"> 18978 key up count: {{count}} 18979 </file> 18980 </example> 18981 */ 18982 18983 18984 /** 18985 * @ngdoc directive 18986 * @name ngKeypress 18987 * 18988 * @description 18989 * Specify custom behavior on keypress event. 18990 * 18991 * @element ANY 18992 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 18993 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 18994 * and can be interrogated for keyCode, altKey, etc.) 18995 * 18996 * @example 18997 <example> 18998 <file name="index.jsp"> 18999 <input ng-keypress="count = count + 1" ng-init="count=0"> 19000 key press count: {{count}} 19001 </file> 19002 </example> 19003 */ 19004 19005 19006 /** 19007 * @ngdoc directive 19008 * @name ngSubmit 19009 * 19010 * @description 19011 * Enables binding angular expressions to onsubmit events. 19012 * 19013 * Additionally it prevents the default action (which for form means sending the request to the 19014 * server and reloading the current page), but only if the form does not contain `action`, 19015 * `data-action`, or `x-action` attributes. 19016 * 19017 * @element form 19018 * @priority 0 19019 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 19020 * ({@link guide/expression#-event- Event object is available as `$event`}) 19021 * 19022 * @example 19023 <example> 19024 <file name="index.jsp"> 19025 <script> 19026 function Ctrl($scope) { 19027 $scope.list = []; 19028 $scope.text = 'hello'; 19029 $scope.submit = function() { 19030 if ($scope.text) { 19031 $scope.list.push(this.text); 19032 $scope.text = ''; 19033 } 19034 }; 19035 } 19036 </script> 19037 <form ng-submit="submit()" ng-controller="Ctrl"> 19038 Enter text and hit enter: 19039 <input type="text" ng-model="text" name="text" /> 19040 <input type="submit" id="submit" value="Submit" /> 19041 <pre>list={{list}}</pre> 19042 </form> 19043 </file> 19044 <file name="protractor.js" type="protractor"> 19045 it('should check ng-submit', function() { 19046 expect(element(by.binding('list')).getText()).toBe('list=[]'); 19047 element(by.css('#submit')).click(); 19048 expect(element(by.binding('list')).getText()).toContain('hello'); 19049 expect(element(by.input('text')).getAttribute('value')).toBe(''); 19050 }); 19051 it('should ignore empty strings', function() { 19052 expect(element(by.binding('list')).getText()).toBe('list=[]'); 19053 element(by.css('#submit')).click(); 19054 element(by.css('#submit')).click(); 19055 expect(element(by.binding('list')).getText()).toContain('hello'); 19056 }); 19057 </file> 19058 </example> 19059 */ 19060 19061 /** 19062 * @ngdoc directive 19063 * @name ngFocus 19064 * 19065 * @description 19066 * Specify custom behavior on focus event. 19067 * 19068 * @element window, input, select, textarea, a 19069 * @priority 0 19070 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 19071 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 19072 * 19073 * @example 19074 * See {@link ng.directive:ngClick ngClick} 19075 */ 19076 19077 /** 19078 * @ngdoc directive 19079 * @name ngBlur 19080 * 19081 * @description 19082 * Specify custom behavior on blur event. 19083 * 19084 * @element window, input, select, textarea, a 19085 * @priority 0 19086 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 19087 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 19088 * 19089 * @example 19090 * See {@link ng.directive:ngClick ngClick} 19091 */ 19092 19093 /** 19094 * @ngdoc directive 19095 * @name ngCopy 19096 * 19097 * @description 19098 * Specify custom behavior on copy event. 19099 * 19100 * @element window, input, select, textarea, a 19101 * @priority 0 19102 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 19103 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 19104 * 19105 * @example 19106 <example> 19107 <file name="index.jsp"> 19108 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 19109 copied: {{copied}} 19110 </file> 19111 </example> 19112 */ 19113 19114 /** 19115 * @ngdoc directive 19116 * @name ngCut 19117 *
19118 * @description 19119 * Specify custom behavior on cut event. 19120 * 19121 * @element window, input, select, textarea, a 19122 * @priority 0 19123 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 19124 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 19125 * 19126 * @example 19127 <example> 19128 <file name="index.jsp"> 19129 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 19130 cut: {{cut}} 19131 </file> 19132 </example> 19133 */ 19134 19135 /** 19136 * @ngdoc directive 19137 * @name ngPaste 19138 * 19139 * @description 19140 * Specify custom behavior on paste event. 19141 * 19142 * @element window, input, select, textarea, a 19143 * @priority 0 19144 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 19145 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 19146 * 19147 * @example 19148 <example> 19149 <file name="index.jsp"> 19150 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 19151 pasted: {{paste}} 19152 </file> 19153 </example> 19154 */ 19155 19156 /** 19157 * @ngdoc directive 19158 * @name ngIf 19159 * @restrict A 19160 * 19161 * @description 19162 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 19163 * {expression}. If the expression assigned to `ngIf` evaluates to a false 19164 * value then the element is removed from the DOM, otherwise a clone of the 19165 * element is reinserted into the DOM. 19166 * 19167 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 19168 * element in the DOM rather than changing its visibility via the `display` css property. A common 19169 * case when this difference is significant is when using css selectors that rely on an element's 19170 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classes. 19171 * 19172 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 19173 * is created when the element is restored. The scope created within `ngIf` inherits from 19174 * its parent scope using 19175 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance). 19176 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 19177 * a javascript primitive defined in the parent scope. In this case any modifications made to the 19178 * variable within the child scope will override (hide) the value in the parent scope. 19179 * 19180 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 19181 * is if an element's class attribute is directly modified after it's compiled, using something like 19182 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 19183 * the added class will be lost because the original compiled state is used to regenerate the element. 19184 * 19185 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 19186 * and `leave` effects. 19187 * 19188 * @animations 19189 * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container 19190 * leave - happens just before the ngIf contents are removed from the DOM 19191 * 19192 * @element ANY 19193 * @scope 19194 * @priority 600 19195 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 19196 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 19197 * element is added to the DOM tree. 19198 * 19199 * @example 19200 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 19201 <file name="index.jsp"> 19202 Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/> 19203 Show when checked: 19204 <span ng-if="checked" class="animate-if"> 19205 I'm removed when the checkbox is unchecked. 19206 </span> 19207 </file> 19208 <file name="animations.css"> 19209 .animate-if { 19210 background:white; 19211 border:1px solid black; 19212 padding:10px; 19213 } 19214 19215 .animate-if.ng-enter, .animate-if.ng-leave { 19216 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19217 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19218 } 19219 19220 .animate-if.ng-enter, 19221 .animate-if.ng-leave.ng-leave-active { 19222 opacity:0; 19223 } 19224 19225 .animate-if.ng-leave, 19226 .animate-if.ng-enter.ng-enter-active { 19227 opacity:1; 19228 } 19229 </file> 19230 </example> 19231 */ 19232 var ngIfDirective = ['$animate', function ($animate) { 19233 return { 19234 transclude: 'element', 19235 priority: 600, 19236 terminal: true, 19237 restrict: 'A', 19238 $$tlb: true, 19239 link: function ($scope, $element, $attr, ctrl, $transclude) { 19240 var block, childScope, previousElements; 19241 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 19242 19243 if (toBoolean(value)) { 19244 if (!childScope) { 19245 childScope = $scope.$new(); 19246 $transclude(childScope, function (clone) { 19247 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 19248 // Note: We only need the first/last node of the cloned
19248nodes. 19249 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 19250 // by a directive with templateUrl when it's template arrives. 19251 block = { 19252 clone: clone 19253 }; 19254 $animate.enter(clone, $element.parent(), $element); 19255 }); 19256 } 19257 } else { 19258 if (previousElements) { 19259 previousElements.remove(); 19260 previousElements = null; 19261 } 19262 if (childScope) { 19263 childScope.$destroy(); 19264 childScope = null; 19265 } 19266 if (block) { 19267 previousElements = getBlockElements(block.clone); 19268 $animate.leave(previousElements, function () { 19269 previousElements = null; 19270 }); 19271 block = null; 19272 } 19273 } 19274 }); 19275 } 19276 }; 19277 }]; 19278 19279 /** 19280 * @ngdoc directive 19281 * @name ngInclude 19282 * @restrict ECA 19283 * 19284 * @description 19285 * Fetches, compiles and includes an external HTML fragment. 19286 * 19287 * By default, the template URL is restricted to the same domain and protocol as the 19288 * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 19289 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 19290 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 19291 * [wrap them](ng.$sce#trustAsResourceUrl) as trusted values. Refer to Angular's {@link 19292 * ng.$sce Strict Contextual Escaping}. 19293 * 19294 * In addition, the browser's 19295 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 19296 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 19297 * policy may further restrict whether the template is successfully loaded. 19298 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 19299 * access on some browsers. 19300 * 19301 * @animations 19302 * enter - animation is used to bring new content into the browser. 19303 * leave - animation is used to animate existing content away. 19304 * 19305 * The enter and leave animation occur concurrently. 19306 * 19307 * @scope 19308 * @priority 400 19309 * 19310 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 19311 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 19312 * @param {string=} onload Expression to evaluate when a new partial is loaded. 19313 * 19314 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 19315 * $anchorScroll} to scroll the viewport after the content is loaded. 19316 * 19317 * - If the attribute is not set, disable scrolling. 19318 * - If the attribute is set without value, enable scrolling. 19319 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 19320 * 19321 * @example 19322 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 19323 <file name="index.jsp"> 19324 <div ng-controller="Ctrl"> 19325 <select ng-model="template" ng-options="t.name for t in templates"> 19326 <option value="">(blank)</option> 19327 </select> 19328 url of the template: <tt>{{template.url}}</tt> 19329 <hr/> 19330 <div class="slide-animate-container"> 19331 <div class="slide-animate" ng-include="template.url"></div> 19332 </div> 19333 </div> 19334 </file> 19335 <file name="script.js"> 19336 function Ctrl($scope) { 19337 $scope.templates = 19338 [ { name: 'template1.html', url: 'template1.html'}, 19339 { name: 'template2.html', url: 'template2.html'} ]; 19340 $scope.template = $scope.templates[0]; 19341 } 19342 </file> 19343 <file name="template1.html"> 19344 Content of template1.html 19345 </file> 19346 <file name="template2.html"> 19347 Content of template2.html 19348 </file> 19349 <file name="animations.css"> 19350 .slide-animate-container { 19351 position:relative; 19352 background:white; 19353 border:1px solid black; 19354 height:40px; 19355 overflow:hidden; 19356 } 19357 19358 .slide-animate { 19359 padding:10px; 19360 } 19361 19362 .slide-animate.ng-enter, .slide-animate.ng-leave { 19363 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19364 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19365 19366 position:absolute; 19367 top:0; 19368 left:0; 19369 right:0; 19370 bottom:0; 19371 display:block; 19372 padding:10px; 19373 } 19374 19375 .slide-animate.ng-enter { 19376 top:-50px; 19377 } 19378 .slide-animate.ng-enter.ng-enter-active { 19379 top:0; 19380 } 19381 19382 .slide-animate.ng-leave { 19383 top:0; 19384 } 19385 .slide-animate.ng-leave.ng-leave-active { 19386 top:50px; 19387 } 19388 </file> 19389 <file name="protractor.js" type="protractor"> 19390 var templateSelect = element(by.model('template')); 19391 var includeElem = element(by.css('[ng-include]')); 19392 19393 it('should load template1.html', function() { 19394 expect(includeElem.getText()).toMatch(/Content of template1.html/); 19395 }); 19396 19397 it('should load template2.html', function() { 19398 if (browser.params.browser == 'firefox') { 19399 // Firefox can't handle using selects 19400 // See https://github.com/angular/protractor/issues/480 19401 return; 19402 } 19403 templateSelect.click(); 19404 templateSelect.element.all(by.css('option')).get(2).click(); 19405 expect(includeElem.getText()).toMatch(/Content of template2.html/); 19406 }); 19407 19408 it('should change to blank', function() { 19409 if (browser.params.browser == 'firefox') { 19410 // Firefox can't handle using selects 19411 return; 19412 } 19413 templateSelect.click(); 19414 templateSelect.element.all(by.css('option')).get(0).click(); 19415 expect(includeElem.isPresent()).toBe(false); 19416 }); 19417 </file> 19418 </example> 19419 */ 19420 19421 19422 /** 19423 * @ngdoc event 19424 * @name ngInclude#$includeContentRequested 19425 * @eventType emit on the scope ngInclude was declared in
19426 * @description 19427 * Emitted every time the ngInclude content is requested. 19428 */ 19429 19430 19431 /** 19432 * @ngdoc event 19433 * @name ngInclude#$includeContentLoaded 19434 * @eventType emit on the current ngInclude scope 19435 * @description 19436 * Emitted every time the ngInclude content is reloaded. 19437 */ 19438 var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce', 19439 function ($http, $templateCache, $anchorScroll, $animate, $sce) { 19440 return { 19441 restrict: 'ECA', 19442 priority: 400, 19443 terminal: true, 19444 transclude: 'element', 19445 controller: angular.noop, 19446 compile: function (element, attr) { 19447 var srcExp = attr.ngInclude || attr.src, 19448 onloadExp = attr.onload || '', 19449 autoScrollExp = attr.autoscroll; 19450 19451 return function (scope, $element, $attr, ctrl, $transclude) { 19452 var changeCounter = 0, 19453 currentScope, 19454 previousElement, 19455 currentElement; 19456 19457 var cleanupLastIncludeContent = function () { 19458 if (previousElement) { 19459 previousElement.remove(); 19460 previousElement = null; 19461 } 19462 if (currentScope) { 19463 currentScope.$destroy(); 19464 currentScope = null; 19465 } 19466 if (currentElement) { 19467 $animate.leave(currentElement, function () { 19468 previousElement = null; 19469 }); 19470 previousElement = currentElement; 19471 currentElement = null; 19472 } 19473 }; 19474 19475 scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { 19476 var afterAnimation = function () { 19477 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 19478 $anchorScroll(); 19479 } 19480 }; 19481 var thisChangeId = ++changeCounter; 19482 19483 if (src) { 19484 $http.get(src, {cache: $templateCache}).success(function (response) { 19485 if (thisChangeId !== changeCounter) return; 19486 var newScope = scope.$new(); 19487 ctrl.template = response; 19488 19489 // Note: This will also link all children of ng-include that were contained in the original 19490 // html. If that content contains controllers, ... they could pollute/change the scope. 19491 // However, using ng-include on an element with additional content does not make sense... 19492 // Note: We can't remove them in the cloneAttchFn of $transclude as that 19493 // function is called before linking the content, which would apply child 19494 // directives to non existing elements. 19495 var clone = $transclude(newScope, function (clone) { 19496 cleanupLastIncludeContent(); 19497 $animate.enter(clone, null, $element, afterAnimation); 19498 }); 19499 19500 currentScope = newScope; 19501 currentElement = clone; 19502 19503 currentScope.$emit('$includeContentLoaded'); 19504 scope.$eval(onloadExp); 19505 }).error(function () { 19506 if (thisChangeId === changeCounter) cleanupLastIncludeContent(); 19507 }); 19508 scope.$emit('$includeContentRequested'); 19509 } else { 19510 cleanupLastIncludeContent(); 19511 ctrl.template = null; 19512 } 19513 }); 19514 }; 19515 } 19516 }; 19517 }]; 19518 19519// This directive is called during the $transclude call of the first `ngInclude` directive. 19520// It will replace and compile the content of the element with the loaded template. 19521// We need this directive so that the element content is already filled when 19522// the link function of another directive on the same element as ngInclude 19523// is called. 19524 var ngIncludeFillContentDirective = ['$compile', 19525 function ($compile) { 19526 return { 19527 restrict: 'ECA', 19528 priority: -400, 19529 require: 'ngInclude', 19530 link: function (scope, $element, $attr, ctrl) { 19531 $element.html(ctrl.template); 19532 $compile($element.contents())(scope); 19533 } 19534 }; 19535 }]; 19536 19537 /** 19538 * @ngdoc directive 19539 * @name ngInit 19540 * @restrict AC 19541 *
19542 * @description 19543 * The `ngInit` directive allows you to evaluate an expression in the 19544 * current scope. 19545 * 19546 * <div class="alert alert-error"> 19547 * The only appropriate use of `ngInit` is for aliasing special properties of 19548 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you 19549 * should use {@link guide/controller controllers} rather than `ngInit` 19550 * to initialize values on a scope. 19551 * </div> 19552 * <div class="alert alert-warning"> 19553 * **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make 19554 * sure you have parenthesis for correct precedence: 19555 * <pre class="prettyprint"> 19556 * <div ng-init="test1 = (data | orderBy:'name')"></div> 19557 * </pre> 19558 * </div> 19559 * 19560 * @priority 450 19561 * 19562 * @element ANY 19563 * @param {expression} ngInit {@link guide/expression Expression} to eval. 19564 * 19565 * @example 19566 <example> 19567 <file name="index.jsp"> 19568 <script> 19569 function Ctrl($scope) { 19570 $scope.list = [['a', 'b'], ['c', 'd']]; 19571 } 19572 </script> 19573 <div ng-controller="Ctrl"> 19574 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 19575 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 19576 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 19577 </div> 19578 </div> 19579 </div> 19580 </file> 19581 <file name="protractor.js" type="protractor"> 19582 it('should alias index positions', function() { 19583 var elements = element.all(by.css('.example-init')); 19584 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 19585 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 19586 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 19587 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 19588 }); 19589 </file> 19590 </example> 19591 */ 19592 var ngInitDirective = ngDirective({ 19593 priority: 450, 19594 compile: function () { 19595 return { 19596 pre: function (scope, element, attrs) { 19597 scope.$eval(attrs.ngInit); 19598 } 19599 }; 19600 } 19601 }); 19602 19603 /** 19604 * @ngdoc directive 19605 * @name ngNonBindable 19606 * @restrict AC 19607 * @priority 1000 19608 * 19609 * @description 19610 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 19611 * DOM element. This is useful if the element contains what appears to be Angular directives and 19612 * bindings but which should be ignored by Angular. This could be the case if you have a site that 19613 * displays snippets of code, for instance. 19614 * 19615 * @element ANY 19616 * 19617 * @example 19618 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 19619 * but the one wrapped in `ngNonBindable` is left alone. 19620 * 19621 * @example 19622 <example> 19623 <file name="index.jsp"> 19624 <div>Normal: {{1 + 2}}</div> 19625 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 19626 </file> 19627 <file name="protractor.js" type="protractor"> 19628 it('should check ng-non-bindable', function() { 19629 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 19630 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 19631 }); 19632 </file> 19633 </example> 19634 */ 19635 var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 19636 19637 /** 19638 * @ngdoc directive 19639 * @name ngPluralize 19640 * @restrict EA 19641 * 19642 * @description 19643 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 19644 * These rules are bundled with angular.js, but can be overridden 19645 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 19646 * by specifying the mappings between 19647 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 19648 * and the strings to be displayed. 19649 * 19650 * # Plural categories and explicit number rules 19651 * There are two 19652 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 19653 * in Angular's default en-US locale: "one" and "other". 19654 * 19655 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 19656 * any number that is not 1), an explicit number rule can only match one number. For example, the 19657 * explicit number rule for "3" matches the number 3. There are examples of plural categories 19658 * and explicit number rules throughout the rest of this documentation. 19659 * 19660 * # Configuring ngPluralize 19661 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 19662 * You can also provide an optional attribute, `offset`. 19663 * 19664 * The value of the `count` attribute can be either a string or an {@link guide/expression 19665 * Angular expression}; these are evaluated on the current scope for its bound value. 19666 * 19667 * The `when` attribute specifies the mappings between plural categories and the actual 19668 * string to be displayed. The value of the attribute should be a JSON object. 19669 * 19670 * The following example shows how to configure ngPluralize: 19671 * 19672 * ```html 19673 * <ng-pluralize count="personCount"
19674 when="{'0': 'Nobody is viewing.', 19675 * 'one': '1 person is viewing.', 19676 * 'other': '{} people are viewing.'}"> 19677 * </ng-pluralize> 19678 *``` 19679 * 19680 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 19681 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 19682 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 19683 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 19684 * show "a dozen people are viewing". 19685 * 19686 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 19687 * into pluralized strings. In the previous example, Angular will replace `{}` with 19688 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 19689 * for <span ng-non-bindable>{{numberExpression}}</span>. 19690 * 19691 * # Configuring ngPluralize with offset 19692 * The `offset` attribute allows further customization of pluralized text, which can result in 19693 * a better user experience. For example, instead of the message "4 people are viewing this document", 19694 * you might display "John, Kate and 2 others are viewing this document". 19695 * The offset attribute allows you to offset a number by any desired value. 19696 * Let's take a look at an example: 19697 * 19698 * ```html 19699 * <ng-pluralize count="personCount" offset=2 19700 * when="{'0': 'Nobody is viewing.', 19701 * '1': '{{person1}} is viewing.', 19702 * '2': '{{person1}} and {{person2}} are viewing.', 19703 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 19704 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 19705 * </ng-pluralize> 19706 * ``` 19707 * 19708 * Notice that we are still using two plural categories(one, other), but we added 19709 * three explicit number rules 0, 1 and 2. 19710 * When one person, perhaps John, views the document, "John is viewing" will be shown. 19711 * When three people view the document, no explicit number rule is found, so 19712 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 19713 * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" 19714 * is shown.
19715 * 19716 * Note that when you specify offsets, you must provide explicit number rules for 19717 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 19718 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 19719 * plural categories "one" and "other". 19720 * 19721 * @param {string|expression} count The variable to be bound to. 19722 * @param {string} when The mapping between plural category to its corresponding strings. 19723 * @param {number=} offset Offset to deduct from the total number. 19724 * 19725 * @example 19726 <example> 19727 <file name="index.jsp"> 19728 <script> 19729 function Ctrl($scope) { 19730 $scope.person1 = 'Igor'; 19731 $scope.person2 = 'Misko'; 19732 $scope.personCount = 1; 19733 } 19734 </script> 19735 <div ng-controller="Ctrl"> 19736 Person 1:<input type="text" ng-model="person1" value="Igor" /><br/> 19737 Person 2:<input type="text" ng-model="person2" value="Misko" /><br/> 19738 Number of People:<input type="text" ng-model="personCount" value="1" /><br/> 19739 19740 <!--- Example with simple pluralization rules for en locale ---> 19741 Without Offset: 19742 <ng-pluralize count="personCount" 19743 when="{'0': 'Nobody is viewing.', 19744 'one': '1 person is viewing.', 19745 'other': '{} people are viewing.'}"> 19746 </ng-pluralize><br> 19747 19748 <!--- Example with offset ---> 19749 With Offset(2): 19750 <ng-pluralize count="personCount" offset=2 19751 when="{'0': 'Nobody is viewing.', 19752 '1': '{{person1}} is viewing.', 19753 '2': '{{person1}} and {{person2}} are viewing.', 19754 'one': '{{person1}}, {{person2}} and one other person are viewing.', 19755 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 19756 </ng-pluralize> 19757 </div> 19758 </file> 19759 <file name="protractor.js" type="protractor"> 19760 it('should show correct pluralized string', function() { 19761 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 19762 var withOffset = element.all(by.css('ng-pluralize')).get(1); 19763 var countInput = element(by.model('personCount')); 19764 19765 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 19766 expect(withOffset.getText()).toEqual('Igor is viewing.'); 19767 19768 countInput.clear(); 19769 countInput.sendKeys('0'); 19770 19771 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 19772 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 19773 19774 countInput.clear(); 19775 countInput.sendKeys('2'); 19776 19777 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 19778 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 19779 19780 countInput.clear(); 19781 countInput.sendKeys('3'); 19782 19783 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 19784 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 19785 19786 countInput.clear(); 19787 countInput.sendKeys('4'); 19788 19789 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 19790 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 19791 }); 19792 it('should show data-bound names', function() { 19793 var withOffset = element.all(by.css('ng-pluralize')).get(1); 19794 var personCount = element(by.model('personCount')); 19795 var person1 = element(by.model('person1')); 19796 var person2 = element(by.model('person2')); 19797 personCount.clear(); 19798 personCount.sendKeys('4'); 19799 person1.clear(); 19800 person1.sendKeys('Di'); 19801 person2.clear(); 19802 person2.sendKeys('Vojta'); 19803 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 19804 }); 19805 </file> 19806 </example> 19807 */ 19808 var ngPluralizeDirective = ['$locale', '$interpolate', function ($locale, $interpolate) { 19809 var BRACE = /{}/g; 19810 return { 19811 restrict: 'EA', 19812 link: function (scope, element, attr) { 19813 var numberExp = attr.count, 19814 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 19815 offset = attr.offset || 0, 19816 whens = scope.$eval(whenExp) || {}, 19817 whensExpFns = {}, 19818 startSymbol = $interpolate.startSymbol(), 19819 endSymbol = $interpolate.endSymbol(), 19820 isWhen = /^when(Minus)?(.+)$/; 19821
19822 forEach(attr, function (expression, attributeName) { 19823 if (isWhen.test(attributeName)) { 19824 whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = 19825 element.attr(attr.$attr[attributeName]); 19826 } 19827 }); 19828 forEach(whens, function (expression, key) { 19829 whensExpFns[key] = 19830 $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + 19831 offset + endSymbol)); 19832 }); 19833 19834 scope.$watch(function ngPluralizeWatch() { 19835 var value = parseFloat(scope.$eval(numberExp)); 19836 19837 if (!isNaN(value)) { 19838 //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, 19839 //check it against pluralization rules in $locale service 19840 if (!(value in whens)) value = $locale.pluralCat(value - offset); 19841 return whensExpFns[value](scope, element, true); 19842 } else { 19843 return ''; 19844 } 19845 }, function ngPluralizeWatchAction(newVal) { 19846 element.text(newVal); 19847 }); 19848 } 19849 }; 19850 }]; 19851 19852 /** 19853 * @ngdoc directive 19854 * @name ngRepeat 19855 * 19856 * @description 19857 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 19858 * instance gets its own scope, where the given loop variable is set to the current collection item, 19859 * and `$index` is set to the item index or key. 19860 * 19861 * Special properties are exposed on the local scope of each template instance, including: 19862 * 19863 * | Variable | Type | Details | 19864 * |-----------|-----------------|-----------------------------------------------------------------------------| 19865 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 19866 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 19867 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 19868 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 19869 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 19870 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 19871 * 19872 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 19873 * This may be useful when, for instance, nesting ngRepeats. 19874 * 19875 * # Special repeat start and end points 19876 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 19877 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 19878 * 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) 19879 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 19880 * 19881 * The example below makes use of this feature: 19882 * ```html 19883 * <header ng-repeat-start="item in items"> 19884 * Header {{ item }} 19885 * </header> 19886 * <div class="body"> 19887 * Body {{ item }} 19888 * </div> 19889 * <footer ng-repeat-end> 19890 * Footer {{ item }} 19891 * </footer> 19892 * ``` 19893 * 19894 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 19895 * ```html 19896 * <header> 19897 * Header A 19898 * </header> 19899 * <div class="body"> 19900 * Body A 19901 * </div> 19902 * <footer> 19903 * Footer A 19904 * </footer> 19905 * <header> 19906 * Header B 19907 * </header> 19908 * <div class="body"> 19909 * Body B 19910 * </div> 19911 * <footer> 19912 * Footer B 19913 * </footer> 19914 * ``` 19915 * 19916 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 19917 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 19918 * 19919 * @animations 19920 * **.enter** - when a new item is added to the list or when an item is revealed after a filter 19921 * 19922 * **.leave** - when an item is removed from the list or when an item is filtered out 19923 * 19924 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 19925 * 19926 * @element ANY 19927 * @scope 19928 * @priority 1000 19929 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 19930 * formats are currently supported: 19931 * 19932 * * `variable in expression` â where variable is the user defined loop variable and `expression` 19933 * is a scope expression giving the collection to enumerate. 19934 * 19935 * For example: `album in artist.albums`. 19936 * 19937 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 19938 * and `expression` is the scope expression giving the collection to enumerate. 19939 * 19940 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 19941 * 19942 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking function 19943 * which can be used to associate the objects in the collection with the DOM elements. If no tracking function 19944 * is specified the ng-repeat associates elements by identity in the collection. It is an error to have 19945 * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are 19946 * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, 19947 * before specifying a tracking expression. 19948 * 19949 * For example: `item in items` is equivalent to `item in items track by $id(item)'. This implies that the DOM elements 19950 * will be associated by item identity in the array. 19951 * 19952 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 19953 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 19954 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 19955 * element in the same way in the DOM. 19956 * 19957 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 19958 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 19959 * property is same. 19960 * 19961 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 19962 * to items in conjunction with a tracking expression. 19963 * 19964 * @example 19965 * This example initializes the scope to a list of names and 19966 * then uses `ngRepeat` to display every person: 19967 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 19968 <file name="index.jsp"> 19969 <div ng-init="friends = [ 19970 {name:'John', age:25, gender:'boy'}, 19971 {name:'Jessie', age:30, gender:'girl'}, 19972 {name:'Johanna', age:28, gender:'girl'}, 19973 {name:'Joy', age:15, gender:'girl'}, 19974 {name:'Mary', age:28, gender:'girl'}, 19975 {name:'Peter', age:95, gender:'boy'}, 19976 {name:'Sebastian', age:50, gender:'boy'}, 19977 {name:'Erika', age:27, gender:'girl'}, 19978 {name:'Patrick', age:40, gender:'boy'}, 19979 {name:'Samantha', age:60, gender:'girl'} 19980 ]"> 19981 I have {{friends.length}} friends. They are: 19982 <input type="search" ng-model="q" placeholder="filter friends..." /> 19983 <ul class="example-animate-container"> 19984 <li class="animate-repeat" ng-repeat="friend in friends | filter:q"> 19985 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 19986 </li> 19987 </ul>
19988 </div> 19989 </file> 19990 <file name="animations.css"> 19991 .example-animate-container { 19992 background:white; 19993 border:1px solid black; 19994 list-style:none; 19995 margin:0; 19996 padding:0 10px; 19997 } 19998 19999 .animate-repeat { 20000 line-height:40px; 20001 list-style:none; 20002 box-sizing:border-box; 20003 } 20004 20005 .animate-repeat.ng-move, 20006 .animate-repeat.ng-enter, 20007 .animate-repeat.ng-leave { 20008 -webkit-transition:all linear 0.5s; 20009 transition:all linear 0.5s; 20010 } 20011 20012 .animate-repeat.ng-leave.ng-leave-active, 20013 .animate-repeat.ng-move, 20014 .animate-repeat.ng-enter { 20015 opacity:0; 20016 max-height:0; 20017 } 20018 20019 .animate-repeat.ng-leave, 20020 .animate-repeat.ng-move.ng-move-active, 20021 .animate-repeat.ng-enter.ng-enter-active { 20022 opacity:1; 20023 max-height:40px; 20024 } 20025 </file> 20026 <file name="protractor.js" type="protractor"> 20027 var friends = element.all(by.repeater('friend in friends')); 20028 20029 it('should render initial data set', function() { 20030 expect(friends.count()).toBe(10); 20031 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 20032 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 20033 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 20034 expect(element(by.binding('friends.length')).getText()) 20035 .toMatch("I have 10 friends. They are:"); 20036 }); 20037 20038 it('should update repeater when filter predicate changes', function() { 20039 expect(friends.count()).toBe(10); 20040 20041 element(by.model('q')).sendKeys('ma'); 20042 20043 expect(friends.count()).toBe(2); 20044 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 20045 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 20046 }); 20047 </file> 20048 </example> 20049 */ 20050 var ngRepeatDirective = ['$parse', '$animate', function ($parse, $animate) { 20051 var NG_REMOVED = '$$NG_REMOVED'; 20052 var ngRepeatMinErr = minErr('ngRepeat'); 20053 return { 20054 transclude: 'element', 20055 priority: 1000, 20056 terminal: true, 20057 $$tlb: true, 20058 link: function ($scope, $element, $attr, ctrl, $transclude) { 20059 var expression = $attr.ngRepeat; 20060 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), 20061 trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, 20062 lhs, rhs, valueIdentifier, keyIdentifier, 20063 hashFnLocals = {$id: hashKey}; 20064 20065 if (!match) { 20066 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 20067 expression); 20068 } 20069 20070 lhs = match[1]; 20071 rhs = match[2]; 20072 trackByExp = match[3]; 20073 20074 if (trackByExp) { 20075 trackByExpGetter = $parse(trackByExp); 20076 trackByIdExpFn = function (key, value, index) { 20077 // assign key, value, and $index to the locals so that they can be used in hash functions 20078 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 20079 hashFnLocals[valueIdentifier] = value; 20080 hashFnLocals.$index = index; 20081 return trackByExpGetter($scope, hashFnLocals); 20082 }; 20083 } else { 20084 trackByIdArrayFn = function (key, value) { 20085 return hashKey(value); 20086 }; 20087 trackByIdObjFn = function (key) { 20088 return key; 20089 }; 20090 } 20091 20092 match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); 20093 if (!match) { 20094 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 20095 lhs); 20096 } 20097 valueIdentifier = match[3] || match[1]; 20098 keyIdentifier = match[2]; 20099 20100 // Store a list of elements from previous run. This is a hash where key is the item from the 20101 // iterator, and the value is objects with following properties. 20102 // - scope: bound scope 20103 // - element: previous element. 20104 // - index: position 20105 var lastBlockMap = {}; 20106 20107 //watch props 20108 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 20109 var index, length, 20110 previousNode = $element[0], // current position of the node 20111 nextNode, 20112 // Same as lastBlockMap but it has the current state. It will become the 20113 // lastBlockMap on the next iteration. 20114 nextBlockMap = {}, 20115 arrayLength, 20116 childScope, 20117 key, value, // key/value of iteration 20118 trackById, 20119 trackByIdFn, 20120 collectionKeys, 20121 block, // last object information {scope, element, id} 20122 nextBlockOrder = [], 20123 elementsToRemove; 20124 20125 20126 if (isArrayLike(collection)) { 20127 collectionKeys = collection; 20128 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 20129 } else { 20130 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 20131 // if object, extract keys, sort them and use to determine order of iteration over obj props 20132 collectionKeys = []; 20133 for (key in collection) { 20134 if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { 20135 collectionKeys.push(key); 20136 } 20137 } 20138 collectionKeys.sort(); 20139 } 20140 20141 arrayLength = collectionKeys.length; 20142 20143 // locate existing items 20144 length = nextBlockOrder.length = collectionKeys.length; 20145 for (index = 0; index < length; index++) { 20146 key = (collection === collectionKeys) ? index : collectionKeys[index]; 20147 value = collection[key]; 20148 trackById = trackByIdFn(key, value, index); 20149 assertNotHasOwnProperty(trackById, '`track by` id'); 20150 if (lastBlockMap.hasOwnProperty(trackById)) { 20151 block = lastBlockMap[trackById]; 20152 delete lastBlockMap[trackById]; 20153 nextBlockMap[trackById] = block; 20154 nextBlockOrder[index] = block; 20155 } else if (nextBlockMap.hasOwnProperty(trackById)) { 20156 // restore lastBlockMap
20157 forEach(nextBlockOrder, function (block) { 20158 if (block && block.scope) lastBlockMap[block.id] = block; 20159 }); 20160 // This is a duplicate and we need to throw an error 20161 throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}", 20162 expression, trackById); 20163 } else { 20164 // new never before seen block 20165 nextBlockOrder[index] = { id: trackById }; 20166 nextBlockMap[trackById] = false; 20167 } 20168 } 20169 20170 // remove existing items 20171 for (key in lastBlockMap) { 20172 // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn 20173 if (lastBlockMap.hasOwnProperty(key)) { 20174 block = lastBlockMap[key]; 20175 elementsToRemove = getBlockElements(block.clone); 20176 $animate.leave(elementsToRemove); 20177 forEach(elementsToRemove, function (element) { 20178 element[NG_REMOVED] = true; 20179 }); 20180 block.scope.$destroy(); 20181 } 20182 } 20183 20184 // we are not using forEach for perf reasons (trying to avoid #call) 20185 for (index = 0, length = collectionKeys.length; index < length; index++) { 20186 key = (collection === collectionKeys) ? index : collectionKeys[index]; 20187 value = collection[key]; 20188 block = nextBlockOrder[index]; 20189 if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]); 20190 20191 if (block.scope) { 20192 // if we have already seen this object, then we need to reuse the 20193 // associated scope/element 20194 childScope = block.scope; 20195 20196 nextNode = previousNode; 20197 do { 20198 nextNode = nextNode.nextSibling; 20199 } while (nextNode && nextNode[NG_REMOVED]); 20200 20201 if (getBlockStart(block) != nextNode) { 20202 // existing item which got moved 20203 $animate.move(getBlockElements(block.clone), null, jqLite(previousNode)); 20204 } 20205 previousNode = getBlockEnd(block); 20206 } else { 20207 // new item which we don't know about 20208 childScope = $scope.$new(); 20209 } 20210 20211 childScope[valueIdentifier] = value; 20212 if (keyIdentifier) childScope[keyIdentifier] = key; 20213 childScope.$index = index; 20214 childScope.$first = (index === 0); 20215 childScope.$last = (index === (arrayLength - 1)); 20216 childScope.$middle = !(childScope.$first || childScope.$last); 20217 // jshint bitwise: false 20218 childScope.$odd = !(childScope.$even = (index & 1) === 0); 20219 // jshint bitwise: true 20220 20221 if (!block.scope) { 20222 $transclude(childScope, function (clone) { 20223 clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); 20224 $animate.enter(clone, null, jqLite(previousNode)); 20225 previousNode = clone; 20226 block.scope = childScope; 20227 // Note: We only need the first/last node of the cloned
20227nodes. 20228 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 20229 // by a directive with templateUrl when it's template arrives. 20230 block.clone = clone; 20231 nextBlockMap[block.id] = block; 20232 }); 20233 } 20234 } 20235 lastBlockMap = nextBlockMap; 20236 }); 20237 } 20238 }; 20239 20240 function getBlockStart(block) { 20241 return block.clone[0]; 20242 } 20243 20244 function getBlockEnd(block) { 20245 return block.clone[block.clone.length - 1]; 20246 } 20247 }]; 20248 20249 /** 20250 * @ngdoc directive 20251 * @name ngShow 20252 * 20253 * @description 20254 * The `ngShow` directive shows or hides the given HTML element based on the expression 20255 * provided to the ngShow attribute. The element is shown or hidden by removing or adding 20256 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 20257 * in AngularJS and sets the display style to none (using an !important flag). 20258 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 20259 * 20260 * ```html 20261 * <!-- when $scope.myValue is truthy (element is visible) --> 20262 * <div ng-show="myValue"></div> 20263 * 20264 * <!-- when $scope.myValue is falsy (element is hidden) --> 20265 * <div ng-show="myValue" class="ng-hide"></div> 20266 * ``` 20267 * 20268 * When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute 20269 * on the element causing it to become hidden. When true, the ng-hide CSS class is removed 20270 * from the element causing the element not to appear hidden. 20271 * 20272 * ## Why is !important used? 20273 * 20274 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 20275 * can be easily overridden by heavier selectors. For example, something as simple 20276 * as changing the display style on a HTML list item would make hidden elements appear visible. 20277 * This also becomes a bigger issue when dealing with CSS frameworks. 20278 * 20279 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 20280 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 20281 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 20282 * 20283 * ### Overriding .ng-hide 20284 * 20285 * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by 20286 * restating the styles for the .ng-hide class in CSS: 20287 * ```css 20288 * .ng-hide { 20289 * //!annotate CSS Specificity|Not to worry, this will override the AngularJS default... 20290 * display:block!important; 20291 * 20292 * //this is just another form of hiding an element 20293 * position:absolute; 20294 * top:-9999px; 20295 * left:-9999px; 20296 * } 20297 * ``` 20298 * 20299 * Just remember to include the important flag so the CSS override will function. 20300 * 20301 * <div class="alert alert-warning"> 20302 * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):<br /> 20303 * "f" / "0" / "false" / "no" / "n" / "[]" 20304 * </div> 20305 * 20306 * ## A note about animations with ngShow 20307 * 20308 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 20309 * is true and false. This system works like the animation system present with ngClass except that 20310 * you must also include the !important flag to override the display property 20311 * so that you can perform an animation when the element is hidden during the time of the animation. 20312 * 20313 * ```css 20314 * // 20315 * //a working example can be found at the bottom of this page 20316 * // 20317 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 20318 * transition:0.5s linear all; 20319 * display:block!important; 20320 * } 20321 * 20322 * .my-element.ng-hide-add { ... } 20323 * .my-element.ng-hide-add.ng-hide-add-active { ... } 20324 * .my-element.ng-hide-remove { ... } 20325 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 20326 * ``` 20327 * 20328 * @animations 20329 * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible 20330 * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden 20331 * 20332 * @element ANY 20333 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 20334 * then the element is shown or hidden respectively. 20335 * 20336 * @example 20337 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20338 <file name="index.jsp"> 20339 Click me: <input type="checkbox" ng-model="checked"><br/> 20340 <div> 20341 Show: 20342 <div class="check-element animate-show" ng-show="checked"> 20343 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 20344 </div>
20345 </div> 20346 <div> 20347 Hide: 20348 <div class="check-element animate-show" ng-hide="checked"> 20349 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 20350 </div> 20351 </div> 20352 </file> 20353 <file name="glyphicons.css"> 20354 @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); 20355 </file> 20356 <file name="animations.css"> 20357 .animate-show { 20358 -webkit-transition:all linear 0.5s; 20359 transition:all linear 0.5s; 20360 line-height:20px; 20361 opacity:1; 20362 padding:10px; 20363 border:1px solid black; 20364 background:white; 20365 } 20366 20367 .animate-show.ng-hide-add, 20368 .animate-show.ng-hide-remove { 20369 display:block!important; 20370 } 20371 20372 .animate-show.ng-hide { 20373 line-height:0; 20374 opacity:0; 20375 padding:0 10px; 20376 } 20377 20378 .check-element { 20379 padding:10px; 20380 border:1px solid black; 20381 background:white; 20382 } 20383 </file> 20384 <file name="protractor.js" type="protractor"> 20385 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 20386 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 20387 20388 it('should check ng-show / ng-hide', function() { 20389 expect(thumbsUp.isDisplayed()).toBeFalsy(); 20390 expect(thumbsDown.isDisplayed()).toBeTruthy(); 20391 20392 element(by.model('checked')).click(); 20393 20394 expect(thumbsUp.isDisplayed()).toBeTruthy(); 20395 expect(thumbsDown.isDisplayed()).toBeFalsy(); 20396 }); 20397 </file> 20398 </example> 20399 */ 20400 var ngShowDirective = ['$animate', function ($animate) { 20401 return function (scope, element, attr) { 20402 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 20403 $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); 20404 }); 20405 }; 20406 }]; 20407 20408 20409 /** 20410 * @ngdoc directive 20411 * @name ngHide 20412 * 20413 * @description 20414 * The `ngHide` directive shows or hides the given HTML element based on the expression 20415 * provided to the ngHide attribute. The element is shown or hidden by removing or adding 20416 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 20417 * in AngularJS and sets the display style to none (using an !important flag). 20418 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 20419 * 20420 * ```html 20421 * <!-- when $scope.myValue is truthy (element is hidden) --> 20422 * <div ng-hide="myValue"></div> 20423 * 20424 * <!-- when $scope.myValue is falsy (element is visible) --> 20425 * <div ng-hide="myValue" class="ng-hide"></div> 20426 * ``` 20427 * 20428 * When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute 20429 * on the element causing it to become hidden. When false, the ng-hide CSS class is removed 20430 * from the element causing the element not to appear hidden. 20431 * 20432 * ## Why is !important used? 20433 * 20434 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 20435 * can be easily overridden by heavier selectors. For example, something as simple 20436 * as changing the display style on a HTML list item would make hidden elements appear visible. 20437 * This also becomes a bigger issue when dealing with CSS frameworks. 20438 * 20439 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 20440 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 20441 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 20442 * 20443 * ### Overriding .ng-hide 20444 * 20445 * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by 20446 * restating the styles for the .ng-hide class in CSS: 20447 * ```css 20448 * .ng-hide { 20449 * //!annotate CSS Specificity|Not to worry, this will override the AngularJS default... 20450 * display:block!important; 20451 * 20452 * //this is just another form of hiding an element 20453 * position:absolute; 20454 * top:-9999px; 20455 * left:-9999px; 20456 * } 20457 * ``` 20458 * 20459 * Just remember to include the important flag so the CSS override will function. 20460 * 20461 * <div class="alert alert-warning">
20462 * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):<br /> 20463 * "f" / "0" / "false" / "no" / "n" / "[]" 20464 * </div> 20465 * 20466 * ## A note about animations with ngHide 20467 * 20468 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 20469 * is true and false. This system works like the animation system present with ngClass, except that 20470 * you must also include the !important flag to override the display property so 20471 * that you can perform an animation when the element is hidden during the time of the animation. 20472 * 20473 * ```css 20474 * // 20475 * //a working example can be found at the bottom of this page 20476 * // 20477 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 20478 * transition:0.5s linear all; 20479 * display:block!important; 20480 * } 20481 * 20482 * .my-element.ng-hide-add { ... } 20483 * .my-element.ng-hide-add.ng-hide-add-active { ... } 20484 * .my-element.ng-hide-remove { ... } 20485 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 20486 * ``` 20487 * 20488 * @animations 20489 * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden 20490 * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible 20491 * 20492 * @element ANY 20493 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 20494 * the element is shown or hidden respectively. 20495 * 20496 * @example 20497 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20498 <file name="index.jsp"> 20499 Click me: <input type="checkbox" ng-model="checked"><br/> 20500 <div> 20501 Show: 20502 <div class="check-element animate-hide" ng-show="checked"> 20503 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 20504 </div> 20505 </div> 20506 <div> 20507 Hide: 20508 <div class="check-element animate-hide" ng-hide="checked"> 20509 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 20510 </div> 20511 </div> 20512 </file> 20513 <file name="glyphicons.css"> 20514 @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); 20515 </file> 20516 <file name="animations.css"> 20517 .animate-hide { 20518 -webkit-transition:all linear 0.5s; 20519 transition:all linear 0.5s; 20520 line-height:20px; 20521 opacity:1; 20522 padding:10px; 20523 border:1px solid black; 20524 background:white; 20525 } 20526 20527 .animate-hide.ng-hide-add, 20528 .animate-hide.ng-hide-remove { 20529 display:block!important; 20530 } 20531 20532 .animate-hide.ng-hide { 20533 line-height:0; 20534 opacity:0; 20535 padding:0 10px; 20536 } 20537 20538 .check-element { 20539 padding:10px; 20540 border:1px solid black; 20541 background:white; 20542 } 20543 </file> 20544 <file name="protractor.js" type="protractor"> 20545 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 20546 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 20547 20548 it('should check ng-show / ng-hide', function() { 20549 expect(thumbsUp.isDisplayed()).toBeFalsy(); 20550 expect(thumbsDown.isDisplayed()).toBeTruthy(); 20551 20552 element(by.model('checked')).click(); 20553 20554 expect(thumbsUp.isDisplayed()).toBeTruthy(); 20555 expect(thumbsDown.isDisplayed()).toBeFalsy(); 20556 }); 20557 </file> 20558 </example> 20559 */ 20560 var ngHideDirective = ['$animate', function ($animate) { 20561 return function (scope, element, attr) { 20562 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 20563 $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); 20564 }); 20565 }; 20566 }]; 20567 20568 /** 20569 * @ngdoc directive 20570 * @name ngStyle 20571 * @restrict AC 20572 * 20573 * @description 20574 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 20575 * 20576 * @element ANY 20577 * @param {expression} ngStyle {@link guide/expression Expression} which evals to an 20578 * object whose keys are CSS style names and values are corresponding values for those CSS 20579 * keys. 20580 * 20581 * @example 20582 <example> 20583 <file name="index.jsp"> 20584 <input type="button" value="set" ng-click="myStyle={color:'red'}"> 20585 <input type="button" value="clear" ng-click="myStyle={}"> 20586 <br/> 20587 <span ng-style="myStyle">Sample Text</span> 20588 <pre>myStyle={{myStyle}}</pre> 20589 </file> 20590 <file name="style.css"> 20591 span { 20592 color: black; 20593 } 20594 </file> 20595 <file name="protractor.js" type="protractor"> 20596 var colorSpan = element(by.css('span')); 20597 20598 it('should check ng-style', function() { 20599 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
20600 element(by.css('input[value=set]')).click(); 20601 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 20602 element(by.css('input[value=clear]')).click(); 20603 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 20604 }); 20605 </file> 20606 </example> 20607 */ 20608 var ngStyleDirective = ngDirective(function (scope, element, attr) { 20609 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 20610 if (oldStyles && (newStyles !== oldStyles)) { 20611 forEach(oldStyles, function (val, style) { 20612 element.css(style, ''); 20613 }); 20614 } 20615 if (newStyles) element.css(newStyles); 20616 }, true); 20617 }); 20618 20619 /** 20620 * @ngdoc directive 20621 * @name ngSwitch 20622 * @restrict EA 20623 * 20624 * @description 20625 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 20626 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 20627 * as specified in the template. 20628 * 20629 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 20630 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 20631 * matches the value obtained from the evaluated expression. In other words, you define a container element 20632 * (where you place the directive), place an expression on the **`on="..."` attribute** 20633 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 20634 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 20635 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 20636 * attribute is displayed. 20637 * 20638 * <div class="alert alert-info"> 20639 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 20640 * as literal string values to match against. 20641 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the 20642 * value of the expression `$scope.someVal`. 20643 * </div> 20644 20645 * @animations 20646 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 20647 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 20648 * 20649 * @usage 20650 * <ANY ng-switch="expression"> 20651 * <ANY ng-switch-when="matchValue1">...</ANY> 20652 * <ANY ng-switch-when="matchValue2">...</ANY> 20653 * <ANY ng-switch-default>...</ANY> 20654 * </ANY> 20655 * 20656 * 20657 * @scope 20658 * @priority 800 20659 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>. 20660 * On child elements add: 20661 * 20662 * * `ngSwitchWhen`: the case statement to match against. If match then this 20663 * case will be displayed. If the same match appears multiple times, all the 20664 * elements will be displayed. 20665 * * `ngSwitchDefault`: the default case when no other case match. If there 20666 * are multiple default cases, all of them will be displayed when no other 20667 * case match. 20668 * 20669 * 20670 * @example 20671 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20672 <file name="index.jsp"> 20673 <div ng-controller="Ctrl"> 20674 <select ng-model="selection" ng-options="item for item in items"> 20675 </select> 20676 <tt>selection={{selection}}</tt> 20677 <hr/> 20678 <div class="animate-switch-container" 20679 ng-switch on="selection"> 20680 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 20681 <div class="animate-switch" ng-switch-when="home">Home Span</div> 20682 <div class="animate-switch" ng-switch-default>default</div> 20683 </div> 20684 </div> 20685 </file> 20686 <file name="script.js"> 20687 function Ctrl($scope) { 20688 $scope.items = ['settings', 'home', 'other']; 20689 $scope.selection = $scope.items[0]; 20690 } 20691 </file> 20692 <file name="animations.css"> 20693 .animate-switch-container { 20694 position:relative; 20695 background:white; 20696 border:1px solid black; 20697 height:40px; 20698 overflow:hidden; 20699 } 20700 20701 .animate-switch { 20702 padding:10px; 20703 } 20704 20705 .animate-switch.ng-animate { 20706 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 20707 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 20708 20709 position:absolute; 20710 top:0; 20711 left:0; 20712 right:0; 20713 bottom:0; 20714 } 20715 20716 .animate-switch.ng-leave.ng-leave-active, 20717 .animate-switch.ng-enter { 20718 top:-50px; 20719 } 20720 .animate-switch.ng-leave, 20721 .animate-switch.ng-enter.ng-enter-active { 20722 top:0; 20723 } 20724 </file> 20725 <file name="protractor.js" type="protractor"> 20726 var switchElem = element(by.css('[ng-switch]')); 20727 var select = element(by.model('selection')); 20728 20729 it('should start in settings', function() { 20730 expect(switchElem.getText()).toMatch(/Settings Div/); 20731 }); 20732 it('should change to home', function() { 20733 select.element.all(by.css('option')).get(1).click(); 20734 expect(switchElem.getText()).toMatch(/Home Span/); 20735 }); 20736 it('should select default', function() { 20737 select.element.all(by.css('option')).get(2).click(); 20738 expect(switchElem.getText()).toMatch(/default/); 20739 }); 20740 </file> 20741 </example> 20742 */ 20743 var ngSwitchDirective = ['$animate', function ($animate) { 20744 return { 20745 restrict: 'EA', 20746 require: 'ngSwitch', 20747 20748 // asks for $scope to fool the BC controller module 20749 controller: ['$scope', function ngSwitchController() { 20750 this.cases = {}; 20751 }], 20752 link: function (scope, element, attr, ngSwitchController) { 20753 var watchExpr = attr.ngSwitch || attr.on, 20754 selectedTranscludes, 20755 selectedElements, 20756 previousElements, 20757 selectedScopes = []; 20758 20759 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 20760 var i, ii = selectedScopes.length; 20761 if (ii > 0) { 20762 if (previousElements) { 20763 for (i = 0; i < ii; i++) { 20764 previousElements[i].remove(); 20765 } 20766 previousElements = null; 20767 } 20768 20769 previousElements = []; 20770 for (i = 0; i < ii; i++) { 20771 var selected = selectedElements[i]; 20772 selectedScopes[i].$destroy(); 20773 previousElements[i] = selected; 20774 $animate.leave(selected, function () { 20775 previousElements.splice(i, 1); 20776 if (previousElements.length === 0) { 20777 previousElements = null; 20778 } 20779 }); 20780 } 20781 } 20782 20783 selectedElements = []; 20784 selectedScopes = []; 20785 20786 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 20787 scope.$eval(attr.change);
20788 forEach(selectedTranscludes, function (selectedTransclude) { 20789 var selectedScope = scope.$new(); 20790 selectedScopes.push(selectedScope); 20791 selectedTransclude.transclude(selectedScope, function (caseElement) { 20792 var anchor = selectedTransclude.element; 20793 20794 selectedElements.push(caseElement); 20795 $animate.enter(caseElement, anchor.parent(), anchor); 20796 }); 20797 }); 20798 } 20799 }); 20800 } 20801 }; 20802 }]; 20803 20804 var ngSwitchWhenDirective = ngDirective({ 20805 transclude: 'element', 20806 priority: 800, 20807 require: '^ngSwitch', 20808 link: function (scope, element, attrs, ctrl, $transclude) { 20809 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 20810 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 20811 } 20812 }); 20813 20814 var ngSwitchDefaultDirective = ngDirective({ 20815 transclude: 'element', 20816 priority: 800, 20817 require: '^ngSwitch', 20818 link: function (scope, element, attr, ctrl, $transclude) { 20819 ctrl.cases['?'] = (ctrl.cases['?'] || []); 20820 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 20821 } 20822 }); 20823 20824 /** 20825 * @ngdoc directive 20826 * @name ngTransclude 20827 * @restrict AC 20828 * 20829 * @description 20830 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 20831 * 20832 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 20833 * 20834 * @element ANY 20835 * 20836 * @example 20837 <example module="transclude"> 20838 <file name="index.jsp"> 20839 <script> 20840 function Ctrl($scope) { 20841 $scope.title = 'Lorem Ipsum'; 20842 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 20843 } 20844 20845 angular.module('transclude', []) 20846 .directive('pane', function(){ 20847 return { 20848 restrict: 'E', 20849 transclude: true, 20850 scope: { title:'@' }, 20851 template: '<div style="border: 1px solid black;">' + 20852 '<div style="background-color: gray">{{title}}</div>' + 20853 '<div ng-transclude></div>' + 20854 '</div>' 20855 }; 20856 }); 20857 </script> 20858 <div ng-controller="Ctrl"> 20859 <input ng-model="title"><br> 20860 <textarea ng-model="text"></textarea> <br/> 20861 <pane title="{{title}}">{{text}}</pane> 20862 </div> 20863 </file> 20864 <file name="protractor.js" type="protractor"> 20865 it('should have transcluded', function() { 20866 var titleElement = element(by.model('title')); 20867 titleElement.clear(); 20868 titleElement.sendKeys('TITLE'); 20869 var textElement = element(by.model('text')); 20870 textElement.clear(); 20871 textElement.sendKeys('TEXT'); 20872 expect(element(by.binding('title')).getText()).toEqual('TITLE'); 20873 expect(element(by.binding('text')).getText()).toEqual('TEXT'); 20874 }); 20875 </file> 20876 </example> 20877 * 20878 */ 20879 var ngTranscludeDirective = ngDirective({ 20880 link: function ($scope, $element, $attrs, controller, $transclude) { 20881 if (!$transclude) { 20882 throw minErr('ngTransclude')('orphan', 20883 'Illegal use of ngTransclude directive in the template! ' + 20884 'No parent directive that requires a transclusion found. ' + 20885 'Element: {0}', 20886 startingTag($element)); 20887 } 20888 20889 $transclude(function (clone) { 20890 $element.empty(); 20891 $element.append(clone); 20892 }); 20893 } 20894 }); 20895 20896 /** 20897 * @ngdoc directive 20898 * @name script 20899 * @restrict E 20900 * 20901 * @description 20902 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 20903 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 20904 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 20905 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 20906 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 20907 * 20908 * @param {string} type Must be set to `'text/ng-template'`. 20909 * @param {string} id Cache name of the template. 20910 * 20911 * @example 20912 <example> 20913 <file name="index.jsp"> 20914 <script type="text/ng-template" id="/tpl.html"> 20915 Content of the template. 20916 </script> 20917 20918 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 20919 <div id="tpl-content" ng-include src="currentTpl"></div> 20920 </file> 20921 <file name="protractor.js" type="protractor"> 20922 it('should load template defined inside script tag', function() {
20923 element(by.css('#tpl-link')).click(); 20924 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 20925 }); 20926 </file> 20927 </example> 20928 */ 20929 var scriptDirective = ['$templateCache', function ($templateCache) { 20930 return { 20931 restrict: 'E', 20932 terminal: true, 20933 compile: function (element, attr) { 20934 if (attr.type == 'text/ng-template') { 20935 var templateUrl = attr.id, 20936 // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent 20937 text = element[0].text; 20938 20939 $templateCache.put(templateUrl, text); 20940 } 20941 } 20942 }; 20943 }]; 20944 20945 var ngOptionsMinErr = minErr('ngOptions'); 20946 /** 20947 * @ngdoc directive 20948 * @name select 20949 * @restrict E 20950 * 20951 * @description 20952 * HTML `SELECT` element with angular data-binding. 20953 * 20954 * # `ngOptions` 20955 * 20956 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 20957 * elements for the `<select>` element using the array or object obtained by evaluating the 20958 * `ngOptions` comprehension_expression. 20959 * 20960 * When an item in the `<select>` menu is selected, the array element or object property 20961 * represented by the selected option will be bound to the model identified by the `ngModel` 20962 * directive. 20963 * 20964 * <div class="alert alert-warning"> 20965 * **Note:** `ngModel` compares by reference, not value. This is important when binding to an 20966 * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). 20967 * </div> 20968 * 20969 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 20970 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 20971 * option. See example below for demonstration. 20972 * 20973 * <div class="alert alert-warning"> 20974 * **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead 20975 * of {@link ng.directive:ngRepeat ngRepeat} when you want the 20976 * `select` model to be bound to a non-string value. This is because an option element can only 20977 * be bound to string values at present. 20978 * </div> 20979 * 20980 * @param {string} ngModel Assignable angular expression to data-bind to. 20981 * @param {string=} name Property name of the form under which the control is published. 20982 * @param {string=} required The control is considered valid only if value is entered. 20983 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20984 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20985 * `required` when you want to data-bind to the `required` attribute. 20986 * @param {comprehension_expression=} ngOptions in one of the following forms: 20987 * 20988 * * for array data sources: 20989 * * `label` **`for`** `value` **`in`** `array` 20990 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 20991 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 20992 * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 20993 * * for object data sources: 20994 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 20995 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 20996 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 20997 * * `select` **`as`** `label` **`group by`** `group` 20998 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 20999 * 21000 * Where: 21001 * 21002 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 21003 * * `value`: local variable which will refer to each item in the `array` or each property value 21004 * of `object` during iteration. 21005 * * `key`: local variable which will refer to a property name in `object` during iteration. 21006 * * `label`: The result of this expression will be the label for `<option>` element. The 21007 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 21008 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 21009 * element. If not specified, `select` expression will default to `value`. 21010 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 21011 * DOM element. 21012 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 21013 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 21014 * `value` variable (e.g. `value.propertyName`). 21015 * 21016 * @example 21017 <example> 21018 <file name="index.jsp"> 21019 <script> 21020 function MyCntrl($scope) { 21021 $scope.colors = [ 21022 {name:'black', shade:'dark'}, 21023 {name:'white', shade:'light'}, 21024 {name:'red', shade:'dark'}, 21025 {name:'blue', shade:'dark'}, 21026 {name:'yellow', shade:'light'} 21027 ]; 21028 $scope.color = $scope.colors[2]; // red 21029 } 21030 </script> 21031 <div ng-controller="MyCntrl"> 21032 <ul> 21033 <li ng-repeat="color in colors"> 21034 Name: <input ng-model="color.name"> 21035 [<a href ng-click="colors.splice($index, 1)">X</a>] 21036 </li> 21037 <li> 21038 [<a href ng-click="colors.push({})">add</a>] 21039 </li> 21040 </ul> 21041 <hr/> 21042 Color (null not allowed): 21043 <select ng-model="color" ng-options="c.name for c in colors"></select><br> 21044 21045 Color (null allowed): 21046 <span class="nullable"> 21047 <select ng-model="color" ng-options="c.name for c in colors"> 21048 <option value="">-- choose color --</option> 21049 </select> 21050 </span><br/> 21051 21052 Color grouped by shade: 21053 <select ng-model="color" ng-options="c.name group by c.shade for c in colors"> 21054 </select><br/> 21055 21056 21057 Select <a href ng-click="color={name:'not in list'}">bogus</a>.<br> 21058 <hr/> 21059 Currently selected: {{ {selected_color:color} }} 21060 <div style="border:solid 1px black; height:20px" 21061 ng-style="{'background-color':color.name}">
21062 </div> 21063 </div> 21064 </file> 21065 <file name="protractor.js" type="protractor"> 21066 it('should check ng-options', function() { 21067 expect(element(by.binding('{selected_color:color}')).getText()).toMatch('red'); 21068 element.all(by.select('color')).first().click(); 21069 element.all(by.css('select[ng-model="color"] option')).first().click(); 21070 expect(element(by.binding('{selected_color:color}')).getText()).toMatch('black'); 21071 element(by.css('.nullable select[ng-model="color"]')).click(); 21072 element.all(by.css('.nullable select[ng-model="color"] option')).first().click(); 21073 expect(element(by.binding('{selected_color:color}')).getText()).toMatch('null'); 21074 }); 21075 </file> 21076 </example> 21077 */ 21078 21079 var ngOptionsDirective = valueFn({ terminal: true }); 21080// jshint maxlen: false 21081 var selectDirective = ['$compile', '$parse', function ($compile, $parse) { 21082 //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 21083 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]+?))?$/, 21084 nullModelCtrl = {$setViewValue: noop}; 21085// jshint maxlen: 100 21086 21087 return { 21088 restrict: 'E', 21089 require: ['select', '?ngModel'], 21090 controller: ['$element', '$scope', '$attrs', function ($element, $scope, $attrs) { 21091 var self = this, 21092 optionsMap = {}, 21093 ngModelCtrl = nullModelCtrl, 21094 nullOption, 21095 unknownOption; 21096 21097 21098 self.databound = $attrs.ngModel; 21099 21100 21101 self.init = function (ngModelCtrl_, nullOption_, unknownOption_) { 21102 ngModelCtrl = ngModelCtrl_; 21103 nullOption = nullOption_; 21104 unknownOption = unknownOption_; 21105 }; 21106 21107 21108 self.addOption = function (value) { 21109 assertNotHasOwnProperty(value, '"option value"'); 21110 optionsMap[value] = true; 21111 21112 if (ngModelCtrl.$viewValue == value) { 21113 $element.val(value); 21114 if (unknownOption.parent()) unknownOption.remove(); 21115 } 21116 }; 21117 21118 21119 self.removeOption = function (value) { 21120 if (this.hasOption(value)) { 21121 delete optionsMap[value]; 21122 if (ngModelCtrl.$viewValue == value) { 21123 this.renderUnknownOption(value); 21124 } 21125 } 21126 }; 21127 21128 21129 self.renderUnknownOption = function (val) { 21130 var unknownVal = '? ' + hashKey(val) + ' ?'; 21131 unknownOption.val(unknownVal); 21132 $element.prepend(unknownOption); 21133 $element.val(unknownVal); 21134 unknownOption.prop('selected', true); // needed for IE 21135 }; 21136 21137 21138 self.hasOption = function (value) { 21139 return optionsMap.hasOwnProperty(value); 21140 }; 21141 21142 $scope.$on('$destroy', function () { 21143 // disable unknown option so that we don't do work when the whole select is being destroyed 21144 self.renderUnknownOption = noop; 21145 }); 21146 }], 21147 21148 link: function (scope, element, attr, ctrls) { 21149 // if ngModel is not defined, we don't need to do anything 21150 if (!ctrls[1]) return; 21151 21152 var selectCtrl = ctrls[0], 21153 ngModelCtrl = ctrls[1], 21154 multiple = attr.multiple, 21155 optionsExp = attr.ngOptions, 21156 nullOption = false, // if false, user will not be able to select it (used by ngOptions) 21157 emptyOption, 21158 // we can't just jqLite('<option>') since jqLite is not smart enough 21159 // to create it in <select> and IE barfs otherwise. 21160 optionTemplate = jqLite(document.createElement('option')), 21161 optGroupTemplate = jqLite(document.createElement('optgroup')), 21162 unknownOption = optionTemplate.clone(); 21163 21164 // find "null" option 21165 for (var i = 0, children = element.children(), ii = children.length; i < ii; i++) { 21166 if (children[i].value === '') { 21167 emptyOption = nullOption = children.eq(i); 21168 break; 21169 } 21170 } 21171 21172 selectCtrl.init(ngModelCtrl, nullOption, unknownOption); 21173 21174 // required validator 21175 if (multiple) { 21176 ngModelCtrl.$isEmpty = function (value) { 21177 return !value || value.length === 0; 21178 }; 21179 } 21180 21181 if (optionsExp) setupAsOptions(scope, element, ngModelCtrl); 21182 else if (multiple) setupAsMultiple(scope, element, ngModelCtrl); 21183 else setupAsSingle(scope, element, ngModelCtrl, selectCtrl); 21184 21185
21186 //////////////////////////// 21187 21188 21189 function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) { 21190 ngModelCtrl.$render = function () { 21191 var viewValue = ngModelCtrl.$viewValue; 21192 21193 if (selectCtrl.hasOption(viewValue)) { 21194 if (unknownOption.parent()) unknownOption.remove(); 21195 selectElement.val(viewValue); 21196 if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy 21197 } else { 21198 if (isUndefined(viewValue) && emptyOption) { 21199 selectElement.val(''); 21200 } else { 21201 selectCtrl.renderUnknownOption(viewValue); 21202 } 21203 } 21204 }; 21205 21206 selectElement.on('change', function () { 21207 scope.$apply(function () { 21208 if (unknownOption.parent()) unknownOption.remove(); 21209 ngModelCtrl.$setViewValue(selectElement.val()); 21210 }); 21211 }); 21212 } 21213 21214 function setupAsMultiple(scope, selectElement, ctrl) { 21215 var lastView; 21216 ctrl.$render = function () { 21217 var items = new HashMap(ctrl.$viewValue); 21218 forEach(selectElement.find('option'), function (option) { 21219 option.selected = isDefined(items.get(option.value)); 21220 }); 21221 }; 21222 21223 // we have to do it on each watch since ngModel watches reference, but 21224 // we need to work of an array, so we need to see if anything was inserted/removed 21225 scope.$watch(function selectMultipleWatch() { 21226 if (!equals(lastView, ctrl.$viewValue)) { 21227 lastView = copy(ctrl.$viewValue); 21228 ctrl.$render(); 21229 } 21230 }); 21231 21232 selectElement.on('change', function () { 21233 scope.$apply(function () { 21234 var array = []; 21235 forEach(selectElement.find('option'), function (option) { 21236 if (option.selected) { 21237 array.push(option.value); 21238 } 21239 }); 21240 ctrl.$setViewValue(array); 21241 }); 21242 }); 21243 } 21244 21245 function setupAsOptions(scope, selectElement, ctrl) { 21246 var match; 21247 21248 if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) { 21249 throw ngOptionsMinErr('iexp', 21250 "Expected expression in form of " + 21251 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 21252 " but got '{0}'. Element: {1}", 21253 optionsExp, startingTag(selectElement)); 21254 } 21255 21256 var displayFn = $parse(match[2] || match[1]), 21257 valueName = match[4] || match[6], 21258 keyName = match[5], 21259 groupByFn = $parse(match[3] || ''), 21260 valueFn = $parse(match[2] ? match[1] : valueName), 21261 valuesFn = $parse(match[7]), 21262 track = match[8], 21263 trackFn = track ? $parse(match[8]) : null, 21264 // This is an array of array of existing option groups in DOM. 21265 // We try to reuse these if possible 21266 // - optionGroupsCache[0] is the options with no option group 21267 // - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element 21268 optionGroupsCache = [ 21269 [ 21270 {element: selectElement, label: ''} 21271 ] 21272 ]; 21273 21274 if (nullOption) { 21275 // compile the element since there might be bindings in it 21276 $compile(nullOption)(scope); 21277 21278 // remove the class, which is added automatically because we recompile the element and it 21279 // becomes the compilation root 21280 nullOption.removeClass('ng-scope'); 21281 21282 // we need to remove it before calling selectElement.empty() because otherwise IE will 21283 // remove the label from the element. wtf? 21284 nullOption.remove(); 21285 } 21286 21287 // clear contents, we'll add what's needed based on the model 21288 selectElement.empty(); 21289 21290 selectElement.on('change', function () { 21291 scope.$apply(function () { 21292 var optionGroup, 21293 collection = valuesFn(scope) || [], 21294 locals = {}, 21295 key, value, optionElement, index, groupIndex, length, groupLength, trackIndex; 21296 21297 if (multiple) { 21298 value = []; 21299 for (groupIndex = 0, groupLength = optionGroupsCache.length; 21300 groupIndex < groupLength; 21301 groupIndex++) { 21302 // list of options for that group. (first item has the parent) 21303 optionGroup = optionGroupsCache[groupIndex]; 21304 21305 for (index = 1, length = optionGroup.length; index < length; index++) { 21306 if ((optionElement = optionGroup[index].element)[0].selected) { 21307 key = optionElement.val(); 21308 if (keyName) locals[keyName] = key; 21309 if (trackFn) { 21310 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 21311 locals[valueName] = collection[trackIndex]; 21312 if (trackFn(scope, locals) == key) break; 21313 } 21314 } else { 21315 locals[valueName] = collection[key]; 21316 } 21317 value.push(valueFn(scope, locals)); 21318 } 21319 } 21320 } 21321 } else { 21322 key = selectElement.val(); 21323 if (key == '?') { 21324 value = undefined; 21325 } else if (key === '') { 21326 value = null; 21327 } else { 21328 if (trackFn) { 21329 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 21330 locals[valueName] = collection[trackIndex]; 21331 if (trackFn(scope, locals) == key) { 21332 value = valueFn(scope, locals); 21333 break; 21334 } 21335 } 21336 } else { 21337 locals[valueName] = collection[key]; 21338 if (keyName) locals[keyName] = key; 21339 value = valueFn(scope, locals); 21340 } 21341 } 21342 // Update the null option's selected property here so $render cleans it up correctly 21343 if (optionGroupsCache[0].length > 1) { 21344 if (optionGroupsCache[0][1].id !== key) { 21345 optionGroupsCache[0][1].selected = false;
21346 } 21347 } 21348 } 21349 ctrl.$setViewValue(value); 21350 }); 21351 }); 21352 21353 ctrl.$render = render; 21354 21355 // TODO(vojta): can't we optimize this ? 21356 scope.$watch(render); 21357 21358 function render() { 21359 // Temporary location for the option groups before we render them 21360 var optionGroups = {'': []}, 21361 optionGroupNames = [''], 21362 optionGroupName, 21363 optionGroup, 21364 option, 21365 existingParent, existingOptions, existingOption, 21366 modelValue = ctrl.$modelValue, 21367 values = valuesFn(scope) || [], 21368 keys = keyName ? sortedKeys(values) : values, 21369 key, 21370 groupLength, length, 21371 groupIndex, index, 21372 locals = {}, 21373 selected, 21374 selectedSet = false, // nothing is selected yet 21375 lastElement, 21376 element, 21377 label; 21378 21379 if (multiple) { 21380 if (trackFn && isArray(modelValue)) { 21381 selectedSet = new HashMap([]); 21382 for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) { 21383 locals[valueName] = modelValue[trackIndex]; 21384 selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]); 21385 } 21386 } else { 21387 selectedSet = new HashMap(modelValue); 21388 } 21389 } 21390 21391 // We now build up the list of options we need (we merge later) 21392 for (index = 0; length = keys.length, index < length; index++) { 21393 21394 key = index; 21395 if (keyName) { 21396 key = keys[index]; 21397 if (key.charAt(0) === '$') continue; 21398 locals[keyName] = key; 21399 } 21400 21401 locals[valueName] = values[key]; 21402 21403 optionGroupName = groupByFn(scope, locals) || ''; 21404 if (!(optionGroup = optionGroups[optionGroupName])) { 21405 optionGroup = optionGroups[optionGroupName] = []; 21406 optionGroupNames.push(optionGroupName); 21407 } 21408 if (multiple) { 21409 selected = isDefined( 21410 selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals)) 21411 ); 21412 } else { 21413 if (trackFn) { 21414 var modelCast = {}; 21415 modelCast[valueName] = modelValue; 21416 selected = trackFn(scope, modelCast) === trackFn(scope, locals); 21417 } else { 21418 selected = modelValue === valueFn(scope, locals); 21419 } 21420 selectedSet = selectedSet || selected; // see if at least one item is selected 21421 } 21422 label = displayFn(scope, locals); // what will be seen by the user 21423 21424 // doing displayFn(scope, locals) || '' overwrites zero values 21425 label = isDefined(label) ? label : ''; 21426 optionGroup.push({ 21427 // either the index into array or key from object 21428 id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index), 21429 label: label, 21430 selected: selected // determine if we should be selected 21431 }); 21432 } 21433 if (!multiple) { 21434 if (nullOption || modelValue === null) { 21435 // insert null option if we have a placeholder, or the model is null 21436 optionGroups[''].unshift({id: '', label: '', selected: !selectedSet}); 21437 } else if (!selectedSet) { 21438 // option could not be found, we have to insert the undefined item 21439 optionGroups[''].unshift({id: '?', label: '', selected: true}); 21440 } 21441 } 21442 21443 // Now we need to update the list of DOM nodes to match the optionGroups we computed above 21444 for (groupIndex = 0, groupLength = optionGroupNames.length; 21445 groupIndex < groupLength; 21446 groupIndex++) { 21447 // current option group name or '' if no group 21448 optionGroupName = optionGroupNames[groupIndex]; 21449 21450 // list of options for that group. (first item has the parent) 21451 optionGroup = optionGroups[optionGroupName]; 21452 21453 if (optionGroupsCache.length <= groupIndex) { 21454 // we need to grow the optionGroups 21455 existingParent = { 21456 element: optGroupTemplate.clone().attr('label', optionGroupName), 21457 label: optionGroup.label 21458 }; 21459 existingOptions = [existingParent]; 21460 optionGroupsCache.push(existingOptions); 21461 selectElement.append(existingParent.element); 21462 } else { 21463 existingOptions = optionGroupsCache[groupIndex]; 21464 existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element 21465 21466 // update the OPTGROUP label if not the same. 21467 if (existingParent.label != optionGroupName) { 21468 existingParent.element.attr('label', existingParent.label = optionGroupName); 21469 } 21470 } 21471 21472 lastElement = null; // start at the beginning 21473 for (index = 0, length = optionGroup.length; index < length; index++) { 21474 option = optionGroup[index]; 21475 if ((existingOption = existingOptions[index + 1])) { 21476 // reuse elements 21477 lastElement = existingOption.element; 21478 if (existingOption.label !== option.label) { 21479 lastElement.text(existingOption.label = option.label); 21480 } 21481 if (existingOption.id !== option.id) { 21482 lastElement.val(existingOption.id = option.id); 21483 } 21484 // lastElement.prop('selected') provided by jQuery has side-effects 21485 if (existingOption.selected !== option.selected) { 21486 lastElement.prop('selected',
21486(existingOption.selected = option.selected)); 21487 } 21488 } else { 21489 // grow elements 21490 21491 // if it's a null option 21492 if (option.id === '' && nullOption) { 21493 // put back the pre-compiled element 21494 element = nullOption; 21495 } else { 21496 // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but 21497 // in this version of jQuery on some browser the .text() returns a string 21498 // rather then the element. 21499 (element = optionTemplate.clone()) 21500 .val(option.id) 21501 .attr('selected', option.selected) 21502 .text(option.label); 21503 } 21504 21505 existingOptions.push(existingOption = { 21506 element: element, 21507 label: option.label, 21508 id: option.id, 21509 selected: option.selected 21510 }); 21511 if (lastElement) { 21512 lastElement.after(element); 21513 } else { 21514 existingParent.element.append(element); 21515 } 21516 lastElement = element; 21517 } 21518 } 21519 // remove any excessive OPTIONs in a group 21520 index++; // increment since the existingOptions[0] is parent element not OPTION 21521 while (existingOptions.length > index) { 21522 existingOptions.pop().element.remove(); 21523 } 21524 } 21525 // remove any excessive OPTGROUPs from select 21526 while (optionGroupsCache.length > groupIndex) { 21527 optionGroupsCache.pop()[0].element.remove(); 21528 } 21529 } 21530 } 21531 } 21532 }; 21533 }]; 21534 21535 var optionDirective = ['$interpolate', function ($interpolate) { 21536 var nullSelectCtrl = { 21537 addOption: noop, 21538 removeOption: noop 21539 }; 21540 21541 return { 21542 restrict: 'E', 21543 priority: 100, 21544 compile: function (element, attr) { 21545 if (isUndefined(attr.value)) { 21546 var interpolateFn = $interpolate(element.text(), true); 21547 if (!interpolateFn) { 21548 attr.$set('value', element.text()); 21549 } 21550 } 21551 21552 return function (scope, element, attr) { 21553 var selectCtrlName = '$selectController', 21554 parent = element.parent(), 21555 selectCtrl = parent.data(selectCtrlName) || 21556 parent.parent().data(selectCtrlName); // in case we are in optgroup 21557 21558 if (selectCtrl && selectCtrl.databound) { 21559 // For some reason Opera defaults to true and if not overridden this messes up the repeater. 21560 // We don't want the view to drive the initialization of the model anyway. 21561 element.prop('selected', false); 21562 } else { 21563 selectCtrl = nullSelectCtrl; 21564 } 21565 21566 if (interpolateFn) { 21567 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 21568 attr.$set('value', newVal); 21569 if (newVal !== oldVal) selectCtrl.removeOption(oldVal); 21570 selectCtrl.addOption(newVal); 21571 }); 21572 } else { 21573 selectCtrl.addOption(attr.value); 21574 } 21575 21576 element.on('$destroy', function () { 21577 selectCtrl.removeOption(attr.value); 21578 }); 21579 }; 21580 } 21581 }; 21582 }]; 21583 21584 var styleDirective = valueFn({ 21585 restrict: 'E', 21586 terminal: true 21587 }); 21588 21589 if (window.angular.bootstrap) { 21590 //AngularJS is already loaded, so we can return here... 21591 console.log('WARNING: Tried to load angular more than once.'); 21592 return; 21593 } 21594 21595 //try to bind to jquery now so that one can write angular.element().read() 21596 //but we will rebind on bootstrap again. 21597 bindJQuery(); 21598 21599 publishExternalAPI(angular); 21600 21601 jqLite(document).ready(function () { 21602 angularInit(document, bootstrap); 21603 }); 21604 21605})(window, document); 21606 21607!angular.$$csp() && angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}.ng-animate-block-transitions{transition:0s all!important;-webkit-transition:0s all!important;}</style>');
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.