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) {'use strict'; 7 8/** 9 * @description 10 * 11 * This object provides a utility for producing rich Error messages within 12 * Angular. It can be called as follows: 13 * 14 * var exampleMinErr = minErr('example'); 15 * throw exampleMinErr('one', 'This {0} is {1}', foo, bar); 16 * 17 * The above creates an instance of minErr in the example namespace. The 18 * resulting error will have a namespaced error code of example.one. The 19 * resulting error will replace {0} with the value of foo, and {1} with the 20 * value of bar. The object is not restricted in the number of arguments it can 21 * take. 22 * 23 * If fewer arguments are specified than necessary for interpolation, the extra 24 * interpolation markers will be preserved in the final string. 25 * 26 * Since data will be parsed statically during a build step, some restrictions 27 * are applied with respect to how minErr instances are created and called. 28 * Instances should have names of the form namespaceMinErr for a minErr created 29 * using minErr('namespace') . Error codes, namespaces and template strings 30 * should all be static strings, not variables or general expressions. 31 * 32 * @param {string} module The namespace to use for the new minErr instance. 33 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 34 */ 35 36function minErr(module) { 37 return function () { 38 var code = arguments[0], 39 prefix = '[' + (module ? module + ':' : '') + code + '] ', 40 template = arguments[1], 41 templateArgs = arguments, 42 stringify = function (obj) { 43 if (typeof obj === 'function') { 44 return obj.toString().replace(/ \{[\s\S]*$/, ''); 45 } else if (typeof obj === 'undefined') { 46 return 'undefined'; 47 } else if (typeof obj !== 'string') { 48 return JSON.stringify(obj); 49 } 50 return obj; 51 }, 52 message, i; 53 54 message = prefix + template.replace(/\{\d+\}/g, function (match) { 55 var index = +match.slice(1, -1), arg; 56 57 if (index + 2 < templateArgs.length) { 58 arg = templateArgs[index + 2]; 59 if (typeof arg === 'function') { 60 return arg.toString().replace(/ ?\{[\s\S]*$/, ''); 61 } else if (typeof arg === 'undefined') { 62 return 'undefined'; 63 } else if (typeof arg !== 'string') { 64 return toJson(arg); 65 } 66 return arg; 67 } 68 return match; 69 }); 70 71 message = message + '\nhttp://errors.angularjs.org/1.2.16/' + 72 (module ? module + '/' : '') + code; 73 for (i = 2; i < arguments.length; i++) { 74 message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + 75 encodeURIComponent(stringify(arguments[i])); 76 } 77 78 return new Error(message); 79 }; 80} 81 82/* We need to tell jshint what variables are being exported */ 83/* global 84 -angular, 85 -msie, 86 -jqLite, 87 -jQuery, 88 -slice, 89 -push, 90 -toString, 91 -ngMinErr, 92 -_angular, 93 -angularModule, 94 -nodeName_, 95 -uid, 96 97 -lowercase, 98 -uppercase, 99 -manualLowercase, 100 -manualUppercase, 101 -nodeName_, 102 -isArrayLike, 103 -forEach, 104 -sortedKeys, 105 -forEachSorted, 106 -reverseParams, 107 -nextUid, 108 -setHashKey, 109 -extend, 110 -int, 111 -inherit, 112 -noop, 113 -identity, 114 -valueFn, 115 -isUndefined, 116 -isDefined, 117 -isObject, 118 -isString, 119 -isNumber, 120 -isDate, 121 -isArray, 122 -isFunction, 123 -isRegExp, 124 -isWindow, 125 -isScope, 126 -isFile, 127 -isBlob, 128 -isBoolean, 129 -trim, 130 -isElement, 131 -makeMap, 132 -map, 133 -size, 134 -includes, 135 -indexOf, 136 -arrayRemove, 137 -isLeafNode, 138 -copy, 139 -shallowCopy, 140 -equals, 141 -csp, 142 -concat, 143 -sliceArgs, 144 -bind, 145 -toJsonReplacer, 146 -toJson, 147 -fromJson, 148 -toBoolean, 149 -startingTag, 150 -tryDecodeURIComponent, 151 -parseKeyValue, 152 -toKeyValue, 153 -encodeUriSegment, 154 -encodeUriQuery, 155 -angularInit, 156 -bootstrap, 157 -snake_case, 158 -bindJQuery, 159 -assertArg, 160 -assertArgFn,
161 -assertNotHasOwnProperty, 162 -getter, 163 -getBlockElements, 164 -hasOwnProperty, 165 166*/ 167 168//////////////////////////////////// 169 170/** 171 * @ngdoc module 172 * @name ng 173 * @module ng 174 * @description 175 * 176 * # ng (core module) 177 * The ng module is loaded by default when an AngularJS application is started. The module itself 178 * contains the essential components for an AngularJS application to function. The table below 179 * lists a high level breakdown of each of the services/factories, filters, directives and testing 180 * components available within this core module. 181 * 182 * <div doc-module-components="ng"></div> 183 */ 184 185/** 186 * @ngdoc function 187 * @name angular.lowercase 188 * @module ng 189 * @function 190 * 191 * @description Converts the specified string to lowercase. 192 * @param {string} string String to be converted to lowercase. 193 * @returns {string} Lowercased string. 194 */ 195var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;}; 196var hasOwnProperty = Object.prototype.hasOwnProperty; 197 198/** 199 * @ngdoc function 200 * @name angular.uppercase 201 * @module ng 202 * @function 203 * 204 * @description Converts the specified string to uppercase. 205 * @param {string} string String to be converted to uppercase. 206 * @returns {string} Uppercased string. 207 */ 208var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;}; 209 210 211var manualLowercase = function(s) { 212 /* jshint bitwise: false */ 213 return isString(s) 214 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 215 : s; 216}; 217var manualUppercase = function(s) { 218 /* jshint bitwise: false */ 219 return isString(s) 220 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 221 : s; 222}; 223 224 225// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 226// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 227// with correct but slower alternatives. 228if ('i' !== 'I'.toLowerCase()) { 229 lowercase = manualLowercase; 230 uppercase = manualUppercase; 231} 232 233 234var /** holds major version number for IE or NaN for real browsers */ 235 msie, 236 jqLite, // delay binding since jQuery could be loaded after us. 237 jQuery, // delay binding 238 slice = [].slice, 239 push = [].push, 240 toString = Object.prototype.toString, 241 ngMinErr = minErr('ng'), 242 243 244 _angular = window.angular, 245 /** @name angular */ 246 angular = window.angular || (window.angular = {}), 247 angularModule, 248 nodeName_, 249 uid = ['0', '0', '0']; 250 251/** 252 * IE 11 changed the format of the UserAgent string. 253 * See http://msdn.microsoft.com/en-us/library/ms537503.aspx 254 */ 255msie = int((/msie (\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); 256if (isNaN(msie)) { 257 msie = int((/trident\/.*; rv:(\d+)/.exec(lowercase(navigator.userAgent)) || [])[1]); 258} 259 260 261/** 262 * @private 263 * @param {*} obj 264 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 265 * String ...) 266 */ 267function isArrayLike(obj) { 268 if (obj == null || isWindow(obj)) { 269 return false; 270 } 271 272 var length = obj.length; 273 274 if (obj.nodeType === 1 && length) { 275 return true; 276 } 277 278 return isString(obj) || isArray(obj) || length === 0 || 279 typeof length === 'number' && length > 0 && (length - 1) in obj; 280} 281 282/** 283 * @ngdoc function 284 * @name angular.forEach 285 * @module ng 286 * @function 287 * 288 * @description 289 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 290 * object or an array. The `iterator` function is invoked with `iterator(value, key)`, where `value` 291 * is the value of an object property or an array element and `key` is the object property key or 292 * array element index. Specifying a `context` for the function is optional. 293 * 294 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 295 * using the `hasOwnProperty` method. 296 * 297 ```js 298 var values = {name: 'misko', gender: 'male'}; 299 var log = []; 300 angular.forEach(values, function(value, key){ 301 this.push(key + ': ' + value); 302 }, log); 303 expect(log).toEqual(['name: misko', 'gender: male']); 304 ``` 305 * 306 * @param {Object|Array} obj Object to iterate over. 307 * @param {Function} iterator Iterator function. 308 * @param {Object=} context Object to become context (`this`) for the iterator function. 309 * @returns {Object|Array} Reference to `obj`. 310 */ 311function forEach(obj, iterator, context) { 312 var key; 313 if (obj) { 314 if (isFunction(obj)){ 315 for (key in obj) { 316 // Need to check if hasOwnProperty exists, 317 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 318 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 319 iterator.call(context, obj[key], key); 320 } 321 } 322 }
322 else if (obj.forEach && obj.forEach !== forEach) { 323 obj.forEach(iterator, context); 324 } else if (isArrayLike(obj)) { 325 for (key = 0; key < obj.length; key++) 326 iterator.call(context, obj[key], key); 327 } else { 328 for (key in obj) { 329 if (obj.hasOwnProperty(key)) { 330 iterator.call(context, obj[key], key); 331 } 332 } 333 } 334 } 335 return obj; 336} 337 338function sortedKeys(obj) { 339 var keys = []; 340 for (var key in obj) { 341 if (obj.hasOwnProperty(key)) { 342 keys.push(key); 343 } 344 } 345 return keys.sort(); 346} 347 348function forEachSorted(obj, iterator, context) { 349 var keys = sortedKeys(obj); 350 for ( var i = 0; i < keys.length; i++) { 351 iterator.call(context, obj[keys[i]], keys[i]); 352 } 353 return keys; 354} 355 356 357/** 358 * when using forEach the params are value, key, but it is often useful to have key, value. 359 * @param {function(string, *)} iteratorFn 360 * @returns {function(*, string)} 361 */ 362function reverseParams(iteratorFn) { 363 return function(value, key) { iteratorFn(key, value); }; 364} 365 366/** 367 * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric 368 * characters such as '012ABC'. The reason why we are not using simply a number counter is that 369 * the number string gets longer over time, and it can also overflow, where as the nextId 370 * will grow much slower, it is a string, and it will never overflow. 371 * 372 * @returns {string} an unique alpha-numeric string 373 */ 374function nextUid() { 375 var index = uid.length; 376 var digit; 377 378 while(index) { 379 index--;
380 digit = uid[index].charCodeAt(0); 381 if (digit == 57 /*'9'*/) { 382 uid[index] = 'A'; 383 return uid.join(''); 384 } 385 if (digit == 90 /*'Z'*/) { 386 uid[index] = '0'; 387 } else { 388 uid[index] = String.fromCharCode(digit + 1); 389 return uid.join(''); 390 } 391 } 392 uid.unshift('0'); 393 return uid.join(''); 394} 395 396 397/** 398 * Set or clear the hashkey for an object. 399 * @param obj object 400 * @param h the hashkey (!truthy to delete the hashkey) 401 */ 402function setHashKey(obj, h) { 403 if (h) { 404 obj.$$hashKey = h; 405 } 406 else { 407 delete obj.$$hashKey; 408 } 409} 410 411/** 412 * @ngdoc function 413 * @name angular.extend 414 * @module ng 415 * @function 416 * 417 * @description 418 * Extends the destination object `dst` by copying all of the properties from the `src` object(s) 419 * to `dst`. You can specify multiple `src` objects. 420 * 421 * @param {Object} dst Destination object. 422 * @param {...Object} src Source object(s). 423 * @returns {Object} Reference to `dst`. 424 */ 425function extend(dst) { 426 var h = dst.$$hashKey; 427 forEach(arguments, function(obj){ 428 if (obj !== dst) { 429 forEach(obj, function(value, key){ 430 dst[key] = value; 431 }); 432 } 433 }); 434 435 setHashKey(dst,h); 436 return dst; 437} 438 439function int(str) { 440 return parseInt(str, 10); 441} 442 443 444function inherit(parent, extra) { 445 return extend(new (extend(function() {}, {prototype:parent}))(), extra); 446} 447 448/** 449 * @ngdoc function 450 * @name angular.noop 451 * @module ng 452 * @function 453 * 454 * @description 455 * A function that performs no operations. This function can be useful when writing code in the 456 * functional style. 457 ```js 458 function foo(callback) { 459 var result = calculateResult(); 460 (callback || angular.noop)(result); 461 } 462 ``` 463 */ 464function noop() {} 465noop.$inject = []; 466 467 468/** 469 * @ngdoc function 470 * @name angular.identity 471 * @module ng 472 * @function 473 * 474 * @description 475 * A function that returns its first argument. This function is useful when writing code in the 476 * functional style. 477 * 478 ```js 479 function transformer(transformationFn, value) { 480 return (transformationFn || angular.identity)(value); 481 }; 482 ``` 483 */ 484function identity($) {return $;} 485identity.$inject = []; 486 487 488function valueFn(value) {return function() {return value;};} 489 490/** 491 * @ngdoc function 492 * @name angular.isUndefined 493 * @module ng 494 * @function 495 * 496 * @description 497 * Determines if a reference is undefined. 498 * 499 * @param {*} value Reference to check. 500 * @returns {boolean} True if `value` is undefined. 501 */ 502function isUndefined(value){return typeof value === 'undefined';} 503 504 505/** 506 * @ngdoc function 507 * @name angular.isDefined 508 * @module ng 509 * @function 510 * 511 * @description 512 * Determines if a reference is defined. 513 * 514 * @param {*} value Reference to check. 515 * @returns {boolean} True if `value` is defined. 516 */ 517function isDefined(value){return typeof value !== 'undefined';} 518 519 520/** 521 * @ngdoc function 522 * @name angular.isObject 523 * @module ng 524 * @function 525 * 526 * @description 527 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 528 * considered to be objects. Note that JavaScript arrays are objects. 529 * 530 * @param {*} value Reference to check. 531 * @returns {boolean} True if `value` is an `Object` but not `null`. 532 */ 533function isObject(value){return value != null && typeof value === 'object';} 534 535 536/** 537 * @ngdoc function 538 * @name angular.isString 539 * @module ng 540 * @function 541 * 542 * @description 543 * Determines if a reference is a `String`. 544 * 545 * @param {*} value Reference to check. 546 * @returns {boolean} True if `value` is a `String`. 547 */ 548function isString(value){return typeof value === 'string';} 549 550 551/** 552 * @ngdoc function 553 * @name angular.isNumber 554 * @module ng 555 * @function 556 * 557 * @description 558 * Determines if a reference is a `Number`. 559 * 560 * @param {*} value Reference to check. 561 * @returns {boolean} True if `value` is a `Number`. 562 */ 563function isNumber(value){return typeof value === 'number';} 564 565 566/** 567 * @ngdoc function 568 * @name angular.isDate 569 * @module ng 570 * @function 571 * 572 * @description 573 * Determines if a value is a date. 574 * 575 * @param {*} value Reference to check. 576 * @returns {boolean} True if `value` is a `Date`. 577 */ 578function isDate(value){ 579 return toString.call(value) === '[object Date]'; 580} 581 582 583/** 584 * @ngdoc function 585 * @name angular.isArray 586 * @module ng 587 * @function 588 * 589 * @description 590 * Determines if a reference is an `Array`. 591 * 592 * @param {*} value Reference to check. 593 * @returns {boolean} True if `value` is an `Array`. 594 */ 595function isArray(value) { 596 return toString.call(value) === '[object Array]'; 597} 598 599 600/** 601 * @ngdoc function 602 * @name angular.isFunction 603 * @module ng 604 * @function 605 * 606 * @description 607 * Determines if a reference is a `Function`. 608 * 609 * @param {*} value Reference to check. 610 * @returns {boolean} True if `value` is a `Function`. 611 */ 612function isFunction(value){return typeof value === 'function';} 613 614 615/** 616 * Determines if a value is a regular expression object. 617 * 618 * @private 619 * @param {*} value Reference to check. 620 * @returns {boolean} True if `value` is a `RegExp`. 621 */ 622function isRegExp(value) { 623 return toString.call(value) === '[object RegExp]'; 624} 625 626 627/** 628 * Checks if `obj` is a window object. 629 * 630 * @private 631 * @param {*} obj Object to check 632 * @returns {boolean} True if `obj` is a window obj. 633 */ 634function isWindow(obj) { 635 return obj && obj.document && obj.location && obj.alert && obj.setInterval; 636} 637 638 639function isScope(obj) { 640 return obj && obj.$evalAsync && obj.$watch; 641} 642 643 644function isFile(obj) { 645 return toString.call(obj) === '[object File]'; 646} 647 648 649function isBlob(obj) { 650 return toString.call(obj) === '[object Blob]'; 651} 652 653 654function isBoolean(value) { 655 return typeof value === 'boolean'; 656} 657 658 659var trim = (function() { 660 // native trim is way faster: http://jsperf.com/angular-trim-test 661 // but IE doesn't have it... :-( 662 // TODO: we should move this into IE/ES5 polyfill 663 if (!String.prototype.trim) { 664 return function(value) { 665 return isString(value) ? value.replace(/^\s\s*/, '').replace(/\s\s*$/, '') : value; 666 }; 667 } 668 return function(value) { 669 return isString(value) ? value.trim() : value; 670 }; 671})(); 672 673 674/** 675 * @ngdoc function 676 * @name angular.isElement 677 * @module ng 678 * @function 679 * 680 * @description 681 * Determines if a reference is a DOM element (or wrapped jQuery element). 682 * 683 * @param {*} value Reference to check. 684 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 685 */ 686function isElement(node) { 687 return !!(node && 688 (node.nodeName // we are a direct element 689 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 690} 691 692/** 693 * @param str 'key1,key2,...' 694 * @returns {object} in the form of {key1:true, key2:true, ...} 695 */ 696function makeMap(str){ 697 var obj = {}, items = str.split(","), i; 698 for ( i = 0; i < items.length; i++ ) 699 obj[ items[i] ] = true; 700 return obj;
701} 702 703 704if (msie < 9) { 705 nodeName_ = function(element) { 706 element = element.nodeName ? element : element[0]; 707 return (element.scopeName && element.scopeName != 'HTML') 708 ? uppercase(element.scopeName + ':' + element.nodeName) : element.nodeName; 709 }; 710} else { 711 nodeName_ = function(element) { 712 return element.nodeName ? element.nodeName : element[0].nodeName; 713 }; 714} 715 716 717function map(obj, iterator, context) { 718 var results = []; 719 forEach(obj, function(value, index, list) { 720 results.push(iterator.call(context, value, index, list)); 721 }); 722 return results; 723} 724 725 726/** 727 * @description 728 * Determines the number of elements in an array, the number of properties an object has, or 729 * the length of a string. 730 * 731 * Note: This function is used to augment the Object type in Angular expressions. See 732 * {@link angular.Object} for more information about Angular arrays. 733 * 734 * @param {Object|Array|string} obj Object, array, or string to inspect. 735 * @param {boolean} [ownPropsOnly=false] Count only "own" properties in an object 736 * @returns {number} The size of `obj` or `0` if `obj` is neither an object nor an array. 737 */ 738function size(obj, ownPropsOnly) { 739 var count = 0, key; 740 741 if (isArray(obj) || isString(obj)) { 742 return obj.length; 743 } else if (isObject(obj)){ 744 for (key in obj) 745 if (!ownPropsOnly || obj.hasOwnProperty(key)) 746 count++; 747 } 748 749 return count; 750} 751 752 753function includes(array, obj) { 754 return indexOf(array, obj) != -1; 755} 756 757function indexOf(array, obj) { 758 if (array.indexOf) return array.indexOf(obj); 759 760 for (var i = 0; i < array.length; i++) { 761 if (obj === array[i]) return i; 762 } 763 return -1; 764} 765 766function arrayRemove(array, value) { 767 var index = indexOf(array, value); 768 if (index >=0) 769 array.splice(index, 1); 770 return value; 771} 772 773function isLeafNode (node) { 774 if (node) { 775 switch (node.nodeName) { 776 case "OPTION": 777 case "PRE": 778 case "TITLE": 779 return true; 780 } 781 } 782 return false; 783} 784 785/** 786 * @ngdoc function 787 * @name angular.copy 788 * @module ng 789 * @function 790 * 791 * @description 792 * Creates a deep copy of `source`, which should be an object or an array. 793 * 794 * * If no destination is supplied, a copy of the object or array is created. 795 * * If a destination is provided, all of its elements (for array) or properties (for objects) 796 * are deleted and then all elements/properties from the source are copied to it. 797 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 798 * * If `source` is identical to 'destination' an exception will be thrown. 799 * 800 * @param {*} source The source that will be used to make a copy. 801 * Can be any type, including primitives, `null`, and `undefined`. 802 * @param {(Object|Array)=} destination Destination into which the source is copied. If 803 * provided, must be of the same type as `source`. 804 * @returns {*} The copy or updated `destination`, if `destination` was specified. 805 * 806 * @example 807 <example> 808 <file name="index.html"> 809 <div ng-controller="Controller"> 810 <form novalidate class="simple-form"> 811 Name: <input type="text" ng-model="user.name" /><br /> 812 E-mail: <input type="email" ng-model="user.email" /><br /> 813 Gender: <input type="radio" ng-model="user.gender" value="male" />male 814 <input type="radio" ng-model="user.gender" value="female" />female<br /> 815 <button ng-click="reset()">RESET</button> 816 <button ng-click="update(user)">SAVE</button> 817 </form> 818 <pre>form = {{user | json}}</pre> 819 <pre>master = {{master | json}}</pre> 820 </div> 821 822 <script> 823 function Controller($scope) { 824 $scope.master= {}; 825 826 $scope.update = function(user) { 827 // Example with 1 argument 828 $scope.master= angular.copy(user); 829 }; 830 831 $scope.reset = function() { 832 // Example with 2 arguments 833 angular.copy($scope.master, $scope.user); 834 }; 835 836 $scope.reset(); 837 } 838 </script> 839 </file> 840 </example> 841 */ 842function copy(source, destination){ 843 if (isWindow(source) || isScope(source)) { 844 throw ngMinErr('cpws',
845 "Can't copy! Making copies of Window or Scope instances is not supported."); 846 } 847 848 if (!destination) { 849 destination = source; 850 if (source) { 851 if (isArray(source)) { 852 destination = copy(source, []); 853 } else if (isDate(source)) { 854 destination = new Date(source.getTime()); 855 } else if (isRegExp(source)) { 856 destination = new RegExp(source.source); 857 } else if (isObject(source)) { 858 destination = copy(source, {}); 859 } 860 } 861 } else { 862 if (source === destination) throw ngMinErr('cpi', 863 "Can't copy! Source and destination are identical."); 864 if (isArray(source)) { 865 destination.length = 0; 866 for ( var i = 0; i < source.length; i++) { 867 destination.push(copy(source[i])); 868 } 869 } else { 870 var h = destination.$$hashKey; 871 forEach(destination, function(value, key){ 872 delete destination[key]; 873 }); 874 for ( var key in source) { 875 destination[key] = copy(source[key]); 876 } 877 setHashKey(destination,h); 878 } 879 } 880 return destination; 881} 882 883/** 884 * Create a shallow copy of an object 885 */ 886function shallowCopy(src, dst) { 887 dst = dst || {}; 888 889 for(var key in src) { 890 // shallowCopy is only ever called by $compile nodeLinkFn, which has control over src 891 // so we don't need to worry about using our custom hasOwnProperty here 892 if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { 893 dst[key] = src[key]; 894 } 895 } 896 897 return dst; 898} 899 900 901/** 902 * @ngdoc function 903 * @name angular.equals 904 * @module ng 905 * @function 906 * 907 * @description 908 * Determines if two objects or two values are equivalent. Supports value types, regular 909 * expressions, arrays and objects. 910 * 911 * Two objects or values are considered equivalent if at least one of the following is true: 912 * 913 * * Both objects or values pass `===` comparison. 914 * * Both objects or values are of the same type and all of their properties are equal by 915 * comparing them with `angular.equals`. 916 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 917 * * Both values represent the same regular expression (In JavasScript, 918 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 919 * representation matches). 920 * 921 * During a property comparison, properties of `function` type and properties with names 922 * that begin with `$` are ignored. 923 * 924 * Scope and DOMWindow objects are being compared only by identify (`===`). 925 * 926 * @param {*} o1 Object or value to compare. 927 * @param {*} o2 Object or value to compare. 928 * @returns {boolean} True if arguments are equal. 929 */ 930function equals(o1, o2) { 931 if (o1 === o2) return true; 932 if (o1 === null || o2 === null) return false; 933 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 934 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 935 if (t1 == t2) { 936 if (t1 == 'object') { 937 if (isArray(o1)) { 938 if (!isArray(o2)) return false; 939 if ((length = o1.length) == o2.length) { 940 for(key=0; key<length; key++) { 941 if (!equals(o1[key], o2[key])) return false; 942 } 943 return true; 944 } 945 } else if (isDate(o1)) { 946 return isDate(o2) && o1.getTime() == o2.getTime(); 947 } else if (isRegExp(o1) && isRegExp(o2)) { 948 return o1.toString() == o2.toString(); 949 } else { 950 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false; 951 keySet = {}; 952 for(key in o1) { 953 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 954 if (!equals(o1[key], o2[key])) return false; 955 keySet[key] = true; 956 } 957 for(key in o2) { 958 if (!keySet.hasOwnProperty(key) && 959 key.charAt(0) !== '$' && 960 o2[key] !== undefined && 961 !isFunction(o2[key])) return false; 962 } 963 return true; 964 } 965 } 966 } 967 return false; 968} 969 970 971function csp() { 972 return (document.securityPolicy && document.securityPolicy.isActive) || 973 (document.querySelector && 974 !!(document.querySelector('[ng-csp]') || document.querySelector('[data-ng-csp]'))); 975} 976 977 978function concat(array1, array2, index) { 979 return array1.concat(slice.call(array2, index)); 980} 981 982function sliceArgs(args, startIndex) { 983 return slice.call(args, startIndex || 0); 984} 985 986
987/* jshint -W101 */ 988/** 989 * @ngdoc function 990 * @name angular.bind 991 * @module ng 992 * @function 993 * 994 * @description 995 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 996 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 997 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 998 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 999 * 1000 * @param {Object} self Context which `fn` should be evaluated in. 1001 * @param {function()} fn Function to be bound. 1002 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1003 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1004 */ 1005/* jshint +W101 */ 1006function bind(self, fn) { 1007 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1008 if (isFunction(fn) && !(fn instanceof RegExp)) { 1009 return curryArgs.length 1010 ? function() { 1011 return arguments.length 1012 ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0))) 1013 : fn.apply(self, curryArgs); 1014 } 1015 : function() { 1016 return arguments.length 1017 ? fn.apply(self, arguments) 1018 : fn.call(self); 1019 }; 1020 } else { 1021 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1022 return fn; 1023 } 1024} 1025 1026 1027function toJsonReplacer(key, value) { 1028 var val = value; 1029 1030 if (typeof key === 'string' && key.charAt(0) === '$') { 1031 val = undefined; 1032 } else if (isWindow(value)) { 1033 val = '$WINDOW'; 1034 } else if (value && document === value) { 1035 val = '$DOCUMENT'; 1036 } else if (isScope(value)) { 1037 val = '$SCOPE'; 1038 } 1039 1040 return val; 1041} 1042 1043 1044/** 1045 * @ngdoc function 1046 * @name angular.toJson 1047 * @module ng 1048 * @function 1049 * 1050 * @description 1051 * Serializes input into a JSON-formatted string. Properties with leading $ characters will be 1052 * stripped since angular uses this notation internally. 1053 * 1054 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1055 * @param {boolean=} pretty If set to true, the JSON output will contain newlines and whitespace. 1056 * @returns {string|undefined} JSON-ified string representing `obj`. 1057 */ 1058function toJson(obj, pretty) { 1059 if (typeof obj === 'undefined') return undefined; 1060 return JSON.stringify(obj, toJsonReplacer, pretty ? ' ' : null); 1061} 1062 1063 1064/** 1065 * @ngdoc function 1066 * @name angular.fromJson 1067 * @module ng 1068 * @function 1069 * 1070 * @description 1071 * Deserializes a JSON string. 1072 * 1073 * @param {string} json JSON string to deserialize. 1074 * @returns {Object|Array|string|number} Deserialized thingy. 1075 */ 1076function fromJson(json) { 1077 return isString(json) 1078 ? JSON.parse(json) 1079 : json; 1080} 1081 1082 1083function toBoolean(value) { 1084 if (typeof value === 'function') { 1085 value = true; 1086 } else if (value && value.length !== 0) { 1087 var v = lowercase("" + value); 1088 value = !(v == 'f' || v == '0' || v == 'false' || v == 'no' || v == 'n' || v == '[]'); 1089 } else { 1090 value = false; 1091 } 1092 return value; 1093} 1094 1095/** 1096 * @returns {string} Returns the string representation of the element. 1097 */ 1098function startingTag(element) { 1099 element = jqLite(element).clone(); 1100 try { 1101 // turns out IE does not let you set .html() on elements which 1102 // are not allowed to have children. So we just ignore it. 1103 element.empty(); 1104 } catch(e) {} 1105 // As Per DOM Standards 1106 var TEXT_NODE = 3; 1107 var elemHtml = jqLite('<div>').append(element).html(); 1108 try { 1109 return element[0].nodeType === TEXT_NODE ? lowercase(elemHtml) : 1110 elemHtml. 1111 match(/^(<[^>]+>)/)[1]. 1112 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1113 } catch(e) { 1114 return lowercase(elemHtml); 1115 } 1116 1117} 1118 1119 1120///////////////////////////////////////////////// 1121 1122/** 1123 * Tries to decode the URI component without throwing an exception. 1124 * 1125 * @private 1126 * @param str value potential URI component to check. 1127 * @returns {boolean} True if `value` can be decoded 1128 * with the decodeURIComponent function. 1129 */ 1130function tryDecodeURIComponent(value) { 1131 try { 1132 return decodeURIComponent(value); 1133 } catch(e) {
1134 // Ignore any invalid uri component 1135 } 1136} 1137 1138 1139/** 1140 * Parses an escaped url query string into key-value pairs. 1141 * @returns {Object.<string,boolean|Array>} 1142 */ 1143function parseKeyValue(/**string*/keyValue) { 1144 var obj = {}, key_value, key; 1145 forEach((keyValue || "").split('&'), function(keyValue){ 1146 if ( keyValue ) { 1147 key_value = keyValue.split('='); 1148 key = tryDecodeURIComponent(key_value[0]); 1149 if ( isDefined(key) ) { 1150 var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; 1151 if (!obj[key]) { 1152 obj[key] = val; 1153 } else if(isArray(obj[key])) { 1154 obj[key].push(val); 1155 } else { 1156 obj[key] = [obj[key],val]; 1157 } 1158 } 1159 } 1160 }); 1161 return obj; 1162} 1163 1164function toKeyValue(obj) { 1165 var parts = []; 1166 forEach(obj, function(value, key) { 1167 if (isArray(value)) { 1168 forEach(value, function(arrayValue) { 1169 parts.push(encodeUriQuery(key, true) + 1170 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1171 }); 1172 } else { 1173 parts.push(encodeUriQuery(key, true) + 1174 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1175 } 1176 }); 1177 return parts.length ? parts.join('&') : ''; 1178} 1179 1180 1181/** 1182 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1183 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1184 * segments: 1185 * segment = *pchar 1186 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1187 * pct-encoded = "%" HEXDIG HEXDIG 1188 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1189 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1190 * / "*" / "+" / "," / ";" / "=" 1191 */ 1192function encodeUriSegment(val) { 1193 return encodeUriQuery(val, true). 1194 replace(/%26/gi, '&'). 1195 replace(/%3D/gi, '='). 1196 replace(/%2B/gi, '+'); 1197} 1198 1199 1200/** 1201 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1202 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1203 * encoded per http://tools.ietf.org/html/rfc3986: 1204 * query = *( pchar / "/" / "?" ) 1205 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1206 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1207 * pct-encoded = "%" HEXDIG HEXDIG 1208 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1209 * / "*" / "+" / "," / ";" / "=" 1210 */ 1211function encodeUriQuery(val, pctEncodeSpaces) { 1212 return encodeURIComponent(val). 1213 replace(/%40/gi, '@'). 1214 replace(/%3A/gi, ':'). 1215 replace(/%24/g, '$'). 1216 replace(/%2C/gi, ','). 1217 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1218} 1219 1220 1221/** 1222 * @ngdoc directive 1223 * @name ngApp 1224 * @module ng 1225 * 1226 * @element ANY 1227 * @param {angular.Module} ngApp an optional application 1228 * {@link angular.module module} name to load. 1229 * 1230 * @description 1231 * 1232 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1233 * designates the **root element** of the application and is typically placed near the root element 1234 * of the page - e.g. on the `<body>` or `<html>` tags. 1235 * 1236 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` 1237 * found in the document will be used to define the root element to auto-bootstrap as an 1238 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1239 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1240 * 1241 * You can specify an **AngularJS module** to be used as the root module for the application. This 1242 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and 1243 * should contain the application code needed or have dependencies on other modules that will 1244 * contain the code. See {@link angular.module} for more information. 1245 * 1246 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1247 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1248 * would not be resolved to `3`. 1249 * 1250 * `ngApp` is the easiest, and most common, way to bootstrap an application. 1251 * 1252 <example module="ngAppDemo"> 1253 <file name="index.html"> 1254 <div ng-controller="ngAppDemoController"> 1255 I can add: {{a}} + {{b}} = {{ a+b }} 1256 </div> 1257 </file> 1258 <file name="script.js"> 1259 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1260 $scope.a = 1; 1261 $scope.b = 2; 1262 }); 1263 </file> 1264 </example> 1265 * 1266 */ 1267function angularInit(element, bootstrap) { 1268 var elements = [element], 1269 appElement, 1270 module, 1271 names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'], 1272 NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/; 1273 1274 function append(element) { 1275 element && elements.push(element); 1276 } 1277 1278 forEach(names, function(name) { 1279 names[name] = true; 1280 append(document.getElementById(name)); 1281 name = name.replace(':', '\\:'); 1282 if (element.querySelectorAll) {
1283 forEach(element.querySelectorAll('.' + name), append); 1284 forEach(element.querySelectorAll('.' + name + '\\:'), append); 1285 forEach(element.querySelectorAll('[' + name + ']'), append); 1286 } 1287 }); 1288 1289 forEach(elements, function(element) { 1290 if (!appElement) { 1291 var className = ' ' + element.className + ' '; 1292 var match = NG_APP_CLASS_REGEXP.exec(className); 1293 if (match) { 1294 appElement = element; 1295 module = (match[2] || '').replace(/\s+/g, ','); 1296 } else { 1297 forEach(element.attributes, function(attr) { 1298 if (!appElement && names[attr.name]) { 1299 appElement = element; 1300 module = attr.value; 1301 } 1302 }); 1303 } 1304 } 1305 }); 1306 if (appElement) { 1307 bootstrap(appElement, module ? [module] : []); 1308 } 1309} 1310 1311/** 1312 * @ngdoc function 1313 * @name angular.bootstrap 1314 * @module ng 1315 * @description 1316 * Use this function to manually start up angular application. 1317 * 1318 * See: {@link guide/bootstrap Bootstrap} 1319 * 1320 * Note that ngScenario-based end-to-end tests cannot use this function to bootstrap manually. 1321 * They must use {@link ng.directive:ngApp ngApp}. 1322 * 1323 * Angular will detect if it has been loaded into the browser more than once and only allow the 1324 * first loaded script to be bootstrapped and will report a warning to the browser console for 1325 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1326 * multiple instances of Angular try to work on the DOM. 1327 * 1328 * <example name="multi-bootstrap" module="multi-bootstrap"> 1329 * <file name="index.html"> 1330 * <script src="../../../angular.js"></script> 1331 * <div ng-controller="BrokenTable"> 1332 * <table> 1333 * <tr> 1334 * <th ng-repeat="heading in headings">{{heading}}</th> 1335 * </tr> 1336 * <tr ng-repeat="filling in fillings"> 1337 * <td ng-repeat="fill in filling">{{fill}}</td> 1338 * </tr> 1339 * </table> 1340 * </div> 1341 * </file> 1342 * <file name="controller.js"> 1343 * var app = angular.module('multi-bootstrap', []) 1344 * 1345 * .controller('BrokenTable', function($scope) { 1346 * $scope.headings = ['One', 'Two', 'Three']; 1347 * $scope.fillings = [[1, 2, 3], ['A', 'B', 'C'], [7, 8, 9]]; 1348 * }); 1349 * </file> 1350 * <file name="protractor.js" type="protractor"> 1351 * it('should only insert one table cell for each item in $scope.fillings', function() { 1352 * expect(element.all(by.css('td')).count()) 1353 * .toBe(9); 1354 * }); 1355 * </file> 1356 * </example> 1357 * 1358 * @param {DOMElement} element DOM element which is the root of angular application. 1359 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1360 * Each item in the array should be the name of a predefined module or a (DI annotated) 1361 * function that will be invoked by the injector as a run block. 1362 * See: {@link angular.module modules} 1363 * @returns {auto.$injector} Returns the newly created injector for this app. 1364 */ 1365function bootstrap(element, modules) { 1366 var doBootstrap = function() { 1367 element = jqLite(element); 1368 1369 if (element.injector()) { 1370 var tag = (element[0] === document) ? 'document' : startingTag(element); 1371 throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag); 1372 } 1373 1374 modules = modules || []; 1375 modules.unshift(['$provide', function($provide) { 1376 $provide.value('$rootElement', element); 1377 }]); 1378 modules.unshift('ng'); 1379 var injector = createInjector(modules); 1380 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate', 1381 function(scope, element, compile, injector, animate) { 1382 scope.$apply(function() { 1383 element.data('$injector', injector); 1384 compile(element)(scope); 1385 }); 1386 }] 1387 ); 1388 return injector; 1389 }; 1390 1391 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1392 1393 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1394 return doBootstrap(); 1395 } 1396 1397 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1398 angular.resumeBootstrap = function(extraModules) { 1399 forEach(extraModules, function(module) { 1400 modules.push(module); 1401 });
vendor: 2,868 bytes, lines 1402-1499
1402 doBootstrap(); 1403 }; 1404} 1405 1406var SNAKE_CASE_REGEXP = /[A-Z]/g; 1407function snake_case(name, separator){ 1408 separator = separator || '_'; 1409 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1410 return (pos ? separator : '') + letter.toLowerCase(); 1411 }); 1412} 1413 1414function bindJQuery() { 1415 // bind to jQuery if present; 1416 jQuery = window.jQuery; 1417 // reset to jQuery or default to us. 1418 if (jQuery) { 1419 jqLite = jQuery; 1420 extend(jQuery.fn, { 1421 scope: JQLitePrototype.scope, 1422 isolateScope: JQLitePrototype.isolateScope, 1423 controller: JQLitePrototype.controller, 1424 injector: JQLitePrototype.injector, 1425 inheritedData: JQLitePrototype.inheritedData 1426 }); 1427 // Method signature: 1428 // jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) 1429 jqLitePatchJQueryRemove('remove', true, true, false); 1430 jqLitePatchJQueryRemove('empty', false, false, false); 1431 jqLitePatchJQueryRemove('html', false, false, true); 1432 } else { 1433 jqLite = JQLite; 1434 } 1435 angular.element = jqLite; 1436} 1437 1438/** 1439 * throw error if the argument is falsy. 1440 */ 1441function assertArg(arg, name, reason) { 1442 if (!arg) { 1443 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1444 } 1445 return arg; 1446} 1447 1448function assertArgFn(arg, name, acceptArrayAnnotation) { 1449 if (acceptArrayAnnotation && isArray(arg)) { 1450 arg = arg[arg.length - 1]; 1451 } 1452 1453 assertArg(isFunction(arg), name, 'not a function, got ' + 1454 (arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1455 return arg; 1456} 1457 1458/** 1459 * throw error if the name given is hasOwnProperty 1460 * @param {String} name the name to test 1461 * @param {String} context the context in which the name is used, such as module or directive 1462 */ 1463function assertNotHasOwnProperty(name, context) { 1464 if (name === 'hasOwnProperty') { 1465 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1466 } 1467} 1468 1469/** 1470 * Return the value accessible from the object by path. Any undefined traversals are ignored 1471 * @param {Object} obj starting object 1472 * @param {String} path path to traverse 1473 * @param {boolean} [bindFnToScope=true] 1474 * @returns {Object} value as accessible by path 1475 */ 1476//TODO(misko): this function needs to be removed 1477function getter(obj, path, bindFnToScope) { 1478 if (!path) return obj; 1479 var keys = path.split('.'); 1480 var key; 1481 var lastInstance = obj; 1482 var len = keys.length; 1483 1484 for (var i = 0; i < len; i++) { 1485 key = keys[i]; 1486 if (obj) { 1487 obj = (lastInstance = obj)[key]; 1488 } 1489 } 1490 if (!bindFnToScope && isFunction(obj)) { 1491 return bind(lastInstance, obj); 1492 } 1493 return obj; 1494} 1495 1496/** 1497 * Return the DOM siblings between the first and last node in the given array. 1498 * @param {Array} array like object 1499 * @returns {DOMElement}
1499 object containing the elements 1500 */ 1501function getBlockElements(nodes) { 1502 var startNode = nodes[0], 1503 endNode = nodes[nodes.length - 1]; 1504 if (startNode === endNode) { 1505 return jqLite(startNode); 1506 } 1507 1508 var element = startNode; 1509 var elements = [element]; 1510 1511 do { 1512 element = element.nextSibling; 1513 if (!element) break; 1514 elements.push(element); 1515 } while (element !== endNode); 1516 1517 return jqLite(elements); 1518} 1519 1520/** 1521 * @ngdoc type 1522 * @name angular.Module 1523 * @module ng 1524 * @description 1525 * 1526 * Interface for configuring angular {@link angular.module modules}. 1527 */ 1528 1529function setupModuleLoader(window) { 1530 1531 var $injectorMinErr = minErr('$injector'); 1532 var ngMinErr = minErr('ng'); 1533 1534 function ensure(obj, name, factory) { 1535 return obj[name] || (obj[name] = factory()); 1536 } 1537 1538 var angular = ensure(window, 'angular', Object); 1539 1540 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1541 angular.$$minErr = angular.$$minErr || minErr; 1542 1543 return ensure(angular, 'module', function() { 1544 /** @type {Object.<string, angular.Module>} */ 1545 var modules = {}; 1546 1547 /** 1548 * @ngdoc function 1549 * @name angular.module 1550 * @module ng 1551 * @description 1552 * 1553 * The `angular.module` is a global place for creating, registering and retrieving Angular 1554 * modules. 1555 * All modules (angular core or 3rd party) that should be available to an application must be 1556 * registered using this mechanism. 1557 * 1558 * When passed two or more arguments, a new module is created. If passed only one argument, an 1559 * existing module (the name passed as the first argument to `module`) is retrieved. 1560 * 1561 * 1562 * # Module 1563 * 1564 * A module is a collection of services, directives, filters, and configuration information. 1565 * `angular.module` is used to configure the {@link auto.$injector $injector}. 1566 * 1567 * ```js 1568 * // Create a new module 1569 * var myModule = angular.module('myModule', []); 1570 * 1571 * // register a new service 1572 * myModule.value('appName', 'MyCoolApp'); 1573 * 1574 * // configure existing services inside initialization blocks. 1575 * myModule.config(['$locationProvider', function($locationProvider) { 1576 * // Configure existing providers 1577 * $locationProvider.hashPrefix('!'); 1578 * }]); 1579 * ``` 1580 * 1581 * Then you can create an injector and load your modules like this: 1582 * 1583 * ```js 1584 * var injector = angular.injector(['ng', 'myModule']) 1585 * ``` 1586 * 1587 * However it's more likely that you'll just use 1588 * {@link ng.directive:ngApp ngApp} or 1589 * {@link angular.bootstrap} to simplify this process for you. 1590 * 1591 * @param {!string} name The name of the module to create or retrieve. 1592<<<<<* @param {!Array.<string>=} requires If specified then new module is being created. If 1593>>>>>* unspecified then the module is being retrieved for further configuration. 1594 * @param {Function} configFn Optional configuration function for the module. Same as 1595 * {@link angular.Module#config Module#config()}. 1596 * @returns {module} new module with the {@link angular.Module} api. 1597 */ 1598 return function module(name, requires, configFn) { 1599 var assertNotHasOwnProperty = function(name, context) { 1600 if (name === 'hasOwnProperty') { 1601 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 1602 } 1603 }; 1604 1605 assertNotHasOwnProperty(name, 'module'); 1606 if (requires && modules.hasOwnProperty(name)) { 1607 modules[name] = null; 1608 } 1609 return ensure(modules, name, function() { 1610 if (!requires) { 1611 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 1612 "the module name or forgot to load it. If registering a module ensure that you " + 1613 "specify the dependencies as the second argument.", name); 1614 } 1615 1616 /** @type {!Array.<Array.<*>>} */ 1617 var invokeQueue = []; 1618
1619 /** @type {!Array.<Function>} */ 1620 var runBlocks = []; 1621 1622 var config = invokeLater('$injector', 'invoke'); 1623 1624 /** @type {angular.Module} */ 1625 var moduleInstance = { 1626 // Private state 1627 _invokeQueue: invokeQueue, 1628 _runBlocks: runBlocks, 1629 1630 /** 1631 * @ngdoc property 1632 * @name angular.Module#requires 1633 * @module ng 1634 * @returns {Array.<string>} List of module names which must be loaded before this module. 1635 * @description 1636 * Holds the list of modules which the injector will load before the current module is 1637 * loaded. 1638 */ 1639 requires: requires, 1640 1641 /** 1642 * @ngdoc property 1643 * @name angular.Module#name 1644 * @module ng 1645 * @returns {string} Name of the module. 1646 * @description 1647 */ 1648 name: name, 1649 1650 1651 /** 1652 * @ngdoc method 1653 * @name angular.Module#provider 1654 * @module ng 1655 * @param {string} name service name 1656 * @param {Function} providerType Construction function for creating new instance of the 1657 * service. 1658 * @description 1659 * See {@link auto.$provide#provider $provide.provider()}. 1660 */ 1661 provider: invokeLater('$provide', 'provider'), 1662 1663 /** 1664 * @ngdoc method 1665 * @name angular.Module#factory 1666 * @module ng 1667 * @param {string} name service name 1668 * @param {Function} providerFunction Function for creating new instance of the service. 1669 * @description 1670 * See {@link auto.$provide#factory $provide.factory()}. 1671 */ 1672 factory: invokeLater('$provide', 'factory'), 1673 1674 /** 1675 * @ngdoc method 1676 * @name angular.Module#service 1677 * @module ng 1678 * @param {string} name service name 1679 * @param {Function} constructor A constructor function that will be instantiated. 1680 * @description 1681 * See {@link auto.$provide#service $provide.service()}. 1682 */ 1683 service: invokeLater('$provide', 'service'), 1684 1685 /** 1686 * @ngdoc method 1687 * @name angular.Module#value 1688 * @module ng 1689 * @param {string} name service name 1690 * @param {*} object Service instance object. 1691 * @description 1692 * See {@link auto.$provide#value $provide.value()}. 1693 */ 1694 value: invokeLater('$provide', 'value'), 1695 1696 /** 1697 * @ngdoc method 1698 * @name angular.Module#constant 1699 * @module ng 1700 * @param {string} name constant name 1701 * @param {*} object Constant value. 1702 * @description 1703 * Because the constant are fixed, they get applied before other provide methods. 1704 * See {@link auto.$provide#constant $provide.constant()}. 1705 */ 1706 constant: invokeLater('$provide', 'constant', 'unshift'), 1707 1708 /** 1709 * @ngdoc method 1710 * @name angular.Module#animation 1711 * @module ng 1712 * @param {string} name animation name 1713 * @param {Function} animationFactory Factory function for creating new instance of an 1714 * animation. 1715 * @description 1716 * 1717 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 1718 * 1719 * 1720 * Defines an animation hook that can be later used with 1721 * {@link ngAnimate.$animate $animate} service and directives that use this service. 1722 * 1723 * ```js 1724 * module.animation('.animation-name', function($inject1, $inject2) { 1725 * return { 1726 * eventName : function(element, done) { 1727 * //code to run the animation 1728 * //once complete, then run done() 1729 * return function cancellationFunction(element) { 1730 * //code to cancel the animation 1731 * } 1732 * } 1733 * } 1734 * }) 1735 * ``` 1736 * 1737 * See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and 1738 * {@link ngAnimate ngAnimate module} for more information. 1739 */ 1740 animation: invokeLater('$animateProvider', 'register'), 1741 1742 /** 1743 * @ngdoc method 1744 * @name angular.Module#filter 1745 * @module ng 1746 * @param {string} name Filter name. 1747 * @param {Function} filterFactory Factory function for creating new instance of filter.
1748 * @description 1749 * See {@link ng.$filterProvider#register $filterProvider.register()}. 1750 */ 1751 filter: invokeLater('$filterProvider', 'register'), 1752 1753 /** 1754 * @ngdoc method 1755 * @name angular.Module#controller 1756 * @module ng 1757 * @param {string|Object} name Controller name, or an object map of controllers where the 1758 * keys are the names and the values are the constructors. 1759 * @param {Function} constructor Controller constructor function. 1760 * @description 1761 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 1762 */ 1763 controller: invokeLater('$controllerProvider', 'register'), 1764 1765 /** 1766 * @ngdoc method 1767 * @name angular.Module#directive 1768 * @module ng 1769 * @param {string|Object} name Directive name, or an object map of directives where the 1770 * keys are the names and the values are the factories. 1771 * @param {Function} directiveFactory Factory function for creating new instance of 1772 * directives. 1773 * @description 1774 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 1775 */ 1776 directive: invokeLater('$compileProvider', 'directive'), 1777 1778 /** 1779 * @ngdoc method 1780 * @name angular.Module#config 1781 * @module ng 1782 * @param {Function} configFn Execute this function on module load. Useful for service 1783 * configuration. 1784 * @description 1785 * Use this method to register work which needs to be performed on module loading. 1786 */ 1787 config: config, 1788 1789 /** 1790 * @ngdoc method 1791 * @name angular.Module#run 1792 * @module ng 1793 * @param {Function} initializationFn Execute this function after injector creation. 1794 * Useful for application initialization. 1795 * @description 1796 * Use this method to register work which should be performed when the injector is done 1797 * loading all modules. 1798 */ 1799 run: function(block) { 1800 runBlocks.push(block); 1801 return this; 1802 } 1803 }; 1804 1805 if (configFn) { 1806 config(configFn); 1807 } 1808 1809 return moduleInstance; 1810 1811 /** 1812 * @param {string} provider 1813 * @param {string} method 1814 * @param {String=} insertMethod 1815 * @returns {angular.Module} 1816 */ 1817 function invokeLater(provider, method, insertMethod) { 1818 return function() { 1819 invokeQueue[insertMethod || 'push']([provider, method, arguments]); 1820 return moduleInstance; 1821 }; 1822 } 1823 }); 1824 }; 1825 }); 1826 1827} 1828 1829/* global 1830 angularModule: true, 1831 version: true, 1832 1833 $LocaleProvider, 1834 $CompileProvider, 1835 1836 htmlAnchorDirective, 1837 inputDirective, 1838 inputDirective, 1839 formDirective, 1840 scriptDirective, 1841 selectDirective, 1842 styleDirective, 1843 optionDirective, 1844 ngBindDirective, 1845 ngBindHtmlDirective, 1846 ngBindTemplateDirective, 1847 ngClassDirective, 1848 ngClassEvenDirective, 1849 ngClassOddDirective, 1850 ngCspDirective, 1851 ngCloakDirective, 1852 ngControllerDirective, 1853 ngFormDirective, 1854 ngHideDirective, 1855 ngIfDirective, 1856 ngIncludeDirective, 1857 ngIncludeFillContentDirective, 1858 ngInitDirective, 1859 ngNonBindableDirective, 1860 ngPluralizeDirective, 1861 ngRepeatDirective, 1862 ngShowDirective, 1863 ngStyleDirective, 1864 ngSwitchDirective, 1865 ngSwitchWhenDirective, 1866 ngSwitchDefaultDirective, 1867 ngOptionsDirective, 1868 ngTranscludeDirective, 1869 ngModelDirective, 1870 ngListDirective, 1871 ngChangeDirective, 1872 requiredDirective, 1873 requiredDirective, 1874 ngValueDirective, 1875 ngAttributeAliasDirectives, 1876 ngEventDirectives, 1877 1878 $AnchorScrollProvider, 1879 $AnimateProvider, 1880 $BrowserProvider, 1881 $CacheFactoryProvider, 1882 $ControllerProvider, 1883 $DocumentProvider, 1884 $ExceptionHandlerProvider, 1885 $FilterProvider, 1886 $InterpolateProvider, 1887 $IntervalProvider, 1888 $HttpProvider, 1889 $HttpBackendProvider, 1890 $LocationProvider, 1891 $LogProvider, 1892 $ParseProvider, 1893 $RootScopeProvider, 1894 $QProvider,
1895 $$SanitizeUriProvider, 1896 $SceProvider, 1897 $SceDelegateProvider, 1898 $SnifferProvider, 1899 $TemplateCacheProvider, 1900 $TimeoutProvider, 1901 $$RAFProvider, 1902 $$AsyncCallbackProvider, 1903 $WindowProvider 1904*/ 1905 1906 1907/** 1908 * @ngdoc object 1909 * @name angular.version 1910 * @module ng 1911 * @description 1912 * An object that contains information about the current AngularJS version. This object has the 1913 * following properties: 1914 * 1915 * - `full` â `{string}` â Full version string, such as "0.9.18". 1916 * - `major` â `{number}` â Major version number, such as "0". 1917 * - `minor` â `{number}` â Minor version number, such as "9". 1918 * - `dot` â `{number}` â Dot version number, such as "18". 1919 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 1920 */ 1921var version = { 1922 full: '1.2.16', // all of these placeholder strings will be replaced by grunt's 1923 major: 1, // package task 1924 minor: 2, 1925 dot: 16, 1926 codeName: 'badger-enumeration' 1927}; 1928 1929 1930function publishExternalAPI(angular){ 1931 extend(angular, { 1932 'bootstrap': bootstrap, 1933 'copy': copy, 1934 'extend': extend, 1935 'equals': equals, 1936 'element': jqLite, 1937 'forEach': forEach, 1938 'injector': createInjector, 1939 'noop':noop, 1940 'bind':bind, 1941 'toJson': toJson, 1942 'fromJson': fromJson, 1943 'identity':identity, 1944 'isUndefined': isUndefined, 1945 'isDefined': isDefined, 1946 'isString': isString, 1947 'isFunction': isFunction, 1948 'isObject': isObject, 1949 'isNumber': isNumber, 1950 'isElement': isElement, 1951 'isArray': isArray, 1952 'version': version, 1953 'isDate': isDate, 1954 'lowercase': lowercase, 1955 'uppercase': uppercase, 1956 'callbacks': {counter: 0}, 1957 '$$minErr': minErr, 1958 '$$csp': csp 1959 }); 1960 1961 angularModule = setupModuleLoader(window); 1962 try { 1963 angularModule('ngLocale'); 1964 } catch (e) { 1965 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 1966 } 1967 1968 angularModule('ng', ['ngLocale'], ['$provide', 1969 function ngModule($provide) { 1970 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 1971 $provide.provider({ 1972 $$sanitizeUri: $$SanitizeUriProvider 1973 }); 1974 $provide.provider('$compile', $CompileProvider). 1975 directive({ 1976 a: htmlAnchorDirective, 1977 input: inputDirective, 1978 textarea: inputDirective, 1979 form: formDirective, 1980 script: scriptDirective, 1981 select: selectDirective, 1982 style: styleDirective, 1983 option: optionDirective, 1984 ngBind: ngBindDirective, 1985 ngBindHtml: ngBindHtmlDirective, 1986 ngBindTemplate: ngBindTemplateDirective, 1987 ngClass: ngClassDirective, 1988 ngClassEven: ngClassEvenDirective, 1989 ngClassOdd: ngClassOddDirective, 1990 ngCloak: ngCloakDirective, 1991 ngController: ngControllerDirective, 1992 ngForm: ngFormDirective, 1993 ngHide: ngHideDirective, 1994 ngIf: ngIfDirective, 1995 ngInclude: ngIncludeDirective, 1996 ngInit: ngInitDirective, 1997 ngNonBindable: ngNonBindableDirective, 1998 ngPluralize: ngPluralizeDirective, 1999 ngRepeat: ngRepeatDirective, 2000 ngShow: ngShowDirective, 2001 ngStyle: ngStyleDirective, 2002 ngSwitch: ngSwitchDirective, 2003 ngSwitchWhen: ngSwitchWhenDirective, 2004 ngSwitchDefault: ngSwitchDefaultDirective, 2005 ngOptions: ngOptionsDirective, 2006 ngTransclude: ngTranscludeDirective, 2007 ngModel: ngModelDirective, 2008 ngList: ngListDirective, 2009 ngChange: ngChangeDirective, 2010 required: requiredDirective, 2011 ngRequired: requiredDirective, 2012 ngValue: ngValueDirective 2013 }). 2014 directive({ 2015 ngInclude: ngIncludeFillContentDirective 2016 }). 2017 directive(ngAttributeAliasDirectives). 2018 directive(ngEventDirectives); 2019 $provide.provider({ 2020 $anchorScroll: $AnchorScrollProvider, 2021 $animate: $AnimateProvider, 2022 $browser: $BrowserProvider,
2023 $cacheFactory: $CacheFactoryProvider, 2024 $controller: $ControllerProvider, 2025 $document: $DocumentProvider, 2026 $exceptionHandler: $ExceptionHandlerProvider, 2027 $filter: $FilterProvider, 2028 $interpolate: $InterpolateProvider, 2029 $interval: $IntervalProvider, 2030 $http: $HttpProvider, 2031 $httpBackend: $HttpBackendProvider, 2032 $location: $LocationProvider, 2033 $log: $LogProvider, 2034 $parse: $ParseProvider, 2035 $rootScope: $RootScopeProvider, 2036 $q: $QProvider, 2037 $sce: $SceProvider, 2038 $sceDelegate: $SceDelegateProvider, 2039 $sniffer: $SnifferProvider, 2040 $templateCache: $TemplateCacheProvider, 2041 $timeout: $TimeoutProvider, 2042 $window: $WindowProvider, 2043 $$rAF: $$RAFProvider, 2044 $$asyncCallback : $$AsyncCallbackProvider 2045 }); 2046 } 2047 ]); 2048} 2049 2050/* global 2051 2052 -JQLitePrototype, 2053 -addEventListenerFn, 2054 -removeEventListenerFn, 2055 -BOOLEAN_ATTR 2056*/ 2057 2058////////////////////////////////// 2059//JQLite 2060////////////////////////////////// 2061 2062/** 2063 * @ngdoc function 2064 * @name angular.element 2065 * @module ng 2066 * @function 2067 * 2068 * @description 2069 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2070 * 2071 * If jQuery is available, `angular.element` is an alias for the 2072 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2073 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." 2074 * 2075 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows 2076 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most 2077 * commonly needed functionality with the goal of having a very small footprint.</div> 2078 * 2079 * To use jQuery, simply load it before `DOMContentLoaded` event fired. 2080 * 2081 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or 2082 * jqLite; they are never raw DOM references.</div> 2083 * 2084 * ## Angular's jqLite 2085 * jqLite provides only the following jQuery methods: 2086 * 2087 * - [`addClass()`](http://api.jquery.com/addClass/) 2088 * - [`after()`](http://api.jquery.com/after/) 2089 * - [`append()`](http://api.jquery.com/append/) 2090 * - [`attr()`](http://api.jquery.com/attr/) 2091 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2092 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2093 * - [`clone()`](http://api.jquery.com/clone/) 2094 * - [`contents()`](http://api.jquery.com/contents/) 2095 * - [`css()`](http://api.jquery.com/css/) 2096 * - [`data()`](http://api.jquery.com/data/) 2097 * - [`empty()`](http://api.jquery.com/empty/) 2098 * - [`eq()`](http://api.jquery.com/eq/) 2099 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2100 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2101 * - [`html()`](http://api.jquery.com/html/) 2102 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2103 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2104 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors 2105 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2106 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2107 * - [`prepend()`](http://api.jquery.com/prepend/) 2108 * - [`prop()`](http://api.jquery.com/prop/) 2109 * - [`ready()`](http://api.jquery.com/ready/) 2110 * - [`remove()`](http://api.jquery.com/remove/) 2111 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2112 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2113 * - [`removeData()`](http://api.jquery.com/removeData/) 2114 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2115 * - [`text()`](http://api.jquery.com/text/) 2116 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2117 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2118 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces 2119 * - [`val()`](http://api.jquery.com/val/) 2120 * - [`wrap()`](http://api.jquery.com/wrap/) 2121 * 2122 * ## jQuery/jqLite Extras 2123 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2124 * 2125 * ### Events 2126 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
2126pis and fires this event 2127 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2128 * element before it is removed. 2129 * 2130 * ### Methods 2131 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2132 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2133 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2134 * `'ngModel'`). 2135 * - `injector()` - retrieves the injector of the current element or its parent. 2136 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2137 * element or its parent. 2138 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2139 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 2140 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2141 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2142 * parent element is reached. 2143 * 2144 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2145 * @returns {Object} jQuery object. 2146 */ 2147 2148var jqCache = JQLite.cache = {}, 2149 jqName = JQLite.expando = 'ng-' + new Date().getTime(), 2150 jqId = 1, 2151 addEventListenerFn = (window.document.addEventListener 2152 ? function(element, type, fn) {element.addEventListener(type, fn, false);} 2153 : function(element, type, fn) {element.attachEvent('on' + type, fn);}), 2154 removeEventListenerFn = (window.document.removeEventListener 2155 ? function(element, type, fn) {element.removeEventListener(type, fn, false); } 2156 : function(element, type, fn) {element.detachEvent('on' + type, fn); }); 2157 2158/* 2159 * !!! This is an undocumented "private" function !!! 2160 */ 2161var jqData = JQLite._data = function(node) { 2162 //jQuery always returns an object on cache miss 2163 return this.cache[node[this.expando]] || {}; 2164}; 2165 2166function jqNextId() { return ++jqId; } 2167 2168 2169var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2170var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2171var jqLiteMinErr = minErr('jqLite'); 2172 2173/** 2174 * Converts snake_case to camelCase. 2175 * Also there is special case for Moz prefix starting with upper case letter. 2176 * @param name Name to normalize 2177 */ 2178function camelCase(name) { 2179 return name. 2180 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2181 return offset ? letter.toUpperCase() : letter; 2182 }). 2183 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2184} 2185 2186///////////////////////////////////////////// 2187// jQuery mutation patch 2188// 2189// In conjunction with bindJQuery intercepts all jQuery's DOM destruction apis and fires a 2190// $destroy event on all DOM nodes being removed. 2191// 2192///////////////////////////////////////////// 2193 2194function jqLitePatchJQueryRemove(name, dispatchThis, filterElems, getterIfNoArguments) { 2195 var originalJqFn = jQuery.fn[name]; 2196 originalJqFn = originalJqFn.$original || originalJqFn; 2197 removePatch.$original = originalJqFn; 2198 jQuery.fn[name] = removePatch; 2199 2200 function removePatch(param) { 2201 // jshint -W040 2202 var list = filterElems && param ? [this.filter(param)] : [this], 2203 fireEvent = dispatchThis, 2204 set, setIndex, setLength, 2205 element, childIndex, childLength, children; 2206 2207 if (!getterIfNoArguments || param != null) { 2208 while(list.length) { 2209 set = list.shift(); 2210 for(setIndex = 0, setLength = set.length; setIndex < setLength; setIndex++) { 2211 element = jqLite(set[setIndex]); 2212 if (fireEvent) { 2213 element.triggerHandler('$destroy'); 2214 } else { 2215 fireEvent = !fireEvent; 2216 } 2217 for(childIndex = 0, childLength = (children = element.children()).length; 2218 childIndex < childLength; 2219 childIndex++) { 2220 list.push(jQuery(children[childIndex])); 2221 } 2222 } 2223 } 2224 } 2225 return originalJqFn.apply(this, arguments); 2226 } 2227} 2228 2229var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/;
2230var HTML_REGEXP = /<|&#?\w+;/; 2231var TAG_NAME_REGEXP = /<([\w:]+)/; 2232var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; 2233 2234var wrapMap = { 2235 'option': [1, '<select multiple="multiple">', '</select>'], 2236 2237 'thead': [1, '<table>', '</table>'], 2238 'col': [2, '<table><colgroup>', '</colgroup></table>'], 2239 'tr': [2, '<table><tbody>', '</tbody></table>'], 2240 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2241 '_default': [0, "", ""] 2242}; 2243 2244wrapMap.optgroup = wrapMap.option; 2245wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2246wrapMap.th = wrapMap.td; 2247 2248function jqLiteIsTextNode(html) { 2249 return !HTML_REGEXP.test(html); 2250} 2251 2252function jqLiteBuildFragment(html, context) { 2253 var elem, tmp, tag, wrap, 2254 fragment = context.createDocumentFragment(), 2255 nodes = [], i, j, jj; 2256 2257 if (jqLiteIsTextNode(html)) { 2258 // Convert non-html into a text node 2259 nodes.push(context.createTextNode(html)); 2260 } else { 2261 tmp = fragment.appendChild(context.createElement('div')); 2262 // Convert html into DOM nodes 2263 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2264 wrap = wrapMap[tag] || wrapMap._default; 2265 tmp.innerHTML = '<div> </div>' + 2266 wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2267 tmp.removeChild(tmp.firstChild); 2268 2269 // Descend through wrappers to the right content 2270 i = wrap[0]; 2271 while (i--) { 2272 tmp = tmp.lastChild; 2273 } 2274 2275 for (j=0, jj=tmp.childNodes.length; j<jj; ++j) nodes.push(tmp.childNodes[j]); 2276 2277 tmp = fragment.firstChild; 2278 tmp.textContent = ""; 2279 } 2280 2281 // Remove wrapper from fragment 2282 fragment.textContent = ""; 2283 fragment.innerHTML = ""; // Clear inner HTML 2284 return nodes; 2285} 2286 2287function jqLiteParseHTML(html, context) { 2288 context = context || document; 2289 var parsed; 2290 2291 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2292 return [context.createElement(parsed[1])]; 2293 } 2294 2295 return jqLiteBuildFragment(html, context); 2296} 2297 2298///////////////////////////////////////////// 2299function JQLite(element) { 2300 if (element instanceof JQLite) { 2301 return element; 2302 } 2303 if (isString(element)) { 2304 element = trim(element); 2305 } 2306 if (!(this instanceof JQLite)) { 2307 if (isString(element) && element.charAt(0) != '<') { 2308 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2309 } 2310 return new JQLite(element); 2311 } 2312 2313 if (isString(element)) { 2314 jqLiteAddNodes(this, jqLiteParseHTML(element)); 2315 var fragment = jqLite(document.createDocumentFragment()); 2316 fragment.append(this); 2317 } else { 2318 jqLiteAddNodes(this, element); 2319 } 2320} 2321 2322function jqLiteClone(element) { 2323 return element.cloneNode(true); 2324} 2325 2326function jqLiteDealoc(element){ 2327 jqLiteRemoveData(element); 2328 for ( var i = 0, children = element.childNodes || []; i < children.length; i++) { 2329 jqLiteDealoc(children[i]); 2330 } 2331} 2332 2333function jqLiteOff(element, type, fn, unsupported) {
2334 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2335 2336 var events = jqLiteExpandoStore(element, 'events'), 2337 handle = jqLiteExpandoStore(element, 'handle'); 2338 2339 if (!handle) return; //no listeners registered 2340 2341 if (isUndefined(type)) { 2342 forEach(events, function(eventHandler, type) { 2343 removeEventListenerFn(element, type, eventHandler); 2344 delete events[type]; 2345 }); 2346 } else { 2347 forEach(type.split(' '), function(type) { 2348 if (isUndefined(fn)) { 2349 removeEventListenerFn(element, type, events[type]); 2350 delete events[type]; 2351 } else { 2352 arrayRemove(events[type] || [], fn); 2353 } 2354 }); 2355 } 2356} 2357 2358function jqLiteRemoveData(element, name) { 2359 var expandoId = element[jqName], 2360 expandoStore = jqCache[expandoId]; 2361 2362 if (expandoStore) { 2363 if (name) { 2364 delete jqCache[expandoId].data[name]; 2365 return; 2366 } 2367 2368 if (expandoStore.handle) { 2369 expandoStore.events.$destroy && expandoStore.handle({}, '$destroy'); 2370 jqLiteOff(element); 2371 } 2372 delete jqCache[expandoId]; 2373 element[jqName] = undefined; // ie does not allow deletion of attributes on elements. 2374 } 2375} 2376 2377function jqLiteExpandoStore(element, key, value) { 2378 var expandoId = element[jqName], 2379 expandoStore = jqCache[expandoId || -1]; 2380 2381 if (isDefined(value)) { 2382 if (!expandoStore) { 2383 element[jqName] = expandoId = jqNextId(); 2384 expandoStore = jqCache[expandoId] = {}; 2385 } 2386 expandoStore[key] = value; 2387 } else { 2388 return expandoStore && expandoStore[key]; 2389 } 2390} 2391 2392function jqLiteData(element, key, value) { 2393 var data = jqLiteExpandoStore(element, 'data'), 2394 isSetter = isDefined(value), 2395 keyDefined = !isSetter && isDefined(key), 2396 isSimpleGetter = keyDefined && !isObject(key); 2397 2398 if (!data && !isSimpleGetter) { 2399 jqLiteExpandoStore(element, 'data', data = {}); 2400 } 2401 2402 if (isSetter) { 2403 data[key] = value; 2404 } else { 2405 if (keyDefined) { 2406 if (isSimpleGetter) { 2407 // don't create data in this case. 2408 return data && data[key]; 2409 } else { 2410 extend(data, key); 2411 } 2412 } else { 2413 return data; 2414 } 2415 } 2416} 2417 2418function jqLiteHasClass(element, selector) { 2419 if (!element.getAttribute) return false; 2420 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2421 indexOf( " " + selector + " " ) > -1); 2422} 2423 2424function jqLiteRemoveClass(element, cssClasses) { 2425 if (cssClasses && element.setAttribute) { 2426 forEach(cssClasses.split(' '), function(cssClass) { 2427 element.setAttribute('class', trim( 2428 (" " + (element.getAttribute('class') || '') + " ") 2429 .replace(/[\n\t]/g, " ") 2430 .replace(" " + trim(cssClass) + " ", " ")) 2431 ); 2432 }); 2433 } 2434} 2435 2436function jqLiteAddClass(element, cssClasses) { 2437 if (cssClasses && element.setAttribute) { 2438 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 2439 .replace(/[\n\t]/g, " "); 2440 2441 forEach(cssClasses.split(' '), function(cssClass) { 2442 cssClass = trim(cssClass); 2443 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 2444 existingClasses += cssClass + ' '; 2445 } 2446 }); 2447 2448 element.setAttribute('class', trim(existingClasses)); 2449 } 2450} 2451 2452function jqLiteAddNodes(root, elements) { 2453 if (elements) { 2454 elements = (!elements.nodeName && isDefined(elements.length) && !isWindow(elements)) 2455 ? elements 2456 : [ elements ]; 2457 for(var i=0; i < elements.length; i++) { 2458 root.push(elements[i]); 2459 } 2460 } 2461} 2462 2463function jqLiteController(element, name) { 2464 return jqLiteInheritedData(element, '$' + (name || 'ngController' ) + 'Controller'); 2465} 2466 2467function jqLiteInheritedData(element, name, value) { 2468 element = jqLite(element); 2469 2470 // if element is the document object work with the html element instead 2471 // this makes $(document).scope() possible 2472 if(element[0].nodeType == 9) { 2473 element = element.find('html'); 2474 } 2475 var names = isArray(name) ? name : [name]; 2476 2477 while (element.length) { 2478 var node = element[0]; 2479 for (var i = 0, ii = names.length; i < ii; i++) { 2480 if ((value = element.data(names[i])) !== undefined) return value; 2481 } 2482 2483 // If dealing with a document fragment node with a host element, and no parent, use the host 2484 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 2485 // to lookup parent controllers. 2486 element = jqLite(node.parentNode || (node.nodeType === 11 && node.host)); 2487 } 2488} 2489 2490function jqLiteEmpty(element) { 2491 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2492 jqLiteDealoc(childNodes[i]); 2493 } 2494 while (element.firstChild) { 2495 element.removeChild(element.firstChild); 2496 } 2497} 2498 2499////////////////////////////////////////// 2500// Functions which are declared directly.
2501////////////////////////////////////////// 2502var JQLitePrototype = JQLite.prototype = { 2503 ready: function(fn) { 2504 var fired = false; 2505 2506 function trigger() { 2507 if (fired) return; 2508 fired = true; 2509 fn(); 2510 } 2511 2512 // check if document already is loaded 2513 if (document.readyState === 'complete'){ 2514 setTimeout(trigger); 2515 } else { 2516 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 2517 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 2518 // jshint -W064 2519 JQLite(window).on('load', trigger); // fallback to window.onload for others 2520 // jshint +W064 2521 } 2522 }, 2523 toString: function() { 2524 var value = []; 2525 forEach(this, function(e){ value.push('' + e);}); 2526 return '[' + value.join(', ') + ']'; 2527 }, 2528 2529 eq: function(index) { 2530 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 2531 }, 2532 2533 length: 0, 2534 push: push, 2535 sort: [].sort, 2536 splice: [].splice 2537}; 2538 2539////////////////////////////////////////// 2540// Functions iterating getter/setters. 2541// these functions return self on setter and 2542// value on get. 2543////////////////////////////////////////// 2544var BOOLEAN_ATTR = {}; 2545forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 2546 BOOLEAN_ATTR[lowercase(value)] = value; 2547}); 2548var BOOLEAN_ELEMENTS = {}; 2549forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 2550 BOOLEAN_ELEMENTS[uppercase(value)] = true; 2551}); 2552 2553function getBooleanAttrName(element, name) { 2554 // check dom last since we will most likely fail on name 2555 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 2556 2557 // booleanAttr is here twice to minimize DOM access 2558 return booleanAttr && BOOLEAN_ELEMENTS[element.nodeName] && booleanAttr; 2559} 2560 2561forEach({ 2562 data: jqLiteData, 2563 inheritedData: jqLiteInheritedData, 2564 2565 scope: function(element) { 2566 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2567 return jqLite(element).data('$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 2568 }, 2569 2570 isolateScope: function(element) { 2571 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2572 return jqLite(element).data('$isolateScope') || jqLite(element).data('$isolateScopeNoTemplate'); 2573 }, 2574 2575 controller: jqLiteController, 2576 2577 injector: function(element) { 2578 return jqLiteInheritedData(element, '$injector'); 2579 }, 2580 2581 removeAttr: function(element,name) { 2582 element.removeAttribute(name); 2583 }, 2584 2585 hasClass: jqLiteHasClass, 2586 2587 css: function(element, name, value) { 2588 name = camelCase(name); 2589 2590 if (isDefined(value)) { 2591 element.style[name] = value; 2592 } else { 2593 var val; 2594 2595 if (msie <= 8) { 2596 // this is some IE specific weirdness that jQuery 1.6.4 does not sure why 2597 val = element.currentStyle && element.currentStyle[name]; 2598 if (val === '') val = 'auto'; 2599 } 2600 2601 val = val || element.style[name]; 2602 2603 if (msie <= 8) { 2604 // jquery weirdness :-/ 2605 val = (val === '') ? undefined : val; 2606 } 2607 2608 return val; 2609 } 2610 }, 2611 2612 attr: function(element, name, value){ 2613 var lowercasedName = lowercase(name); 2614 if (BOOLEAN_ATTR[lowercasedName]) { 2615 if (isDefined(value)) { 2616 if (!!value) { 2617 element[name] = true; 2618 element.setAttribute(name, lowercasedName); 2619 } else { 2620 element[name] = false; 2621 element.removeAttribute(lowercasedName); 2622 } 2623 } else { 2624 return (element[name] || 2625 (element.attributes.getNamedItem(name)|| noop).specified) 2626 ? lowercasedName 2627 : undefined; 2628 } 2629 } else if (isDefined(value)) { 2630 element.setAttribute(name, value); 2631 } else if (element.getAttribute) { 2632 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 2633 // some elements (e.g. Document) don't have get attribute, so return undefined 2634 var ret = element.getAttribute(name, 2); 2635 // normalize non-existing attributes to undefined (as jQuery) 2636 return ret === null ? undefined : ret; 2637 } 2638 }, 2639 2640 prop: function(element, name, value) { 2641 if (isDefined(value)) { 2642 element[name] = value; 2643 } else { 2644 return element[name]; 2645 } 2646 }, 2647 2648 text: (function() { 2649 var NODE_TYPE_TEXT_PROPERTY = []; 2650 if (msie < 9) { 2651 NODE_TYPE_TEXT_PROPERTY[1] = 'innerText'; /** Element **/ 2652 NODE_TYPE_TEXT_PROPERTY[3] = 'nodeValue'; /** Text **/ 2653 } else { 2654 NODE_TYPE_TEXT_PROPERTY[1] = /** Element **/ 2655 NODE_TYPE_TEXT_PROPERTY[3] = 'textContent'; /** Text **/ 2656 } 2657 getText.$dv = ''; 2658 return getText; 2659 2660 function getText(element, value) { 2661 var textProp = NODE_TYPE_TEXT_PROPERTY[element.nodeType]; 2662 if (isUndefined(value)) { 2663 return textProp ? element[textProp] : ''; 2664 } 2665 element[textProp] = value; 2666 } 2667 })(), 2668 2669 val: function(element, value) { 2670 if (isUndefined(value)) { 2671 if (nodeName_(element) === 'SELECT' && element.multiple) { 2672 var result = [];
2673 forEach(element.options, function (option) { 2674 if (option.selected) { 2675 result.push(option.value || option.text); 2676 } 2677 }); 2678 return result.length === 0 ? null : result; 2679 } 2680 return element.value; 2681 } 2682 element.value = value; 2683 }, 2684 2685 html: function(element, value) { 2686 if (isUndefined(value)) { 2687 return element.innerHTML; 2688 } 2689 for (var i = 0, childNodes = element.childNodes; i < childNodes.length; i++) { 2690 jqLiteDealoc(childNodes[i]); 2691 } 2692 element.innerHTML = value; 2693 }, 2694 2695 empty: jqLiteEmpty 2696}, function(fn, name){ 2697 /** 2698 * Properties: writes return selection, reads return first value 2699 */ 2700 JQLite.prototype[name] = function(arg1, arg2) { 2701 var i, key; 2702 2703 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 2704 // in a way that survives minification. 2705 // jqLiteEmpty takes no arguments but is a setter. 2706 if (fn !== jqLiteEmpty && 2707 (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { 2708 if (isObject(arg1)) { 2709 2710 // we are a write, but the object properties are the key/values 2711 for (i = 0; i < this.length; i++) { 2712 if (fn === jqLiteData) { 2713 // data() takes the whole object in jQuery 2714 fn(this[i], arg1); 2715 } else { 2716 for (key in arg1) { 2717 fn(this[i], key, arg1[key]); 2718 } 2719 } 2720 } 2721 // return self for chaining 2722 return this; 2723 } else { 2724 // we are a read, so read the first child. 2725 var value = fn.$dv; 2726 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 2727 var jj = (value === undefined) ? Math.min(this.length, 1) : this.length; 2728 for (var j = 0; j < jj; j++) { 2729 var nodeValue = fn(this[j], arg1, arg2); 2730 value = value ? value + nodeValue : nodeValue; 2731 } 2732 return value; 2733 } 2734 } else { 2735 // we are a write, so apply to all children 2736 for (i = 0; i < this.length; i++) { 2737 fn(this[i], arg1, arg2); 2738 } 2739 // return self for chaining 2740 return this; 2741 } 2742 }; 2743}); 2744 2745function createEventHandler(element, events) { 2746 var eventHandler = function (event, type) { 2747 if (!event.preventDefault) { 2748 event.preventDefault = function() { 2749 event.returnValue = false; //ie 2750 }; 2751 } 2752 2753 if (!event.stopPropagation) { 2754 event.stopPropagation = function() { 2755 event.cancelBubble = true; //ie 2756 }; 2757 } 2758 2759 if (!event.target) { 2760 event.target = event.srcElement || document; 2761 } 2762 2763 if (isUndefined(event.defaultPrevented)) { 2764 var prevent = event.preventDefault; 2765 event.preventDefault = function() { 2766 event.defaultPrevented = true; 2767 prevent.call(event); 2768 }; 2769 event.defaultPrevented = false; 2770 } 2771 2772 event.isDefaultPrevented = function() { 2773 return event.defaultPrevented || event.returnValue === false; 2774 }; 2775 2776 // Copy event handlers in case event handlers array is modified during execution. 2777 var eventHandlersCopy = shallowCopy(events[type || event.type] || []); 2778 2779 forEach(eventHandlersCopy, function(fn) { 2780 fn.call(element, event); 2781 }); 2782 2783 // Remove monkey-patched methods (IE), 2784 // as they would cause memory leaks in IE8. 2785 if (msie <= 8) { 2786 // IE7/8 does not allow to delete property on native object 2787 event.preventDefault = null; 2788 event.stopPropagation = null; 2789 event.isDefaultPrevented = null; 2790 } else { 2791 // It shouldn't affect normal browsers (native methods are defined on prototype). 2792 delete event.preventDefault; 2793 delete event.stopPropagation; 2794 delete event.isDefaultPrevented; 2795 } 2796 }; 2797 eventHandler.elem = element; 2798 return eventHandler; 2799} 2800 2801////////////////////////////////////////// 2802// Functions iterating traversal. 2803// These functions chain results into a single 2804// selector.
2805////////////////////////////////////////// 2806forEach({ 2807 removeData: jqLiteRemoveData, 2808 2809 dealoc: jqLiteDealoc, 2810 2811 on: function onFn(element, type, fn, unsupported){ 2812 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 2813 2814 var events = jqLiteExpandoStore(element, 'events'), 2815 handle = jqLiteExpandoStore(element, 'handle'); 2816 2817 if (!events) jqLiteExpandoStore(element, 'events', events = {}); 2818 if (!handle) jqLiteExpandoStore(element, 'handle', handle = createEventHandler(element, events)); 2819 2820 forEach(type.split(' '), function(type){ 2821 var eventFns = events[type]; 2822 2823 if (!eventFns) { 2824 if (type == 'mouseenter' || type == 'mouseleave') { 2825 var contains = document.body.contains || document.body.compareDocumentPosition ? 2826 function( a, b ) { 2827 // jshint bitwise: false 2828 var adown = a.nodeType === 9 ? a.documentElement : a, 2829 bup = b && b.parentNode; 2830 return a === bup || !!( bup && bup.nodeType === 1 && ( 2831 adown.contains ? 2832 adown.contains( bup ) : 2833 a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 2834 )); 2835 } : 2836 function( a, b ) { 2837 if ( b ) { 2838 while ( (b = b.parentNode) ) { 2839 if ( b === a ) { 2840 return true; 2841 } 2842 } 2843 } 2844 return false; 2845 }; 2846 2847 events[type] = []; 2848 2849 // Refer to jQuery's implementation of mouseenter & mouseleave 2850 // Read about mouseenter and mouseleave: 2851 // http://www.quirksmode.org/js/events_mouse.html#link8 2852 var eventmap = { mouseleave : "mouseout", mouseenter : "mouseover"}; 2853 2854 onFn(element, eventmap[type], function(event) { 2855 var target = this, related = event.relatedTarget; 2856 // For mousenter/leave call the handler if related is outside the target. 2857 // NB: No relatedTarget if the mouse left/entered the browser window 2858 if ( !related || (related !== target && !contains(target, related)) ){ 2859 handle(event, type); 2860 } 2861 }); 2862 2863 } else { 2864 addEventListenerFn(element, type, handle); 2865 events[type] = []; 2866 } 2867 eventFns = events[type]; 2868 } 2869 eventFns.push(fn); 2870 }); 2871 }, 2872 2873 off: jqLiteOff, 2874 2875 one: function(element, type, fn) { 2876 element = jqLite(element); 2877 2878 //add the listener twice so that when it is called 2879 //you can remove the original function and still be 2880 //able to call element.off(ev, fn) normally 2881 element.on(type, function onFn() { 2882 element.off(type, fn); 2883 element.off(type, onFn); 2884 }); 2885 element.on(type, fn); 2886 }, 2887 2888 replaceWith: function(element, replaceNode) { 2889 var index, parent = element.parentNode; 2890 jqLiteDealoc(element); 2891 forEach(new JQLite(replaceNode), function(node){ 2892 if (index) { 2893 parent.insertBefore(node, index.nextSibling); 2894 } else { 2895 parent.replaceChild(node, element); 2896 } 2897 index = node; 2898 }); 2899 }, 2900 2901 children: function(element) { 2902 var children = []; 2903 forEach(element.childNodes, function(element){ 2904 if (element.nodeType === 1) 2905 children.push(element); 2906 }); 2907 return children; 2908 }, 2909 2910 contents: function(element) { 2911 return element.contentDocument || element.childNodes || []; 2912 }, 2913 2914 append: function(element, node) { 2915 forEach(new JQLite(node), function(child){ 2916 if (element.nodeType === 1 || element.nodeType === 11) { 2917 element.appendChild(child); 2918 } 2919 }); 2920 }, 2921 2922 prepend: function(element, node) { 2923 if (element.nodeType === 1) { 2924 var index = element.firstChild; 2925 forEach(new JQLite(node), function(child){ 2926 element.insertBefore(child, index); 2927 }); 2928 } 2929 }, 2930 2931 wrap: function(element, wrapNode) { 2932 wrapNode = jqLite(wrapNode)[0]; 2933 var parent = element.parentNode; 2934 if (parent) { 2935 parent.replaceChild(wrapNode, element); 2936 } 2937 wrapNode.appendChild(element); 2938 }, 2939 2940 remove: function(element) { 2941 jqLiteDealoc(element); 2942 var parent = element.parentNode; 2943 if (parent) parent.removeChild(element); 2944 }, 2945 2946 after: function(element, newElement) { 2947 var index = element, parent = element.parentNode;
2948 forEach(new JQLite(newElement), function(node){ 2949 parent.insertBefore(node, index.nextSibling); 2950 index = node; 2951 }); 2952 }, 2953 2954 addClass: jqLiteAddClass, 2955 removeClass: jqLiteRemoveClass, 2956 2957 toggleClass: function(element, selector, condition) { 2958 if (selector) { 2959 forEach(selector.split(' '), function(className){ 2960 var classCondition = condition; 2961 if (isUndefined(classCondition)) { 2962 classCondition = !jqLiteHasClass(element, className); 2963 } 2964 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 2965 }); 2966 } 2967 }, 2968 2969 parent: function(element) { 2970 var parent = element.parentNode; 2971 return parent && parent.nodeType !== 11 ? parent : null; 2972 }, 2973 2974 next: function(element) { 2975 if (element.nextElementSibling) { 2976 return element.nextElementSibling; 2977 } 2978 2979 // IE8 doesn't have nextElementSibling 2980 var elm = element.nextSibling; 2981 while (elm != null && elm.nodeType !== 1) { 2982 elm = elm.nextSibling; 2983 } 2984 return elm; 2985 }, 2986 2987 find: function(element, selector) { 2988 if (element.getElementsByTagName) { 2989 return element.getElementsByTagName(selector); 2990 } else { 2991 return []; 2992 } 2993 }, 2994 2995 clone: jqLiteClone, 2996 2997 triggerHandler: function(element, eventName, eventData) { 2998 var eventFns = (jqLiteExpandoStore(element, 'events') || {})[eventName]; 2999 3000 eventData = eventData || []; 3001 3002 var event = [{ 3003 preventDefault: noop, 3004 stopPropagation: noop 3005 }]; 3006 3007 forEach(eventFns, function(fn) { 3008 fn.apply(element, event.concat(eventData)); 3009 }); 3010 } 3011}, function(fn, name){ 3012 /** 3013 * chaining functions 3014 */ 3015 JQLite.prototype[name] = function(arg1, arg2, arg3) { 3016 var value; 3017 for(var i=0; i < this.length; i++) { 3018 if (isUndefined(value)) { 3019 value = fn(this[i], arg1, arg2, arg3); 3020 if (isDefined(value)) { 3021 // any function which returns a value needs to be wrapped 3022 value = jqLite(value); 3023 } 3024 } else { 3025 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3026 } 3027 } 3028 return isDefined(value) ? value : this; 3029 }; 3030 3031 // bind legacy bind/unbind to on/off 3032 JQLite.prototype.bind = JQLite.prototype.on; 3033 JQLite.prototype.unbind = JQLite.prototype.off; 3034}); 3035 3036/** 3037 * Computes a hash of an 'obj'. 3038 * Hash of a: 3039 * string is string 3040 * number is number as string 3041 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3042 * that is also assigned to the $$hashKey property of the object. 3043 * 3044 * @param obj 3045 * @returns {string} hash string such that the same input will have the same hash string. 3046 * The resulting string key is in 'type:hashKey' format. 3047 */ 3048function hashKey(obj) { 3049 var objType = typeof obj, 3050 key; 3051 3052 if (objType == 'object' && obj !== null) { 3053 if (typeof (key = obj.$$hashKey) == 'function') { 3054 // must invoke on object to keep the right this 3055 key = obj.$$hashKey(); 3056 } else if (key === undefined) { 3057 key = obj.$$hashKey = nextUid(); 3058 } 3059 } else { 3060 key = obj; 3061 } 3062 3063 return objType + ':' + key; 3064} 3065 3066/** 3067 * HashMap which can use objects as keys 3068 */ 3069function HashMap(array){ 3070 forEach(array, this.put, this); 3071} 3072HashMap.prototype = { 3073 /** 3074 * Store key value pair 3075 * @param key key to store can be any type 3076 * @param value value to store can be any type 3077 */ 3078 put: function(key, value) { 3079 this[hashKey(key)] = value; 3080 }, 3081 3082 /** 3083 * @param key 3084 * @returns {Object} the value for the key 3085 */ 3086 get: function(key) { 3087 return this[hashKey(key)]; 3088 }, 3089 3090 /** 3091 * Remove the key/value pair 3092 * @param key 3093 */ 3094 remove: function(key) { 3095 var value = this[key = hashKey(key)]; 3096 delete this[key]; 3097 return value; 3098 } 3099}; 3100 3101/** 3102 * @ngdoc function 3103 * @module ng 3104 * @name angular.injector 3105 * @function 3106 * 3107 * @description 3108 * Creates an injector function that can be used for retrieving services as well as for 3109 * dependency injection (see {@link guide/di dependency injection}). 3110 * 3111 3112 * @param {Array.<string|Function>}
3112 modules A list of module functions or their aliases. See 3113 * {@link angular.module}. The `ng` module must be explicitly added. 3114 * @returns {function()} Injector function. See {@link auto.$injector $injector}. 3115 * 3116 * @example 3117 * Typical usage 3118 * ```js 3119 * // create an injector 3120 * var $injector = angular.injector(['ng']); 3121 * 3122 * // use the injector to kick off your application 3123 * // use the type inference to auto inject arguments, or use implicit injection 3124 * $injector.invoke(function($rootScope, $compile, $document){ 3125 * $compile($document)($rootScope); 3126 * $rootScope.$digest(); 3127 * }); 3128 * ``` 3129 * 3130 * Sometimes you want to get access to the injector of a currently running Angular app 3131 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3132 * application has been bootstrapped. You can do this using extra `injector()` added 3133 * to JQuery/jqLite elements. See {@link angular.element}. 3134 * 3135 * *This is fairly rare but could be the case if a third party library is injecting the 3136 * markup.* 3137 * 3138 * In the following example a new block of HTML containing a `ng-controller` 3139 * directive is added to the end of the document body by JQuery. We then compile and link 3140 * it into the current AngularJS scope. 3141 * 3142 * ```js 3143 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3144 * $(document.body).append($div); 3145 * 3146 * angular.element(document).injector().invoke(function($compile) { 3147 * var scope = angular.element($div).scope(); 3148 * $compile($div)(scope); 3149 * }); 3150 * ``` 3151 */ 3152 3153 3154/** 3155 * @ngdoc module 3156 * @name auto 3157 * @description 3158 * 3159 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3160 */ 3161 3162var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; 3163var FN_ARG_SPLIT = /,/; 3164var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3165var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3166var $injectorMinErr = minErr('$injector'); 3167function annotate(fn) { 3168 var $inject, 3169 fnText, 3170 argDecl, 3171 last; 3172 3173 if (typeof fn == 'function') { 3174 if (!($inject = fn.$inject)) { 3175 $inject = []; 3176 if (fn.length) { 3177 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3178 argDecl = fnText.match(FN_ARGS); 3179 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg){ 3180 arg.replace(FN_ARG, function(all, underscore, name){ 3181 $inject.push(name); 3182 }); 3183 }); 3184 } 3185 fn.$inject = $inject; 3186 } 3187 } else if (isArray(fn)) { 3188 last = fn.length - 1; 3189 assertArgFn(fn[last], 'fn'); 3190 $inject = fn.slice(0, last); 3191 } else { 3192 assertArgFn(fn, 'fn', true); 3193 } 3194 return $inject; 3195} 3196 3197/////////////////////////////////////// 3198 3199/** 3200 * @ngdoc service 3201 * @name $injector 3202 * @function 3203 * 3204 * @description 3205 * 3206 * `$injector` is used to retrieve object instances as defined by 3207 * {@link auto.$provide provider}, instantiate types, invoke methods, 3208 * and load modules. 3209 * 3210 * The following always holds true: 3211 * 3212 * ```js 3213 * var $injector = angular.injector(); 3214 * expect($injector.get('$injector')).toBe($injector); 3215 * expect($injector.invoke(function($injector){ 3216 * return $injector; 3217 * }).toBe($injector); 3218 * ``` 3219 * 3220 * # Injection Function Annotation 3221 * 3222 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3223 * following are all valid ways of annotating function with injection arguments and are equivalent. 3224 * 3225 * ```js 3226 * // inferred (only works if code not minified/obfuscated) 3227 * $injector.invoke(function(serviceA){}); 3228 * 3229 * // annotated 3230 * function explicit(serviceA) {}; 3231 * explicit.$inject = ['serviceA']; 3232 * $injector.invoke(explicit); 3233 * 3234 * // inline 3235 * $injector.invoke(['serviceA', function(serviceA){}]); 3236 * ``` 3237 * 3238 * ## Inference 3239 * 3240 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3241 * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with 3242 * minification, and obfuscation tools since these tools change the argument names. 3243 * 3244 * ## `$inject` Annotation 3245 * By adding a `$inject` property onto a function the injection parameters can be specified. 3246 * 3247 * ## Inline 3248 * As an array of injection names, where the last item in the array is the function to call. 3249 */ 3250 3251/** 3252 * @ngdoc method 3253 * @name $injector#get 3254 * 3255 * @description 3256 * Return an instance of the service. 3257 * 3258 * @param {string} name The name of the instance to retrieve. 3259 * @return {*} The instance. 3260 */ 3261 3262/** 3263 * @ngdoc method 3264 * @name $injector#invoke 3265 * 3266 * @description 3267 * Invoke the method and supply the method arguments from the `$injector`. 3268 * 3269 * @param {!Function} fn The function to invoke. Function parameters are injected according to the 3270 * {@link guide/di $inject Annotation} rules. 3271 * @param {Object=} self The `this` for the invoked method. 3272 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3273 * object first, before the `$injector` is consulted. 3274 * @returns {*} the value returned by the invoked `fn` function. 3275 */ 3276 3277/** 3278 * @ngdoc method 3279 * @name $injector#has 3280 * 3281 * @description 3282 * Allows the user to query if the particular service exist. 3283 * 3284 * @param {string} Name of the service to query. 3285 * @returns {boolean} returns true if injector has given service. 3286 */ 3287 3288/** 3289 * @ngdoc method 3290 * @name $injector#instantiate 3291 * @description 3292 * Create a new instance of JS type. The method takes a constructor function invokes the new 3293 * operator and supplies all of the arguments to the constructor function as specified by the 3294 * constructor annotation. 3295 * 3296 * @param {Function} Type Annotated constructor function. 3297 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3298 * object first, before the `$injector` is consulted. 3299 * @returns {Object} new instance of `Type`. 3300 */ 3301 3302/** 3303 * @ngdoc method 3304 * @name $injector#annotate 3305 * 3306 * @description 3307 * Returns an array of service names which the function is requesting for injection. This API is 3308 * used by the injector to determine which services need to be injected into the function when the 3309 * function is invoked. There are three ways in which the function can be annotated with the needed 3310 * dependencies. 3311 * 3312 * # Argument names 3313 * 3314 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3315 * by converting the function into a string using `toString()` method and extracting the argument 3316 * names. 3317 * ```js 3318 * // Given 3319 * function MyController($scope, $route) { 3320 * // ... 3321 * } 3322 * 3323 * // Then 3324 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3325 * ``` 3326 * 3327 * This method does not work with code minification / obfuscation. For this reason the following 3328 * annotation strategies are supported. 3329 * 3330 * # The `$inject` property 3331 * 3332 * If a function has an `$inject` property and its value is an array of strings, then the strings 3333 * represent names of services to be injected into the function. 3334 * ```js 3335 * // Given 3336 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 3337 * // ... 3338 * } 3339 * // Define function dependencies 3340 * MyController['$inject'] = ['$scope', '$route']; 3341 * 3342 * // Then 3343 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3344 * ``` 3345 * 3346 * # The array notation 3347 * 3348 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 3349 * is very inconvenient. In these situations using the array notation to specify the dependencies in 3350 * a way that survives minification is a better choice: 3351 * 3352 * ```js 3353 * // We wish to write this (not minification / obfuscation safe) 3354 * injector.invoke(function($compile, $rootScope) { 3355 * // ... 3356 * }); 3357 * 3358 * // We are forced to write break inlining 3359 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 3360 * // ... 3361 * }; 3362 * tmpFn.$inject = ['$compile', '$rootScope']; 3363 * injector.invoke(tmpFn); 3364 * 3365 * // To better support inline function the inline annotation is supported 3366 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 3367 * // ... 3368 * }]); 3369 * 3370 * // Therefore 3371 * expect(injector.annotate( 3372 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 3373 * ).toEqual(['$compile', '$rootScope']); 3374 * ``` 3375 * 3376 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 3377 * be retrieved as described above. 3378 * 3379 * @returns {Array.<string>} The names of the services which the function requires. 3380 */ 3381 3382 3383 3384 3385/** 3386 * @ngdoc object 3387 * @name $provide 3388 * 3389 * @description 3390 * 3391 * The {@link auto.$provide $provide} service has a number of methods for registering components 3392 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 3393 * {@link angular.Module}. 3394 * 3395 * An Angular **service** is a singleton object created by a **service factory**. These **service 3396 * factories** are functions which, in turn, are created by a **service provider**. 3397 * The **service providers** are constructor functions. When instantiated they must contain a 3398 * property called `$get`, which holds the **service factory** function. 3399 * 3400 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 3401 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 3402 * function to get the instance of the **service**. 3403 * 3404 * Often services have no configuration options and there is no need to add methods to the service 3405 * provider. The provider will be no more than a constructor function with a `$get` property. For 3406 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 3407 * services without specifying a provider. 3408 * 3409 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 3410 * {@link auto.$injector $injector} 3411 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 3412 * providers and services. 3413 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 3414 * services, not providers. 3415 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 3416 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 3417 * given factory function. 3418 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 3419 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 3420 * a new object using the given constructor function. 3421 * 3422 * See the individual methods for more information and examples. 3423 */ 3424 3425/** 3426 * @ngdoc method 3427 * @name $provide#provider 3428 * @description 3429 * 3430 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 3431 * are constructor functions, whose instances are responsible for "providing" a factory for a 3432 * service. 3433 * 3434 * Service provider names start with the name of the service they provide followed by `Provider`. 3435 * For example, the {@link ng.$log $log} service has a provider called 3436 * {@link ng.$logProvider $logProvider}. 3437 * 3438 * Service provider objects can have additional methods which allow configuration of the provider 3439 * and its service. Importantly, you can configure what kind of service is created by the `$get` 3440 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 3441 * method {@link ng.$logProvider#debugEnabled debugEnabled} 3442 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 3443 * console or not. 3444 * 3445 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 3446 'Provider'` key. 3447 * @param {(Object|function())} provider If the provider is: 3448 * 3449 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 3450 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 3451 * - `Constructor`: a new instance of the provider will be created using 3452 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 3453 * 3454 * @returns {Object} registered provider instance 3455 3456 * @example 3457 * 3458 * The following example shows how to create a simple event tracking service and register it using 3459 * {@link auto.$provide#provider $provide.provider()}. 3460 * 3461 * ```js 3462 * // Define the eventTracker provider 3463 * function EventTrackerProvider() { 3464 * var trackingUrl = '/track'; 3465 * 3466 * // A provider method for configuring where the tracked events should been saved 3467 * this.setTrackingUrl = function(url) { 3468 * trackingUrl = url; 3469 * }; 3470 * 3471 * // The service factory function 3472 * this.$get = ['$http', function($http) { 3473 * var trackedEvents = {}; 3474 * return { 3475 * // Call this to track an event 3476 * event: function(event) { 3477 * var count = trackedEvents[event] || 0; 3478 * count += 1; 3479 * trackedEvents[event] = count; 3480 * return count; 3481 * }, 3482 * // Call this to save the tracked events to the trackingUrl 3483 * save: function() { 3484 * $http.post(trackingUrl, trackedEvents); 3485 * } 3486 * }; 3487 * }]; 3488 * } 3489 * 3490 * describe('eventTracker', function() { 3491 * var postSpy; 3492 * 3493 * beforeEach(module(function($provide) { 3494 * // Register the eventTracker provider 3495 * $provide.provider('eventTracker', EventTrackerProvider); 3496 * })); 3497 * 3498 * beforeEach(module(function(eventTrackerProvider) { 3499 * // Configure eventTracker provider 3500 * eventTrackerProvider.setTrackingUrl('/custom-track'); 3501 * })); 3502 * 3503 * it('tracks events', inject(function(eventTracker) { 3504 * expect(eventTracker.event('login')).toEqual(1); 3505 * expect(eventTracker.event('login')).toEqual(2); 3506 * })); 3507 * 3508 * it('saves to the tracking url', inject(function(eventTracker, $http) { 3509 * postSpy = spyOn($http, 'post'); 3510 * eventTracker.event('login'); 3511 * eventTracker.save(); 3512 * expect(postSpy).toHaveBeenCalled(); 3513 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 3514 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 3515 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 3516 * })); 3517 * }); 3518 * ``` 3519 */ 3520 3521/** 3522 * @ngdoc method 3523 * @name $provide#factory 3524 * @description 3525 * 3526 * Register a **service factory**, which will be called to return the service instance. 3527 * This is short for registering a service where its provider consists of only a `$get` property, 3528 * which is the given service factory function. 3529 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 3530 * configure your service in a provider. 3531 * 3532 * @param {string} name The name of the instance. 3533 * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand 3534 * for `$provide.provider(name, {$get: $getFn})`. 3535 * @returns {Object} registered provider instance 3536 * 3537 * @example 3538 * Here is an example of registering a service 3539 * ```js 3540 * $provide.factory('ping', ['$http', function($http) { 3541 * return function ping() { 3542 * return $http.send('/ping'); 3543 * }; 3544 * }]); 3545 * ``` 3546 * You would then inject and use this service like this: 3547 * ```js 3548 * someModule.controller('Ctrl', ['ping', function(ping) { 3549 * ping(); 3550 * }]); 3551 * ``` 3552 */ 3553 3554 3555/** 3556 * @ngdoc method 3557 * @name $provide#service 3558 * @description 3559 * 3560 * Register a **service constructor**, which will be invoked with `new` to create the service 3561 * instance. 3562 * This is short for registering a service where its provider's `$get` property is the service 3563 * constructor function that will be used to instantiate the service instance. 3564 * 3565 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 3566 * as a type/class. 3567 * 3568 * @param {string} name The name of the instance. 3569 * @param {Function} constructor A class (constructor function) that will be instantiated. 3570 * @returns {Object} registered provider instance 3571 * 3572 * @example 3573 * Here is an example of registering a service using 3574 * {@link auto.$provide#service $provide.service(class)}. 3575 * ```js 3576 * var Ping = function($http) { 3577 * this.$http = $http; 3578 * }; 3579 * 3580 * Ping.$inject = ['$http']; 3581 * 3582 * Ping.prototype.send = function() { 3583 * return this.$http.get('/ping'); 3584 * }; 3585 * $provide.service('ping', Ping); 3586 * ``` 3587 * You would then inject and use this service like this: 3588 * ```js 3589 * someModule.controller('Ctrl', ['ping', function(ping) { 3590 * ping.send(); 3591 * }]); 3592 * ``` 3593 */ 3594 3595 3596/** 3597 * @ngdoc method 3598 * @name $provide#value 3599 * @description 3600 * 3601 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 3602 * number, an array, an object or a function. This is short for registering a service where its 3603 * provider's `$get` property is a factory function that takes no arguments and returns the **value 3604 * service**. 3605 * 3606 * Value services are similar to constant services, except that they cannot be injected into a 3607 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 3608 * an Angular 3609 * {@link auto.$provide#decorator decorator}. 3610 * 3611 * @param {string} name The name of the instance. 3612 * @param {*} value The value. 3613 * @returns {Object} registered provider instance 3614 * 3615 * @example 3616 * Here are some examples of creating value services. 3617 * ```js 3618 * $provide.value('ADMIN_USER', 'admin'); 3619 *
3620 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 3621 * 3622 * $provide.value('halfOf', function(value) { 3623 * return value / 2; 3624 * }); 3625 * ``` 3626 */ 3627 3628 3629/** 3630 * @ngdoc method 3631 * @name $provide#constant 3632 * @description 3633 * 3634 * Register a **constant service**, such as a string, a number, an array, an object or a function, 3635 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be 3636 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 3637 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 3638 * 3639 * @param {string} name The name of the constant. 3640 * @param {*} value The constant value. 3641 * @returns {Object} registered instance 3642 * 3643 * @example 3644 * Here a some examples of creating constants: 3645 * ```js 3646 * $provide.constant('SHARD_HEIGHT', 306); 3647 * 3648 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 3649 * 3650 * $provide.constant('double', function(value) { 3651 * return value * 2; 3652 * }); 3653 * ``` 3654 */ 3655 3656 3657/** 3658 * @ngdoc method 3659 * @name $provide#decorator 3660 * @description 3661 * 3662 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 3663 * intercepts the creation of a service, allowing it to override or modify the behaviour of the 3664 * service. The object returned by the decorator may be the original service, or a new service 3665 * object which replaces or wraps and delegates to the original service. 3666 * 3667 * @param {string} name The name of the service to decorate. 3668 * @param {function()} decorator This function will be invoked when the service needs to be 3669 * instantiated and should return the decorated service instance. The function is called using 3670 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 3671 * Local injection arguments: 3672 * 3673 * * `$delegate` - The original service instance, which can be monkey patched, configured, 3674 * decorated or delegated to. 3675 * 3676 * @example 3677 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 3678 * calls to {@link ng.$log#error $log.warn()}. 3679 * ```js 3680 * $provide.decorator('$log', ['$delegate', function($delegate) { 3681 * $delegate.warn = $delegate.error; 3682 * return $delegate; 3683 * }]); 3684 * ``` 3685 */ 3686 3687 3688function createInjector(modulesToLoad) { 3689 var INSTANTIATING = {}, 3690 providerSuffix = 'Provider', 3691 path = [], 3692 loadedModules = new HashMap(), 3693 providerCache = { 3694 $provide: { 3695 provider: supportObject(provider), 3696 factory: supportObject(factory), 3697 service: supportObject(service), 3698 value: supportObject(value), 3699 constant: supportObject(constant), 3700 decorator: decorator 3701 } 3702 }, 3703 providerInjector = (providerCache.$injector = 3704 createInternalInjector(providerCache, function() { 3705 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 3706 })), 3707 instanceCache = {}, 3708 instanceInjector = (instanceCache.$injector = 3709 createInternalInjector(instanceCache, function(servicename) { 3710 var provider = providerInjector.get(servicename + providerSuffix); 3711 return instanceInjector.invoke(provider.$get, provider); 3712 })); 3713 3714 3715 forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); 3716 3717 return instanceInjector; 3718 3719 //////////////////////////////////// 3720 // $provider 3721 //////////////////////////////////// 3722 3723 function supportObject(delegate) { 3724 return function(key, value) { 3725 if (isObject(key)) { 3726 forEach(key, reverseParams(delegate)); 3727 } else { 3728 return delegate(key, value); 3729 } 3730 }; 3731 } 3732 3733 function provider(name, provider_) { 3734 assertNotHasOwnProperty(name, 'service'); 3735 if (isFunction(provider_) || isArray(provider_)) { 3736 provider_ = providerInjector.instantiate(provider_); 3737 } 3738 if (!provider_.$get) { 3739 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 3740 } 3741 return providerCache[name + providerSuffix] = provider_; 3742 } 3743 3744 function factory(name, factoryFn) { return provider(name, { $get: factoryFn }); } 3745 3746 function service(name, constructor) { 3747 return factory(name, ['$injector', function($injector) { 3748 return $injector.instantiate(constructor); 3749 }]); 3750 } 3751 3752 function value(name, val) { return factory(name, valueFn(val)); } 3753 3754 function constant(name, value) { 3755 assertNotHasOwnProperty(name, 'constant'); 3756 providerCache[name] = value; 3757 instanceCache[name] = value; 3758 } 3759 3760 function decorator(serviceName, decorFn) { 3761 var origProvider = providerInjector.get(serviceName + providerSuffix), 3762 orig$get = origProvider.$get; 3763 3764 origProvider.$get = function() { 3765 var origInstance = instanceInjector.invoke(orig$get, origProvider); 3766 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 3767 }; 3768 } 3769 3770 //////////////////////////////////// 3771 // Module Loading 3772 //////////////////////////////////// 3773 function loadModules(modulesToLoad){ 3774 var runBlocks = [], moduleFn, invokeQueue, i, ii;
3775 forEach(modulesToLoad, function(module) { 3776 if (loadedModules.get(module)) return; 3777 loadedModules.put(module, true); 3778 3779 try { 3780 if (isString(module)) { 3781 moduleFn = angularModule(module); 3782 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 3783 3784 for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) { 3785 var invokeArgs = invokeQueue[i], 3786 provider = providerInjector.get(invokeArgs[0]); 3787 3788 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 3789 } 3790 } else if (isFunction(module)) { 3791 runBlocks.push(providerInjector.invoke(module)); 3792 } else if (isArray(module)) { 3793 runBlocks.push(providerInjector.invoke(module)); 3794 } else { 3795 assertArgFn(module, 'module'); 3796 } 3797 } catch (e) { 3798 if (isArray(module)) { 3799 module = module[module.length - 1]; 3800 } 3801 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 3802 // Safari & FF's stack traces don't contain error.message content 3803 // unlike those of Chrome and IE 3804 // So if stack doesn't contain message, we create a new string that contains both. 3805 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 3806 /* jshint -W022 */ 3807 e = e.message + '\n' + e.stack; 3808 } 3809 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 3810 module, e.stack || e.message || e); 3811 } 3812 }); 3813 return runBlocks; 3814 } 3815 3816 //////////////////////////////////// 3817 // internal Injector 3818 //////////////////////////////////// 3819 3820 function createInternalInjector(cache, factory) { 3821 3822 function getService(serviceName) { 3823 if (cache.hasOwnProperty(serviceName)) { 3824 if (cache[serviceName] === INSTANTIATING) { 3825 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- ')); 3826 } 3827 return cache[serviceName]; 3828 } else { 3829 try { 3830 path.unshift(serviceName); 3831 cache[serviceName] = INSTANTIATING; 3832 return cache[serviceName] = factory(serviceName); 3833 } catch (err) { 3834 if (cache[serviceName] === INSTANTIATING) { 3835 delete cache[serviceName]; 3836 } 3837 throw err; 3838 } finally { 3839 path.shift(); 3840 } 3841 } 3842 } 3843 3844 function invoke(fn, self, locals){ 3845 var args = [], 3846 $inject = annotate(fn), 3847 length, i, 3848 key; 3849 3850 for(i = 0, length = $inject.length; i < length; i++) { 3851 key = $inject[i]; 3852 if (typeof key !== 'string') { 3853 throw $injectorMinErr('itkn', 3854 'Incorrect injection token! Expected service name as string, got {0}', key); 3855 } 3856 args.push( 3857 locals && locals.hasOwnProperty(key) 3858 ? locals[key] 3859 : getService(key) 3860 ); 3861 } 3862 if (!fn.$inject) { 3863 // this means that we must be an array. 3864 fn = fn[length]; 3865 } 3866 3867 // http://jsperf.com/angularjs-invoke-apply-vs-switch 3868 // #5388 3869 return fn.apply(self, args); 3870 } 3871 3872 function instantiate(Type, locals) { 3873 var Constructor = function() {}, 3874 instance, returnedValue; 3875 3876 // Check if Type is annotated and use just the given function at n-1 as parameter 3877 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 3878 Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; 3879 instance = new Constructor(); 3880 returnedValue = invoke(Type, instance, locals); 3881 3882 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 3883 } 3884 3885 return { 3886 invoke: invoke, 3887 instantiate: instantiate, 3888 get: getService, 3889 annotate: annotate, 3890 has: function(name) { 3891 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 3892 } 3893 }; 3894 } 3895} 3896 3897/** 3898 * @ngdoc service 3899 * @name $anchorScroll 3900 * @kind function 3901 * @requires $window 3902 * @requires $location 3903 * @requires $rootScope 3904 * 3905 * @description 3906 * When called, it checks current value of `$location.hash()` and scroll to related element, 3907 * according to rules specified in 3908 * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). 3909 * 3910 * It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor. 3911 * This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`. 3912 * 3913 * @example 3914 <example> 3915 <file name="index.html"> 3916 <div id="scrollArea" ng-controller="ScrollCtrl"> 3917 <a ng-click="gotoBottom()">Go to bottom</a> 3918 <a id="bottom"></a> You're at the bottom! 3919 </div> 3920 </file> 3921 <file name="script.js"> 3922 function ScrollCtrl($scope, $location, $anchorScroll) { 3923 $scope.gotoBottom = function (){ 3924 // set the location.hash to the id of
3925 // the element you wish to scroll to. 3926 $location.hash('bottom'); 3927 3928 // call $anchorScroll() 3929 $anchorScroll(); 3930 }; 3931 } 3932 </file> 3933 <file name="style.css"> 3934 #scrollArea { 3935 height: 350px; 3936 overflow: auto; 3937 } 3938 3939 #bottom { 3940 display: block; 3941 margin-top: 2000px; 3942 } 3943 </file> 3944 </example> 3945 */ 3946function $AnchorScrollProvider() { 3947 3948 var autoScrollingEnabled = true; 3949 3950 this.disableAutoScrolling = function() { 3951 autoScrollingEnabled = false; 3952 }; 3953 3954 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 3955 var document = $window.document; 3956 3957 // helper function to get first anchor from a NodeList 3958 // can't use filter.filter, as it accepts only instances of Array 3959 // and IE can't convert NodeList to an array using [].slice 3960 // TODO(vojta): use filter if we change it to accept lists as well 3961 function getFirstAnchor(list) { 3962 var result = null; 3963 forEach(list, function(element) { 3964 if (!result && lowercase(element.nodeName) === 'a') result = element; 3965 }); 3966 return result; 3967 } 3968 3969 function scroll() { 3970 var hash = $location.hash(), elm; 3971 3972 // empty hash, scroll to the top of the page 3973 if (!hash) $window.scrollTo(0, 0); 3974 3975 // element with given id 3976 else if ((elm = document.getElementById(hash))) elm.scrollIntoView(); 3977 3978 // first anchor with given name :-D 3979 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView(); 3980 3981 // no element and hash == 'top', scroll to the top of the page 3982 else if (hash === 'top') $window.scrollTo(0, 0); 3983 } 3984 3985 // does not scroll when user clicks on anchor link that is currently on 3986 // (no url change, no $location.hash() change), browser native does scroll 3987 if (autoScrollingEnabled) { 3988 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 3989 function autoScrollWatchAction() { 3990 $rootScope.$evalAsync(scroll); 3991 }); 3992 } 3993 3994 return scroll; 3995 }]; 3996} 3997 3998var $animateMinErr = minErr('$animate'); 3999 4000/** 4001 * @ngdoc provider 4002 * @name $animateProvider 4003 * 4004 * @description 4005 * Default implementation of $animate that doesn't perform any animations, instead just 4006 * synchronously performs DOM 4007 * updates and calls done() callbacks. 4008 * 4009 * In order to enable animations the ngAnimate module has to be loaded. 4010 * 4011 * To see the functional implementation check out src/ngAnimate/animate.js 4012 */ 4013var $AnimateProvider = ['$provide', function($provide) { 4014 4015 4016 this.$$selectors = {}; 4017 4018 4019 /** 4020 * @ngdoc method 4021 * @name $animateProvider#register 4022 * 4023 * @description 4024 * Registers a new injectable animation factory function. The factory function produces the 4025 * animation object which contains callback functions for each event that is expected to be 4026 * animated. 4027 * 4028 * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` 4029 * must be called once the element animation is complete. If a function is returned then the 4030 * animation service will use this function to cancel the animation whenever a cancel event is 4031 * triggered. 4032 * 4033 * 4034 * ```js 4035 * return { 4036 * eventFn : function(element, done) { 4037 * //code to run the animation 4038 * //once complete, then run done() 4039 * return function cancellationFunction() { 4040 * //code to cancel the animation 4041 * } 4042 * } 4043 * } 4044 * ``` 4045 * 4046 * @param {string} name The name of the animation. 4047 * @param {Function} factory The factory function that will be executed to return the animation 4048 * object. 4049 */ 4050 this.register = function(name, factory) { 4051 var key = name + '-animation'; 4052 if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', 4053 "Expecting class selector starting with '.' got '{0}'.", name); 4054 this.$$selectors[name.substr(1)] = key; 4055 $provide.factory(key, factory); 4056 }; 4057 4058 /** 4059 * @ngdoc method 4060 * @name $animateProvider#classNameFilter 4061 * 4062 * @description 4063 * Sets and/or returns the CSS class regular expression that is checked when performing 4064 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 4065 * therefore enable $animate to attempt to perform an animation on any element. 4066 * When setting the classNameFilter value, animations will only be performed on elements 4067 * that successfully match the filter expression. This in turn can boost performance 4068 * for low-powered devices as well as applications containing a lot of structural operations. 4069 * @param {RegExp=} expression The className expression which will be checked against all animations 4070 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 4071 */ 4072 this.classNameFilter = function(expression) { 4073 if(arguments.length === 1) { 4074 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 4075 } 4076 return this.$$classNameFilter; 4077 }; 4078 4079 this.$get = ['$timeout', '$$asyncCallback', function($timeout, $$asyncCallback) { 4080 4081 function async(fn) { 4082 fn && $$asyncCallback(fn); 4083 } 4084 4085 /** 4086 * 4087 * @ngdoc service 4088 * @name $animate
4089 * @description The $animate service provides rudimentary DOM manipulation functions to 4090 * insert, remove and move elements within the DOM, as well as adding and removing classes. 4091 * This service is the core service used by the ngAnimate $animator service which provides 4092 * high-level animation hooks for CSS and JavaScript. 4093 * 4094 * $animate is available in the AngularJS core, however, the ngAnimate module must be included 4095 * to enable full out animation support. Otherwise, $animate will only perform simple DOM 4096 * manipulation operations. 4097 * 4098 * To learn more about enabling animation support, click here to visit the {@link ngAnimate 4099 * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service 4100 * page}. 4101 */ 4102 return { 4103 4104 /** 4105 * 4106 * @ngdoc method 4107 * @name $animate#enter 4108 * @function 4109 * @description Inserts the element into the DOM either after the `after` element or within 4110 * the `parent` element. Once complete, the done() callback will be fired (if provided). 4111 * @param {DOMElement} element the element which will be inserted into the DOM 4112 * @param {DOMElement} parent the parent element which will append the element as 4113 * a child (if the after element is not present) 4114 * @param {DOMElement} after the sibling element which will append the element 4115 * after itself 4116 * @param {Function=} done callback function that will be called after the element has been 4117 * inserted into the DOM 4118 */ 4119 enter : function(element, parent, after, done) { 4120 if (after) { 4121 after.after(element); 4122 } else { 4123 if (!parent || !parent[0]) { 4124 parent = after.parent(); 4125 } 4126 parent.append(element); 4127 } 4128 async(done); 4129 }, 4130 4131 /** 4132 * 4133 * @ngdoc method 4134 * @name $animate#leave 4135 * @function 4136 * @description Removes the element from the DOM. Once complete, the done() callback will be 4137 * fired (if provided). 4138 * @param {DOMElement} element the element which will be removed from the DOM 4139 * @param {Function=} done callback function that will be called after the element has been 4140 * removed from the DOM 4141 */ 4142 leave : function(element, done) { 4143 element.remove(); 4144 async(done); 4145 }, 4146 4147 /** 4148 * 4149 * @ngdoc method 4150 * @name $animate#move 4151 * @function 4152 * @description Moves the position of the provided element within the DOM to be placed 4153 * either after the `after` element or inside of the `parent` element. Once complete, the 4154 * done() callback will be fired (if provided). 4155 * 4156 * @param {DOMElement} element the element which will be moved around within the 4157 * DOM 4158 * @param {DOMElement} parent the parent element where the element will be 4159 * inserted into (if the after element is not present) 4160 * @param {DOMElement} after the sibling element where the element will be 4161 * positioned next to 4162 * @param {Function=} done the callback function (if provided) that will be fired after the 4163 * element has been moved to its new position 4164 */ 4165 move : function(element, parent, after, done) { 4166 // Do not remove element before insert. Removing will cause data associated with the 4167 // element to be dropped. Insert will implicitly do the remove. 4168 this.enter(element, parent, after, done); 4169 }, 4170 4171 /** 4172 * 4173 * @ngdoc method 4174 * @name $animate#addClass 4175 * @function 4176 * @description Adds the provided className CSS class value to the provided element. Once 4177 * complete, the done() callback will be fired (if provided). 4178 * @param {DOMElement} element the element which will have the className value 4179 * added to it 4180 * @param {string} className the CSS class which will be added to the element 4181 * @param {Function=} done the callback function (if provided) that will be fired after the 4182 * className value has been added to the element 4183 */ 4184 addClass : function(element, className, done) { 4185 className = isString(className) ? 4186 className : 4187 isArray(className) ? className.join(' ') : '';
4188 forEach(element, function (element) { 4189 jqLiteAddClass(element, className); 4190 }); 4191 async(done); 4192 }, 4193 4194 /** 4195 * 4196 * @ngdoc method 4197 * @name $animate#removeClass 4198 * @function 4199 * @description Removes the provided className CSS class value from the provided element. 4200 * Once complete, the done() callback will be fired (if provided). 4201 * @param {DOMElement} element the element which will have the className value 4202 * removed from it 4203 * @param {string} className the CSS class which will be removed from the element 4204 * @param {Function=} done the callback function (if provided) that will be fired after the 4205 * className value has been removed from the element 4206 */ 4207 removeClass : function(element, className, done) { 4208 className = isString(className) ? 4209 className : 4210 isArray(className) ? className.join(' ') : ''; 4211 forEach(element, function (element) { 4212 jqLiteRemoveClass(element, className); 4213 }); 4214 async(done); 4215 }, 4216 4217 /** 4218 * 4219 * @ngdoc method 4220 * @name $animate#setClass 4221 * @function 4222 * @description Adds and/or removes the given CSS classes to and from the element. 4223 * Once complete, the done() callback will be fired (if provided). 4224 * @param {DOMElement} element the element which will it's CSS classes changed 4225 * removed from it 4226 * @param {string} add the CSS classes which will be added to the element 4227 * @param {string} remove the CSS class which will be removed from the element 4228 * @param {Function=} done the callback function (if provided) that will be fired after the 4229 * CSS classes have been set on the element 4230 */ 4231 setClass : function(element, add, remove, done) { 4232 forEach(element, function (element) { 4233 jqLiteAddClass(element, add); 4234 jqLiteRemoveClass(element, remove); 4235 }); 4236 async(done); 4237 }, 4238 4239 enabled : noop 4240 }; 4241 }]; 4242}]; 4243 4244function $$AsyncCallbackProvider(){ 4245 this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { 4246 return $$rAF.supported 4247 ? function(fn) { return $$rAF(fn); } 4248 : function(fn) { 4249 return $timeout(fn, 0, false); 4250 }; 4251 }]; 4252} 4253 4254/** 4255 * ! This is a private undocumented service ! 4256 * 4257 * @name $browser 4258 * @requires $log 4259 * @description 4260 * This object has two goals: 4261 * 4262 * - hide all the global state in the browser caused by the window object 4263 * - abstract away all the browser specific features and inconsistencies 4264 * 4265 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 4266 * service, which can be used for convenient testing of the application without the interaction with 4267 * the real browser apis. 4268 */ 4269/** 4270 * @param {object} window The global window object. 4271 * @param {object} document jQuery wrapped document. 4272 * @param {function()} XHR XMLHttpRequest constructor. 4273 * @param {object} $log console.log or an object with the same interface. 4274 * @param {object} $sniffer $sniffer service 4275 */ 4276function Browser(window, document, $log, $sniffer) { 4277 var self = this, 4278 rawDocument = document[0], 4279 location = window.location, 4280 history = window.history, 4281 setTimeout = window.setTimeout, 4282 clearTimeout = window.clearTimeout, 4283 pendingDeferIds = {}; 4284 4285 self.isMock = false; 4286 4287 var outstandingRequestCount = 0; 4288 var outstandingRequestCallbacks = []; 4289 4290 // TODO(vojta): remove this temporary api 4291 self.$$completeOutstandingRequest = completeOutstandingRequest; 4292 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 4293 4294 /** 4295 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 4296 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 4297 */ 4298 function completeOutstandingRequest(fn) { 4299 try { 4300 fn.apply(null, sliceArgs(arguments, 1)); 4301 } finally { 4302 outstandingRequestCount--; 4303 if (outstandingRequestCount === 0) { 4304 while(outstandingRequestCallbacks.length) { 4305 try { 4306 outstandingRequestCallbacks.pop()(); 4307 } catch (e) { 4308 $log.error(e); 4309 } 4310 } 4311 } 4312 } 4313 } 4314 4315 /** 4316 * @private 4317 * Note: this method is used only by scenario runner 4318 * TODO(vojta): prefix this method with $$ ? 4319 * @param {function()} callback Function that will be called when no outstanding request 4320 */ 4321 self.notifyWhenNoOutstandingRequests = function(callback) { 4322 // force browser to execute all pollFns - this is needed so that cookies and other pollers fire 4323 // at some deterministic time in respect to the test runner's actions. Leaving things up to the 4324 // regular poller would result in flaky tests.
4325 forEach(pollFns, function(pollFn){ pollFn(); }); 4326 4327 if (outstandingRequestCount === 0) { 4328 callback(); 4329 } else { 4330 outstandingRequestCallbacks.push(callback); 4331 } 4332 }; 4333 4334 ////////////////////////////////////////////////////////////// 4335 // Poll Watcher API 4336 ////////////////////////////////////////////////////////////// 4337 var pollFns = [], 4338 pollTimeout; 4339 4340 /** 4341 * @name $browser#addPollFn 4342 * 4343 * @param {function()} fn Poll function to add 4344 * 4345 * @description 4346 * Adds a function to the list of functions that poller periodically executes, 4347 * and starts polling if not started yet. 4348 * 4349 * @returns {function()} the added function 4350 */ 4351 self.addPollFn = function(fn) { 4352 if (isUndefined(pollTimeout)) startPoller(100, setTimeout); 4353 pollFns.push(fn); 4354 return fn; 4355 }; 4356 4357 /** 4358 * @param {number} interval How often should browser call poll functions (ms) 4359 * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. 4360 * 4361 * @description 4362 * Configures the poller to run in the specified intervals, using the specified 4363 * setTimeout fn and kicks it off. 4364 */ 4365 function startPoller(interval, setTimeout) { 4366 (function check() { 4367 forEach(pollFns, function(pollFn){ pollFn(); }); 4368 pollTimeout = setTimeout(check, interval); 4369 })(); 4370 } 4371 4372 ////////////////////////////////////////////////////////////// 4373 // URL API 4374 ////////////////////////////////////////////////////////////// 4375 4376 var lastBrowserUrl = location.href, 4377 baseElement = document.find('base'), 4378 newLocation = null; 4379 4380 /** 4381 * @name $browser#url 4382 * 4383 * @description 4384 * GETTER: 4385 * Without any argument, this method just returns current value of location.href. 4386 * 4387 * SETTER: 4388 * With at least one argument, this method sets url to new value. 4389 * If html5 history api supported, pushState/replaceState is used, otherwise 4390 * location.href/location.replace is used. 4391 * Returns its own instance to allow chaining 4392 * 4393 * NOTE: this api is intended for use only by the $location service. Please use the 4394 * {@link ng.$location $location service} to change url. 4395 * 4396 * @param {string} url New url (when used as setter) 4397 * @param {boolean=} replace Should new url replace current history record ? 4398 */ 4399 self.url = function(url, replace) { 4400 // Android Browser BFCache causes location, history reference to become stale. 4401 if (location !== window.location) location = window.location; 4402 if (history !== window.history) history = window.history; 4403 4404 // setter 4405 if (url) { 4406 if (lastBrowserUrl == url) return; 4407 lastBrowserUrl = url; 4408 if ($sniffer.history) { 4409 if (replace) history.replaceState(null, '', url); 4410 else { 4411 history.pushState(null, '', url); 4412 // Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 4413 baseElement.attr('href', baseElement.attr('href')); 4414 } 4415 } else { 4416 newLocation = url; 4417 if (replace) { 4418 location.replace(url); 4419 } else { 4420 location.href = url; 4421 } 4422 } 4423 return self; 4424 // getter 4425 } else { 4426 // - newLocation is a workaround for an IE7-9 issue with location.replace and location.href 4427 // methods not updating location.href synchronously. 4428 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 4429 return newLocation || location.href.replace(/%27/g,"'"); 4430 } 4431 }; 4432 4433 var urlChangeListeners = [], 4434 urlChangeInit = false; 4435 4436 function fireUrlChange() { 4437 newLocation = null; 4438 if (lastBrowserUrl == self.url()) return; 4439 4440 lastBrowserUrl = self.url(); 4441 forEach(urlChangeListeners, function(listener) { 4442 listener(self.url()); 4443 }); 4444 } 4445 4446 /** 4447 * @name $browser#onUrlChange 4448 * 4449 * @description 4450 * Register callback function that will be called, when url changes. 4451 * 4452 * It's only called when the url is changed from outside of angular: 4453 * - user types different url into address bar 4454 * - user clicks on history (forward/back) button 4455 * - user clicks on a link 4456 * 4457 * It's not called when url is changed by $browser.url() method 4458 * 4459 * The listener gets called with new url as parameter. 4460 * 4461 * NOTE: this api is intended for use only by the $location service. Please use the 4462 * {@link ng.$location $location service} to monitor url changes in angular apps. 4463 * 4464 * @param {function(string)} listener Listener function to be called when url changes. 4465 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 4466 */ 4467 self.onUrlChange = function(callback) { 4468 // TODO(vojta): refactor to use node's syntax for events 4469 if (!urlChangeInit) { 4470 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 4471 // don't fire popstate when user change the address bar and don't fire hashchange when url 4472 // changed by push/replaceState 4473 4474 // html5 history api - popstate event 4475 if ($sniffer.history) jqLite(window).on('popstate', fireUrlChange); 4476 // hashchange event 4477 if ($sniffer.hashchange) jqLite(window).on('hashchange', fireUrlChange); 4478 // polling 4479 else self.addPollFn(fireUrlChange); 4480 4481 urlChangeInit = true; 4482 } 4483 4484 urlChangeListeners.push(callback); 4485 return callback; 4486 }; 4487 4488 ////////////////////////////////////////////////////////////// 4489 // Misc API 4490 ////////////////////////////////////////////////////////////// 4491 4492 /** 4493 * @name $browser#baseHref 4494 * 4495 * @description 4496 * Returns current <base href> 4497 * (always relative - without domain) 4498 * 4499 * @returns {string} The current base href 4500 */ 4501 self.baseHref = function() { 4502 var href = baseElement.attr('href'); 4503 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 4504 }; 4505 4506 ////////////////////////////////////////////////////////////// 4507 // Cookies API 4508 ////////////////////////////////////////////////////////////// 4509 var lastCookies = {}; 4510 var lastCookieString = ''; 4511 var cookiePath = self.baseHref(); 4512 4513 /** 4514 * @name $browser#cookies 4515 * 4516 * @param {string=} name Cookie name 4517 * @param {string=} value Cookie value 4518 * 4519 * @description 4520 * The cookies method provides a 'private' low level access to browser cookies. 4521 * It is not meant to be used directly, use the $cookie service instead. 4522 * 4523 * The return values vary depending on the arguments that the method was called with as follows: 4524 * 4525 * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify 4526 * it 4527 * - cookies(name, value) -> set name to value, if value is undefined delete the cookie 4528 * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that 4529 * way) 4530 * 4531 * @returns {Object} Hash of all cookies (if called without any parameter) 4532 */ 4533 self.cookies = function(name, value) { 4534 /* global escape: false, unescape: false */ 4535 var cookieLength, cookieArray, cookie, i, index; 4536 4537 if (name) { 4538 if (value === undefined) { 4539 rawDocument.cookie = escape(name) + "=;
4539path=" + cookiePath + 4540 ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; 4541 } else { 4542 if (isString(value)) { 4543 cookieLength = (rawDocument.cookie = escape(name) + '=' + escape(value) + 4544 ';path=' + cookiePath).length + 1; 4545 4546 // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: 4547 // - 300 cookies 4548 // - 20 cookies per unique domain 4549 // - 4096 bytes per cookie 4550 if (cookieLength > 4096) { 4551 $log.warn("Cookie '"+ name + 4552 "' possibly not set or overflowed because it was too large ("+ 4553 cookieLength + " > 4096 bytes)!"); 4554 } 4555 } 4556 } 4557 } else { 4558 if (rawDocument.cookie !== lastCookieString) { 4559 lastCookieString = rawDocument.cookie; 4560 cookieArray = lastCookieString.split("; "); 4561 lastCookies = {}; 4562 4563 for (i = 0; i < cookieArray.length; i++) { 4564 cookie = cookieArray[i]; 4565 index = cookie.indexOf('='); 4566 if (index > 0) { //ignore nameless cookies 4567 name = unescape(cookie.substring(0, index)); 4568 // the first value that is seen for a cookie is the most 4569 // specific one. values for the same cookie name that 4570 // follow are for less specific paths. 4571 if (lastCookies[name] === undefined) { 4572 lastCookies[name] = unescape(cookie.substring(index + 1)); 4573 } 4574 } 4575 } 4576 } 4577 return lastCookies; 4578 } 4579 }; 4580 4581 4582 /** 4583 * @name $browser#defer 4584 * @param {function()} fn A function, who's execution should be deferred. 4585 * @param {number=} [delay=0] of milliseconds to defer the function execution. 4586 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 4587 * 4588 * @description 4589 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 4590 * 4591 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 4592 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 4593 * via `$browser.defer.flush()`. 4594 * 4595 */ 4596 self.defer = function(fn, delay) { 4597 var timeoutId; 4598 outstandingRequestCount++; 4599 timeoutId = setTimeout(function() { 4600 delete pendingDeferIds[timeoutId]; 4601 completeOutstandingRequest(fn); 4602 }, delay || 0); 4603 pendingDeferIds[timeoutId] = true; 4604 return timeoutId; 4605 }; 4606 4607 4608 /** 4609 * @name $browser#defer.cancel 4610 * 4611 * @description 4612 * Cancels a deferred task identified with `deferId`. 4613 * 4614 * @param {*} deferId Token returned by the `$browser.defer` function. 4615 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 4616 * canceled. 4617 */ 4618 self.defer.cancel = function(deferId) { 4619 if (pendingDeferIds[deferId]) { 4620 delete pendingDeferIds[deferId]; 4621 clearTimeout(deferId); 4622 completeOutstandingRequest(noop); 4623 return true; 4624 } 4625 return false; 4626 }; 4627 4628} 4629 4630function $BrowserProvider(){ 4631 this.$get = ['$window', '$log', '$sniffer', '$document', 4632 function( $window, $log, $sniffer, $document){ 4633 return new Browser($window, $document, $log, $sniffer); 4634 }]; 4635} 4636 4637/** 4638 * @ngdoc service 4639 * @name $cacheFactory 4640 * 4641 * @description 4642 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 4643 * them. 4644 * 4645 * ```js 4646 * 4647 * var cache = $cacheFactory('cacheId'); 4648 * expect($cacheFactory.get('cacheId')).toBe(cache); 4649 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 4650 * 4651 * cache.put("key", "value"); 4652 * cache.put("another key", "another value"); 4653 * 4654 * // We've specified no options on creation 4655 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 4656 * 4657 * ``` 4658 * 4659 * 4660 * @param {string} cacheId Name or id of the newly created cache. 4661 * @param {object=} options Options object that specifies the cache behavior. Properties: 4662 * 4663 * - `{number=}` `capacity` â turns the cache into LRU cache. 4664 * 4665 * @returns {object} Newly created cache object with the following set of methods: 4666 * 4667 * - `{object}` `info()` â Returns id, size, and options of cache. 4668 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 4669 * it. 4670 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 4671 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 4672 * - `{void}` `removeAll()` â Removes all cached values. 4673 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 4674 * 4675 * @example 4676 <example module="cacheExampleApp"> 4677 <file name="index.html"> 4678 <div ng-controller="CacheController"> 4679 <input ng-model="newCacheKey" placeholder="Key"> 4680 <input ng-model="newCacheValue" placeholder="Value"> 4681 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 4682 4683 <p ng-if="keys.length">Cached Values</p> 4684 <div ng-repeat="key in keys">
4685 <span ng-bind="key"></span> 4686 <span>: </span> 4687 <b ng-bind="cache.get(key)"></b> 4688 </div> 4689 4690 <p>Cache Info</p> 4691 <div ng-repeat="(key, value) in cache.info()"> 4692 <span ng-bind="key"></span> 4693 <span>: </span> 4694 <b ng-bind="value"></b> 4695 </div> 4696 </div> 4697 </file> 4698 <file name="script.js"> 4699 angular.module('cacheExampleApp', []). 4700 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 4701 $scope.keys = []; 4702 $scope.cache = $cacheFactory('cacheId'); 4703 $scope.put = function(key, value) { 4704 $scope.cache.put(key, value); 4705 $scope.keys.push(key); 4706 }; 4707 }]); 4708 </file> 4709 <file name="style.css"> 4710 p { 4711 margin: 10px 0 3px; 4712 } 4713 </file> 4714 </example> 4715 */ 4716function $CacheFactoryProvider() { 4717 4718 this.$get = function() { 4719 var caches = {}; 4720 4721 function cacheFactory(cacheId, options) { 4722 if (cacheId in caches) { 4723 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 4724 } 4725 4726 var size = 0, 4727 stats = extend({}, options, {id: cacheId}), 4728 data = {}, 4729 capacity = (options && options.capacity) || Number.MAX_VALUE, 4730 lruHash = {}, 4731 freshEnd = null, 4732 staleEnd = null; 4733 4734 /** 4735 * @ngdoc type 4736 * @name $cacheFactory.Cache 4737 * 4738 * @description 4739 * A cache object used to store and retrieve data, primarily used by 4740 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 4741 * templates and other data. 4742 * 4743 * ```js 4744 * angular.module('superCache') 4745 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 4746 * return $cacheFactory('super-cache'); 4747 * }]); 4748 * ``` 4749 * 4750 * Example test: 4751 * 4752 * ```js 4753 * it('should behave like a cache', inject(function(superCache) { 4754 * superCache.put('key', 'value'); 4755 * superCache.put('another key', 'another value'); 4756 * 4757 * expect(superCache.info()).toEqual({ 4758 * id: 'super-cache', 4759 * size: 2 4760 * }); 4761 * 4762 * superCache.remove('another key'); 4763 * expect(superCache.get('another key')).toBeUndefined(); 4764 * 4765 * superCache.removeAll(); 4766 * expect(superCache.info()).toEqual({ 4767 * id: 'super-cache', 4768 * size: 0 4769 * }); 4770 * })); 4771 * ``` 4772 */ 4773 return caches[cacheId] = { 4774 4775 /** 4776 * @ngdoc method 4777 * @name $cacheFactory.Cache#put 4778 * @function 4779 * 4780 * @description 4781 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 4782 * retrieved later, and incrementing the size of the cache if the key was not already 4783 * present in the cache. If behaving like an LRU cache, it will also remove stale 4784 * entries from the set. 4785 * 4786 * It will not insert undefined values into the cache. 4787 * 4788 * @param {string} key the key under which the cached data is stored. 4789 * @param {*} value the value to store alongside the key. If it is undefined, the key 4790 * will not be stored. 4791 * @returns {*} the value stored. 4792 */ 4793 put: function(key, value) { 4794 if (capacity < Number.MAX_VALUE) { 4795 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 4796 4797 refresh(lruEntry); 4798 } 4799 4800 if (isUndefined(value)) return; 4801 if (!(key in data)) size++; 4802 data[key] = value; 4803 4804 if (size > capacity) { 4805 this.remove(staleEnd.key); 4806 } 4807 4808 return value; 4809 }, 4810 4811 /** 4812 * @ngdoc method 4813 * @name $cacheFactory.Cache#get 4814 * @function 4815 *
4816 * @description 4817 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 4818 * 4819 * @param {string} key the key of the data to be retrieved 4820 * @returns {*} the value stored. 4821 */ 4822 get: function(key) { 4823 if (capacity < Number.MAX_VALUE) { 4824 var lruEntry = lruHash[key]; 4825 4826 if (!lruEntry) return; 4827 4828 refresh(lruEntry); 4829 } 4830 4831 return data[key]; 4832 }, 4833 4834 4835 /** 4836 * @ngdoc method 4837 * @name $cacheFactory.Cache#remove 4838 * @function 4839 * 4840 * @description 4841 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 4842 * 4843 * @param {string} key the key of the entry to be removed 4844 */ 4845 remove: function(key) { 4846 if (capacity < Number.MAX_VALUE) { 4847 var lruEntry = lruHash[key]; 4848 4849 if (!lruEntry) return; 4850 4851 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 4852 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 4853 link(lruEntry.n,lruEntry.p); 4854 4855 delete lruHash[key]; 4856 } 4857 4858 delete data[key]; 4859 size--; 4860 }, 4861 4862 4863 /** 4864 * @ngdoc method 4865 * @name $cacheFactory.Cache#removeAll 4866 * @function 4867 * 4868 * @description 4869 * Clears the cache object of any entries. 4870 */ 4871 removeAll: function() { 4872 data = {}; 4873 size = 0; 4874 lruHash = {}; 4875 freshEnd = staleEnd = null; 4876 }, 4877 4878 4879 /** 4880 * @ngdoc method 4881 * @name $cacheFactory.Cache#destroy 4882 * @function 4883 * 4884 * @description 4885 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 4886 * removing it from the {@link $cacheFactory $cacheFactory} set. 4887 */ 4888 destroy: function() { 4889 data = null; 4890 stats = null; 4891 lruHash = null; 4892 delete caches[cacheId]; 4893 }, 4894 4895 4896 /** 4897 * @ngdoc method 4898 * @name $cacheFactory.Cache#info 4899 * @function 4900 * 4901 * @description 4902 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 4903 * 4904 * @returns {object} an object with the following properties: 4905 * <ul> 4906 * <li>**id**: the id of the cache instance</li> 4907 * <li>**size**: the number of entries kept in the cache instance</li> 4908 * <li>**...**: any additional properties from the options object when creating the 4909 * cache.</li> 4910 * </ul> 4911 */ 4912 info: function() { 4913 return extend({}, stats, {size: size}); 4914 } 4915 }; 4916 4917 4918 /** 4919 * makes the `entry` the freshEnd of the LRU linked list 4920 */ 4921 function refresh(entry) { 4922 if (entry != freshEnd) { 4923 if (!staleEnd) { 4924 staleEnd = entry; 4925 } else if (staleEnd == entry) { 4926 staleEnd = entry.n;
vendor: 4,907 bytes, lines 4927-5090
4927 } 4928 4929 link(entry.n, entry.p); 4930 link(entry, freshEnd); 4931 freshEnd = entry; 4932 freshEnd.n = null; 4933 } 4934 } 4935 4936 4937 /** 4938 * bidirectionally links two entries of the LRU linked list 4939 */ 4940 function link(nextEntry, prevEntry) { 4941 if (nextEntry != prevEntry) { 4942 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 4943 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 4944 } 4945 } 4946 } 4947 4948 4949 /** 4950 * @ngdoc method 4951 * @name $cacheFactory#info 4952 * 4953 * @description 4954 * Get information about all the of the caches that have been created 4955 * 4956 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 4957 */ 4958 cacheFactory.info = function() { 4959 var info = {}; 4960 forEach(caches, function(cache, cacheId) { 4961 info[cacheId] = cache.info(); 4962 }); 4963 return info; 4964 }; 4965 4966 4967 /** 4968 * @ngdoc method 4969 * @name $cacheFactory#get 4970 * 4971 * @description 4972 * Get access to a cache object by the `cacheId` used when it was created. 4973 * 4974 * @param {string} cacheId Name or id of a cache to access. 4975 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 4976 */ 4977 cacheFactory.get = function(cacheId) { 4978 return caches[cacheId]; 4979 }; 4980 4981 4982 return cacheFactory; 4983 }; 4984} 4985 4986/** 4987 * @ngdoc service 4988 * @name $templateCache 4989 * 4990 * @description 4991 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 4992 * can load templates directly into the cache in a `script` tag, or by consuming the 4993 * `$templateCache` service directly. 4994 * 4995 * Adding via the `script` tag: 4996 * 4997 * ```html 4998 * <script type="text/ng-template" id="templateId.html"> 4999 * <p>This is the content of the template</p> 5000 * </script> 5001 * ``` 5002 * 5003 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 5004 * the document, but it must be below the `ng-app` definition. 5005 * 5006 * Adding via the $templateCache service: 5007 * 5008 * ```js 5009 * var myApp = angular.module('myApp', []); 5010 * myApp.run(function($templateCache) { 5011 * $templateCache.put('templateId.html', 'This is the content of the template'); 5012 * }); 5013 * ``` 5014 * 5015 * To retrieve the template later, simply use it in your HTML: 5016 * ```html 5017 * <div ng-include=" 'templateId.html' "></div> 5018 * ``` 5019 * 5020 * or get it via Javascript: 5021 * ```js 5022 * $templateCache.get('templateId.html') 5023 * ``` 5024 * 5025 * See {@link ng.$cacheFactory $cacheFactory}. 5026 * 5027 */ 5028function $TemplateCacheProvider() { 5029 this.$get = ['$cacheFactory', function($cacheFactory) { 5030 return $cacheFactory('templates'); 5031 }]; 5032} 5033 5034/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 5035 * 5036 * DOM-related variables: 5037 * 5038 * - "node" - DOM Node 5039 * - "element" - DOM Element or Node 5040 * - "$node" or "$element" - jqLite-wrapped node or element 5041 * 5042 * 5043 * Compiler related stuff: 5044 * 5045 * - "linkFn" - linking fn of a single directive 5046 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 5047 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 5048 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 5049 */ 5050 5051 5052/** 5053 * @ngdoc service 5054 * @name $compile 5055 * @function 5056 * 5057 * @description 5058 * Compiles an HTML string or DOM into a template and produces a template function, which 5059 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 5060 * 5061 * The compilation is a process of walking the DOM tree and matching DOM elements to 5062 * {@link ng.$compileProvider#directive directives}. 5063 * 5064 * <div class="alert alert-warning"> 5065 * **Note:** This document is an in-depth reference of all directive options. 5066 * For a gentle introduction to directives with examples of common use cases, 5067 * see the {@link guide/directive directive guide}. 5068 * </div> 5069 * 5070 * ## Comprehensive Directive API 5071 * 5072 * There are many different options for a directive. 5073 * 5074 * The difference resides in the return value of the factory function. 5075 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 5076 * or just the `postLink` function (all other properties will have the default values). 5077 * 5078 * <div class="alert alert-success"> 5079 * **Best Practice:** It's recommended to use the "directive definition object" form. 5080 * </div> 5081 * 5082 * Here's an example directive declared with a Directive Definition Object: 5083 * 5084 * ```js 5085 * var myModule = angular.module(...); 5086 * 5087 * myModule.directive('directiveName', function factory(injectables) { 5088 * var directiveDefinitionObject = { 5089 * priority: 0, 5090 * template: '<div></div>
5090', // or // function(tElement, tAttrs) { ... }, 5091 * // or 5092 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 5093 * replace: false, 5094 * transclude: false, 5095 * restrict: 'A', 5096 * scope: false, 5097 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 5098 * controllerAs: 'stringAlias', 5099 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 5100 * compile: function compile(tElement, tAttrs, transclude) { 5101 * return { 5102 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 5103 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 5104 * } 5105 * // or 5106 * // return function postLink( ... ) { ... } 5107 * }, 5108 * // or 5109 * // link: { 5110 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 5111 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 5112 * // } 5113 * // or 5114 * // link: function postLink( ... ) { ... } 5115 * }; 5116 * return directiveDefinitionObject; 5117 * }); 5118 * ``` 5119 * 5120 * <div class="alert alert-warning"> 5121 * **Note:** Any unspecified options will use the default value. You can see the default values below. 5122 * </div> 5123 * 5124 * Therefore the above can be simplified as: 5125 * 5126 * ```js 5127 * var myModule = angular.module(...); 5128 * 5129 * myModule.directive('directiveName', function factory(injectables) { 5130 * var directiveDefinitionObject = { 5131 * link: function postLink(scope, iElement, iAttrs) { ... } 5132 * }; 5133 * return directiveDefinitionObject; 5134 * // or 5135 * // return function postLink(scope, iElement, iAttrs) { ... } 5136 * }); 5137 * ``` 5138 * 5139 * 5140 * 5141 * ### Directive Definition Object 5142 * 5143 * The directive definition object provides instructions to the {@link ng.$compile 5144 * compiler}. The attributes are: 5145 * 5146 * #### `priority` 5147 * When there are multiple directives defined on a single DOM element, sometimes it 5148 * is necessary to specify the order in which the directives are applied. The `priority` is used 5149 * to sort the directives before their `compile` functions get called. Priority is defined as a 5150 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 5151 * are also run in priority order, but post-link functions are run in reverse order. The order 5152 * of directives with the same priority is undefined. The default priority is `0`. 5153 * 5154 * #### `terminal` 5155 * If set to true then the current `priority` will be the last set of directives 5156 * which will execute (any directives at the current priority will still execute 5157 * as the order of execution on same `priority` is undefined). 5158 * 5159 * #### `scope` 5160 * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the 5161 * same element request a new scope, only one new scope is created. The new scope rule does not 5162 * apply for the root of the template since the root of the template always gets a new scope. 5163 * 5164 * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from 5165 * normal scope in that it does not prototypically inherit from the parent scope. This is useful 5166 * when creating reusable components, which should not accidentally read or modify data in the 5167 * parent scope. 5168 * 5169 * The 'isolate' scope takes an object hash which defines a set of local scope properties 5170 * derived from the parent scope. These local properties are useful for aliasing values for 5171 * templates. Locals definition is a hash of local scope property to its source: 5172 * 5173 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is 5174 * always a string since DOM attributes are strings. If no `attr` name is specified then the 5175 * attribute name is assumed to be the same as the local name. 5176 * Given `<widget my-attr="hello {{name}}">` and widget definition 5177 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 5178 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 5179 * `localName` property on the widget scope. The `name` is read from the parent scope (not 5180 * component scope). 5181 * 5182 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 5183 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 5184 * name is specified then the attribute name is assumed to be the same as the local name. 5185 * Given `<widget my-attr="parentModel">` and widget definition of 5186 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 5187 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 5188 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 5189 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 5190 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. 5191 * 5192 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 5193 * If no `attr` name is specified then the attribute name is assumed to be the same as the 5194 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 5195 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 5196 * a function wrapper for the `count = count + value` expression. Often it's desirable to 5197 * pass data from the isolated scope via an expression and to the parent scope, this can be 5198 * done by passing a map of local variable names and values into the expression wrapper fn. 5199 * For example, if the expression is `increment(amount)` then we can specify the amount value 5200 * by calling the `localFn` as `localFn({amount: 22})`. 5201 * 5202 * 5203 * 5204 * #### `controller` 5205 * Controller constructor function. The controller is instantiated before the 5206 * pre-linking phase and it is shared with other directives (see 5207 * `require` attribute). This allows the directives to communicate with each other and augment 5208 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 5209 * 5210 * * `$scope` - Current scope associated with the element 5211 * * `$element` - Current element 5212 * * `$attrs` - Current attributes object for the element 5213 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope. 5214 * The scope can be overridden by an optional first argument. 5215 * `function([scope], cloneLinkingFn)`. 5216 * 5217 * 5218 * #### `require` 5219 * Require another directive and inject its controller as the fourth argument to the linking function. The
5220 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 5221 * injected argument will be an array in corresponding order. If no such directive can be 5222 * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: 5223 * 5224 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 5225 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 5226 * * `^` - Locate the required controller by searching the element's parents. Throw an error if not found. 5227 * * `?^` - Attempt to locate the required controller by searching the element's parents or pass `null` to the 5228 * `link` fn if not found. 5229 * 5230 * 5231 * #### `controllerAs` 5232 * Controller alias at the directive scope. An alias for the controller so it 5233 * can be referenced at the directive template. The directive needs to define a scope for this 5234 * configuration to be used. Useful in the case when directive is used as component. 5235 * 5236 * 5237 * #### `restrict` 5238 * String of subset of `EACM` which restricts the directive to a specific directive 5239 * declaration style. If omitted, the default (attributes only) is used. 5240 * 5241 * * `E` - Element name: `<my-directive></my-directive>` 5242 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 5243 * * `C` - Class: `<div class="my-directive: exp;"></div>` 5244 * * `M` - Comment: `<!-- directive: my-directive exp -->` 5245 * 5246 * 5247 * #### `template` 5248 * replace the current element with the contents of the HTML. The replacement process 5249 * migrates all of the attributes / classes from the old element to the new one. See the 5250 * {@link guide/directive#creating-custom-directives_creating-directives_template-expanding-directive 5251 * Directives Guide} for an example. 5252 * 5253 * You can specify `template` as a string representing the template or as a function which takes 5254 * two arguments `tElement` and `tAttrs` (described in the `compile` function api below) and 5255 * returns a string value representing the template. 5256 * 5257 * 5258 * #### `templateUrl` 5259 * Same as `template` but the template is loaded from the specified URL. Because 5260 * the template loading is asynchronous the compilation/linking is suspended until the template 5261 * is loaded. 5262 * 5263 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 5264 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 5265 * a string value representing the url. In either case, the template URL is passed through {@link 5266 * api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 5267 * 5268 * 5269 * #### `replace` 5270 * specify where the template should be inserted. Defaults to `false`. 5271 * 5272 * * `true` - the template will replace the current element. 5273 * * `false` - the template will replace the contents of the current element. 5274 * 5275 * 5276 * #### `transclude` 5277 * compile the content of the element and make it available to the directive. 5278 * Typically used with {@link ng.directive:ngTransclude 5279 * ngTransclude}. The advantage of transclusion is that the linking function receives a 5280 * transclusion function which is pre-bound to the correct scope. In a typical setup the widget 5281 * creates an `isolate` scope, but the transclusion is not a child, but a sibling of the `isolate` 5282 * scope. This makes it possible for the widget to have private state, and the transclusion to 5283 * be bound to the parent (pre-`isolate`) scope. 5284 * 5285 * * `true` - transclude the content of the directive. 5286 * * `'element'` - transclude the whole element including any directives defined at lower priority. 5287 * 5288 * 5289 * #### `compile` 5290 * 5291 * ```js 5292 * function compile(tElement, tAttrs, transclude) { ... } 5293 * ``` 5294 * 5295 * The compile function deals with transforming the template DOM. Since most directives do not do 5296 * template transformation, it is not used often. Examples that require compile functions are 5297 * directives that transform template DOM, such as {@link 5298 * api/ng.directive:ngRepeat ngRepeat}, or load the contents 5299 * asynchronously, such as {@link ngRoute.directive:ngView ngView}. The 5300 * compile function takes the following arguments. 5301 * 5302 * * `tElement` - template element - The element where the directive has been declared. It is 5303 * safe to do template transformation on the element and child elements only. 5304 * 5305 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 5306 * between all directive compile functions. 5307 * 5308 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 5309 * 5310 * <div class="alert alert-warning"> 5311 * **Note:** The template instance and the link instance may be different objects if the template has 5312 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 5313 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
5314 * should be done in a linking function rather than in a compile function. 5315 * </div> 5316 5317 * <div class="alert alert-warning"> 5318 * **Note:** The compile function cannot handle directives that recursively use themselves in their 5319 * own templates or compile functions. Compiling these directives results in an infinite loop and a 5320 * stack overflow errors. 5321 * 5322 * This can be avoided by manually using $compile in the postLink function to imperatively compile 5323 * a directive's template instead of relying on automatic template compilation via `template` or 5324 * `templateUrl` declaration or manual compilation inside the compile function. 5325 * </div> 5326 * 5327 * <div class="alert alert-error"> 5328 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 5329 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 5330 * to the link function instead. 5331 * </div> 5332 5333 * A compile function can have a return value which can be either a function or an object. 5334 * 5335 * * returning a (post-link) function - is equivalent to registering the linking function via the 5336 * `link` property of the config object when the compile function is empty. 5337 * 5338 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 5339 * control when a linking function should be called during the linking phase. See info about 5340 * pre-linking and post-linking functions below. 5341 * 5342 * 5343 * #### `link` 5344 * This property is used only if the `compile` property is not defined. 5345 * 5346 * ```js 5347 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 5348 * ``` 5349 * 5350 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 5351 * executed after the template has been cloned. This is where most of the directive logic will be 5352 * put. 5353 * 5354 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 5355 * directive for registering {@link ng.$rootScope.Scope#$watch watches}. 5356 * 5357 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 5358 * manipulate the children of the element only in `postLink` function since the children have 5359 * already been linked. 5360 * 5361 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 5362 * between all directive linking functions. 5363 * 5364 * * `controller` - a controller instance - A controller instance if at least one directive on the 5365 * element defines a controller. The controller is shared among all the directives, which allows 5366 * the directives to use the controllers as a communication channel. 5367 * 5368 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 5369 * The scope can be overridden by an optional first argument. This is the same as the `$transclude` 5370 * parameter of directive controllers. 5371 * `function([scope], cloneLinkingFn)`. 5372 * 5373 * 5374 * #### Pre-linking function 5375 * 5376 * Executed before the child elements are linked. Not safe to do DOM transformation since the 5377 * compiler linking function will fail to locate the correct elements for linking. 5378 * 5379 * #### Post-linking function 5380 * 5381 * Executed after the child elements are linked. It is safe to do DOM transformation in the post-linking function. 5382 * 5383 * <a name="Attributes"></a> 5384 * ### Attributes 5385 * 5386 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 5387 * `link()` or `compile()` functions. It has a variety of uses. 5388 * 5389 * accessing *Normalized attribute names:* 5390 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 5391 * the attributes object allows for normalized access to 5392 * the attributes. 5393 * 5394 * * *Directive inter-communication:* All directives share the same instance of the attributes 5395 * object which allows the directives to use the attributes object as inter directive 5396 * communication. 5397 * 5398 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 5399 * allowing other directives to read the interpolated value. 5400 * 5401 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 5402 * that contain interpolation (e.g. `src="{{bar}}
5402"`). Not only is this very efficient but it's also 5403 * the only way to easily get the actual value because during the linking phase the interpolation 5404 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 5405 * 5406 * ```js 5407 * function linkingFn(scope, elm, attrs, ctrl) { 5408 * // get the attribute value 5409 * console.log(attrs.ngModel); 5410 * 5411 * // change the attribute 5412 * attrs.$set('ngModel', 'new value'); 5413 * 5414 * // observe changes to interpolated attribute 5415 * attrs.$observe('ngModel', function(value) { 5416 * console.log('ngModel has changed value to ' + value); 5417 * }); 5418 * } 5419 * ``` 5420 * 5421 * Below is an example using `$compileProvider`. 5422 * 5423 * <div class="alert alert-warning"> 5424 * **Note**: Typically directives are registered with `module.directive`. The example below is 5425 * to illustrate how `$compile` works. 5426 * </div> 5427 * 5428 <example module="compile"> 5429 <file name="index.html"> 5430 <script> 5431 angular.module('compile', [], function($compileProvider) { 5432 // configure new 'compile' directive by passing a directive 5433 // factory function. The factory function injects the '$compile' 5434 $compileProvider.directive('compile', function($compile) { 5435 // directive factory creates a link function 5436 return function(scope, element, attrs) { 5437 scope.$watch( 5438 function(scope) { 5439 // watch the 'compile' expression for changes 5440 return scope.$eval(attrs.compile); 5441 }, 5442 function(value) { 5443 // when the 'compile' expression changes 5444 // assign it into the current DOM 5445 element.html(value); 5446 5447 // compile the new DOM and link it to the current 5448 // scope. 5449 // NOTE: we only compile .childNodes so that 5450 // we don't get into infinite loop compiling ourselves 5451 $compile(element.contents())(scope); 5452 } 5453 ); 5454 }; 5455 }) 5456 }); 5457 5458 function Ctrl($scope) { 5459 $scope.name = 'Angular'; 5460 $scope.html = 'Hello {{name}}'; 5461 } 5462 </script> 5463 <div ng-controller="Ctrl"> 5464 <input ng-model="name"> <br> 5465 <textarea ng-model="html"></textarea> <br> 5466 <div compile="html"></div> 5467 </div> 5468 </file> 5469 <file name="protractor.js" type="protractor"> 5470 it('should auto compile', function() { 5471 var textarea = $('textarea'); 5472 var output = $('div[compile]'); 5473 // The initial state reads 'Hello Angular'. 5474 expect(output.getText()).toBe('Hello Angular'); 5475 textarea.clear(); 5476 textarea.sendKeys('{{name}}!'); 5477 expect(output.getText()).toBe('Angular!'); 5478 }); 5479 </file> 5480 </example> 5481 5482 * 5483 * 5484 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 5485 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives. 5486 * @param {number} maxPriority only apply directives lower than given priority (Only effects the 5487 * root element(s), not their children) 5488 * @returns {function(scope, cloneAttachFn=)} a link function which is used to bind template 5489 * (a DOM element/tree) to a scope. Where: 5490 * 5491 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 5492 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 5493 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 5494 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 5495 * called as: <br> `cloneAttachFn(clonedElement, scope)` where: 5496 * 5497 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 5498 * * `scope` - is the current scope with which the linking function is working with. 5499 * 5500 * Calling the linking function returns the element of the template. It is either the original 5501 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 5502 * 5503 * After linking the view is not updated until after a call to $digest which typically is done by 5504 * Angular automatically. 5505 * 5506 * If you need access to the bound view, there are two ways to do it: 5507 * 5508 * - If you are not asking the linking function to clone the template, create the DOM element(s) 5509 * before you send them to the compiler and keep this reference around. 5510 * ```js 5511 * var element = $compile('<p>{{total}}</p>')(scope); 5512 * ``` 5513 * 5514 * - if on the other hand, you need the element to be cloned, the view reference from the original 5515 * example would not point to the clone, but rather to the original template that was cloned. In 5516 * this case, you can access the clone via the cloneAttachFn: 5517 * ```js 5518 * var templateElement = angular.element('<p>{{total}}</p>'), 5519 * scope = ....; 5520 * 5521 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 5522 * //attach the clone to DOM document at the right place 5523 * }); 5524 * 5525 * //now we have reference to the cloned DOM via `clonedElement` 5526 * ``` 5527 * 5528 * 5529 * For information on how the compiler works, see the 5530 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 5531 */ 5532 5533var $compileMinErr = minErr('$compile'); 5534 5535/** 5536 * @ngdoc provider 5537 * @name $compileProvider 5538 * @function 5539 * 5540 * @description 5541 */
vendor: 4,653 bytes, lines 5542-5650
5542$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 5543function $CompileProvider($provide, $$sanitizeUriProvider) { 5544 var hasDirectives = {}, 5545 Suffix = 'Directive', 5546 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/, 5547 CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/; 5548 5549 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 5550 // The assumption is that future DOM event attribute names will begin with 5551 // 'on' and be composed of only English letters. 5552 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 5553 5554 /** 5555 * @ngdoc method 5556 * @name $compileProvider#directive 5557 * @function 5558 * 5559 * @description 5560 * Register a new directive with the compiler. 5561 * 5562 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 5563 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 5564 * names and the values are the factories. 5565 * @param {Function|Array} directiveFactory An injectable directive factory function. See 5566 * {@link guide/directive} for more info. 5567 * @returns {ng.$compileProvider} Self for chaining. 5568 */ 5569 this.directive = function registerDirective(name, directiveFactory) { 5570 assertNotHasOwnProperty(name, 'directive'); 5571 if (isString(name)) { 5572 assertArg(directiveFactory, 'directiveFactory'); 5573 if (!hasDirectives.hasOwnProperty(name)) { 5574 hasDirectives[name] = []; 5575 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 5576 function($injector, $exceptionHandler) { 5577 var directives = []; 5578 forEach(hasDirectives[name], function(directiveFactory, index) { 5579 try { 5580 var directive = $injector.invoke(directiveFactory); 5581 if (isFunction(directive)) { 5582 directive = { compile: valueFn(directive) }; 5583 } else if (!directive.compile && directive.link) { 5584 directive.compile = valueFn(directive.link); 5585 } 5586 directive.priority = directive.priority || 0; 5587 directive.index = index; 5588 directive.name = directive.name || name; 5589 directive.require = directive.require || (directive.controller && directive.name); 5590 directive.restrict = directive.restrict || 'A'; 5591 directives.push(directive); 5592 } catch (e) { 5593 $exceptionHandler(e); 5594 } 5595 }); 5596 return directives; 5597 }]); 5598 } 5599 hasDirectives[name].push(directiveFactory); 5600 } else { 5601 forEach(name, reverseParams(registerDirective)); 5602 } 5603 return this; 5604 }; 5605 5606 5607 /** 5608 * @ngdoc method 5609 * @name $compileProvider#aHrefSanitizationWhitelist 5610 * @function 5611 * 5612 * @description 5613 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5614 * urls during a[href] sanitization. 5615 * 5616 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5617 * 5618 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 5619 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 5620 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 5621 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5622 * 5623 * @param {RegExp=} regexp New regexp to whitelist urls with. 5624 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5625 * chaining otherwise. 5626 */ 5627 this.aHrefSanitizationWhitelist = function(regexp) { 5628 if (isDefined(regexp)) { 5629 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 5630 return this; 5631 } else { 5632 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 5633 } 5634 }; 5635 5636 5637 /** 5638 * @ngdoc method 5639 * @name $compileProvider#imgSrcSanitizationWhitelist 5640 * @function 5641 * 5642 * @description 5643 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 5644 * urls during img[src] sanitization. 5645 * 5646 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 5647 * 5648 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 5649 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 5650 * regular expression. If a match is found, the original url is
5650written into the dom. Otherwise, 5651 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 5652 * 5653 * @param {RegExp=} regexp New regexp to whitelist urls with. 5654 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 5655 * chaining otherwise. 5656 */ 5657 this.imgSrcSanitizationWhitelist = function(regexp) { 5658 if (isDefined(regexp)) { 5659 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 5660 return this; 5661 } else { 5662 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 5663 } 5664 }; 5665 5666 this.$get = [ 5667 '$injector', '$interpolate', '$exceptionHandler', '$http', '$templateCache', '$parse', 5668 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 5669 function($injector, $interpolate, $exceptionHandler, $http, $templateCache, $parse, 5670 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 5671 5672 var Attributes = function(element, attr) { 5673 this.$$element = element; 5674 this.$attr = attr || {}; 5675 }; 5676 5677 Attributes.prototype = { 5678 $normalize: directiveNormalize, 5679 5680 5681 /** 5682 * @ngdoc method 5683 * @name $compile.directive.Attributes#$addClass 5684 * @function 5685 * 5686 * @description 5687 * Adds the CSS class value specified by the classVal parameter to the element. If animations 5688 * are enabled then an animation will be triggered for the class addition. 5689 * 5690 * @param {string} classVal The className value that will be added to the element 5691 */ 5692 $addClass : function(classVal) { 5693 if(classVal && classVal.length > 0) { 5694 $animate.addClass(this.$$element, classVal); 5695 } 5696 }, 5697 5698 /** 5699 * @ngdoc method 5700 * @name $compile.directive.Attributes#$removeClass 5701 * @function 5702 * 5703 * @description 5704 * Removes the CSS class value specified by the classVal parameter from the element. If 5705 * animations are enabled then an animation will be triggered for the class removal. 5706 * 5707 * @param {string} classVal The className value that will be removed from the element 5708 */ 5709 $removeClass : function(classVal) { 5710 if(classVal && classVal.length > 0) { 5711 $animate.removeClass(this.$$element, classVal); 5712 } 5713 }, 5714 5715 /** 5716 * @ngdoc method 5717 * @name $compile.directive.Attributes#$updateClass 5718 * @function 5719 * 5720 * @description 5721 * Adds and removes the appropriate CSS class values to the element based on the difference 5722 * between the new and old CSS class values (specified as newClasses and oldClasses). 5723 * 5724 * @param {string} newClasses The current CSS className value 5725 * @param {string} oldClasses The former CSS className value 5726 */ 5727 $updateClass : function(newClasses, oldClasses) { 5728 var toAdd = tokenDifference(newClasses, oldClasses); 5729 var toRemove = tokenDifference(oldClasses, newClasses); 5730 5731 if(toAdd.length === 0) { 5732 $animate.removeClass(this.$$element, toRemove); 5733 } else if(toRemove.length === 0) { 5734 $animate.addClass(this.$$element, toAdd); 5735 } else { 5736 $animate.setClass(this.$$element, toAdd, toRemove); 5737 } 5738 }, 5739 5740 /** 5741 * Set a normalized attribute on the element in a way such that all directives 5742 * can share the attribute. This function properly handles boolean attributes. 5743 * @param {string} key Normalized key. (ie ngAttribute) 5744 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 5745 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 5746 * Defaults to true. 5747 * @param {string=} attrName Optional none normalized name. Defaults to key. 5748 */ 5749 $set: function(key, value, writeAttr, attrName) { 5750 // TODO: decide whether or not to throw an error if "class" 5751 //is set through this function since it may cause $updateClass to 5752 //become unstable. 5753 5754 var booleanKey = getBooleanAttrName(this.$$element[0], key), 5755 normalizedVal, 5756 nodeName; 5757 5758 if (booleanKey) { 5759 this.$$element.prop(key, value); 5760 attrName = booleanKey; 5761 } 5762 5763 this[key] = value; 5764 5765 // translate normalized key to actual key 5766 if (attrName) { 5767 this.$attr[key] = attrName; 5768 } else { 5769 attrName = this.$attr[key]; 5770 if (!attrName) { 5771 this.$attr[key] = attrName = snake_case(key, '-'); 5772 } 5773 } 5774 5775 nodeName = nodeName_(this.$$element); 5776 5777 // sanitize a[href] and img[src] values 5778 if ((nodeName === 'A' && key === 'href') || 5779 (nodeName === 'IMG' && key === 'src')) { 5780 this[key] = value = $$sanitizeUri(value, key === 'src'); 5781 } 5782 5783 if (writeAttr !== false) { 5784 if (value === null || value === undefined) { 5785 this.$$element.removeAttr(attrName); 5786 } else { 5787 this.$$element.attr(attrName, value); 5788 } 5789 } 5790 5791 // fire observers 5792 var $$observers = this.$$observers;
5793 $$observers && forEach($$observers[key], function(fn) { 5794 try { 5795 fn(value); 5796 } catch (e) { 5797 $exceptionHandler(e); 5798 } 5799 }); 5800 }, 5801 5802 5803 /** 5804 * @ngdoc method 5805 * @name $compile.directive.Attributes#$observe 5806 * @function 5807 * 5808 * @description 5809 * Observes an interpolated attribute. 5810 * 5811 * The observer function will be invoked once during the next `$digest` following 5812 * compilation. The observer is then invoked whenever the interpolated value 5813 * changes. 5814 * 5815 * @param {string} key Normalized key. (ie ngAttribute) . 5816 * @param {function(interpolatedValue)} fn Function that will be called whenever 5817 the interpolated value of the attribute changes. 5818 * See the {@link guide/directive#Attributes Directives} guide for more info. 5819 * @returns {function()} the `fn` parameter. 5820 */ 5821 $observe: function(key, fn) { 5822 var attrs = this, 5823 $$observers = (attrs.$$observers || (attrs.$$observers = {})), 5824 listeners = ($$observers[key] || ($$observers[key] = [])); 5825 5826 listeners.push(fn); 5827 $rootScope.$evalAsync(function() { 5828 if (!listeners.$$inter) { 5829 // no one registered attribute interpolation function, so lets call it manually 5830 fn(attrs[key]); 5831 } 5832 }); 5833 return fn; 5834 } 5835 }; 5836 5837 var startSymbol = $interpolate.startSymbol(), 5838 endSymbol = $interpolate.endSymbol(), 5839 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 5840 ? identity 5841 : function denormalizeTemplate(template) { 5842 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 5843 }, 5844 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 5845 5846 5847 return compile; 5848 5849 //================================ 5850 5851 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 5852 previousCompileContext) { 5853 if (!($compileNodes instanceof jqLite)) { 5854 // jquery always rewraps, whereas we need to preserve the original selector so that we can 5855 // modify it. 5856 $compileNodes = jqLite($compileNodes); 5857 } 5858 // We can not compile top level text elements since text nodes can be merged and we will 5859 // not be able to attach scope data to them, so we will wrap them in <span> 5860 forEach($compileNodes, function(node, index){ 5861 if (node.nodeType == 3 /* text node */ && node.nodeValue.match(/\S+/) /* non-empty */ ) { 5862 $compileNodes[index] = node = jqLite(node).wrap('<span></span>').parent()[0]; 5863 } 5864 }); 5865 var compositeLinkFn = 5866 compileNodes($compileNodes, transcludeFn, $compileNodes, 5867 maxPriority, ignoreDirective, previousCompileContext); 5868 safeAddClass($compileNodes, 'ng-scope'); 5869 return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){ 5870 assertArg(scope, 'scope'); 5871 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 5872 // and sometimes changes the structure of the DOM. 5873 var $linkNode = cloneConnectFn 5874 ? JQLitePrototype.clone.call($compileNodes) // IMPORTANT!!! 5875 : $compileNodes; 5876 5877 forEach(transcludeControllers, function(instance, name) { 5878 $linkNode.data('$' + name + 'Controller', instance); 5879 }); 5880 5881 // Attach scope only to non-text nodes. 5882 for(var i = 0, ii = $linkNode.length; i<ii; i++) { 5883 var node = $linkNode[i], 5884 nodeType = node.nodeType; 5885 if (nodeType === 1 /* element */ || nodeType === 9 /* document */) { 5886 $linkNode.eq(i).data('$scope', scope); 5887 } 5888 } 5889 5890 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 5891 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode); 5892 return $linkNode; 5893 }; 5894 } 5895 5896 function safeAddClass($element, className) {
5897 try { 5898 $element.addClass(className); 5899 } catch(e) { 5900 // ignore, since it means that we are trying to set class on 5901 // SVG element, where class name is read-only. 5902 } 5903 } 5904 5905 /** 5906 * Compile function matches each node in nodeList against the directives. Once all directives 5907 * for a particular node are collected their compile functions are executed. The compile 5908 * functions return values - the linking functions - are combined into a composite linking 5909 * function, which is the a linking function for the node. 5910 * 5911 * @param {NodeList} nodeList an array of nodes or NodeList to compile 5912 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 5913 * scope argument is auto-generated to the new child of the transcluded parent scope. 5914 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 5915 * the rootElement must be set the jqLite collection of the compile root. This is 5916 * needed so that the jqLite collection items can be replaced with widgets. 5917 * @param {number=} maxPriority Max directive priority. 5918 * @returns {Function} A composite linking function of all of the matched directives or null. 5919 */ 5920 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 5921 previousCompileContext) { 5922 var linkFns = [], 5923 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound; 5924 5925 for (var i = 0; i < nodeList.length; i++) { 5926 attrs = new Attributes(); 5927 5928 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 5929 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 5930 ignoreDirective); 5931 5932 nodeLinkFn = (directives.length) 5933 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 5934 null, [], [], previousCompileContext) 5935 : null; 5936 5937 if (nodeLinkFn && nodeLinkFn.scope) { 5938 safeAddClass(jqLite(nodeList[i]), 'ng-scope'); 5939 } 5940 5941 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 5942 !(childNodes = nodeList[i].childNodes) || 5943 !childNodes.length) 5944 ? null 5945 : compileNodes(childNodes, 5946 nodeLinkFn ? nodeLinkFn.transclude : transcludeFn); 5947 5948 linkFns.push(nodeLinkFn, childLinkFn); 5949 linkFnFound = linkFnFound || nodeLinkFn || childLinkFn; 5950 //use the previous context only for the first element in the virtual group 5951 previousCompileContext = null; 5952 } 5953 5954 // return a linking function if we have found anything, null otherwise 5955 return linkFnFound ? compositeLinkFn : null; 5956 5957 function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) { 5958 var nodeLinkFn, childLinkFn, node, $node, childScope, childTranscludeFn, i, ii, n; 5959 5960 // copy nodeList so that linking doesn't break due to live list updates. 5961 var nodeListLength = nodeList.length, 5962 stableNodeList = new Array(nodeListLength); 5963 for (i = 0; i < nodeListLength; i++) { 5964 stableNodeList[i] = nodeList[i]; 5965 } 5966 5967 for(i = 0, n = 0, ii = linkFns.length; i < ii; n++) { 5968 node = stableNodeList[n]; 5969 nodeLinkFn = linkFns[i++]; 5970 childLinkFn = linkFns[i++]; 5971 $node = jqLite(node); 5972 5973 if (nodeLinkFn) { 5974 if (nodeLinkFn.scope) { 5975 childScope = scope.$new(); 5976 $node.data('$scope', childScope); 5977 } else { 5978 childScope = scope; 5979 } 5980 childTranscludeFn = nodeLinkFn.transclude; 5981 if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) { 5982 nodeLinkFn(childLinkFn, childScope, node, $rootElement, 5983 createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn) 5984 ); 5985 } else { 5986 nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn);
5987 } 5988 } else if (childLinkFn) { 5989 childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn); 5990 } 5991 } 5992 } 5993 } 5994 5995 function createBoundTranscludeFn(scope, transcludeFn) { 5996 return function boundTranscludeFn(transcludedScope, cloneFn, controllers) { 5997 var scopeCreated = false; 5998 5999 if (!transcludedScope) { 6000 transcludedScope = scope.$new(); 6001 transcludedScope.$$transcluded = true; 6002 scopeCreated = true; 6003 } 6004 6005 var clone = transcludeFn(transcludedScope, cloneFn, controllers); 6006 if (scopeCreated) { 6007 clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy)); 6008 } 6009 return clone; 6010 }; 6011 } 6012 6013 /** 6014 * Looks for directives on the given node and adds them to the directive collection which is 6015 * sorted. 6016 * 6017 * @param node Node to search. 6018 * @param directives An array to which the directives are added to. This array is sorted before 6019 * the function returns. 6020 * @param attrs The shared attrs object which is used to populate the normalized attributes. 6021 * @param {number=} maxPriority Max directive priority. 6022 */ 6023 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 6024 var nodeType = node.nodeType, 6025 attrsMap = attrs.$attr, 6026 match, 6027 className; 6028 6029 switch(nodeType) { 6030 case 1: /* Element */ 6031 // use the node name: <directive> 6032 addDirective(directives, 6033 directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); 6034 6035 // iterate over the attributes 6036 for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes, 6037 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 6038 var attrStartName = false; 6039 var attrEndName = false; 6040 6041 attr = nAttrs[j]; 6042 if (!msie || msie >= 8 || attr.specified) { 6043 name = attr.name; 6044 // support ngAttr attribute binding 6045 ngAttrName = directiveNormalize(name); 6046 if (NG_ATTR_BINDING.test(ngAttrName)) { 6047 name = snake_case(ngAttrName.substr(6), '-'); 6048 } 6049 6050 var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); 6051 if (ngAttrName === directiveNName + 'Start') { 6052 attrStartName = name; 6053 attrEndName = name.substr(0, name.length - 5) + 'end'; 6054 name = name.substr(0, name.length - 6); 6055 } 6056 6057 nName = directiveNormalize(name.toLowerCase()); 6058 attrsMap[nName] = name; 6059 attrs[nName] = value = trim(attr.value); 6060 if (getBooleanAttrName(node, nName)) { 6061 attrs[nName] = true; // presence means true 6062 } 6063 addAttrInterpolateDirective(node, directives, value, nName); 6064 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 6065 attrEndName); 6066 } 6067 } 6068 6069 // use class as directive 6070 className = node.className; 6071 if (isString(className) && className !== '') { 6072 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 6073 nName = directiveNormalize(match[2]); 6074 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 6075 attrs[nName] = trim(match[3]); 6076 } 6077 className = className.substr(match.index + match[0].length); 6078 } 6079 } 6080 break; 6081 case 3: /* Text Node */ 6082 addTextInterpolateDirective(directives, node.nodeValue); 6083 break; 6084 case 8: /* Comment */ 6085 try { 6086 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 6087 if (match) { 6088 nName = directiveNormalize(match[1]); 6089 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 6090 attrs[nName] = trim(match[2]); 6091 } 6092 } 6093 } catch (e) { 6094 // turns out that under some circumstances IE9 throws errors when one attempts to read 6095 // comment's node value. 6096 // Just ignore it and continue. (Can't seem to reproduce in test case.) 6097 } 6098 break; 6099 } 6100 6101 directives.sort(byPriority); 6102 return directives; 6103 } 6104 6105 /**
6106 * Given a node with an directive-start it collects all of the siblings until it finds 6107 * directive-end. 6108 * @param node 6109 * @param attrStart 6110 * @param attrEnd 6111 * @returns {*} 6112 */ 6113 function groupScan(node, attrStart, attrEnd) { 6114 var nodes = []; 6115 var depth = 0; 6116 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 6117 var startNode = node; 6118 do { 6119 if (!node) { 6120 throw $compileMinErr('uterdir', 6121 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 6122 attrStart, attrEnd); 6123 } 6124 if (node.nodeType == 1 /** Element **/) { 6125 if (node.hasAttribute(attrStart)) depth++; 6126 if (node.hasAttribute(attrEnd)) depth--; 6127 } 6128 nodes.push(node); 6129 node = node.nextSibling; 6130 } while (depth > 0); 6131 } else { 6132 nodes.push(node); 6133 } 6134 6135 return jqLite(nodes); 6136 } 6137 6138 /** 6139 * Wrapper for linking function which converts normal linking function into a grouped 6140 * linking function. 6141 * @param linkFn 6142 * @param attrStart 6143 * @param attrEnd 6144 * @returns {Function} 6145 */ 6146 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 6147 return function(scope, element, attrs, controllers, transcludeFn) { 6148 element = groupScan(element[0], attrStart, attrEnd); 6149 return linkFn(scope, element, attrs, controllers, transcludeFn); 6150 }; 6151 } 6152 6153 /** 6154 * Once the directives have been collected, their compile functions are executed. This method 6155 * is responsible for inlining directive templates as well as terminating the application 6156 * of the directives if the terminal directive has been reached. 6157 * 6158 * @param {Array} directives Array of collected directives to execute their compile function. 6159 * this needs to be pre-sorted by priority order. 6160 * @param {Node} compileNode The raw DOM node to apply the compile functions to 6161 * @param {Object} templateAttrs The shared attribute function 6162 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 6163 * scope argument is auto-generated to the new 6164 * child of the transcluded parent scope. 6165 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 6166 * argument has the root jqLite array so that we can replace nodes 6167 * on it. 6168 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 6169 * compiling the transclusion. 6170 * @param {Array.<Function>} preLinkFns 6171 * @param {Array.<Function>} postLinkFns 6172 * @param {Object} previousCompileContext Context used for previous compilation of the current 6173 * node 6174 * @returns {Function} linkFn 6175 */ 6176 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 6177 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 6178 previousCompileContext) { 6179 previousCompileContext = previousCompileContext || {}; 6180 6181 var terminalPriority = -Number.MAX_VALUE, 6182 newScopeDirective, 6183 controllerDirectives = previousCompileContext.controllerDirectives, 6184 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 6185 templateDirective = previousCompileContext.templateDirective, 6186 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 6187 hasTranscludeDirective = false, 6188 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 6189 $compileNode = templateAttrs.$$element = jqLite(compileNode), 6190 directive, 6191 directiveName, 6192 $template, 6193 replaceDirective = originalReplaceDirective, 6194 childTranscludeFn = transcludeFn, 6195 linkFn, 6196 directiveValue; 6197 6198 // executes all directives on the current element 6199 for(var i = 0, ii = directives.length; i < ii; i++) { 6200 directive = directives[i]; 6201 var attrStart = directive.$$start; 6202 var attrEnd = directive.$$end; 6203 6204 // collect multiblock sections 6205 if (attrStart) { 6206 $compileNode = groupScan(compileNode, attrStart, attrEnd); 6207 } 6208 $template = undefined; 6209 6210 if (terminalPriority > directive.priority) { 6211 break; // prevent further processing of directives 6212 } 6213 6214 if (directiveValue = directive.scope) { 6215 newScopeDirective = newScopeDirective || directive; 6216 6217 // skip the check for directives with async templates, we'll check the derived sync 6218 // directive when the template arrives 6219 if (!directive.templateUrl) { 6220 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 6221 $compileNode); 6222 if (isObject(directiveValue)) { 6223 newIsolateScopeDirective = directive; 6224 } 6225 } 6226 } 6227 6228 directiveName = directive.name; 6229 6230 if (!directive.templateUrl && directive.controller) { 6231 directiveValue = directive.controller; 6232 controllerDirectives = controllerDirectives || {}; 6233 assertNoDuplicate("'" + directiveName + "' controller", 6234 controllerDirectives[directiveName], directive, $compileNode); 6235 controllerDirectives[directiveName] = directive; 6236 } 6237 6238 if (directiveValue = directive.transclude) { 6239 hasTranscludeDirective = true; 6240 6241 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 6242 // This option should only be used by directives that know how to safely handle element transclusion, 6243 // where the transcluded nodes are added or replaced after linking. 6244 if (!directive.$$tlb) { 6245 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 6246 nonTlbTranscludeDirective = directive; 6247 } 6248 6249 if (directiveValue == 'element') { 6250 hasElementTranscludeDirective = true; 6251 terminalPriority = directive.priority; 6252 $template = groupScan(compileNode, attrStart, attrEnd); 6253 $compileNode = templateAttrs.$$element = 6254 jqLite(document.createComment(' ' + directiveName + ': ' + 6255 templateAttrs[directiveName] + ' ')); 6256 compileNode = $compileNode[0]; 6257 replaceWith(jqCollection, jqLite(sliceArgs($template)), compileNode); 6258 6259 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 6260 replaceDirective && replaceDirective.name, { 6261 // Don't pass in: 6262 // - controllerDirectives - otherwise we'll create duplicates controllers 6263 // - newIsolateScopeDirective or templateDirective - comb
6263ining templates with 6264 // element transclusion doesn't make sense. 6265 // 6266 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 6267 // on the same element more than once. 6268 nonTlbTranscludeDirective: nonTlbTranscludeDirective 6269 }); 6270 } else { 6271 $template = jqLite(jqLiteClone(compileNode)).contents(); 6272 $compileNode.empty(); // clear contents 6273 childTranscludeFn = compile($template, transcludeFn); 6274 } 6275 } 6276 6277 if (directive.template) { 6278 assertNoDuplicate('template', templateDirective, directive, $compileNode); 6279 templateDirective = directive; 6280 6281 directiveValue = (isFunction(directive.template)) 6282 ? directive.template($compileNode, templateAttrs) 6283 : directive.template; 6284 6285 directiveValue = denormalizeTemplate(directiveValue); 6286 6287 if (directive.replace) { 6288 replaceDirective = directive; 6289 if (jqLiteIsTextNode(directiveValue)) { 6290 $template = []; 6291 } else { 6292 $template = jqLite(directiveValue); 6293 } 6294 compileNode = $template[0]; 6295 6296 if ($template.length != 1 || compileNode.nodeType !== 1) { 6297 throw $compileMinErr('tplrt', 6298 "Template for directive '{0}' must have exactly one root element. {1}", 6299 directiveName, ''); 6300 } 6301 6302 replaceWith(jqCollection, $compileNode, compileNode); 6303 6304 var newTemplateAttrs = {$attr: {}}; 6305 6306 // combine directives from the original node and from the template: 6307 // - take the array of directives for this element 6308 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 6309 // - collect directives from the template and sort them by priority 6310 // - combine directives as: processed + template + unprocessed 6311 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 6312 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 6313 6314 if (newIsolateScopeDirective) { 6315 markDirectivesAsIsolate(templateDirectives); 6316 } 6317 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 6318 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 6319 6320 ii = directives.length; 6321 } else { 6322 $compileNode.html(directiveValue); 6323 } 6324 } 6325 6326 if (directive.templateUrl) { 6327 assertNoDuplicate('template', templateDirective, directive, $compileNode); 6328 templateDirective = directive; 6329 6330 if (directive.replace) { 6331 replaceDirective = directive; 6332 } 6333 6334 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 6335 templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, { 6336 controllerDirectives: controllerDirectives, 6337 newIsolateScopeDirective: newIsolateScopeDirective, 6338 templateDirective: templateDirective, 6339 nonTlbTranscludeDirective: nonTlbTranscludeDirective 6340 }); 6341 ii = directives.length; 6342 } else if (directive.compile) { 6343 try { 6344 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 6345 if (isFunction(linkFn)) { 6346 addLinkFns(null, linkFn, attrStart, attrEnd); 6347 } else if (linkFn) { 6348 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 6349 } 6350 } catch (e) { 6351 $exceptionHandler(e, startingTag($compileNode)); 6352 } 6353 } 6354 6355 if (directive.terminal) { 6356 nodeLinkFn.terminal = true; 6357 terminalPriority = Math.max(terminalPriority, directive.priority); 6358 } 6359 6360 } 6361 6362 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 6363 nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn; 6364 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 6365
6366 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 6367 return nodeLinkFn; 6368 6369 //////////////////// 6370 6371 function addLinkFns(pre, post, attrStart, attrEnd) { 6372 if (pre) { 6373 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 6374 pre.require = directive.require; 6375 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6376 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 6377 } 6378 preLinkFns.push(pre); 6379 } 6380 if (post) { 6381 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 6382 post.require = directive.require; 6383 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 6384 post = cloneAndAnnotateFn(post, {isolateScope: true}); 6385 } 6386 postLinkFns.push(post); 6387 } 6388 } 6389 6390 6391 function getControllers(require, $element, elementControllers) { 6392 var value, retrievalMethod = 'data', optional = false; 6393 if (isString(require)) { 6394 while((value = require.charAt(0)) == '^' || value == '?') { 6395 require = require.substr(1); 6396 if (value == '^') { 6397 retrievalMethod = 'inheritedData'; 6398 } 6399 optional = optional || value == '?'; 6400 } 6401 value = null; 6402 6403 if (elementControllers && retrievalMethod === 'data') { 6404 value = elementControllers[require]; 6405 } 6406 value = value || $element[retrievalMethod]('$' + require + 'Controller'); 6407 6408 if (!value && !optional) { 6409 throw $compileMinErr('ctreq', 6410 "Controller '{0}', required by directive '{1}', can't be found!", 6411 require, directiveName); 6412 } 6413 return value; 6414 } else if (isArray(require)) { 6415 value = []; 6416 forEach(require, function(require) { 6417 value.push(getControllers(require, $element, elementControllers)); 6418 }); 6419 } 6420 return value; 6421 } 6422 6423 6424 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 6425 var attrs, $element, i, ii, linkFn, controller, isolateScope, elementControllers = {}, transcludeFn; 6426 6427 if (compileNode === linkNode) { 6428 attrs = templateAttrs; 6429 } else { 6430 attrs = shallowCopy(templateAttrs, new Attributes(jqLite(linkNode), templateAttrs.$attr)); 6431 } 6432 $element = attrs.$$element; 6433 6434 if (newIsolateScopeDirective) { 6435 var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; 6436 var $linkNode = jqLite(linkNode); 6437 6438 isolateScope = scope.$new(true); 6439 6440 if (templateDirective && (templateDirective === newIsolateScopeDirective.$$originalDirective)) { 6441 $linkNode.data('$isolateScope', isolateScope) ; 6442 } else { 6443 $linkNode.data('$isolateScopeNoTemplate', isolateScope); 6444 } 6445 6446 6447 6448 safeAddClass($linkNode, 'ng-isolate-scope'); 6449 6450 forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { 6451 var match = definition.match(LOCAL_REGEXP) || [], 6452 attrName = match[3] || scopeName, 6453 optional = (match[2] == '?'), 6454 mode = match[1], // @, =, or & 6455 lastValue, 6456 parentGet, parentSet, compare; 6457 6458 isolateScope.$$isolateBindings[scopeName] = mode + attrName; 6459 6460 switch (mode) { 6461 6462 case '@': 6463 attrs.$observe(attrName, function(value) { 6464 isolateScope[scopeName] = value; 6465 }); 6466 attrs.$$observers[attrName].$$scope = scope; 6467 if( attrs[attrName] ) { 6468 // If the attribute has been provided then we trigger an interpolation to ensure 6469 // the value is there for use in the link fn 6470 isolateScope[scopeName] = $interpolate(attrs[attrName])(scope); 6471 } 6472 break; 6473 6474 case '=': 6475 if (optional && !attrs[attrName]) { 6476 return; 6477 } 6478 parentGet = $parse(attrs[attrName]); 6479 if (parentGet.literal) { 6480 compare = equals; 6481 } else { 6482 compare = function(a,b) { return a === b; }; 6483 } 6484 parentSet = parentGet.assign || function() { 6485 // reset the change, or we will throw this exception on every $digest
6486 lastValue = isolateScope[scopeName] = parentGet(scope); 6487 throw $compileMinErr('nonassign', 6488 "Expression '{0}' used with directive '{1}' is non-assignable!", 6489 attrs[attrName], newIsolateScopeDirective.name); 6490 }; 6491 lastValue = isolateScope[scopeName] = parentGet(scope); 6492 isolateScope.$watch(function parentValueWatch() { 6493 var parentValue = parentGet(scope); 6494 if (!compare(parentValue, isolateScope[scopeName])) { 6495 // we are out of sync and need to copy 6496 if (!compare(parentValue, lastValue)) { 6497 // parent changed and it has precedence 6498 isolateScope[scopeName] = parentValue; 6499 } else { 6500 // if the parent can be assigned then do so 6501 parentSet(scope, parentValue = isolateScope[scopeName]); 6502 } 6503 } 6504 return lastValue = parentValue; 6505 }, null, parentGet.literal); 6506 break; 6507 6508 case '&': 6509 parentGet = $parse(attrs[attrName]); 6510 isolateScope[scopeName] = function(locals) { 6511 return parentGet(scope, locals); 6512 }; 6513 break; 6514 6515 default: 6516 throw $compileMinErr('iscp', 6517 "Invalid isolate scope definition for directive '{0}'." + 6518 " Definition: {... {1}: '{2}' ...}", 6519 newIsolateScopeDirective.name, scopeName, definition); 6520 } 6521 }); 6522 } 6523 transcludeFn = boundTranscludeFn && controllersBoundTransclude; 6524 if (controllerDirectives) { 6525 forEach(controllerDirectives, function(directive) { 6526 var locals = { 6527 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 6528 $element: $element, 6529 $attrs: attrs, 6530 $transclude: transcludeFn 6531 }, controllerInstance; 6532 6533 controller = directive.controller; 6534 if (controller == '@') { 6535 controller = attrs[directive.name]; 6536 } 6537 6538 controllerInstance = $controller(controller, locals); 6539 // For directives with element transclusion the element is a comment, 6540 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 6541 // clean up (http://bugs.jquery.com/ticket/8335). 6542 // Instead, we save the controllers for the element in a local hash and attach to .data 6543 // later, once we have the actual element. 6544 elementControllers[directive.name] = controllerInstance; 6545 if (!hasElementTranscludeDirective) { 6546 $element.data('$' + directive.name + 'Controller', controllerInstance); 6547 } 6548 6549 if (directive.controllerAs) { 6550 locals.$scope[directive.controllerAs] = controllerInstance; 6551 } 6552 }); 6553 } 6554 6555 // PRELINKING 6556 for(i = 0, ii = preLinkFns.length; i < ii; i++) { 6557 try { 6558 linkFn = preLinkFns[i]; 6559 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6560 linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); 6561 } catch (e) { 6562 $exceptionHandler(e, startingTag($element)); 6563 } 6564 } 6565 6566 // RECURSION 6567 // We only pass the isolate scope, if the isolate directive has a template, 6568 // otherwise the child elements do not belong to the isolate directive. 6569 var scopeToChild = scope; 6570 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 6571 scopeToChild = isolateScope; 6572 } 6573 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn);
6574 6575 // POSTLINKING 6576 for(i = postLinkFns.length - 1; i >= 0; i--) { 6577 try { 6578 linkFn = postLinkFns[i]; 6579 linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs, 6580 linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn); 6581 } catch (e) { 6582 $exceptionHandler(e, startingTag($element)); 6583 } 6584 } 6585 6586 // This is the function that is injected as `$transclude`. 6587 function controllersBoundTransclude(scope, cloneAttachFn) { 6588 var transcludeControllers; 6589 6590 // no scope passed 6591 if (arguments.length < 2) { 6592 cloneAttachFn = scope; 6593 scope = undefined; 6594 } 6595 6596 if (hasElementTranscludeDirective) { 6597 transcludeControllers = elementControllers; 6598 } 6599 6600 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers); 6601 } 6602 } 6603 } 6604 6605 function markDirectivesAsIsolate(directives) { 6606 // mark all directives as needing isolate scope. 6607 for (var j = 0, jj = directives.length; j < jj; j++) { 6608 directives[j] = inherit(directives[j], {$$isolateScope: true}); 6609 } 6610 } 6611 6612 /** 6613 * looks up the directive and decorates it with exception handling and proper parameters. We 6614 * call this the boundDirective. 6615 * 6616 * @param {string} name name of the directive to look up. 6617 * @param {string} location The directive must be found in specific format. 6618 * String containing any of theses characters: 6619 * 6620 * * `E`: element name 6621 * * `A': attribute 6622 * * `C`: class 6623 * * `M`: comment 6624 * @returns {boolean} true if directive was added. 6625 */ 6626 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 6627 endAttrName) { 6628 if (name === ignoreDirective) return null; 6629 var match = null; 6630 if (hasDirectives.hasOwnProperty(name)) { 6631 for(var directive, directives = $injector.get(name + Suffix), 6632 i = 0, ii = directives.length; i<ii; i++) { 6633 try { 6634 directive = directives[i]; 6635 if ( (maxPriority === undefined || maxPriority > directive.priority) && 6636 directive.restrict.indexOf(location) != -1) { 6637 if (startAttrName) { 6638 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 6639 } 6640 tDirectives.push(directive); 6641 match = directive; 6642 } 6643 } catch(e) { $exceptionHandler(e); } 6644 } 6645 } 6646 return match; 6647 } 6648 6649 6650 /** 6651 * When the element is replaced with HTML template then the new attributes 6652 * on the template need to be merged with the existing attributes in the DOM. 6653 * The desired effect is to have both of the attributes present. 6654 * 6655 * @param {object} dst destination attributes (original DOM) 6656 * @param {object} src source attributes (from the directive template) 6657 */ 6658 function mergeTemplateAttributes(dst, src) { 6659 var srcAttr = src.$attr, 6660 dstAttr = dst.$attr, 6661 $element = dst.$$element; 6662 6663 // reapply the old attributes to the new element 6664 forEach(dst, function(value, key) { 6665 if (key.charAt(0) != '$') { 6666 if (src[key]) { 6667 value += (key === 'style' ? ';' : ' ') + src[key]; 6668 } 6669 dst.$set(key, value, true, srcAttr[key]); 6670 } 6671 }); 6672 6673 // copy the new attributes on the old attrs object 6674 forEach(src, function(value, key) { 6675 if (key == 'class') { 6676 safeAddClass($element, value); 6677 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 6678 } else if (key == 'style') { 6679 $element.attr('style', $element.attr('style') + ';' + value); 6680 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 6681 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 6682 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 6683 // have an attribute like "has-own-property" or "data-has-own-property", etc. 6684 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 6685 dst[key] = value; 6686 dstAttr[key] = srcAttr[key]; 6687 } 6688 }); 6689 } 6690 6691 6692 function compileTemplateUrl(directives, $compileNode, tAttrs, 6693 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 6694 var linkQueue = [], 6695 afterTemplateNodeLinkFn, 6696 afterTemplateChildLinkFn, 6697 beforeTemplateCompileNode = $compileNode[0], 6698 origAsyncDirective = directives.shift(), 6699 // The fact that we have to copy and patch the directive seems wrong! 6700 derivedSyncDirective = extend({}, origAsyncDirective, { 6701 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 6702 }), 6703 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 6704 ? origAsyncDirective.templateUrl($compileNode, tAttrs
6704) 6705 : origAsyncDirective.templateUrl; 6706 6707 $compileNode.empty(); 6708 6709 $http.get($sce.getTrustedResourceUrl(templateUrl), {cache: $templateCache}). 6710 success(function(content) { 6711 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 6712 6713 content = denormalizeTemplate(content); 6714 6715 if (origAsyncDirective.replace) { 6716 if (jqLiteIsTextNode(content)) { 6717 $template = []; 6718 } else { 6719 $template = jqLite(content); 6720 } 6721 compileNode = $template[0]; 6722 6723 if ($template.length != 1 || compileNode.nodeType !== 1) { 6724 throw $compileMinErr('tplrt', 6725 "Template for directive '{0}' must have exactly one root element. {1}", 6726 origAsyncDirective.name, templateUrl); 6727 } 6728 6729 tempTemplateAttrs = {$attr: {}}; 6730 replaceWith($rootElement, $compileNode, compileNode); 6731 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 6732 6733 if (isObject(origAsyncDirective.scope)) { 6734 markDirectivesAsIsolate(templateDirectives); 6735 } 6736 directives = templateDirectives.concat(directives); 6737 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 6738 } else { 6739 compileNode = beforeTemplateCompileNode; 6740 $compileNode.html(content); 6741 } 6742 6743 directives.unshift(derivedSyncDirective); 6744 6745 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 6746 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 6747 previousCompileContext); 6748 forEach($rootElement, function(node, i) { 6749 if (node == compileNode) { 6750 $rootElement[i] = $compileNode[0]; 6751 } 6752 }); 6753 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 6754 6755 6756 while(linkQueue.length) { 6757 var scope = linkQueue.shift(), 6758 beforeTemplateLinkNode = linkQueue.shift(), 6759 linkRootElement = linkQueue.shift(), 6760 boundTranscludeFn = linkQueue.shift(), 6761 linkNode = $compileNode[0]; 6762 6763 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 6764 var oldClasses = beforeTemplateLinkNode.className; 6765 6766 if (!(previousCompileContext.hasElementTranscludeDirective && 6767 origAsyncDirective.replace)) { 6768 // it was cloned therefore we have to clone as well. 6769 linkNode = jqLiteClone(compileNode); 6770 } 6771 6772 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 6773 6774 // Copy in CSS classes from original node 6775 safeAddClass(jqLite(linkNode), oldClasses); 6776 } 6777 if (afterTemplateNodeLinkFn.transclude) { 6778 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude); 6779 } else { 6780 childBoundTranscludeFn = boundTranscludeFn; 6781 } 6782 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 6783 childBoundTranscludeFn); 6784 } 6785 linkQueue = null; 6786 }). 6787 error(function(response, code, headers, config) { 6788 throw $compileMinErr('tpload', 'Failed to load template: {0}', config.url); 6789 }); 6790 6791 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 6792 if (linkQueue) { 6793 linkQueue.push(scope); 6794 linkQueue.push(node); 6795 linkQueue.push(rootElement); 6796 linkQueue.push(boundTranscludeFn); 6797 } else { 6798 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn); 6799 } 6800 }; 6801 } 6802 6803 6804 /** 6805 * Sorting function for bound directives. 6806 */ 6807 function byPriority(a, b) { 6808 var diff = b.priority - a.priority; 6809 if (diff !== 0) return diff; 6810 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 6811 return a.index - b.index; 6812 } 6813 6814 6815 function assertNoDuplicate(what, previousDirective, directive, element) { 6816 if (previousDirective) { 6817 throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', 6818 previousDirective.name, directive.name, what, startingTag(element)); 6819 } 6820 } 6821 6822 6823 function addTextInterpolateDirective(directives, text) { 6824 var interpolateFn = $interpolate(text, true); 6825 if (interpolateFn) { 6826 directives.push({ 6827 priority: 0, 6828 compile: valueFn(function textInterpolateLinkFn(scope, node) { 6829 var parent = node.parent(), 6830 bindings = parent.data('$binding') || []; 6831 bindings.push(interpolateFn); 6832 safeAddClass(parent.data('$binding', bindings), 'ng-binding'); 6833 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 6834 node[0].nodeValue = value; 6835 }); 6836 }) 6837 }); 6838 } 6839 } 6840 6841 6842 function getTrustedContext(node, attrNormalizedName) { 6843 if (attrNormalizedName == "srcdoc") { 6844 return $sce.HTML; 6845 } 6846 var tag = nodeName_(node); 6847 // maction[xlink:href] can source SVG. It's not limited to <maction>. 6848 if (attrNormalizedName == "xlinkHref" || 6849 (tag == "FORM" && attrNormalizedName == "action") || 6850 (tag != "IMG" && (attrNormalizedName == "src" || 6851 attrNormalizedName == "ngSrc"))) { 6852 return $sce.RESOURCE_URL; 6853 } 6854 } 6855 6856 6857 function addAttrInterpolateDirective(node, directives, value, name) { 6858 var interpolateFn = $interpolate(value, true); 6859 6860 // no interpolation found -> ignore 6861 if (!interpolateFn) return; 6862 6863 6864 if (name === "multiple" && nodeName_(node) === "SELECT") { 6865 throw $compileMinErr("selmulti", 6866 "Binding to the 'multiple' attribute is not supported. Element: {0}", 6867 startingTag(node));
6868 } 6869 6870 directives.push({ 6871 priority: 100, 6872 compile: function() { 6873 return { 6874 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 6875 var $$observers = (attr.$$observers || (attr.$$observers = {})); 6876 6877 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 6878 throw $compileMinErr('nodomevents', 6879 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 6880 "ng- versions (such as ng-click instead of onclick) instead."); 6881 } 6882 6883 // we need to interpolate again, in case the attribute value has been updated 6884 // (e.g. by another directive's compile function) 6885 interpolateFn = $interpolate(attr[name], true, getTrustedContext(node, name)); 6886 6887 // if attribute was updated so that there is no interpolation going on we don't want to 6888 // register any observers 6889 if (!interpolateFn) return; 6890 6891 // TODO(i): this should likely be attr.$set(name, iterpolateFn(scope) so that we reset the 6892 // actual attr value 6893 attr[name] = interpolateFn(scope); 6894 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 6895 (attr.$$observers && attr.$$observers[name].$$scope || scope). 6896 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 6897 //special case for class attribute addition + removal 6898 //so that class changes can tap into the animation 6899 //hooks provided by the $animate service. Be sure to 6900 //skip animations when the first digest occurs (when 6901 //both the new and the old values are the same) since 6902 //the CSS classes are the non-interpolated values 6903 if(name === 'class' && newValue != oldValue) { 6904 attr.$updateClass(newValue, oldValue); 6905 } else { 6906 attr.$set(name, newValue); 6907 } 6908 }); 6909 } 6910 }; 6911 } 6912 }); 6913 } 6914 6915 6916 /** 6917 * This is a special jqLite.replaceWith, which can replace items which 6918 * have no parents, provided that the containing jqLite collection is provided. 6919 * 6920 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 6921 * in the root of the tree. 6922 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 6923 * the shell, but replace its DOM node reference. 6924 * @param {Node} newNode The new DOM node. 6925 */ 6926 function replaceWith($rootElement, elementsToRemove, newNode) { 6927 var firstElementToRemove = elementsToRemove[0], 6928 removeCount = elementsToRemove.length, 6929 parent = firstElementToRemove.parentNode, 6930 i, ii; 6931 6932 if ($rootElement) { 6933 for(i = 0, ii = $rootElement.length; i < ii; i++) { 6934 if ($rootElement[i] == firstElementToRemove) { 6935 $rootElement[i++] = newNode; 6936 for (var j = i, j2 = j + removeCount - 1, 6937 jj = $rootElement.length; 6938 j < jj; j++, j2++) { 6939 if (j2 < jj) { 6940 $rootElement[j] = $rootElement[j2]; 6941 } else { 6942 delete $rootElement[j]; 6943 } 6944 } 6945 $rootElement.length -= removeCount - 1; 6946 break; 6947 } 6948 } 6949 } 6950 6951 if (parent) { 6952 parent.replaceChild(newNode, firstElementToRemove); 6953 } 6954 var fragment = document.createDocumentFragment(); 6955 fragment.appendChild(firstElementToRemove); 6956 newNode[jqLite.expando] = firstElementToRemove[jqLite.expando]; 6957 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 6958 var element = elementsToRemove[k]; 6959 jqLite(element).remove(); // must do this way to clean up expando 6960 fragment.appendChild(element); 6961 delete elementsToRemove[k]; 6962 } 6963 6964 elementsToRemove[0] = newNode; 6965 elementsToRemove.length = 1; 6966 } 6967 6968 6969 function cloneAndAnnotateFn(fn, annotation) { 6970 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 6971 } 6972 }]; 6973} 6974 6975var PREFIX_REGEXP = /^(x[\:\-_]|data[\:\-_])/i; 6976/** 6977 * Converts all accepted directives format into proper directive name. 6978 * All of these will become 'myDirective': 6979 * my:Directive 6980 * my-directive 6981 * x-my-directive 6982 * data-my:directive 6983 * 6984 * Also there is special case for Moz prefix starting with upper case letter. 6985 * @param name Name to normalize 6986 */ 6987function directiveNormalize(name) { 6988 return camelCase(name.replace(PREFIX_REGEXP, '')); 6989} 6990 6991/** 6992 * @ngdoc type 6993 * @name $compile.directive.Attributes 6994 * 6995 * @description 6996 * A shared object between directive compile / linking functions which contains
6996normalized DOM 6997 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 6998 * needed since all of these are treated as equivalent in Angular: 6999 * 7000 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 7001 */ 7002 7003/** 7004 * @ngdoc property 7005 * @name $compile.directive.Attributes#$attr 7006 * @returns {object} A map of DOM element attribute names to the normalized name. This is 7007 * needed to do reverse lookup from normalized name back to actual name. 7008 */ 7009 7010 7011/** 7012 * @ngdoc method 7013 * @name $compile.directive.Attributes#$set 7014 * @function 7015 * 7016 * @description 7017 * Set DOM element attribute value. 7018 * 7019 * 7020 * @param {string} name Normalized element attribute name of the property to modify. The name is 7021 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 7022 * property to the original name. 7023 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 7024 */ 7025 7026 7027 7028/** 7029 * Closure compiler type information 7030 */ 7031 7032function nodesetLinkingFn( 7033 /* angular.Scope */ scope, 7034 /* NodeList */ nodeList, 7035 /* Element */ rootElement, 7036 /* function(Function) */ boundTranscludeFn 7037){} 7038 7039function directiveLinkingFn( 7040 /* nodesetLinkingFn */ nodesetLinkingFn, 7041 /* angular.Scope */ scope, 7042 /* Node */ node, 7043 /* Element */ rootElement, 7044 /* function(Function) */ boundTranscludeFn 7045){} 7046 7047function tokenDifference(str1, str2) { 7048 var values = '', 7049 tokens1 = str1.split(/\s+/), 7050 tokens2 = str2.split(/\s+/); 7051 7052 outer: 7053 for(var i = 0; i < tokens1.length; i++) { 7054 var token = tokens1[i]; 7055 for(var j = 0; j < tokens2.length; j++) { 7056 if(token == tokens2[j]) continue outer; 7057 } 7058 values += (values.length > 0 ? ' ' : '') + token; 7059 } 7060 return values; 7061} 7062 7063/** 7064 * @ngdoc provider 7065 * @name $controllerProvider 7066 * @description 7067 * The {@link ng.$controller $controller service} is used by Angular to create new 7068 * controllers. 7069 * 7070 * This provider allows controller registration via the 7071 * {@link ng.$controllerProvider#register register} method. 7072 */ 7073function $ControllerProvider() { 7074 var controllers = {}, 7075 CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 7076 7077 7078 /** 7079 * @ngdoc method 7080 * @name $controllerProvider#register 7081 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 7082 * the names and the values are the constructors. 7083 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 7084 * annotations in the array notation). 7085 */ 7086 this.register = function(name, constructor) { 7087 assertNotHasOwnProperty(name, 'controller'); 7088 if (isObject(name)) { 7089 extend(controllers, name); 7090 } else { 7091 controllers[name] = constructor; 7092 } 7093 }; 7094 7095 7096 this.$get = ['$injector', '$window', function($injector, $window) { 7097 7098 /** 7099 * @ngdoc service 7100 * @name $controller 7101 * @requires $injector 7102 * 7103 * @param {Function|string} constructor If called with a function then it's considered to be the 7104 * controller constructor function. Otherwise it's considered to be a string which is used 7105 * to retrieve the controller constructor using the following steps: 7106 * 7107 * * check if a controller with given name is registered via `$controllerProvider` 7108 * * check if evaluating the string on the current scope returns a constructor 7109 * * check `window[constructor]` on the global `window` object 7110 * 7111 * @param {Object} locals Injection locals for Controller. 7112 * @return {Object} Instance of given controller. 7113 * 7114 * @description 7115 * `$controller` service is responsible for instantiating controllers. 7116 * 7117 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 7118 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 7119 */ 7120 return function(expression, locals) { 7121 var instance, match, constructor, identifier; 7122 7123 if(isString(expression)) { 7124 match = expression.match(CNTRL_REG), 7125 constructor = match[1], 7126 identifier = match[3]; 7127 expression = controllers.hasOwnProperty(constructor) 7128 ? controllers[constructor] 7129 : getter(locals.$scope, constructor, true) || getter($window, constructor, true); 7130 7131 assertArgFn(expression, constructor, true); 7132 } 7133 7134 instance = $injector.instantiate(expression, locals); 7135 7136 if (identifier) { 7137 if (!(locals && typeof locals.$scope == 'object')) { 7138 throw minErr('$controller')('noscp', 7139 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 7140 constructor || expression.name, identifier); 7141 } 7142 7143 locals.$scope[identifier] = instance; 7144 } 7145 7146 return instance; 7147 }; 7148 }]; 7149} 7150 7151/** 7152 * @ngdoc service 7153 * @name $document 7154 * @requires $window 7155 * 7156 * @description 7157 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 7158 * 7159 * @example 7160 <example> 7161 <file name="index.html"> 7162 <div ng-controller="MainCtrl"> 7163 <p>$document title: <b ng-bind="title"></b></p> 7164 <p>window.document title: <b ng-bind="windowTitle"></b></p> 7165 </div> 7166 </file> 7167 <file name="script.js"> 7168 function MainCtrl($scope, $document) { 7169 $scope.title = $document[0].title; 7170 $scope.windowTitle = angular.element(window.document)[0].title; 7171 } 7172 </file> 7173 </example> 7174 */ 7175function $DocumentProvider(){ 7176 this.$get = ['$window', function(window){ 7177 return jqLite(window.document); 7178 }]; 7179} 7180 7181/** 7182 * @ngdoc service 7183 * @name $exceptionHandler 7184 * @requires ng.$log 7185 * 7186 * @description 7187 * Any uncaught exception in angular expressions is delegated to this service. 7188 * The default implementation simply delegates to `$log.error` which logs it into 7189 * the browser console. 7190 * 7191 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 7192 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 7193 * 7194 * ## Example: 7195 * 7196 * ```js 7197 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function () { 7198 * return function (exception, cause) { 7199 * exception.message += ' (caused by "' + cause + '")'; 7200 * throw exception; 7201 * }; 7202 * }); 7203 * ``` 7204 * 7205 * This example will override the normal action of `$exceptionHandler`, to make angular
7206 * exceptions fail hard when they happen, instead of just logging to the console. 7207 * 7208 * @param {Error} exception Exception associated with the error. 7209 * @param {string=} cause optional information about the context in which 7210 * the error was thrown. 7211 * 7212 */ 7213function $ExceptionHandlerProvider() { 7214 this.$get = ['$log', function($log) { 7215 return function(exception, cause) { 7216 $log.error.apply($log, arguments); 7217 }; 7218 }]; 7219} 7220 7221/** 7222 * Parse headers into key value object 7223 * 7224 * @param {string} headers Raw headers as a string 7225 * @returns {Object} Parsed headers as key value object 7226 */ 7227function parseHeaders(headers) { 7228 var parsed = {}, key, val, i; 7229 7230 if (!headers) return parsed; 7231 7232 forEach(headers.split('\n'), function(line) { 7233 i = line.indexOf(':'); 7234 key = lowercase(trim(line.substr(0, i))); 7235 val = trim(line.substr(i + 1)); 7236 7237 if (key) { 7238 if (parsed[key]) { 7239 parsed[key] += ', ' + val; 7240 } else { 7241 parsed[key] = val; 7242 } 7243 } 7244 }); 7245 7246 return parsed; 7247} 7248 7249 7250/** 7251 * Returns a function that provides access to parsed headers. 7252 * 7253 * Headers are lazy parsed when first requested. 7254 * @see parseHeaders 7255 * 7256 * @param {(string|Object)} headers Headers to provide access to. 7257 * @returns {function(string=)} Returns a getter function which if called with: 7258 * 7259 * - if called with single an argument returns a single header value or null
7260 * - if called with no arguments returns an object containing all headers. 7261 */ 7262function headersGetter(headers) { 7263 var headersObj = isObject(headers) ? headers : undefined; 7264 7265 return function(name) { 7266 if (!headersObj) headersObj = parseHeaders(headers); 7267 7268 if (name) { 7269 return headersObj[lowercase(name)] || null; 7270 } 7271 7272 return headersObj; 7273 }; 7274} 7275 7276 7277/** 7278 * Chain all given functions 7279 * 7280 * This function is used for both request and response transforming 7281 * 7282 * @param {*} data Data to transform. 7283 * @param {function(string=)} headers Http headers getter fn. 7284 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 7285 * @returns {*} Transformed data. 7286 */ 7287function transformData(data, headers, fns) { 7288 if (isFunction(fns)) 7289 return fns(data, headers); 7290 7291 forEach(fns, function(fn) { 7292 data = fn(data, headers); 7293 }); 7294 7295 return data; 7296} 7297 7298 7299function isSuccess(status) { 7300 return 200 <= status && status < 300; 7301} 7302 7303 7304function $HttpProvider() { 7305 var JSON_START = /^\s*(\[|\{[^\{])/, 7306 JSON_END = /[\}\]]\s*$/, 7307 PROTECTION_PREFIX = /^\)\]\}',?\n/, 7308 CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'}; 7309 7310 var defaults = this.defaults = { 7311 // transform incoming response data 7312 transformResponse: [function(data) { 7313 if (isString(data)) { 7314 // strip json vulnerability protection prefix 7315 data = data.replace(PROTECTION_PREFIX, ''); 7316 if (JSON_START.test(data) && JSON_END.test(data)) 7317 data = fromJson(data); 7318 } 7319 return data; 7320 }], 7321 7322 // transform outgoing request data 7323 transformRequest: [function(d) { 7324 return isObject(d) && !isFile(d) && !isBlob(d) ? toJson(d) : d; 7325 }], 7326 7327 // default headers 7328 headers: { 7329 common: { 7330 'Accept': 'application/json, text/plain, */*' 7331 }, 7332 post: copy(CONTENT_TYPE_APPLICATION_JSON), 7333 put: copy(CONTENT_TYPE_APPLICATION_JSON), 7334 patch: copy(CONTENT_TYPE_APPLICATION_JSON) 7335 }, 7336 7337 xsrfCookieName: 'XSRF-TOKEN', 7338 xsrfHeaderName: 'X-XSRF-TOKEN' 7339 }; 7340 7341 /** 7342 * Are ordered by request, i.e. they are applied in the same order as the 7343 * array, on request, but reverse order, on response. 7344 */ 7345 var interceptorFactories = this.interceptors = []; 7346 7347 /** 7348 * For historical reasons, response interceptors are ordered by the order in which 7349 * they are applied to the response. (This is the opposite of interceptorFactories) 7350 */ 7351 var responseInterceptorFactories = this.responseInterceptors = []; 7352 7353 this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', 7354 function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { 7355 7356 var defaultCache = $cacheFactory('$http'); 7357 7358 /** 7359 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 7360 * The reversal is needed so that we can build up the interception chain around the 7361 * server request. 7362 */ 7363 var reversedInterceptors = []; 7364 7365 forEach(interceptorFactories, function(interceptorFactory) { 7366 reversedInterceptors.unshift(isString(interceptorFactory) 7367 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 7368 }); 7369 7370 forEach(responseInterceptorFactories, function(interceptorFactory, index) { 7371 var responseFn = isString(interceptorFactory) 7372 ? $injector.get(interceptorFactory) 7373 : $injector.invoke(interceptorFactory); 7374 7375 /** 7376 * Response interceptors go before "around" interceptors (no real reason, just 7377 * had to pick one.) But they are already reversed, so we can't use unshift, hence 7378 * the splice. 7379 */ 7380 reversedInterceptors.splice(index, 0, { 7381 response: function(response) { 7382 return responseFn($q.when(response)); 7383 }, 7384 responseError: function(response) { 7385 return responseFn($q.reject(response)); 7386 } 7387 }); 7388 }); 7389 7390 7391 /** 7392 * @ngdoc service 7393 * @kind function 7394 * @name $http 7395 * @requires ng.$httpBackend 7396 * @requires $cacheFactory 7397 * @requires $rootScope 7398 * @requires $q 7399 * @requires $injector 7400 *
7401 * @description 7402 * The `$http` service is a core Angular service that facilitates communication with the remote 7403 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 7404 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 7405 * 7406 * For unit testing applications that use `$http` service, see 7407 * {@link ngMock.$httpBackend $httpBackend mock}. 7408 * 7409 * For a higher level of abstraction, please check out the {@link ngResource.$resource 7410 * $resource} service. 7411 * 7412 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 7413 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 7414 * it is important to familiarize yourself with these APIs and the guarantees they provide. 7415 * 7416 * 7417 * # General usage 7418 * The `$http` service is a function which takes a single argument â a configuration object â 7419 * that is used to generate an HTTP request and returns a {@link ng.$q promise} 7420 * with two $http specific methods: `success` and `error`. 7421 * 7422 * ```js 7423 * $http({method: 'GET', url: '/someUrl'}). 7424 * success(function(data, status, headers, config) { 7425 * // this callback will be called asynchronously 7426 * // when the response is available 7427 * }). 7428 * error(function(data, status, headers, config) { 7429 * // called asynchronously if an error occurs 7430 * // or server returns response with an error status. 7431 * }); 7432 * ``` 7433 * 7434 * Since the returned value of calling the $http function is a `promise`, you can also use 7435 * the `then` method to register callbacks, and these callbacks will receive a single argument â 7436 * an object representing the response. See the API signature and type info below for more 7437 * details. 7438 * 7439 * A response status code between 200 and 299 is considered a success status and 7440 * will result in the success callback being called. Note that if the response is a redirect, 7441 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 7442 * called for such responses. 7443 * 7444 * # Writing Unit Tests that use $http 7445 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 7446 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 7447 * request using trained responses. 7448 * 7449 * ``` 7450 * $httpBackend.expectGET(...); 7451 * $http.get(...); 7452 * $httpBackend.flush(); 7453 * ``` 7454 * 7455 * # Shortcut methods 7456 * 7457 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 7458 * request data must be passed in for POST/PUT requests. 7459 * 7460 * ```js 7461 * $http.get('/someUrl').success(successCallback); 7462 * $http.post('/someUrl', data).success(successCallback); 7463 * ``` 7464 * 7465 * Complete list of shortcut methods: 7466 * 7467 * - {@link ng.$http#get $http.get} 7468 * - {@link ng.$http#head $http.head} 7469 * - {@link ng.$http#post $http.post} 7470 * - {@link ng.$http#put $http.put} 7471 * - {@link ng.$http#delete $http.delete} 7472 * - {@link ng.$http#jsonp $http.jsonp} 7473 * 7474 * 7475 * # Setting HTTP Headers 7476 * 7477 * The $http service will automatically add certain HTTP headers to all requests. These defaults 7478 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 7479 * object, which currently contains this default configuration: 7480 * 7481 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 7482 * - `Accept: application/json, text/plain, * / *` 7483 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 7484 * - `Content-Type: application/json` 7485 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 7486 * - `Content-Type: application/json` 7487 * 7488 * To add or overwrite these defaults, simply add or remove a property from these configuration 7489 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
7490 * with the lowercased HTTP method name as the key, e.g. 7491 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. 7492 * 7493 * The defaults can also be set at runtime via the `$http.defaults` object in the same 7494 * fashion. For example: 7495 * 7496 * ``` 7497 * module.run(function($http) { 7498 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' 7499 * }); 7500 * ``` 7501 * 7502 * In addition, you can supply a `headers` property in the config object passed when 7503 * calling `$http(config)`, which overrides the defaults without changing them globally. 7504 * 7505 * 7506 * # Transforming Requests and Responses 7507 * 7508 * Both requests and responses can be transformed using transform functions. By default, Angular 7509 * applies these transformations: 7510 * 7511 * Request transformations: 7512 * 7513 * - If the `data` property of the request configuration object contains an object, serialize it 7514 * into JSON format. 7515 * 7516 * Response transformations: 7517 * 7518 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 7519 * - If JSON response is detected, deserialize it using a JSON parser. 7520 * 7521 * To globally augment or override the default transforms, modify the 7522 * `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse` 7523 * properties. These properties are by default an array of transform functions, which allows you 7524 * to `push` or `unshift` a new transformation function into the transformation chain. You can 7525 * also decide to completely override any default transformations by assigning your 7526 * transformation functions to these properties directly without the array wrapper. These defaults 7527 * are again available on the $http factory at run-time, which may be useful if you have run-time 7528 * services you wish to be involved in your transformations. 7529 * 7530 * Similarly, to locally override the request/response transforms, augment the 7531 * `transformRequest` and/or `transformResponse` properties of the configuration object passed 7532 * into `$http`. 7533 * 7534 * 7535 * # Caching 7536 * 7537 * To enable caching, set the request configuration `cache` property to `true` (to use default 7538 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 7539 * When the cache is enabled, `$http` stores the response from the server in the specified 7540 * cache. The next time the same request is made, the response is served from the cache without 7541 * sending a request to the server. 7542 * 7543 * Note that even if the response is served from cache, delivery of the data is asynchronous in 7544 * the same way that real requests are. 7545 * 7546 * If there are multiple GET requests for the same URL that should be cached using the same 7547 * cache, but the cache is not populated yet, only one request to the server will be made and 7548 * the remaining requests will be fulfilled using the response from the first request. 7549 * 7550 * You can change the default cache to a new object (built with 7551 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 7552 * {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set 7553 * their `cache` property to `true` will now use this cache object. 7554 * 7555 * If you set the default cache to `false` then only requests that specify their own custom 7556 * cache object will be cached. 7557 * 7558 * # Interceptors 7559 * 7560 * Before you start creating interceptors, be sure to understand the 7561 * {@link ng.$q $q and deferred/promise APIs}. 7562 * 7563 * For purposes of global error handling, authentication, or any kind of synchronous or 7564 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 7565 * able to intercept requests before they are handed to the server and 7566 * responses before they are handed over to the application code that 7567 * initiated these requests. The interceptors leverage the {@link ng.$q 7568 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 7569 * 7570 * The interceptors are service factories that are registered with the `$httpProvider` by 7571 * adding them to the `$httpProvider.interceptors` array. The factory is called and 7572 * injected with dependencies (if specified) and returns the interceptor. 7573 * 7574 * There are two kinds of interceptors (and two kinds of rejection interceptors): 7575 * 7576 * * `request`: interceptors get called with http `config` object. The function is free to 7577 * modify the `config` or create a new one. The function needs to return the `config` 7578 * directly or as a promise. 7579 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 7580 * resolved with a rejection. 7581 * * `response`: interceptors get called with http `response` object. The function is free to 7582 * modify the `response` or create a new one. The function needs to return the `response` 7583 * directly or as a promise. 7584 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 7585 * resolved with a rejection. 7586 * 7587 * 7588 * ```js 7589 * // register the interceptor as a service 7590 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7591 * return { 7592 * // optional method 7593 * 'request': function(config) {
7594 * // do something on success 7595 * return config || $q.when(config); 7596 * }, 7597 * 7598 * // optional method 7599 * 'requestError': function(rejection) { 7600 * // do something on error 7601 * if (canRecover(rejection)) { 7602 * return responseOrNewPromise 7603 * } 7604 * return $q.reject(rejection); 7605 * }, 7606 * 7607 * 7608 * 7609 * // optional method 7610 * 'response': function(response) { 7611 * // do something on success 7612 * return response || $q.when(response); 7613 * }, 7614 * 7615 * // optional method 7616 * 'responseError': function(rejection) { 7617 * // do something on error 7618 * if (canRecover(rejection)) { 7619 * return responseOrNewPromise 7620 * } 7621 * return $q.reject(rejection); 7622 * } 7623 * }; 7624 * }); 7625 * 7626 * $httpProvider.interceptors.push('myHttpInterceptor'); 7627 * 7628 * 7629 * // alternatively, register the interceptor via an anonymous factory 7630 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 7631 * return { 7632 * 'request': function(config) { 7633 * // same as above 7634 * }, 7635 * 7636 * 'response': function(response) { 7637 * // same as above 7638 * } 7639 * }; 7640 * }); 7641 * ``` 7642 * 7643 * # Response interceptors (DEPRECATED) 7644 * 7645 * Before you start creating interceptors, be sure to understand the 7646 * {@link ng.$q $q and deferred/promise APIs}. 7647 * 7648 * For purposes of global error handling, authentication or any kind of synchronous or 7649 * asynchronous preprocessing of received responses, it is desirable to be able to intercept 7650 * responses for http requests before they are handed over to the application code that 7651 * initiated these requests. The response interceptors leverage the {@link ng.$q 7652 * promise apis} to fulfil this need for both synchronous and asynchronous preprocessing. 7653 * 7654 * The interceptors are service factories that are registered with the $httpProvider by 7655 * adding them to the `$httpProvider.responseInterceptors` array. The factory is called and 7656 * injected with dependencies (if specified) and returns the interceptor â a function that 7657 * takes a {@link ng.$q promise} and returns the original or a new promise. 7658 * 7659 * ```js 7660 * // register the interceptor as a service 7661 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 7662 * return function(promise) { 7663 * return promise.then(function(response) { 7664 * // do something on success 7665 * return response; 7666 * }, function(response) { 7667 * // do something on error 7668 * if (canRecover(response)) { 7669 * return responseOrNewPromise 7670 * } 7671 * return $q.reject(response); 7672 * }); 7673 * } 7674 * }); 7675 * 7676 * $httpProvider.responseInterceptors.push('myHttpInterceptor'); 7677 * 7678 * 7679 * // register the interceptor via an anonymous factory 7680 * $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) { 7681 * return function(promise) { 7682 * // same as above 7683 * } 7684 * }); 7685 * ``` 7686 * 7687 * 7688 * # Security Considerations 7689 * 7690 * When designing web applications, consider security threats from: 7691 * 7692 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 7693 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 7694 * 7695 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 7696 * pre-configured with strategies that address these issues, but for this to work backend server 7697 * cooperation is required. 7698 * 7699 * ## JSON Vulnerability Protection 7700 * 7701 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 7702 * allows third party website to turn your JSON resource URL into 7703 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 7704 * counter this your server can prefix all JSON requests with following str
7704ing `")]}',\n"`. 7705 * Angular will automatically strip the prefix before processing it as JSON. 7706 * 7707 * For example if your server needs to return: 7708 * ```js 7709 * ['one','two'] 7710 * ``` 7711 * 7712 * which is vulnerable to attack, your server can return: 7713 * ```js 7714 * )]}', 7715 * ['one','two'] 7716 * ``` 7717 * 7718 * Angular will strip the prefix, before processing the JSON. 7719 * 7720 * 7721 * ## Cross Site Request Forgery (XSRF) Protection 7722 * 7723 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which 7724 * an unauthorized site can gain your user's private data. Angular provides a mechanism 7725 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 7726 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 7727 * JavaScript that runs on your domain could read the cookie, your server can be assured that 7728 * the XHR came from JavaScript running on your domain. The header will not be set for 7729 * cross-domain requests. 7730 * 7731 * To take advantage of this, your server needs to set a token in a JavaScript readable session 7732 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 7733 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 7734 * that only JavaScript running on your domain could have sent the request. The token must be 7735 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 7736 * making up its own tokens). We recommend that the token is a digest of your site's 7737 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 7738 * for added security. 7739 * 7740 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 7741 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 7742 * or the per-request config object. 7743 * 7744 * 7745 * @param {object} config Object describing the request to be made and how it should be 7746 * processed. The object has following properties: 7747 * 7748 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 7749 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 7750 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be turned 7751 * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be 7752 * JSONified. 7753 * - **data** â `{string|Object}` â Data to be sent as the request message data. 7754 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 7755 * HTTP headers to send to the server. If the return value of a function is null, the 7756 * header will not be sent. 7757 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 7758 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 7759 * - **transformRequest** â 7760 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 7761 * transform function or an array of such functions. The transform function takes the http 7762 * request body and headers and returns its transformed (typically serialized) version. 7763 * - **transformResponse** â 7764 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` â 7765 * transform function or an array of such functions. The transform function takes the http 7766 * response body and headers and returns its transformed (typically deserialized) version. 7767 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 7768 * GET request, otherwise if a cache instance built with 7769 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 7770 * caching. 7771 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 7772 * that should abort the request when resolved. 7773 * - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the 7774 * XHR object. See [requests with credentials]https://developer.mozilla.org/en/http_access_control#section_5 7775 * for more information. 7776 * - **responseType** - `{string}` - see 7777 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). 7778 * 7779 * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the 7780 * standard `then` method and two http specific methods: `success` and `error`. The `then` 7781 * method takes two arguments a success and an error callback which will be called with a 7782 * response object. The `success` and `error` methods take a single argument - a function that 7783 * will be called when the request succeeds or fails respectively. The arguments passed into 7784 * these functions are destructured representation of the response object passed into the 7785 * `then` method. The response object has these properties: 7786 * 7787 * - **data** â `{string|Object}` â The response body transformed with the transform 7788 * functions. 7789 * - **status** â `{number}` â HTTP status code of the response. 7790 * - **headers** â `{function([headerName])}` â Header getter function. 7791 * - **config** â `{Object}` â The configuration object that was used to generate the request. 7792 * - **statusText** â `{string}` â HTTP status text of the response. 7793 * 7794 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 7795 * requests. This is primarily meant to be used for debugging purposes. 7796 * 7797 * 7798 * @example 7799<example> 7800<file name="index.html"> 7801 <div ng-controller="FetchCtrl"> 7802 <select ng-model="method"> 7803 <option>GET</option> 7804 <option>JSONP</option> 7805 </select> 7806 <input type="text" ng-model="url" size="80"/> 7807 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 7808 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 7809 <button id="samplejsonpbtn" 7810 ng-click="updateModel('JSONP', 7811 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 7812 Sample JSONP 7813 </button> 7814 <button id="invalidjsonpbtn" 7815 ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 7816 Invalid JSONP 7817 </button> 7818 <pre>http status code: {{status}}</pre> 7819 <pre>http response data: {{data}}</pre> 7820 </div> 7821</file> 7822<file name="script.js"> 7823 function FetchCtrl($scope, $http, $templateCache) { 7824 $scope.method = 'GET'; 7825 $scope.url = 'http-hello.html'; 7826 7827 $scope.fetch = function() { 7828 $scope.code = null; 7829 $scope.response = null; 7830 7831 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 7832 success(function(data, status) { 7833 $scope.status = status; 7834 $scope.data = data; 7835 }). 7836 error(function(data, status) { 7837 $scope.data = data || "Request failed"; 7838 $scope.status = status; 7839 }); 7840 }; 7841 7842 $scope.updateModel = function(method, url) { 7843 $scope.method = method; 7844 $scope.url = url; 7845 }; 7846 } 7847</file> 7848<file name="http-hello.html"> 7849 Hello, $http! 7850</file> 7851<file name="protractor.js" type="protractor"> 7852 var status = element(by.binding('status')); 7853 var data = element(by.binding('data')); 7854 var fetchBtn = element(by.id('fetchbtn')); 7855 var sampleGetBtn = element(by.id('samplegetbtn')); 7856 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 7857 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 7858 7859 it('should make an xhr GET request', function() { 7860 sampleGetBtn.click(); 7861 fetchBtn.click(); 7862 expect(status.getText()).toMatch('200'); 7863 expect(data.getText()).toMatch(/Hello, \$http!/); 7864 }); 7865 7866 it('should make a JSONP request to angularjs.org', function() { 7867 sampleJsonpBtn.click(); 7868 fetchBtn.click(); 7869 expect(status.getText()).toMatch('200'); 7870 expect(data.getText()).toMatch(/Super Hero!/); 7871 }); 7872
7873 it('should make JSONP request to invalid URL and invoke the error handler', 7874 function() { 7875 invalidJsonpBtn.click(); 7876 fetchBtn.click(); 7877 expect(status.getText()).toMatch('0'); 7878 expect(data.getText()).toMatch('Request failed'); 7879 }); 7880</file> 7881</example> 7882 */ 7883 function $http(requestConfig) { 7884 var config = { 7885 method: 'get', 7886 transformRequest: defaults.transformRequest, 7887 transformResponse: defaults.transformResponse 7888 }; 7889 var headers = mergeHeaders(requestConfig); 7890 7891 extend(config, requestConfig); 7892 config.headers = headers; 7893 config.method = uppercase(config.method); 7894 7895 var xsrfValue = urlIsSameOrigin(config.url) 7896 ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] 7897 : undefined; 7898 if (xsrfValue) { 7899 headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 7900 } 7901 7902 7903 var serverRequest = function(config) { 7904 headers = config.headers; 7905 var reqData = transformData(config.data, headersGetter(headers), config.transformRequest); 7906 7907 // strip content-type if data is undefined 7908 if (isUndefined(config.data)) { 7909 forEach(headers, function(value, header) { 7910 if (lowercase(header) === 'content-type') { 7911 delete headers[header]; 7912 } 7913 }); 7914 } 7915 7916 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 7917 config.withCredentials = defaults.withCredentials; 7918 } 7919 7920 // send request 7921 return sendReq(config, reqData, headers).then(transformResponse, transformResponse); 7922 }; 7923 7924 var chain = [serverRequest, undefined]; 7925 var promise = $q.when(config); 7926 7927 // apply interceptors 7928 forEach(reversedInterceptors, function(interceptor) { 7929 if (interceptor.request || interceptor.requestError) { 7930 chain.unshift(interceptor.request, interceptor.requestError); 7931 } 7932 if (interceptor.response || interceptor.responseError) { 7933 chain.push(interceptor.response, interceptor.responseError); 7934 } 7935 }); 7936 7937 while(chain.length) { 7938 var thenFn = chain.shift(); 7939 var rejectFn = chain.shift(); 7940 7941 promise = promise.then(thenFn, rejectFn); 7942 } 7943 7944 promise.success = function(fn) { 7945 promise.then(function(response) { 7946 fn(response.data, response.status, response.headers, config); 7947 }); 7948 return promise; 7949 }; 7950 7951 promise.error = function(fn) { 7952 promise.then(null, function(response) { 7953 fn(response.data, response.status, response.headers, config); 7954 }); 7955 return promise; 7956 }; 7957 7958 return promise; 7959 7960 function transformResponse(response) { 7961 // make a copy since the response must be cacheable 7962 var resp = extend({}, response, { 7963 data: transformData(response.data, response.headers, config.transformResponse) 7964 }); 7965 return (isSuccess(response.status)) 7966 ? resp 7967 : $q.reject(resp); 7968 } 7969 7970 function mergeHeaders(config) { 7971 var defHeaders = defaults.headers, 7972 reqHeaders = extend({}, config.headers), 7973 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 7974 7975 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 7976 7977 // execute if header value is function 7978 execHeaders(defHeaders); 7979 execHeaders(reqHeaders); 7980 7981 // using for-in instead of forEach to avoid unecessary iteration after header has been found 7982 defaultHeadersIteration: 7983 for (defHeaderName in defHeaders) { 7984 lowercaseDefHeaderName = lowercase(defHeaderName); 7985 7986 for (reqHeaderName in reqHeaders) { 7987 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 7988 continue defaultHeadersIteration; 7989 } 7990 } 7991 7992 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 7993 } 7994 7995 return reqHeaders; 7996 7997 function execHeaders(headers) { 7998 var headerContent; 7999
8000 forEach(headers, function(headerFn, header) { 8001 if (isFunction(headerFn)) { 8002 headerContent = headerFn(); 8003 if (headerContent != null) { 8004 headers[header] = headerContent; 8005 } else { 8006 delete headers[header]; 8007 } 8008 } 8009 }); 8010 } 8011 } 8012 } 8013 8014 $http.pendingRequests = []; 8015 8016 /** 8017 * @ngdoc method 8018 * @name $http#get 8019 * 8020 * @description 8021 * Shortcut method to perform `GET` request. 8022 * 8023 * @param {string} url Relative or absolute URL specifying the destination of the request 8024 * @param {Object=} config Optional configuration object 8025 * @returns {HttpPromise} Future object 8026 */ 8027 8028 /** 8029 * @ngdoc method 8030 * @name $http#delete 8031 * 8032 * @description 8033 * Shortcut method to perform `DELETE` request. 8034 * 8035 * @param {string} url Relative or absolute URL specifying the destination of the request 8036 * @param {Object=} config Optional configuration object 8037 * @returns {HttpPromise} Future object 8038 */ 8039 8040 /** 8041 * @ngdoc method 8042 * @name $http#head 8043 * 8044 * @description 8045 * Shortcut method to perform `HEAD` request. 8046 * 8047 * @param {string} url Relative or absolute URL specifying the destination of the request 8048 * @param {Object=} config Optional configuration object 8049 * @returns {HttpPromise} Future object 8050 */ 8051 8052 /** 8053 * @ngdoc method 8054 * @name $http#jsonp 8055 * 8056 * @description 8057 * Shortcut method to perform `JSONP` request. 8058 * 8059 * @param {string} url Relative or absolute URL specifying the destination of the request. 8060 * Should contain `JSON_CALLBACK` string. 8061 * @param {Object=} config Optional configuration object 8062 * @returns {HttpPromise} Future object 8063 */ 8064 createShortMethods('get', 'delete', 'head', 'jsonp'); 8065 8066 /** 8067 * @ngdoc method 8068 * @name $http#post 8069 * 8070 * @description 8071 * Shortcut method to perform `POST` request. 8072 * 8073 * @param {string} url Relative or absolute URL specifying the destination of the request 8074 * @param {*} data Request content 8075 * @param {Object=} config Optional configuration object 8076 * @returns {HttpPromise} Future object 8077 */ 8078 8079 /** 8080 * @ngdoc method 8081 * @name $http#put 8082 * 8083 * @description 8084 * Shortcut method to perform `PUT` request. 8085 * 8086 * @param {string} url Relative or absolute URL specifying the destination of the request 8087 * @param {*} data Request content 8088 * @param {Object=} config Optional configuration object 8089 * @returns {HttpPromise} Future object 8090 */ 8091 createShortMethodsWithData('post', 'put'); 8092 8093 /** 8094 * @ngdoc property 8095 * @name $http#defaults 8096 * 8097 * @description 8098 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 8099 * default headers, withCredentials as well as request and response transformations. 8100 * 8101 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 8102 */ 8103 $http.defaults = defaults; 8104 8105 8106 return $http; 8107 8108 8109 function createShortMethods(names) { 8110 forEach(arguments, function(name) { 8111 $http[name] = function(url, config) { 8112 return $http(extend(config || {}, { 8113 method: name, 8114 url: url 8115 })); 8116 }; 8117 }); 8118 } 8119 8120 8121 function createShortMethodsWithData(name) { 8122 forEach(arguments, function(name) { 8123 $http[name] = function(url, data, config) { 8124 return $http(extend(config || {}, { 8125 method: name, 8126 url: url, 8127 data: data 8128 })); 8129 }; 8130 }); 8131 } 8132 8133 8134 /** 8135 * Makes the request. 8136 * 8137 * !!! ACCESSES CLOSURE VARS: 8138 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 8139 */ 8140 function sendReq(config, reqData, reqHeaders) { 8141 var deferred = $q.defer(), 8142 promise = deferred.promise, 8143 cache, 8144 cachedResp, 8145 url = buildUrl(config.url, config.params); 8146 8147 $http.pendingRequests.push(config); 8148 promise.then(removePendingReq, removePendingReq); 8149 8150 8151 if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') { 8152 cache = isObject(config.cache) ? config.cache 8153 : isObject(defaults.cache) ? defaults.cache 8154 : defaultCache; 8155 } 8156 8157 if (cache) { 8158 cachedResp = cache.get(url); 8159 if (isDefined(cachedResp)) { 8160 if (cachedResp.then) { 8161 // cached request has already been sent, but there is no response yet 8162 cachedResp.then(removePendingReq, removePendingReq); 8163 return cachedResp; 8164 } else { 8165 // serving from cache 8166 if (isArray(cachedResp)) { 8167 resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]), cachedResp[3]); 8168 } else { 8169 resolvePromise(cachedResp, 200, {}, 'OK'); 8170 } 8171 } 8172 } else { 8173 // put the promise for the non-transformed response into cache as a placeholder 8174 cache.put(url, promise); 8175 } 8176 } 8177 8178 // if we won't have the response in cache, send the request to the backend 8179 if (isUndefined(cachedResp)) { 8180 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 8181 config.withCredentials, config.responseType); 8182 } 8183 8184 return promise; 8185 8186 8187 /** 8188 * Callback registered to $httpBackend(): 8189 * - caches the response if desired 8190 * - resolves the raw $http promise 8191 * - calls $apply 8192 */ 8193 function done(status, response, headersString, statusText) { 8194 if (cache) { 8195 if (isSuccess(status)) { 8196 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 8197 } else { 8198 // remove promise from the cache 8199 cache.remove(url); 8200 } 8201 } 8202 8203 resolvePromise(response, status, headersString, statusText); 8204 if (!$rootScope.$$phase) $rootScope.$apply(); 8205 } 8206 8207 8208 /** 8209 * Resolves the raw $http promise. 8210 */ 8211 function resolvePromise(response, status, headers, statusText) { 8212 // normalize internal statuses to 0 8213 status = Math.max(status, 0); 8214 8215 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 8216 data: response, 8217 status: status, 8218 headers: headersGetter(headers), 8219 config: config, 8220 statusText : statusText 8221 }); 8222 } 8223 8224 8225 function removePendingReq() { 8226 var idx = indexOf($http.pendingRequests, config); 8227 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 8228 } 8229 } 8230 8231 8232 function buildUrl(url, params) { 8233 if (!params) return url; 8234 var parts = [];
8235 forEachSorted(params, function(value, key) { 8236 if (value === null || isUndefined(value)) return; 8237 if (!isArray(value)) value = [value]; 8238 8239 forEach(value, function(v) { 8240 if (isObject(v)) { 8241 v = toJson(v); 8242 } 8243 parts.push(encodeUriQuery(key) + '=' + 8244 encodeUriQuery(v)); 8245 }); 8246 }); 8247 if(parts.length > 0) { 8248 url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); 8249 } 8250 return url; 8251 } 8252 8253 8254 }]; 8255} 8256 8257function createXhr(method) { 8258 //if IE and the method is not RFC2616 compliant, or if XMLHttpRequest 8259 //is not available, try getting an ActiveXObject. Otherwise, use XMLHttpRequest 8260 //if it is available 8261 if (msie <= 8 && (!method.match(/^(get|post|head|put|delete|options)$/i) || 8262 !window.XMLHttpRequest)) { 8263 return new window.ActiveXObject("Microsoft.XMLHTTP"); 8264 } else if (window.XMLHttpRequest) { 8265 return new window.XMLHttpRequest(); 8266 } 8267 8268 throw minErr('$httpBackend')('noxhr', "This browser does not support XMLHttpRequest."); 8269} 8270 8271/** 8272 * @ngdoc service 8273 * @name $httpBackend 8274 * @requires $window 8275 * @requires $document 8276 * 8277 * @description 8278 * HTTP backend used by the {@link ng.$http service} that delegates to 8279 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 8280 * 8281 * You should never need to use this service directly, instead use the higher-level abstractions: 8282 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 8283 * 8284 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 8285 * $httpBackend} which can be trained with responses. 8286 */ 8287function $HttpBackendProvider() { 8288 this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { 8289 return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); 8290 }]; 8291} 8292 8293function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 8294 var ABORTED = -1; 8295 8296 // TODO(vojta): fix the signature 8297 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 8298 var status; 8299 $browser.$$incOutstandingRequestCount(); 8300 url = url || $browser.url(); 8301 8302 if (lowercase(method) == 'jsonp') { 8303 var callbackId = '_' + (callbacks.counter++).toString(36); 8304 callbacks[callbackId] = function(data) { 8305 callbacks[callbackId].data = data; 8306 }; 8307 8308 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 8309 function() { 8310 if (callbacks[callbackId].data) { 8311 completeRequest(callback, 200, callbacks[callbackId].data); 8312 } else { 8313 completeRequest(callback, status || -2); 8314 } 8315 callbacks[callbackId] = angular.noop; 8316 }); 8317 } else { 8318 8319 var xhr = createXhr(method); 8320 8321 xhr.open(method, url, true); 8322 forEach(headers, function(value, key) { 8323 if (isDefined(value)) { 8324 xhr.setRequestHeader(key, value); 8325 } 8326 }); 8327 8328 // In IE6 and 7, this might be called synchronously when xhr.send below is called and the 8329 // response is in the cache. the promise api will ensure that to the app code the api is 8330 // always async 8331 xhr.onreadystatechange = function() { 8332 // onreadystatechange might get called multiple times with readyState === 4 on mobile webkit caused by 8333 // xhrs that are resolved while the app is in the background (see #5426). 8334 // since calling completeRequest sets the `xhr` variable to null, we just check if it's not null before 8335 // continuing 8336 // 8337 // we can't set xhr.onreadystatechange to undefined or delete it because that breaks IE8 (method=PATCH) and 8338 // Safari respectively. 8339 if (xhr && xhr.readyState == 4) { 8340 var responseHeaders = null, 8341 response = null; 8342 8343 if(status !== ABORTED) { 8344 responseHeaders = xhr.getAllResponseHeaders(); 8345 8346 // responseText is the old-school way of retrieving response
8346(supported by IE8 & 9) 8347 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 8348 response = ('response' in xhr) ? xhr.response : xhr.responseText; 8349 } 8350 8351 completeRequest(callback, 8352 status || xhr.status, 8353 response, 8354 responseHeaders, 8355 xhr.statusText || ''); 8356 } 8357 }; 8358 8359 if (withCredentials) { 8360 xhr.withCredentials = true; 8361 } 8362 8363 if (responseType) { 8364 try { 8365 xhr.responseType = responseType; 8366 } catch (e) { 8367 // WebKit added support for the json responseType value on 09/03/2013 8368 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 8369 // known to throw when setting the value "json" as the response type. Other older 8370 // browsers implementing the responseType 8371 // 8372 // The json response type can be ignored if not supported, because JSON payloads are 8373 // parsed on the client-side regardless. 8374 if (responseType !== 'json') { 8375 throw e; 8376 } 8377 } 8378 } 8379 8380 xhr.send(post || null); 8381 } 8382 8383 if (timeout > 0) { 8384 var timeoutId = $browserDefer(timeoutRequest, timeout); 8385 } else if (timeout && timeout.then) { 8386 timeout.then(timeoutRequest); 8387 } 8388 8389 8390 function timeoutRequest() { 8391 status = ABORTED; 8392 jsonpDone && jsonpDone(); 8393 xhr && xhr.abort(); 8394 } 8395 8396 function completeRequest(callback, status, response, headersString, statusText) { 8397 // cancel timeout and subsequent timeout promise resolution 8398 timeoutId && $browserDefer.cancel(timeoutId); 8399 jsonpDone = xhr = null; 8400 8401 // fix status code when it is 0 (0 status is undocumented). 8402 // Occurs when accessing file resources or on Android 4.1 stock browser 8403 // while retrieving files from application cache. 8404 if (status === 0) { 8405 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 8406 } 8407 8408 // normalize IE bug (http://bugs.jquery.com/ticket/1450) 8409 status = status === 1223 ? 204 : status; 8410 statusText = statusText || ''; 8411 8412 callback(status, response, headersString, statusText); 8413 $browser.$$completeOutstandingRequest(noop); 8414 } 8415 }; 8416 8417 function jsonpReq(url, done) { 8418 // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: 8419 // - fetches local scripts via XHR and evals them 8420 // - adds and immediately removes script elements from the document 8421 var script = rawDocument.createElement('script'), 8422 doneWrapper = function() { 8423 script.onreadystatechange = script.onload = script.onerror = null; 8424 rawDocument.body.removeChild(script); 8425 if (done) done(); 8426 }; 8427 8428 script.type = 'text/javascript'; 8429 script.src = url; 8430 8431 if (msie && msie <= 8) { 8432 script.onreadystatechange = function() { 8433 if (/loaded|complete/.test(script.readyState)) { 8434 doneWrapper(); 8435 } 8436 }; 8437 } else { 8438 script.onload = script.onerror = function() { 8439 doneWrapper(); 8440 }; 8441 } 8442 8443 rawDocument.body.appendChild(script); 8444 return doneWrapper; 8445 } 8446} 8447 8448var $interpolateMinErr = minErr('$interpolate'); 8449 8450/** 8451 * @ngdoc provider 8452 * @name $interpolateProvider 8453 * @function 8454 * 8455 * @description 8456 *
8457 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 8458 * 8459 * @example 8460<example module="customInterpolationApp"> 8461<file name="index.html"> 8462<script> 8463 var customInterpolationApp = angular.module('customInterpolationApp', []); 8464 8465 customInterpolationApp.config(function($interpolateProvider) { 8466 $interpolateProvider.startSymbol('//'); 8467 $interpolateProvider.endSymbol('//'); 8468 }); 8469 8470 8471 customInterpolationApp.controller('DemoController', function DemoController() { 8472 this.label = "This binding is brought you by // interpolation symbols."; 8473 }); 8474</script> 8475<div ng-app="App" ng-controller="DemoController as demo"> 8476 //demo.label// 8477</div> 8478</file> 8479<file name="protractor.js" type="protractor"> 8480 it('should interpolate binding with custom symbols', function() { 8481 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 8482 }); 8483</file> 8484</example> 8485 */ 8486function $InterpolateProvider() { 8487 var startSymbol = '{{'; 8488 var endSymbol = '}}'; 8489 8490 /** 8491 * @ngdoc method 8492 * @name $interpolateProvider#startSymbol 8493 * @description 8494 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 8495 * 8496 * @param {string=} value new value to set the starting symbol to. 8497 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8498 */ 8499 this.startSymbol = function(value){ 8500 if (value) { 8501 startSymbol = value; 8502 return this; 8503 } else { 8504 return startSymbol; 8505 } 8506 }; 8507 8508 /** 8509 * @ngdoc method 8510 * @name $interpolateProvider#endSymbol 8511 * @description 8512 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8513 * 8514 * @param {string=} value new value to set the ending symbol to. 8515 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 8516 */ 8517 this.endSymbol = function(value){ 8518 if (value) { 8519 endSymbol = value; 8520 return this; 8521 } else { 8522 return endSymbol; 8523 } 8524 }; 8525 8526 8527 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 8528 var startSymbolLength = startSymbol.length, 8529 endSymbolLength = endSymbol.length; 8530 8531 /** 8532 * @ngdoc service 8533 * @name $interpolate 8534 * @function 8535 * 8536 * @requires $parse 8537 * @requires $sce 8538 * 8539 * @description 8540 * 8541 * Compiles a string with markup into an interpolation function. This service is used by the 8542 * HTML {@link ng.$compile $compile} service for data binding. See 8543 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 8544 * interpolation markup. 8545 * 8546 * 8547 * ```js 8548 * var $interpolate = ...; // injected 8549 * var exp = $interpolate('Hello {{name | uppercase}}!'); 8550 * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); 8551 * ``` 8552 * 8553 * 8554 * @param {string} text The text with markup to interpolate. 8555 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 8556 * embedded expression in order to return an interpolation function. Strings with no 8557 * embedded expression will return null for the interpolation function. 8558 * @param {string=} trustedContext when provided, the returned function passes the interpolated 8559 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 8560 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 8561 * provides Strict Contextual Escaping for details. 8562 * @returns {function(context)} an interpolation function which is used to compute the 8563 * interpolated string. The function has these parameters: 8564 * 8565 * * `context`: an object against which any expressions embedded in the strings are evaluated 8566 * against. 8567 * 8568 */ 8569 function $interpolate(text, mustHaveExpression, trustedContext) { 8570 var startIndex, 8571 endIndex, 8572 index = 0, 8573 parts = [], 8574 length = text.length, 8575 hasInterpolation = false, 8576 fn, 8577 exp, 8578 concat = []; 8579 8580 while(index < length) { 8581 if ( ((startIndex = text.indexOf(startSymbol, index)) != -1) && 8582 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1) ) { 8583 (index != startIndex) && parts.push(text.substring(index, startIndex)); 8584 parts.push(fn = $parse(exp = text.substring(startIndex + startSymbolLength, endIndex))); 8585 fn.exp = exp;
8586 index = endIndex + endSymbolLength; 8587 hasInterpolation = true; 8588 } else { 8589 // we did not find anything, so we have to add the remainder to the parts array 8590 (index != length) && parts.push(text.substring(index)); 8591 index = length; 8592 } 8593 } 8594 8595 if (!(length = parts.length)) { 8596 // we added, nothing, must have been an empty string. 8597 parts.push(''); 8598 length = 1; 8599 } 8600 8601 // Concatenating expressions makes it hard to reason about whether some combination of 8602 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 8603 // single expression be used for iframe[src], object[src], etc., we ensure that the value 8604 // that's used is assigned or constructed by some JS code somewhere that is more testable or 8605 // make it obvious that you bound the value to some user controlled value. This helps reduce 8606 // the load when auditing for XSS issues. 8607 if (trustedContext && parts.length > 1) { 8608 throw $interpolateMinErr('noconcat', 8609 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 8610 "interpolations that concatenate multiple expressions when a trusted value is " + 8611 "required. See http://docs.angularjs.org/api/ng.$sce", text); 8612 } 8613 8614 if (!mustHaveExpression || hasInterpolation) { 8615 concat.length = length; 8616 fn = function(context) { 8617 try { 8618 for(var i = 0, ii = length, part; i<ii; i++) { 8619 if (typeof (part = parts[i]) == 'function') { 8620 part = part(context); 8621 if (trustedContext) { 8622 part = $sce.getTrusted(trustedContext, part); 8623 } else { 8624 part = $sce.valueOf(part); 8625 } 8626 if (part === null || isUndefined(part)) { 8627 part = ''; 8628 } else if (typeof part != 'string') { 8629 part = toJson(part); 8630 } 8631 } 8632 concat[i] = part; 8633 } 8634 return concat.join(''); 8635 } 8636 catch(err) { 8637 var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, 8638 err.toString()); 8639 $exceptionHandler(newErr); 8640 } 8641 }; 8642 fn.exp = text; 8643 fn.parts = parts; 8644 return fn; 8645 } 8646 } 8647 8648 8649 /** 8650 * @ngdoc method 8651 * @name $interpolate#startSymbol 8652 * @description 8653 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 8654 * 8655 * Use {@link ng.$interpolateProvider#startSymbol $interpolateProvider#startSymbol} to change 8656 * the symbol. 8657 * 8658 * @returns {string} start symbol. 8659 */ 8660 $interpolate.startSymbol = function() { 8661 return startSymbol; 8662 }; 8663 8664 8665 /** 8666 * @ngdoc method 8667 * @name $interpolate#endSymbol 8668 * @description 8669 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 8670 * 8671 * Use {@link ng.$interpolateProvider#endSymbol $interpolateProvider#endSymbol} to change 8672 * the symbol. 8673 * 8674 * @returns {string} end symbol. 8675 */ 8676 $interpolate.endSymbol = function() { 8677 return endSymbol; 8678 }; 8679 8680 return $interpolate; 8681 }]; 8682} 8683 8684function $IntervalProvider() { 8685 this.$get = ['$rootScope', '$window', '$q', 8686 function($rootScope, $window, $q) { 8687 var intervals = {}; 8688 8689 8690 /** 8691 * @ngdoc service 8692 * @name $interval 8693 * 8694 * @description 8695 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 8696 * milliseconds. 8697 * 8698 * The return value of registering an interval function is a promise. This promise will be 8699 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 8700 * run indefinitely if `count` is not defined. The value of the notification will be the 8701 * number of iterations that have run. 8702 * To cancel an interval, call `$interval.cancel(promise)`. 8703 * 8704 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 8705 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 8706 * time. 8707 * 8708 * <div class="alert alert-warning">
8709 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 8710 * with them. In particular they are not automatically destroyed when a controller's scope or a 8711 * directive's element are destroyed. 8712 * You should take this into consideration and make sure to always cancel the interval at the 8713 * appropriate moment. See the example below for more details on how and when to do this. 8714 * </div> 8715 * 8716 * @param {function()} fn A function that should be called repeatedly. 8717 * @param {number} delay Number of milliseconds between each function call. 8718 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 8719 * indefinitely. 8720 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 8721 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 8722 * @returns {promise} A promise which will be notified on each iteration. 8723 * 8724 * @example 8725 * <example module="time"> 8726 * <file name="index.html"> 8727 * <script> 8728 * function Ctrl2($scope,$interval) { 8729 * $scope.format = 'M/d/yy h:mm:ss a'; 8730 * $scope.blood_1 = 100; 8731 * $scope.blood_2 = 120; 8732 * 8733 * var stop; 8734 * $scope.fight = function() { 8735 * // Don't start a new fight if we are already fighting 8736 * if ( angular.isDefined(stop) ) return; 8737 * 8738 * stop = $interval(function() { 8739 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 8740 * $scope.blood_1 = $scope.blood_1 - 3; 8741 * $scope.blood_2 = $scope.blood_2 - 4; 8742 * } else { 8743 * $scope.stopFight(); 8744 * } 8745 * }, 100); 8746 * }; 8747 * 8748 * $scope.stopFight = function() { 8749 * if (angular.isDefined(stop)) { 8750 * $interval.cancel(stop); 8751 * stop = undefined; 8752 * } 8753 * }; 8754 * 8755 * $scope.resetFight = function() { 8756 * $scope.blood_1 = 100; 8757 * $scope.blood_2 = 120; 8758 * } 8759 * 8760 * $scope.$on('$destroy', function() { 8761 * // Make sure that the interval is destroyed too 8762 * $scope.stopFight(); 8763 * }); 8764 * } 8765 * 8766 * angular.module('time', []) 8767 * // Register the 'myCurrentTime' directive factory method. 8768 * // We inject $interval and dateFilter service since the factory method is DI. 8769 * .directive('myCurrentTime', function($interval, dateFilter) { 8770 * // return the directive link function. (compile function not needed) 8771 * return function(scope, element, attrs) { 8772 * var format, // date format 8773 * stopTime; // so that we can cancel the time updates 8774 * 8775 * // used to update the UI 8776 * function updateTime() { 8777 * element.text(dateFilter(new Date(), format)); 8778 * } 8779 * 8780 * // watch the expression, and update the UI on change. 8781 * scope.$watch(attrs.myCurrentTime, function(value) { 8782 * format = value; 8783 * updateTime(); 8784 * }); 8785 * 8786 * stopTime = $interval(updateTime, 1000); 8787 * 8788 * // listen on DOM destroy (removal) event, and cancel the next UI update 8789 * // to prevent updating time ofter the DOM element was removed. 8790 * element.bind('$destroy', function() { 8791 * $interval.cancel(stopTime); 8792 * }); 8793 * } 8794 * }); 8795 * </script> 8796 * 8797 * <div> 8798 * <div ng-controller="Ctrl2"> 8799 * Date format: <input ng-model="format"> <hr/> 8800 * Current time is: <span my-current-time="format"></span> 8801 * <hr/> 8802 * Blood 1 : <font color='red'>{{blood_1}}</font> 8803 * Blood 2 : <font color='red'>{{blood_2}}</font> 8804 * <button type="button" data-ng-click="fight()">Fight</button> 8805 * <button type="button" data-ng-click="stopFight()">StopFight</button> 8806 * <button type="button" data-ng-click="resetFight()">resetFight</button> 8807 * </div>
8808 * </div> 8809 * 8810 * </file> 8811 * </example> 8812 */ 8813 function interval(fn, delay, count, invokeApply) { 8814 var setInterval = $window.setInterval, 8815 clearInterval = $window.clearInterval, 8816 deferred = $q.defer(), 8817 promise = deferred.promise, 8818 iteration = 0, 8819 skipApply = (isDefined(invokeApply) && !invokeApply); 8820 8821 count = isDefined(count) ? count : 0; 8822 8823 promise.then(null, null, fn); 8824 8825 promise.$$intervalId = setInterval(function tick() { 8826 deferred.notify(iteration++); 8827 8828 if (count > 0 && iteration >= count) { 8829 deferred.resolve(iteration); 8830 clearInterval(promise.$$intervalId); 8831 delete intervals[promise.$$intervalId]; 8832 } 8833 8834 if (!skipApply) $rootScope.$apply(); 8835 8836 }, delay); 8837 8838 intervals[promise.$$intervalId] = deferred; 8839 8840 return promise; 8841 } 8842 8843 8844 /** 8845 * @ngdoc method 8846 * @name $interval#cancel 8847 * 8848 * @description 8849 * Cancels a task associated with the `promise`. 8850 * 8851 * @param {promise} promise returned by the `$interval` function. 8852 * @returns {boolean} Returns `true` if the task was successfully canceled. 8853 */ 8854 interval.cancel = function(promise) { 8855 if (promise && promise.$$intervalId in intervals) { 8856 intervals[promise.$$intervalId].reject('canceled'); 8857 clearInterval(promise.$$intervalId); 8858 delete intervals[promise.$$intervalId]; 8859 return true; 8860 } 8861 return false; 8862 }; 8863 8864 return interval; 8865 }]; 8866} 8867 8868/** 8869 * @ngdoc service 8870 * @name $locale 8871 * 8872 * @description 8873 * $locale service provides localization rules for various Angular components. As of right now the 8874 * only public api is: 8875 * 8876 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 8877 */ 8878function $LocaleProvider(){ 8879 this.$get = function() { 8880 return { 8881 id: 'en-us', 8882 8883 NUMBER_FORMATS: { 8884 DECIMAL_SEP: '.', 8885 GROUP_SEP: ',', 8886 PATTERNS: [ 8887 { // Decimal Pattern 8888 minInt: 1, 8889 minFrac: 0, 8890 maxFrac: 3, 8891 posPre: '', 8892 posSuf: '', 8893 negPre: '-', 8894 negSuf: '', 8895 gSize: 3, 8896 lgSize: 3 8897 },{ //Currency Pattern 8898 minInt: 1, 8899 minFrac: 2, 8900 maxFrac: 2, 8901 posPre: '\u00A4', 8902 posSuf: '', 8903 negPre: '(\u00A4', 8904 negSuf: ')', 8905 gSize: 3, 8906 lgSize: 3 8907 } 8908 ], 8909 CURRENCY_SYM: '$' 8910 }, 8911 8912 DATETIME_FORMATS: { 8913 MONTH: 8914 'January,February,March,April,May,June,July,August,September,October,November,December' 8915 .split(','), 8916 SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), 8917 DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), 8918 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), 8919 AMPMS: ['AM','PM'], 8920 medium: 'MMM d, y h:mm:ss a', 8921 short: 'M/d/yy h:mm a', 8922 fullDate: 'EEEE, MMMM d, y', 8923 longDate: 'MMMM d, y', 8924 mediumDate: 'MMM d, y', 8925 shortDate: 'M/d/yy', 8926 mediumTime: 'h:mm:ss a', 8927 shortTime: 'h:mm a' 8928 }, 8929 8930 pluralCat: function(num) { 8931 if (num === 1) { 8932 return 'one'; 8933 } 8934 return 'other'; 8935 } 8936 }; 8937 }; 8938} 8939 8940var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 8941 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 8942var $locationMinErr = minErr('$location'); 8943 8944 8945/** 8946 * Encode path using encodeUriSegment, ignoring forward slashes 8947 * 8948 * @param {string} path Path to encode 8949 * @returns {string} 8950 */ 8951function encodePath(path) { 8952 var segments = path.split('/'), 8953 i = segments.length; 8954 8955 while (i--) { 8956 segments[i] = encodeUriSegment(segments[i]); 8957 } 8958 8959 return segments.join('/'); 8960} 8961 8962function parseAbsoluteUrl(absoluteUrl, locationObj, appBase) {
8963 var parsedUrl = urlResolve(absoluteUrl, appBase); 8964 8965 locationObj.$$protocol = parsedUrl.protocol; 8966 locationObj.$$host = parsedUrl.hostname; 8967 locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 8968} 8969 8970 8971function parseAppUrl(relativeUrl, locationObj, appBase) { 8972 var prefixed = (relativeUrl.charAt(0) !== '/'); 8973 if (prefixed) { 8974 relativeUrl = '/' + relativeUrl; 8975 } 8976 var match = urlResolve(relativeUrl, appBase); 8977 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 8978 match.pathname.substring(1) : match.pathname); 8979 locationObj.$$search = parseKeyValue(match.search); 8980 locationObj.$$hash = decodeURIComponent(match.hash); 8981 8982 // make sure path starts with '/'; 8983 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 8984 locationObj.$$path = '/' + locationObj.$$path; 8985 } 8986} 8987 8988 8989/** 8990 * 8991 * @param {string} begin 8992 * @param {string} whole 8993 * @returns {string} returns text from whole after begin or undefined if it does not begin with 8994 * expected string. 8995 */ 8996function beginsWith(begin, whole) { 8997 if (whole.indexOf(begin) === 0) { 8998 return whole.substr(begin.length); 8999 } 9000} 9001 9002 9003function stripHash(url) { 9004 var index = url.indexOf('#'); 9005 return index == -1 ? url : url.substr(0, index); 9006} 9007 9008 9009function stripFile(url) { 9010 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 9011} 9012 9013/* return the server only (scheme://host:port) */ 9014function serverBase(url) { 9015 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 9016} 9017 9018 9019/** 9020 * LocationHtml5Url represents an url 9021 * This object is exposed as $location service when HTML5 mode is enabled and supported 9022 * 9023 * @constructor 9024 * @param {string} appBase application base URL 9025 * @param {string} basePrefix url path prefix 9026 */ 9027function LocationHtml5Url(appBase, basePrefix) { 9028 this.$$html5 = true; 9029 basePrefix = basePrefix || ''; 9030 var appBaseNoFile = stripFile(appBase); 9031 parseAbsoluteUrl(appBase, this, appBase); 9032 9033 9034 /** 9035 * Parse given html5 (regular) url string into properties 9036 * @param {string} newAbsoluteUrl HTML5 url 9037 * @private 9038 */ 9039 this.$$parse = function(url) { 9040 var pathUrl = beginsWith(appBaseNoFile, url); 9041 if (!isString(pathUrl)) { 9042 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 9043 appBaseNoFile); 9044 } 9045 9046 parseAppUrl(pathUrl, this, appBase); 9047 9048 if (!this.$$path) { 9049 this.$$path = '/'; 9050 } 9051 9052 this.$$compose(); 9053 }; 9054 9055 /** 9056 * Compose url and update `absUrl` property 9057 * @private 9058 */ 9059 this.$$compose = function() { 9060 var search = toKeyValue(this.$$search), 9061 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9062 9063 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9064 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 9065 }; 9066 9067 this.$$rewrite = function(url) { 9068 var appUrl, prevAppUrl; 9069 9070 if ( (appUrl = beginsWith(appBase, url)) !== undefined ) { 9071 prevAppUrl = appUrl; 9072 if ( (appUrl = beginsWith(basePrefix, appUrl)) !== undefined ) { 9073 return appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 9074 } else { 9075 return appBase + prevAppUrl; 9076 } 9077 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) !== undefined ) { 9078 return appBaseNoFile + appUrl; 9079 } else if (appBaseNoFile == url + '/') { 9080 return appBaseNoFile; 9081 } 9082 }; 9083} 9084 9085 9086/** 9087 * LocationHashbangUrl represents url 9088 * This object is exposed as $location service when developer doesn't opt into html5 mode. 9089 * It also serves as the base class for html5 mode fallback on legacy browsers. 9090 * 9091 * @constructor 9092 * @param {string} appBase application base URL 9093 * @param {string} hashPrefix hashbang prefix 9094 */ 9095function LocationHashbangUrl(appBase, hashPrefix) { 9096 var appBaseNoFile = stripFile(appBase); 9097 9098 parseAbsoluteUrl(appBase, this, appBase); 9099 9100 9101 /** 9102 * Parse given hashbang url into properties 9103 * @param {string} url Hashbang url 9104 * @private 9105 */ 9106 this.$$parse = function(url) { 9107 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 9108 var withoutHashUrl = withoutBaseUrl.charAt(0) == '#' 9109 ? beginsWith(hashPrefix, withoutBaseUrl) 9110 : (this.$$html5) 9111 ? withoutBaseUrl 9112 : ''; 9113 9114 if (!isString(withoutHashUrl)) { 9115 throw $locationMinErr('ihshprfx', 'Invalid url "{0}", missing hash prefix "{1}".', url, 9116 hashPrefix); 9117 } 9118 parseAppUrl(withoutHashUrl, this, appBase); 9119 9120 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 9121 9122 this.$$compose(); 9123 9124 /* 9125 * In Windows, on an anchor node on documents loaded from 9126 * the filesystem, the browser will return a pathname
9127 * prefixed with the drive name ('/C:/path') when a 9128 * pathname without a drive is set: 9129 * * a.setAttribute('href', '/foo') 9130 * * a.pathname === '/C:/foo' //true 9131 * 9132 * Inside of Angular, we're always using pathnames that 9133 * do not include drive names for routing. 9134 */ 9135 function removeWindowsDriveName (path, url, base) { 9136 /* 9137 Matches paths for file protocol on windows, 9138 such as /C:/foo/bar, and captures only /foo/bar. 9139 */ 9140 var windowsFilePathExp = /^\/?.*?:(\/.*)/; 9141 9142 var firstPathSegmentMatch; 9143 9144 //Get the relative path from the input URL. 9145 if (url.indexOf(base) === 0) { 9146 url = url.replace(base, ''); 9147 } 9148 9149 /* 9150 * The input URL intentionally contains a 9151 * first path segment that ends with a colon. 9152 */ 9153 if (windowsFilePathExp.exec(url)) { 9154 return path; 9155 } 9156 9157 firstPathSegmentMatch = windowsFilePathExp.exec(path); 9158 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 9159 } 9160 }; 9161 9162 /** 9163 * Compose hashbang url and update `absUrl` property 9164 * @private 9165 */ 9166 this.$$compose = function() { 9167 var search = toKeyValue(this.$$search), 9168 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 9169 9170 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 9171 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 9172 }; 9173 9174 this.$$rewrite = function(url) { 9175 if(stripHash(appBase) == stripHash(url)) { 9176 return url; 9177 } 9178 }; 9179} 9180 9181 9182/** 9183 * LocationHashbangUrl represents url 9184 * This object is exposed as $location service when html5 history api is enabled but the browser 9185 * does not support it. 9186 * 9187 * @constructor 9188 * @param {string} appBase application base URL 9189 * @param {string} hashPrefix hashbang prefix 9190 */ 9191function LocationHashbangInHtml5Url(appBase, hashPrefix) { 9192 this.$$html5 = true; 9193 LocationHashbangUrl.apply(this, arguments); 9194 9195 var appBaseNoFile = stripFile(appBase); 9196 9197 this.$$rewrite = function(url) { 9198 var appUrl; 9199 9200 if ( appBase == stripHash(url) ) { 9201 return url; 9202 } else if ( (appUrl = beginsWith(appBaseNoFile, url)) ) { 9203 return appBase + hashPrefix + appUrl; 9204 } else if ( appBaseNoFile === url + '/') { 9205 return appBaseNoFile; 9206 } 9207 }; 9208} 9209 9210 9211LocationHashbangInHtml5Url.prototype = 9212 LocationHashbangUrl.prototype = 9213 LocationHtml5Url.prototype = { 9214 9215 /** 9216 * Are we in html5 mode? 9217 * @private 9218 */ 9219 $$html5: false, 9220 9221 /** 9222 * Has any change been replacing ? 9223 * @private 9224 */ 9225 $$replace: false, 9226 9227 /** 9228 * @ngdoc method 9229 * @name $location#absUrl 9230 * 9231 * @description 9232 * This method is getter only. 9233 * 9234 * Return full url representation with all segments encoded according to rules specified in 9235 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 9236 * 9237 * @return {string} full url 9238 */ 9239 absUrl: locationGetter('$$absUrl'), 9240 9241 /** 9242 * @ngdoc method 9243 * @name $location#url 9244 * 9245 * @description 9246 * This method is getter / setter. 9247 * 9248 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 9249 * 9250 * Change path, search and hash, when called with parameter and return `$location`. 9251 * 9252 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 9253 * @param {string=} replace The path that will be changed 9254 * @return {string} url 9255 */ 9256 url: function(url, replace) { 9257 if (isUndefined(url)) 9258 return this.$$url; 9259 9260 var match = PATH_MATCH.exec(url); 9261 if (match[1]) this.path(decodeURIComponent(match[1])); 9262 if (match[2] || match[1]) this.search(match[3] || ''); 9263 this.hash(match[5] || '', replace); 9264 9265 return this; 9266 }, 9267 9268 /** 9269 * @ngdoc method 9270 * @name $location#protocol 9271 * 9272 * @description 9273 * This method is getter only. 9274 * 9275 * Return protocol of current url. 9276 * 9277 * @return {string} protocol of current url 9278 */ 9279 protocol: locationGetter('$$protocol'), 9280 9281 /** 9282 * @ngdoc method 9283 * @name $location#host 9284 * 9285 * @description 9286 * This method is getter only. 9287 * 9288 * Return host of current url. 9289 * 9290 * @return {string} host of current url. 9291 */ 9292 host: locationGetter('$$host'), 9293 9294 /** 9295 * @ngdoc method 9296 * @name $location#port 9297 * 9298 * @description 9299 * This method is getter only. 9300 * 9301 * Return port of current url. 9302 * 9303 * @return {Number} port 9304 */ 9305 port: locationGetter('$$port'), 9306 9307 /** 9308 * @ngdoc method 9309 * @name $location#path 9310 * 9311 * @description 9312 * This method is getter / setter. 9313 * 9314 * Return path of current url when called without any parameter. 9315 * 9316 * Change path when called with parameter and return `$location`. 9317 * 9318 * Note: Path should always begin with forward slash (/), this method will add the forward slash 9319 * if it is missing. 9320 * 9321 * @param {string=} path New path 9322 * @return {string} path 9323 */ 9324 path: locationGetterSetter('$$path', function(path) { 9325 return path.charAt(0) == '/' ? path : '/' + path; 9326 }), 9327 9328 /** 9329 * @ngdoc method 9330 * @name $location#search 9331 * 9332 * @description 9333 * This method is getter / setter. 9334 * 9335 * Return search part (as object) of current url when called without any parameter. 9336 * 9337 * Change search part when called with parameter and return `$location`. 9338 * 9339 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 9340 * hash object. Hash object may contain an array of values, which will be decoded as duplicates in 9341 * the url. 9342 * 9343 * @param {(string|Array<string>)=} paramValue If `search` is a string, then `paramValue` will override only a 9344 * single search parameter. If `paramValue` is an array, it will set the parameter as a 9345 * comma-separated value. If `paramValue` is `null`, the parameter will be deleted. 9346 * 9347 * @return {string} search 9348 */ 9349 search: function(search, paramValue) { 9350 switch (arguments.length) { 9351 case 0: 9352 return this.$$search; 9353 case 1: 9354 if (isString(search)) { 9355 this.$$search = parseKeyValue(search); 9356 } else if (isObject(search)) { 9357 this.$$search = search; 9358 } else { 9359 throw $locationMinErr('isrcharg', 9360 'The first argument of the `$location#search()` call must be a string or an object.'); 9361 } 9362 break; 9363 default: 9364 if (isUndefined(paramValue) || paramValue === null) { 9365 delete this.$$search[search];
vendor: 4,885 bytes, lines 9366-9534
9366 } else { 9367 this.$$search[search] = paramValue; 9368 } 9369 } 9370 9371 this.$$compose(); 9372 return this; 9373 }, 9374 9375 /** 9376 * @ngdoc method 9377 * @name $location#hash 9378 * 9379 * @description 9380 * This method is getter / setter. 9381 * 9382 * Return hash fragment when called without any parameter. 9383 * 9384 * Change hash fragment when called with parameter and return `$location`. 9385 * 9386 * @param {string=} hash New hash fragment 9387 * @return {string} hash 9388 */ 9389 hash: locationGetterSetter('$$hash', identity), 9390 9391 /** 9392 * @ngdoc method 9393 * @name $location#replace 9394 * 9395 * @description 9396 * If called, all changes to $location during current `$digest` will be replacing current history 9397 * record, instead of adding new one. 9398 */ 9399 replace: function() { 9400 this.$$replace = true; 9401 return this; 9402 } 9403}; 9404 9405function locationGetter(property) { 9406 return function() { 9407 return this[property]; 9408 }; 9409} 9410 9411 9412function locationGetterSetter(property, preprocess) { 9413 return function(value) { 9414 if (isUndefined(value)) 9415 return this[property]; 9416 9417 this[property] = preprocess(value); 9418 this.$$compose(); 9419 9420 return this; 9421 }; 9422} 9423 9424 9425/** 9426 * @ngdoc service 9427 * @name $location 9428 * 9429 * @requires $rootElement 9430 * 9431 * @description 9432 * The $location service parses the URL in the browser address bar (based on the 9433 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 9434 * available to your application. Changes to the URL in the address bar are reflected into 9435 * $location service and changes to $location are reflected into the browser address bar. 9436 * 9437 * **The $location service:** 9438 * 9439 * - Exposes the current URL in the browser address bar, so you can 9440 * - Watch and observe the URL. 9441 * - Change the URL. 9442 * - Synchronizes the URL with the browser when the user 9443 * - Changes the address bar. 9444 * - Clicks the back or forward button (or clicks a History link). 9445 * - Clicks on a link. 9446 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 9447 * 9448 * For more information see {@link guide/$location Developer Guide: Using $location} 9449 */ 9450 9451/** 9452 * @ngdoc provider 9453 * @name $locationProvider 9454 * @description 9455 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 9456 */ 9457function $LocationProvider(){ 9458 var hashPrefix = '', 9459 html5Mode = false; 9460 9461 /** 9462 * @ngdoc property 9463 * @name $locationProvider#hashPrefix 9464 * @description 9465 * @param {string=} prefix Prefix for hash part (containing path and search) 9466 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9467 */ 9468 this.hashPrefix = function(prefix) { 9469 if (isDefined(prefix)) { 9470 hashPrefix = prefix; 9471 return this; 9472 } else { 9473 return hashPrefix; 9474 } 9475 }; 9476 9477 /** 9478 * @ngdoc property 9479 * @name $locationProvider#html5Mode 9480 * @description 9481 * @param {boolean=} mode Use HTML5 strategy if available. 9482 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9483 */ 9484 this.html5Mode = function(mode) { 9485 if (isDefined(mode)) { 9486 html5Mode = mode; 9487 return this; 9488 } else { 9489 return html5Mode; 9490 } 9491 }; 9492 9493 /** 9494 * @ngdoc event 9495 * @name $location#$locationChangeStart 9496 * @eventType broadcast on root scope 9497 * @description 9498 * Broadcasted before a URL will change. This change can be prevented by calling 9499 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 9500 * details about event object. Upon successful change 9501 * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired. 9502 * 9503 * @param {Object} angularEvent Synthetic event object. 9504 * @param {string} newUrl New URL 9505 * @param {string=} oldUrl URL that was before it was changed. 9506 */ 9507 9508 /** 9509 * @ngdoc event 9510 * @name $location#$locationChangeSuccess 9511 * @eventType broadcast on root scope 9512 * @description 9513 * Broadcasted after a URL was changed. 9514 * 9515 * @param {Object} angularEvent Synthetic event object. 9516 * @param {string} newUrl New URL 9517 * @param {string=} oldUrl URL that was before it was changed. 9518 */ 9519 9520 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', 9521 function( $rootScope, $browser, $sniffer, $rootElement) { 9522 var $location, 9523 LocationMode, 9524 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 9525 initialUrl = $browser.url(), 9526 appBase; 9527 9528 if (html5Mode) { 9529 appBase = serverBase(initialUrl) + (baseHref || '/'); 9530 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 9531 } else { 9532 appBase = stripHash(initialUrl); 9533 LocationMode = LocationHashbangUrl; 9534 }
9535 $location = new LocationMode(appBase, '#' + hashPrefix); 9536 $location.$$parse($location.$$rewrite(initialUrl)); 9537 9538 $rootElement.on('click', function(event) { 9539 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 9540 // currently we open nice url link and redirect then 9541 9542 if (event.ctrlKey || event.metaKey || event.which == 2) return; 9543 9544 var elm = jqLite(event.target); 9545 9546 // traverse the DOM up to find first A tag 9547 while (lowercase(elm[0].nodeName) !== 'a') { 9548 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 9549 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 9550 } 9551 9552 var absHref = elm.prop('href'); 9553 9554 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 9555 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 9556 // an animation. 9557 absHref = urlResolve(absHref.animVal).href; 9558 } 9559 9560 var rewrittenUrl = $location.$$rewrite(absHref); 9561 9562 if (absHref && !elm.attr('target') && rewrittenUrl && !event.isDefaultPrevented()) { 9563 event.preventDefault(); 9564 if (rewrittenUrl != $browser.url()) { 9565 // update location manually 9566 $location.$$parse(rewrittenUrl); 9567 $rootScope.$apply(); 9568 // hack to work around FF6 bug 684208 when scenario runner clicks on links 9569 window.angular['ff-684208-preventDefault'] = true; 9570 } 9571 } 9572 }); 9573 9574 9575 // rewrite hashbang url <> html5 url 9576 if ($location.absUrl() != initialUrl) { 9577 $browser.url($location.absUrl(), true); 9578 } 9579 9580 // update $location when $browser url changes 9581 $browser.onUrlChange(function(newUrl) { 9582 if ($location.absUrl() != newUrl) { 9583 $rootScope.$evalAsync(function() { 9584 var oldUrl = $location.absUrl(); 9585 9586 $location.$$parse(newUrl); 9587 if ($rootScope.$broadcast('$locationChangeStart', newUrl, 9588 oldUrl).defaultPrevented) { 9589 $location.$$parse(oldUrl); 9590 $browser.url(oldUrl); 9591 } else { 9592 afterLocationChange(oldUrl); 9593 } 9594 }); 9595 if (!$rootScope.$$phase) $rootScope.$digest(); 9596 } 9597 }); 9598 9599 // update browser 9600 var changeCounter = 0; 9601 $rootScope.$watch(function $locationWatch() { 9602 var oldUrl = $browser.url(); 9603 var currentReplace = $location.$$replace; 9604 9605 if (!changeCounter || oldUrl != $location.absUrl()) { 9606 changeCounter++; 9607 $rootScope.$evalAsync(function() { 9608 if ($rootScope.$broadcast('$locationChangeStart', $location.absUrl(), oldUrl). 9609 defaultPrevented) { 9610 $location.$$parse(oldUrl); 9611 } else { 9612 $browser.url($location.absUrl(), currentReplace); 9613 afterLocationChange(oldUrl); 9614 } 9615 }); 9616 } 9617 $location.$$replace = false; 9618 9619 return changeCounter; 9620 }); 9621 9622 return $location; 9623 9624 function afterLocationChange(oldUrl) { 9625 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl); 9626 } 9627}]; 9628} 9629 9630/** 9631 * @ngdoc service 9632 * @name $log 9633 * @requires $window 9634 * 9635 * @description 9636 * Simple service for logging. Default implementation safely writes the message 9637 * into the browser's console (if present). 9638 * 9639 * The main purpose of this service is to simplify debugging and troubleshooting. 9640 * 9641 * The default is to log `debug` messages. You can use 9642 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 9643 * 9644 * @example 9645 <example> 9646 <file name="script.js"> 9647 function LogCtrl($scope, $log) { 9648 $scope.$log = $log; 9649 $scope.message = 'Hello World!'; 9650 } 9651 </file> 9652 <file name="index.html"> 9653 <div ng-controller="LogCtrl"> 9654 <p>Reload this page with open console, enter text and hit the log button...</p> 9655 Message: 9656 <input type="text" ng-model="message"/> 9657 <button ng-click="$log.log(message)">log</button> 9658 <button ng-click="$log.warn(message)">warn</button> 9659 <button ng-click="$log.info(message)">info</button> 9660 <button ng-click="$log.error(message)">error</button> 9661 </div> 9662 </file> 9663 </example> 9664 */ 9665 9666/** 9667 * @ngdoc provider 9668 * @name $logProvider 9669 * @description 9670 * Use the `$logProvider` to configure how the application logs messages 9671 */ 9672function $LogProvider(){ 9673 var debug = true, 9674 self = this; 9675 9676 /** 9677 * @ngdoc property 9678 * @name $logProvider#debugEnabled 9679 * @description 9680 * @param {boolean=} flag enable or disable debug level messages 9681 * @returns {*} current value if used as getter or itself (chaining) if used as setter 9682 */ 9683 this.debugEnabled = function(flag) { 9684 if (isDefined(flag)) { 9685 debug = flag; 9686 return this; 9687 } else { 9688 return debug; 9689 } 9690 }; 9691 9692 this.$get = ['$window', function($window){ 9693 return { 9694 /** 9695 * @ngdoc method 9696 * @name $log#log 9697 *
9698 * @description 9699 * Write a log message 9700 */ 9701 log: consoleLog('log'), 9702 9703 /** 9704 * @ngdoc method 9705 * @name $log#info 9706 * 9707 * @description 9708 * Write an information message 9709 */ 9710 info: consoleLog('info'), 9711 9712 /** 9713 * @ngdoc method 9714 * @name $log#warn 9715 * 9716 * @description 9717 * Write a warning message 9718 */ 9719 warn: consoleLog('warn'), 9720 9721 /** 9722 * @ngdoc method 9723 * @name $log#error 9724 * 9725 * @description 9726 * Write an error message 9727 */ 9728 error: consoleLog('error'), 9729 9730 /** 9731 * @ngdoc method 9732 * @name $log#debug 9733 * 9734 * @description 9735 * Write a debug message 9736 */ 9737 debug: (function () { 9738 var fn = consoleLog('debug'); 9739 9740 return function() { 9741 if (debug) { 9742 fn.apply(self, arguments); 9743 } 9744 }; 9745 }()) 9746 }; 9747 9748 function formatError(arg) { 9749 if (arg instanceof Error) { 9750 if (arg.stack) { 9751 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 9752 ? 'Error: ' + arg.message + '\n' + arg.stack 9753 : arg.stack; 9754 } else if (arg.sourceURL) { 9755 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 9756 } 9757 } 9758 return arg; 9759 } 9760 9761 function consoleLog(type) { 9762 var console = $window.console || {}, 9763 logFn = console[type] || console.log || noop, 9764 hasApply = false; 9765 9766 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 9767 // The reason behind this is that console.log has type "object" in IE8... 9768 try { 9769 hasApply = !!logFn.apply; 9770 } catch (e) {} 9771 9772 if (hasApply) { 9773 return function() { 9774 var args = []; 9775 forEach(arguments, function(arg) { 9776 args.push(formatError(arg)); 9777 }); 9778 return logFn.apply(console, args); 9779 }; 9780 } 9781 9782 // we are IE which either doesn't have window.console => this is noop and we do nothing, 9783 // or we are IE where console.log doesn't have apply so we log at least first 2 args 9784 return function(arg1, arg2) { 9785 logFn(arg1, arg2 == null ? '' : arg2); 9786 }; 9787 } 9788 }]; 9789} 9790 9791var $parseMinErr = minErr('$parse'); 9792var promiseWarningCache = {}; 9793var promiseWarning; 9794 9795// Sandboxing Angular Expressions 9796// ------------------------------ 9797// Angular expressions are generally considered safe because these expressions only have direct 9798// access to $scope and locals. However, one can obtain the ability to execute arbitrary JS code by 9799// obtaining a reference to native JS functions such as the Function constructor. 9800// 9801// As an example, consider the following Angular expression: 9802// 9803// {}.toString.constructor(alert("evil JS code")) 9804// 9805// We want to prevent this type of access. For the sake of performance, during the lexing phase we 9806// disallow any "dotted" access to any member named "constructor". 9807// 9808// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor 9809// while evaluating the expression, which is a stronger but more expensive test. Since reflective 9810// calls are expensive anyway, this is not such a big deal compared to static dereferencing. 9811// 9812// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 9813// against the expression language, but not to prevent exploits that were enabled by exposing 9814// sensitive JavaScript or browser apis on Scope. Exposing such objects on a Scope is never a good 9815// practice and therefore we are not even trying to protect against interaction with an object 9816// explicitly exposed in this way. 9817// 9818// A developer could foil the name check by aliasing the Function constructor under a different 9819// name on the scope. 9820// 9821// In general, it is not possible to access a Window object from an angular expression unless a 9822// window or some DOM object that has a reference to window is published onto a Scope. 9823 9824function ensureSafeMemberName(name, fullExpression) { 9825 if (name === "constructor") { 9826 throw $parseMinErr('isecfld', 9827 'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', 9828 fullExpression); 9829 } 9830 return name; 9831} 9832 9833function ensureSafeObject(obj, fullExpression) { 9834 // nifty check if obj is Function that is fast and works across iframes and other contexts 9835 if (obj) { 9836 if (obj.constructor === obj) { 9837 throw $parseMinErr('isecfn', 9838 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 9839 fullExpression); 9840 } else if (// isWindow(obj) 9841 obj.document && obj.location && obj.alert && obj.setInterval) { 9842 throw $parseMinErr('isecwindow', 9843 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 9844 fullExpression); 9845 } else if (// isElement(obj) 9846 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 9847 throw $parseMinErr('isecdom', 9848 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 9849 fullExpression); 9850 } 9851 } 9852 return obj;
9853} 9854 9855var OPERATORS = { 9856 /* jshint bitwise : false */ 9857 'null':function(){return null;}, 9858 'true':function(){return true;}, 9859 'false':function(){return false;}, 9860 undefined:noop, 9861 '+':function(self, locals, a,b){ 9862 a=a(self, locals); b=b(self, locals); 9863 if (isDefined(a)) { 9864 if (isDefined(b)) { 9865 return a + b; 9866 } 9867 return a; 9868 } 9869 return isDefined(b)?b:undefined;}, 9870 '-':function(self, locals, a,b){ 9871 a=a(self, locals); b=b(self, locals); 9872 return (isDefined(a)?a:0)-(isDefined(b)?b:0); 9873 }, 9874 '*':function(self, locals, a,b){return a(self, locals)*b(self, locals);}, 9875 '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, 9876 '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, 9877 '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, 9878 '=':noop, 9879 '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, 9880 '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, 9881 '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, 9882 '!=':function(self, locals, a,b){return a(self, locals)!=b(self, locals);}, 9883 '<':function(self, locals, a,b){return a(self, locals)<b(self, locals);}, 9884 '>':function(self, locals, a,b){return a(self, locals)>b(self, locals);}, 9885 '<=':function(self, locals, a,b){return a(self, locals)<=b(self, locals);}, 9886 '>=':function(self, locals, a,b){return a(self, locals)>=b(self, locals);}, 9887 '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, 9888 '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, 9889 '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, 9890// '|':function(self, locals, a,b){return a|b;}, 9891 '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, 9892 '!':function(self, locals, a){return !a(self, locals);} 9893}; 9894/* jshint bitwise: true */ 9895var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 9896 9897 9898///////////////////////////////////////// 9899 9900 9901/** 9902 * @constructor 9903 */ 9904var Lexer = function (options) { 9905 this.options = options; 9906}; 9907 9908Lexer.prototype = { 9909 constructor: Lexer, 9910 9911 lex: function (text) { 9912 this.text = text; 9913 9914 this.index = 0; 9915 this.ch = undefined; 9916 this.lastCh = ':'; // can start regexp 9917 9918 this.tokens = []; 9919 9920 var token; 9921 var json = []; 9922 9923 while (this.index < this.text.length) { 9924 this.ch = this.text.charAt(this.index); 9925 if (this.is('"\'')) { 9926 this.readString(this.ch); 9927 } else if (this.isNumber(this.ch) || this.is('.') && this.isNumber(this.peek())) { 9928 this.readNumber(); 9929 } else if (this.isIdent(this.ch)) { 9930 this.readIdent(); 9931 // identifiers can only be if the preceding char was a { or , 9932 if (this.was('{,') && json[0] === '{' && 9933 (token = this.tokens[this.tokens.length - 1])) { 9934 token.json = token.text.indexOf('.') === -1; 9935 } 9936 } else if (this.is('(){}[].,;:?')) { 9937 this.tokens.push({ 9938 index: this.index, 9939 text: this.ch, 9940 json: (this.was(':[,') && this.is('{[')) || this.is('}]:,') 9941 }); 9942 if (this.is('{[')) json.unshift(this.ch); 9943 if (this.is('}]')) json.shift(); 9944 this.index++; 9945 } else if (this.isWhitespace(this.ch)) { 9946 this.index++; 9947 continue; 9948 } else { 9949 var ch2 = this.ch + this.peek(); 9950 var ch3 = ch2 + this.peek(2); 9951 var fn = OPERATORS[this.ch]; 9952 var fn2 = OPERATORS[ch2]; 9953 var fn3 = OPERATORS[ch3]; 9954 if (fn3) { 9955 this.tokens.push({index: this.index, text: ch3, fn: fn3}); 9956 this.index += 3; 9957 } else if (fn2) { 9958 this.tokens.push({index: this.index, text: ch2, fn: fn2}); 9959 this.index += 2; 9960 } else if (fn) { 9961 this.tokens.push({ 9962 index: this.index, 9963 text: this.ch, 9964 fn: fn, 9965 json: (this.was('[,:') && this.is('+-')) 9966 }); 9967 this.index += 1;
9968 } else { 9969 this.throwError('Unexpected next character ', this.index, this.index + 1); 9970 } 9971 } 9972 this.lastCh = this.ch; 9973 } 9974 return this.tokens; 9975 }, 9976 9977 is: function(chars) { 9978 return chars.indexOf(this.ch) !== -1; 9979 }, 9980 9981 was: function(chars) { 9982 return chars.indexOf(this.lastCh) !== -1; 9983 }, 9984 9985 peek: function(i) { 9986 var num = i || 1; 9987 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 9988 }, 9989 9990 isNumber: function(ch) { 9991 return ('0' <= ch && ch <= '9'); 9992 }, 9993 9994 isWhitespace: function(ch) { 9995 // IE treats non-breaking space as \u00A0 9996 return (ch === ' ' || ch === '\r' || ch === '\t' || 9997 ch === '\n' || ch === '\v' || ch === '\u00A0'); 9998 }, 9999 10000 isIdent: function(ch) { 10001 return ('a' <= ch && ch <= 'z' || 10002 'A' <= ch && ch <= 'Z' || 10003 '_' === ch || ch === '$'); 10004 }, 10005 10006 isExpOperator: function(ch) { 10007 return (ch === '-' || ch === '+' || this.isNumber(ch)); 10008 }, 10009 10010 throwError: function(error, start, end) { 10011 end = end || this.index; 10012 var colStr = (isDefined(start) 10013 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 10014 : ' ' + end); 10015 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 10016 error, colStr, this.text); 10017 }, 10018 10019 readNumber: function() { 10020 var number = ''; 10021 var start = this.index; 10022 while (this.index < this.text.length) { 10023 var ch = lowercase(this.text.charAt(this.index)); 10024 if (ch == '.' || this.isNumber(ch)) { 10025 number += ch; 10026 } else { 10027 var peekCh = this.peek(); 10028 if (ch == 'e' && this.isExpOperator(peekCh)) { 10029 number += ch; 10030 } else if (this.isExpOperator(ch) && 10031 peekCh && this.isNumber(peekCh) && 10032 number.charAt(number.length - 1) == 'e') { 10033 number += ch; 10034 } else if (this.isExpOperator(ch) && 10035 (!peekCh || !this.isNumber(peekCh)) && 10036 number.charAt(number.length - 1) == 'e') { 10037 this.throwError('Invalid exponent'); 10038 } else { 10039 break; 10040 } 10041 } 10042 this.index++; 10043 } 10044 number = 1 * number; 10045 this.tokens.push({ 10046 index: start, 10047 text: number, 10048 json: true, 10049 fn: function() { return number; } 10050 }); 10051 }, 10052 10053 readIdent: function() { 10054 var parser = this; 10055 10056 var ident = ''; 10057 var start = this.index; 10058 10059 var lastDot, peekIndex, methodName, ch; 10060 10061 while (this.index < this.text.length) { 10062 ch = this.text.charAt(this.index); 10063 if (ch === '.' || this.isIdent(ch) || this.isNumber(ch)) { 10064 if (ch === '.') lastDot = this.index; 10065 ident += ch; 10066 } else { 10067 break; 10068 } 10069 this.index++; 10070 } 10071 10072 //check if this is not a method invocation and if it is back out to last dot 10073 if (lastDot) { 10074 peekIndex = this.index; 10075 while (peekIndex < this.text.length) { 10076 ch = this.text.charAt(peekIndex); 10077 if (ch === '(') { 10078 methodName = ident.substr(lastDot - start + 1); 10079 ident = ident.substr(0, lastDot - start); 10080 this.index = peekIndex; 10081 break; 10082 } 10083 if (this.isWhitespace(ch)) { 10084 peekIndex++; 10085 } else { 10086 break; 10087 } 10088 } 10089 } 10090 10091 10092 var token = { 10093 index: start, 10094 text: ident 10095 }; 10096 10097 // OPERATORS is our own object so we don't need to use special hasOwnPropertyFn 10098 if (OPERATORS.hasOwnProperty(ident)) { 10099 token.fn = OPERATORS[ident]; 10100 token.json = OPERATORS[ident]; 10101 } else { 10102 var getter = getterFn(ident, this.options, this.text); 10103 token.fn = extend(function(self, locals) { 10104 return (getter(self, locals)); 10105 }, { 10106 assign: function(self, value) { 10107 return setter(self, ident, value, parser.text, parser.options); 10108 } 10109 }); 10110 } 10111 10112 this.tokens.push(token); 10113 10114 if (methodName) { 10115 this.tokens.push({ 10116 index:lastDot, 10117 text: '.', 10118 json: false 10119 }); 10120 this.tokens.push({ 10121 index: lastDot + 1, 10122 text: methodName, 10123 json: false 10124 }); 10125 } 10126 }, 10127 10128 readString: function(quote) { 10129 var start = this.index; 10130 this.index++; 10131 var string = ''; 10132 var rawString = quote; 10133 var escape = false; 10134 while (this.index < this.text.length) { 10135 var ch = this.text.charAt(this.index); 10136 rawString += ch; 10137 if (escape) { 10138 if (ch === 'u') { 10139 var hex = this.text.substring(this.index + 1, this.index + 5); 10140 if (!hex.match(/[\da-f]{4}/i)) 10141 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 10142 this.index += 4;
10143 string += String.fromCharCode(parseInt(hex, 16)); 10144 } else { 10145 var rep = ESCAPE[ch]; 10146 if (rep) { 10147 string += rep; 10148 } else { 10149 string += ch; 10150 } 10151 } 10152 escape = false; 10153 } else if (ch === '\\') { 10154 escape = true; 10155 } else if (ch === quote) { 10156 this.index++; 10157 this.tokens.push({ 10158 index: start, 10159 text: rawString, 10160 string: string, 10161 json: true, 10162 fn: function() { return string; } 10163 }); 10164 return; 10165 } else { 10166 string += ch; 10167 } 10168 this.index++; 10169 } 10170 this.throwError('Unterminated quote', start); 10171 } 10172}; 10173 10174 10175/** 10176 * @constructor 10177 */ 10178var Parser = function (lexer, $filter, options) { 10179 this.lexer = lexer; 10180 this.$filter = $filter; 10181 this.options = options; 10182}; 10183 10184Parser.ZERO = extend(function () { 10185 return 0; 10186}, { 10187 constant: true 10188}); 10189 10190Parser.prototype = { 10191 constructor: Parser, 10192 10193 parse: function (text, json) { 10194 this.text = text; 10195 10196 //TODO(i): strip all the obsolte json stuff from this file 10197 this.json = json; 10198 10199 this.tokens = this.lexer.lex(text); 10200 10201 if (json) { 10202 // The extra level of aliasing is here, just in case the lexer misses something, so that 10203 // we prevent any accidental execution in JSON. 10204 this.assignment = this.logicalOR; 10205 10206 this.functionCall = 10207 this.fieldAccess = 10208 this.objectIndex = 10209 this.filterChain = function() { 10210 this.throwError('is not valid json', {text: text, index: 0}); 10211 }; 10212 } 10213 10214 var value = json ? this.primary() : this.statements(); 10215 10216 if (this.tokens.length !== 0) { 10217 this.throwError('is an unexpected token', this.tokens[0]); 10218 } 10219 10220 value.literal = !!value.literal; 10221 value.constant = !!value.constant; 10222 10223 return value; 10224 }, 10225 10226 primary: function () { 10227 var primary; 10228 if (this.expect('(')) { 10229 primary = this.filterChain(); 10230 this.consume(')'); 10231 } else if (this.expect('[')) { 10232 primary = this.arrayDeclaration(); 10233 } else if (this.expect('{')) { 10234 primary = this.object(); 10235 } else { 10236 var token = this.expect(); 10237 primary = token.fn; 10238 if (!primary) { 10239 this.throwError('not a primary expression', token); 10240 } 10241 if (token.json) { 10242 primary.constant = true; 10243 primary.literal = true; 10244 } 10245 } 10246 10247 var next, context; 10248 while ((next = this.expect('(', '[', '.'))) { 10249 if (next.text === '(') { 10250 primary = this.functionCall(primary, context); 10251 context = null; 10252 } else if (next.text === '[') { 10253 context = primary; 10254 primary = this.objectIndex(primary); 10255 } else if (next.text === '.') { 10256 context = primary; 10257 primary = this.fieldAccess(primary); 10258 } else { 10259 this.throwError('IMPOSSIBLE'); 10260 } 10261 } 10262 return primary; 10263 }, 10264 10265 throwError: function(msg, token) { 10266 throw $parseMinErr('syntax', 10267 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 10268 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 10269 }, 10270 10271 peekToken: function() { 10272 if (this.tokens.length === 0) 10273 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 10274 return this.tokens[0]; 10275 }, 10276 10277 peek: function(e1, e2, e3, e4) { 10278 if (this.tokens.length > 0) { 10279 var token = this.tokens[0]; 10280 var t = token.text; 10281 if (t === e1 || t === e2 || t === e3 || t === e4 || 10282 (!e1 && !e2 && !e3 && !e4)) { 10283 return token; 10284 } 10285 } 10286 return false; 10287 }, 10288 10289 expect: function(e1, e2, e3, e4){ 10290 var token = this.peek(e1, e2, e3, e4); 10291 if (token) { 10292 if (this.json && !token.json) { 10293 this.throwError('is not valid json', token); 10294 } 10295 this.tokens.shift(); 10296 return token; 10297 } 10298 return false; 10299 }, 10300 10301 consume: function(e1){ 10302 if (!this.expect(e1)) { 10303 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 10304 } 10305 }, 10306 10307 unaryFn: function(fn, right) { 10308 return extend(function(self, locals) { 10309 return fn(self, locals, right); 10310 }, { 10311 constant:right.constant 10312 });
10313 }, 10314 10315 ternaryFn: function(left, middle, right){ 10316 return extend(function(self, locals){ 10317 return left(self, locals) ? middle(self, locals) : right(self, locals); 10318 }, { 10319 constant: left.constant && middle.constant && right.constant 10320 }); 10321 }, 10322 10323 binaryFn: function(left, fn, right) { 10324 return extend(function(self, locals) { 10325 return fn(self, locals, left, right); 10326 }, { 10327 constant:left.constant && right.constant 10328 }); 10329 }, 10330 10331 statements: function() { 10332 var statements = []; 10333 while (true) { 10334 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 10335 statements.push(this.filterChain()); 10336 if (!this.expect(';')) { 10337 // optimize for the common case where there is only one statement. 10338 // TODO(size): maybe we should not support multiple statements? 10339 return (statements.length === 1) 10340 ? statements[0] 10341 : function(self, locals) { 10342 var value; 10343 for (var i = 0; i < statements.length; i++) { 10344 var statement = statements[i]; 10345 if (statement) { 10346 value = statement(self, locals); 10347 } 10348 } 10349 return value; 10350 }; 10351 } 10352 } 10353 }, 10354 10355 filterChain: function() { 10356 var left = this.expression(); 10357 var token; 10358 while (true) { 10359 if ((token = this.expect('|'))) { 10360 left = this.binaryFn(left, token.fn, this.filter()); 10361 } else { 10362 return left; 10363 } 10364 } 10365 }, 10366 10367 filter: function() { 10368 var token = this.expect(); 10369 var fn = this.$filter(token.text); 10370 var argsFn = []; 10371 while (true) { 10372 if ((token = this.expect(':'))) { 10373 argsFn.push(this.expression()); 10374 } else { 10375 var fnInvoke = function(self, locals, input) { 10376 var args = [input]; 10377 for (var i = 0; i < argsFn.length; i++) { 10378 args.push(argsFn[i](self, locals)); 10379 } 10380 return fn.apply(self, args); 10381 }; 10382 return function() { 10383 return fnInvoke; 10384 }; 10385 } 10386 } 10387 }, 10388 10389 expression: function() { 10390 return this.assignment(); 10391 }, 10392 10393 assignment: function() { 10394 var left = this.ternary(); 10395 var right; 10396 var token; 10397 if ((token = this.expect('='))) { 10398 if (!left.assign) { 10399 this.throwError('implies assignment but [' + 10400 this.text.substring(0, token.index) + '] can not be assigned to', token); 10401 } 10402 right = this.ternary(); 10403 return function(scope, locals) { 10404 return left.assign(scope, right(scope, locals), locals); 10405 }; 10406 } 10407 return left; 10408 }, 10409 10410 ternary: function() { 10411 var left = this.logicalOR(); 10412 var middle; 10413 var token; 10414 if ((token = this.expect('?'))) { 10415 middle = this.ternary(); 10416 if ((token = this.expect(':'))) { 10417 return this.ternaryFn(left, middle, this.ternary()); 10418 } else { 10419 this.throwError('expected :', token); 10420 } 10421 } else { 10422 return left; 10423 } 10424 }, 10425 10426 logicalOR: function() { 10427 var left = this.logicalAND(); 10428 var token; 10429 while (true) { 10430 if ((token = this.expect('||'))) { 10431 left = this.binaryFn(left, token.fn, this.logicalAND()); 10432 } else { 10433 return left; 10434 } 10435 } 10436 }, 10437 10438 logicalAND: function() { 10439 var left = this.equality(); 10440 var token; 10441 if ((token = this.expect('&&'))) { 10442 left = this.binaryFn(left, token.fn, this.logicalAND()); 10443 } 10444 return left; 10445 }, 10446 10447 equality: function() { 10448 var left = this.relational(); 10449 var token; 10450 if ((token = this.expect('==','!=','===','!=='))) { 10451 left = this.binaryFn(left, token.fn, this.equality()); 10452 } 10453 return left; 10454 }, 10455 10456 relational: function() { 10457 var left = this.additive(); 10458 var token; 10459 if ((token = this.expect('<', '>', '<=', '>='))) { 10460 left = this.binaryFn(left, token.fn, this.relational()); 10461 } 10462 return left; 10463 }, 10464 10465 additive: function() { 10466 var left = this.multiplicative(); 10467 var token; 10468 while ((token = this.expect('+','-'))) { 10469 left = this.binaryFn(left, token.fn, this.multiplicative()); 10470 } 10471 return left; 10472 }, 10473 10474 multiplicative: function() { 10475 var left = this.unary(); 10476 var token; 10477 while ((token = this.expect('*','/','%'))) { 10478 left = this.binaryFn(left, token.fn, this.unary()); 10479 } 10480 return left; 10481 }, 10482 10483 unary: function() { 10484 var token; 10485 if (this.expect('+')) { 10486 return this.primary();
10487 } else if ((token = this.expect('-'))) { 10488 return this.binaryFn(Parser.ZERO, token.fn, this.unary()); 10489 } else if ((token = this.expect('!'))) { 10490 return this.unaryFn(token.fn, this.unary()); 10491 } else { 10492 return this.primary(); 10493 } 10494 }, 10495 10496 fieldAccess: function(object) { 10497 var parser = this; 10498 var field = this.expect().text; 10499 var getter = getterFn(field, this.options, this.text); 10500 10501 return extend(function(scope, locals, self) { 10502 return getter(self || object(scope, locals)); 10503 }, { 10504 assign: function(scope, value, locals) { 10505 return setter(object(scope, locals), field, value, parser.text, parser.options); 10506 } 10507 }); 10508 }, 10509 10510 objectIndex: function(obj) { 10511 var parser = this; 10512 10513 var indexFn = this.expression(); 10514 this.consume(']'); 10515 10516 return extend(function(self, locals) { 10517 var o = obj(self, locals), 10518 i = indexFn(self, locals), 10519 v, p; 10520 10521 if (!o) return undefined; 10522 v = ensureSafeObject(o[i], parser.text); 10523 if (v && v.then && parser.options.unwrapPromises) { 10524 p = v; 10525 if (!('$$v' in v)) { 10526 p.$$v = undefined; 10527 p.then(function(val) { p.$$v = val; }); 10528 } 10529 v = v.$$v; 10530 } 10531 return v; 10532 }, { 10533 assign: function(self, value, locals) { 10534 var key = indexFn(self, locals); 10535 // prevent overwriting of Function.constructor which would break ensureSafeObject check 10536 var safe = ensureSafeObject(obj(self, locals), parser.text); 10537 return safe[key] = value; 10538 } 10539 }); 10540 }, 10541 10542 functionCall: function(fn, contextGetter) { 10543 var argsFn = []; 10544 if (this.peekToken().text !== ')') { 10545 do { 10546 argsFn.push(this.expression()); 10547 } while (this.expect(',')); 10548 } 10549 this.consume(')'); 10550 10551 var parser = this; 10552 10553 return function(scope, locals) { 10554 var args = []; 10555 var context = contextGetter ? contextGetter(scope, locals) : scope; 10556 10557 for (var i = 0; i < argsFn.length; i++) { 10558 args.push(argsFn[i](scope, locals)); 10559 } 10560 var fnPtr = fn(scope, locals, context) || noop; 10561 10562 ensureSafeObject(context, parser.text); 10563 ensureSafeObject(fnPtr, parser.text); 10564 10565 // IE stupidity! (IE doesn't have apply for some native functions) 10566 var v = fnPtr.apply 10567 ? fnPtr.apply(context, args) 10568 : fnPtr(args[0], args[1], args[2], args[3], args[4]); 10569 10570 return ensureSafeObject(v, parser.text); 10571 }; 10572 }, 10573 10574 // This is used with json array declaration 10575 arrayDeclaration: function () { 10576 var elementFns = []; 10577 var allConstant = true; 10578 if (this.peekToken().text !== ']') { 10579 do { 10580 if (this.peek(']')) { 10581 // Support trailing commas per ES5.1. 10582 break; 10583 } 10584 var elementFn = this.expression(); 10585 elementFns.push(elementFn); 10586 if (!elementFn.constant) { 10587 allConstant = false; 10588 } 10589 } while (this.expect(',')); 10590 } 10591 this.consume(']'); 10592 10593 return extend(function(self, locals) { 10594 var array = []; 10595 for (var i = 0; i < elementFns.length; i++) { 10596 array.push(elementFns[i](self, locals)); 10597 } 10598 return array; 10599 }, { 10600 literal: true, 10601 constant: allConstant 10602 }); 10603 }, 10604 10605 object: function () { 10606 var keyValues = []; 10607 var allConstant = true; 10608 if (this.peekToken().text !== '}') { 10609 do { 10610 if (this.peek('}')) { 10611 // Support trailing commas per ES5.1. 10612 break; 10613 } 10614 var token = this.expect(), 10615 key = token.string || token.text; 10616 this.consume(':'); 10617 var value = this.expression(); 10618 keyValues.push({key: key, value: value}); 10619 if (!value.constant) { 10620 allConstant = false; 10621 } 10622 } while (this.expect(',')); 10623 } 10624 this.consume('}'); 10625 10626 return extend(function(self, locals) { 10627 var object = {}; 10628 for (var i = 0; i < keyValues.length; i++) { 10629 var keyValue = keyValues[i]; 10630 object[keyValue.key] = keyValue.value(self, locals); 10631 } 10632 return object; 10633 }, { 10634 literal: true, 10635 constant: allConstant 10636 });
10637 } 10638}; 10639 10640 10641////////////////////////////////////////////////// 10642// Parser helper functions 10643////////////////////////////////////////////////// 10644 10645function setter(obj, path, setValue, fullExp, options) { 10646 //needed? 10647 options = options || {}; 10648 10649 var element = path.split('.'), key; 10650 for (var i = 0; element.length > 1; i++) { 10651 key = ensureSafeMemberName(element.shift(), fullExp); 10652 var propertyObj = obj[key]; 10653 if (!propertyObj) { 10654 propertyObj = {}; 10655 obj[key] = propertyObj; 10656 } 10657 obj = propertyObj; 10658 if (obj.then && options.unwrapPromises) { 10659 promiseWarning(fullExp); 10660 if (!("$$v" in obj)) { 10661 (function(promise) { 10662 promise.then(function(val) { promise.$$v = val; }); } 10663 )(obj); 10664 } 10665 if (obj.$$v === undefined) { 10666 obj.$$v = {}; 10667 } 10668 obj = obj.$$v; 10669 } 10670 } 10671 key = ensureSafeMemberName(element.shift(), fullExp); 10672 obj[key] = setValue; 10673 return setValue; 10674} 10675 10676var getterFnCache = {}; 10677 10678/** 10679 * Implementation of the "Black Hole" variant from: 10680 * - http://jsperf.com/angularjs-parse-getter/4 10681 * - http://jsperf.com/path-evaluation-simplified/7 10682 */ 10683function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, options) { 10684 ensureSafeMemberName(key0, fullExp); 10685 ensureSafeMemberName(key1, fullExp); 10686 ensureSafeMemberName(key2, fullExp); 10687 ensureSafeMemberName(key3, fullExp); 10688 ensureSafeMemberName(key4, fullExp); 10689 10690 return !options.unwrapPromises 10691 ? function cspSafeGetter(scope, locals) { 10692 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; 10693 10694 if (pathVal == null) return pathVal; 10695 pathVal = pathVal[key0]; 10696 10697 if (!key1) return pathVal; 10698 if (pathVal == null) return undefined; 10699 pathVal = pathVal[key1]; 10700 10701 if (!key2) return pathVal; 10702 if (pathVal == null) return undefined; 10703 pathVal = pathVal[key2]; 10704 10705 if (!key3) return pathVal; 10706 if (pathVal == null) return undefined; 10707 pathVal = pathVal[key3]; 10708 10709 if (!key4) return pathVal; 10710 if (pathVal == null) return undefined; 10711 pathVal = pathVal[key4]; 10712 10713 return pathVal; 10714 } 10715 : function cspSafePromiseEnabledGetter(scope, locals) { 10716 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope, 10717 promise; 10718 10719 if (pathVal == null) return pathVal; 10720 10721 pathVal = pathVal[key0]; 10722 if (pathVal && pathVal.then) { 10723 promiseWarning(fullExp); 10724 if (!("$$v" in pathVal)) { 10725 promise = pathVal; 10726 promise.$$v = undefined; 10727 promise.then(function(val) { promise.$$v = val; }); 10728 } 10729 pathVal = pathVal.$$v; 10730 } 10731 10732 if (!key1) return pathVal; 10733 if (pathVal == null) return undefined; 10734 pathVal = pathVal[key1]; 10735 if (pathVal && pathVal.then) { 10736 promiseWarning(fullExp); 10737 if (!("$$v" in pathVal)) { 10738 promise = pathVal; 10739 promise.$$v = undefined; 10740 promise.then(function(val) { promise.$$v = val; }); 10741 } 10742 pathVal = pathVal.$$v; 10743 } 10744 10745 if (!key2) return pathVal; 10746 if (pathVal == null) return undefined; 10747 pathVal = pathVal[key2]; 10748 if (pathVal && pathVal.then) { 10749 promiseWarning(fullExp); 10750 if (!("$$v" in pathVal)) { 10751 promise = pathVal; 10752 promise.$$v = undefined; 10753 promise.then(function(val) { promise.$$v = val; }); 10754 } 10755 pathVal = pathVal.$$v; 10756 } 10757 10758 if (!key3) return pathVal; 10759 if (pathVal == null) return undefined; 10760 pathVal = pathVal[key3]; 10761 if (pathVal && pathVal.then) { 10762 promiseWarning(fullExp); 10763 if (!("$$v" in pathVal)) { 10764 promise = pathVal; 10765 promise.$$v = undefined; 10766 promise.then(function(val) { promise.$$v = val; }); 10767 } 10768 pathVal = pathVal.$$v; 10769 } 10770 10771 if (!key4) return pathVal; 10772 if (pathVal == null) return undefined; 10773 pathVal = pathVal[key4]; 10774 if (pathVal && pathVal.then) { 10775 promiseWarning(fullExp); 10776 if (!("$$v" in pathVal)) { 10777 promise = pathVal; 10778 promise.$$v = undefined; 10779 promise.then(function(val) { promise.$$v = val; }); 10780 } 10781 pathVal = pathVal.$$v; 10782 } 10783 return pathVal; 10784 }; 10785} 10786 10787function simpleGetterFn1(key0, fullExp) { 10788 ensureSafeMemberName(key0, fullExp); 10789 10790 return function simpleGetterFn1(scope, locals) { 10791 if (scope == null) return undefined; 10792 return ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; 10793 }; 10794} 10795 10796function simpleGetterFn2(key0, key1, fullExp) { 10797 ensureSafeMemberName(key0, fullExp); 10798 ensureSafeMemberName(key1, fullExp); 10799 10800 return function simpleGetterFn2(scope, locals) { 10801 if (scope == null) return undefined; 10802 scope = ((locals && locals.hasOwnProperty(key0)) ? locals : scope)[key0]; 10803 return scope == null ? undefined : scope[key1]; 10804 }; 10805} 10806 10807function getterFn(path, options, fullExp) { 10808 // Check whether the cache has this getter already. 10809 // We can use hasOwnProperty directly on the cache because we ensure, 10810 // see below, that the cache never stores a path called 'hasOwnProperty' 10811 if (getterFnCache.hasOwnProperty(path)) { 10812 return getterFnCache[path]; 10813 } 10814 10815 var pathKeys = path.split('.'), 10816 pathKeysLength = pathKeys.length, 10817 fn; 10818 10819 // When we have only 1 or 2 tokens, use optimized special case closures. 10820 // http://jsperf.com/angularjs-parse-getter/6 10821 if (!options.unwrapPromises && pathKeysLength === 1) { 10822 fn = simpleGetterFn1(pathKeys[0], fullExp); 10823 } else if (!options.unwrapPromises && pathKeysLength === 2) { 10824 fn = simpleGetterFn2(pathKeys[0], pathKeys[1], fullExp); 10825 } else if (options.csp) { 10826 if (pathKeysLength < 6) { 10827 fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, 10828 options); 10829 } else { 10830 fn = function(scope, locals) { 10831 var i = 0, val; 10832 do { 10833 val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], 10834 pathKeys[i++], fullExp, options)(scope, locals); 10835 10836 locals = undefined; // clear after first iteration 10837 scope = val; 10838 } while (i < pathKeysLength); 10839 return val; 10840 }; 10841 } 10842 } else { 10843 var code = 'var p;\n';
10844 forEach(pathKeys, function(key, index) { 10845 ensureSafeMemberName(key, fullExp); 10846 code += 'if(s == null) return undefined;\n' + 10847 's='+ (index 10848 // we simply dereference 's' on any .dot notation 10849 ? 's' 10850 // but if we are first then we check locals first, and if so read it first 10851 : '((k&&k.hasOwnProperty("' + key + '"))?k:s)') + '["' + key + '"]' + ';\n' + 10852 (options.unwrapPromises 10853 ? 'if (s && s.then) {\n' + 10854 ' pw("' + fullExp.replace(/(["\r\n])/g, '\\$1') + '");\n' + 10855 ' if (!("$$v" in s)) {\n' + 10856 ' p=s;\n' + 10857 ' p.$$v = undefined;\n' + 10858 ' p.then(function(v) {p.$$v=v;});\n' + 10859 '}\n' + 10860 ' s=s.$$v\n' + 10861 '}\n' 10862 : ''); 10863 }); 10864 code += 'return s;'; 10865 10866 /* jshint -W054 */ 10867 var evaledFnGetter = new Function('s', 'k', 'pw', code); // s=scope, k=locals, pw=promiseWarning 10868 /* jshint +W054 */ 10869 evaledFnGetter.toString = valueFn(code); 10870 fn = options.unwrapPromises ? function(scope, locals) { 10871 return evaledFnGetter(scope, locals, promiseWarning); 10872 } : evaledFnGetter; 10873 } 10874 10875 // Only cache the value if it's not going to mess up the cache object 10876 // This is more performant that using Object.prototype.hasOwnProperty.call 10877 if (path !== 'hasOwnProperty') { 10878 getterFnCache[path] = fn; 10879 } 10880 return fn; 10881} 10882 10883/////////////////////////////////// 10884 10885/** 10886 * @ngdoc service 10887 * @name $parse 10888 * @kind function 10889 * 10890 * @description 10891 * 10892 * Converts Angular {@link guide/expression expression} into a function. 10893 * 10894 * ```js 10895 * var getter = $parse('user.name'); 10896 * var setter = getter.assign; 10897 * var context = {user:{name:'angular'}}; 10898 * var locals = {user:{name:'local'}}; 10899 * 10900 * expect(getter(context)).toEqual('angular'); 10901 * setter(context, 'newValue'); 10902 * expect(context.user.name).toEqual('newValue'); 10903 * expect(getter(context, locals)).toEqual('local'); 10904 * ``` 10905 * 10906 * 10907 * @param {string} expression String expression to compile. 10908 * @returns {function(context, locals)} a function which represents the compiled expression: 10909 * 10910 * * `context` â `{object}` â an object against which any expressions embedded in the strings 10911 * are evaluated against (typically a scope object). 10912 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 10913 * `context`. 10914 * 10915 * The returned function also has the following properties: 10916 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 10917 * literal. 10918 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 10919 * constant literals. 10920 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 10921 * set to a function to change its value on the given context. 10922 * 10923 */ 10924 10925 10926/** 10927 * @ngdoc provider 10928 * @name $parseProvider 10929 * @function 10930 * 10931 * @description 10932 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 10933 * service. 10934 */ 10935function $ParseProvider() { 10936 var cache = {}; 10937 10938 var $parseOptions = { 10939 csp: false, 10940 unwrapPromises: false, 10941 logPromiseWarnings: true 10942 }; 10943 10944 10945 /** 10946 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 10947 * 10948 * @ngdoc method 10949 * @name $parseProvider#unwrapPromises 10950 * @description 10951 * 10952 * **This feature is deprecated, see deprecation notes below for more info** 10953 * 10954 * If set to true (default is false), $parse will unwrap promises automatically when a promise is 10955 * found at any part of the expression. In other words, if set to true, the expression will always 10956 * result in a non-promise value. 10957 * 10958 * While the promise is unresolved, it's treated as undefined, but once resolved and fulfilled, 10959 * the fulfillment value is used in place of the promise while evaluating the expression. 10960 * 10961 * **Deprecation notice** 10962 * 10963 * This is a feature that didn't prove to be wildly useful or popular, primarily because of the 10964 * dichotomy between data access in templates (accessed as raw values) and controller code 10965 * (accessed as promises). 10966 * 10967 * In most code we ended up resolving promises manually in controllers anyway and thus unifying 10968 * the model access there. 10969 * 10970 * Other downsides of automatic promise unwrapping: 10971 *
10972 * - when building components it's often desirable to receive the raw promises 10973 * - adds complexity and slows down expression evaluation 10974 * - makes expression code pre-generation unattractive due to the amount of code that needs to be 10975 * generated 10976 * - makes IDE auto-completion and tool support hard 10977 * 10978 * **Warning Logs** 10979 * 10980 * If the unwrapping is enabled, Angular will log a warning about each expression that unwraps a 10981 * promise (to reduce the noise, each expression is logged only once). To disable this logging use 10982 * `$parseProvider.logPromiseWarnings(false)` api. 10983 * 10984 * 10985 * @param {boolean=} value New value. 10986 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 10987 * setter. 10988 */ 10989 this.unwrapPromises = function(value) { 10990 if (isDefined(value)) { 10991 $parseOptions.unwrapPromises = !!value; 10992 return this; 10993 } else { 10994 return $parseOptions.unwrapPromises; 10995 } 10996 }; 10997 10998 10999 /** 11000 * @deprecated Promise unwrapping via $parse is deprecated and will be removed in the future. 11001 * 11002 * @ngdoc method 11003 * @name $parseProvider#logPromiseWarnings 11004 * @description 11005 * 11006 * Controls whether Angular should log a warning on any encounter of a promise in an expression. 11007 * 11008 * The default is set to `true`. 11009 * 11010 * This setting applies only if `$parseProvider.unwrapPromises` setting is set to true as well. 11011 * 11012 * @param {boolean=} value New value. 11013 * @returns {boolean|self} Returns the current setting when used as getter and self if used as 11014 * setter. 11015 */ 11016 this.logPromiseWarnings = function(value) { 11017 if (isDefined(value)) { 11018 $parseOptions.logPromiseWarnings = value; 11019 return this; 11020 } else { 11021 return $parseOptions.logPromiseWarnings; 11022 } 11023 }; 11024 11025 11026 this.$get = ['$filter', '$sniffer', '$log', function($filter, $sniffer, $log) { 11027 $parseOptions.csp = $sniffer.csp; 11028 11029 promiseWarning = function promiseWarningFn(fullExp) { 11030 if (!$parseOptions.logPromiseWarnings || promiseWarningCache.hasOwnProperty(fullExp)) return; 11031 promiseWarningCache[fullExp] = true; 11032 $log.warn('[$parse] Promise found in the expression `' + fullExp + '`. ' + 11033 'Automatic unwrapping of promises in Angular expressions is deprecated.'); 11034 }; 11035 11036 return function(exp) { 11037 var parsedExpression; 11038 11039 switch (typeof exp) { 11040 case 'string': 11041 11042 if (cache.hasOwnProperty(exp)) { 11043 return cache[exp]; 11044 } 11045 11046 var lexer = new Lexer($parseOptions); 11047 var parser = new Parser(lexer, $filter, $parseOptions); 11048 parsedExpression = parser.parse(exp, false); 11049 11050 if (exp !== 'hasOwnProperty') { 11051 // Only cache the value if it's not going to mess up the cache object 11052 // This is more performant that using Object.prototype.hasOwnProperty.call 11053 cache[exp] = parsedExpression; 11054 } 11055 11056 return parsedExpression; 11057 11058 case 'function': 11059 return exp; 11060 11061 default: 11062 return noop; 11063 } 11064 }; 11065 }]; 11066} 11067 11068/** 11069 * @ngdoc service 11070 * @name $q 11071 * @requires $rootScope 11072 * 11073 * @description 11074 * A promise/deferred implementation inspired by [Kris Kowal's Q](https://github.com/kriskowal/q). 11075 * 11076 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 11077 * interface for interacting with an object that represents the result of an action that is 11078 * performed asynchronously, and may or may not be finished at any given point in time. 11079 * 11080 * From the perspective of dealing with error handling, deferred and promise APIs are to 11081 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 11082 * 11083 * ```js 11084 * // for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet` 11085 * // are available in the current lexical scope (they could have been injected or passed in). 11086 * 11087 * function asyncGreet(name) { 11088 * var deferred = $q.defer(); 11089 * 11090 * setTimeout(function() { 11091 * // since this fn executes async in a future turn of the event loop, we need to wrap 11092 * // our code into an $apply call so that the model changes are properly observed. 11093 * scope.$apply(function() { 11094 * deferred.notify('About to greet ' + name + '.'); 11095 * 11096 * if (okToGreet(name)) { 11097 * deferred.resolve('Hello, ' + name + '!'); 11098 * } else { 11099 * deferred.reject('Greeting ' + name + ' is not allowed.'); 11100 * } 11101 * }); 11102 * }, 1000); 11103 * 11104 * return deferred.promise; 11105 * } 11106 * 11107 * var promise = asyncGreet('Robin Hood'); 11108 * promise.then(function(greeting) { 11109 * alert('Success: ' + greeting); 11110 * }, function(reason) { 11111 * alert('Failed: ' + reason); 11112 * }, function(update) { 11113 * alert('Got notification: ' + update); 11114 * }); 11115 * ``` 11116 * 11117 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 11118 * comes in the way of guarantees that promise and deferred APIs make, see 11119 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 11120 *
11121 * Additionally the promise api allows for composition that is very hard to do with the 11122 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 11123 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 11124 * section on serial or parallel joining of promises. 11125 * 11126 * 11127 * # The Deferred API 11128 * 11129 * A new instance of deferred is constructed by calling `$q.defer()`. 11130 * 11131 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 11132 * that can be used for signaling the successful or unsuccessful completion, as well as the status 11133 * of the task. 11134 * 11135 * **Methods** 11136 * 11137 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 11138 * constructed via `$q.reject`, the promise will be rejected instead. 11139 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 11140 * resolving it with a rejection constructed via `$q.reject`. 11141 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 11142 * multiple times before the promise is either resolved or rejected. 11143 * 11144 * **Properties** 11145 * 11146 * - promise â `{Promise}` â promise object associated with this deferred. 11147 * 11148 * 11149 * # The Promise API 11150 * 11151 * A new promise instance is created when a deferred instance is created and can be retrieved by 11152 * calling `deferred.promise`. 11153 * 11154 * The purpose of the promise object is to allow for interested parties to get access to the result 11155 * of the deferred task when it completes. 11156 * 11157 * **Methods** 11158 * 11159 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 11160 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 11161 * as soon as the result is available. The callbacks are called with a single argument: the result 11162 * or rejection reason. Additionally, the notify callback may be called zero or more times to 11163 * provide a progress indication, before the promise is resolved or rejected. 11164 * 11165 * This method *returns a new promise* which is resolved or rejected via the return value of the 11166 * `successCallback`, `errorCallback`. It also notifies via the return value of the 11167 * `notifyCallback` method. The promise can not be resolved or rejected from the notifyCallback 11168 * method. 11169 * 11170 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 11171 * 11172 * - `finally(callback)` â allows you to observe either the fulfillment or rejection of a promise, 11173 * but to do so without modifying the final value. This is useful to release resources or do some 11174 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 11175 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 11176 * more information. 11177 * 11178 * Because `finally` is a reserved word in JavaScript and reserved keywords are not supported as 11179 * property names by ES3, you'll need to invoke the method like `promise['finally'](callback)` to 11180 * make your code IE8 and Android 2.x compatible. 11181 * 11182 * # Chaining promises 11183 * 11184 * Because calling the `then` method of a promise returns a new derived promise, it is easily 11185 * possible to create a chain of promises: 11186 * 11187 * ```js 11188 * promiseB = promiseA.then(function(result) { 11189 * return result + 1; 11190 * }); 11191 * 11192 * // promiseB will be resolved immediately after promiseA is resolved and its value 11193 * // will be the result of promiseA incremented by 1 11194 * ``` 11195 * 11196 * It is possible to create chains of any length and since a promise can be resolved with another 11197 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 11198 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 11199 * $http's response interceptors. 11200 * 11201 * 11202 * # Differences between Kris Kowal's Q and $q 11203 * 11204 * There are two main differences: 11205 *
11206 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 11207 * mechanism in angular, which means faster propagation of resolution or rejection into your 11208 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 11209 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains 11210 * all the important functionality needed for common async tasks. 11211 * 11212 * # Testing 11213 * 11214 * ```js 11215 * it('should simulate promise', inject(function($q, $rootScope) { 11216 * var deferred = $q.defer(); 11217 * var promise = deferred.promise; 11218 * var resolvedValue; 11219 * 11220 * promise.then(function(value) { resolvedValue = value; }); 11221 * expect(resolvedValue).toBeUndefined(); 11222 * 11223 * // Simulate resolving of promise 11224 * deferred.resolve(123); 11225 * // Note that the 'then' function does not get called synchronously. 11226 * // This is because we want the promise API to always be async, whether or not 11227 * // it got called synchronously or asynchronously. 11228 * expect(resolvedValue).toBeUndefined(); 11229 * 11230 * // Propagate promise resolution to 'then' functions using $apply(). 11231 * $rootScope.$apply(); 11232 * expect(resolvedValue).toEqual(123); 11233 * })); 11234 * ``` 11235 */ 11236function $QProvider() { 11237 11238 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 11239 return qFactory(function(callback) { 11240 $rootScope.$evalAsync(callback); 11241 }, $exceptionHandler); 11242 }]; 11243} 11244 11245 11246/** 11247 * Constructs a promise manager. 11248 * 11249 * @param {function(Function)} nextTick Function for executing functions in the next turn. 11250 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 11251 * debugging purposes. 11252 * @returns {object} Promise manager. 11253 */ 11254function qFactory(nextTick, exceptionHandler) { 11255 11256 /** 11257 * @ngdoc method 11258 * @name $q#defer 11259 * @function 11260 * 11261 * @description 11262 * Creates a `Deferred` object which represents a task which will finish in the future. 11263 * 11264 * @returns {Deferred} Returns a new instance of deferred. 11265 */ 11266 var defer = function() { 11267 var pending = [], 11268 value, deferred; 11269 11270 deferred = { 11271 11272 resolve: function(val) { 11273 if (pending) { 11274 var callbacks = pending; 11275 pending = undefined; 11276 value = ref(val); 11277 11278 if (callbacks.length) { 11279 nextTick(function() { 11280 var callback; 11281 for (var i = 0, ii = callbacks.length; i < ii; i++) { 11282 callback = callbacks[i]; 11283 value.then(callback[0], callback[1], callback[2]); 11284 } 11285 }); 11286 } 11287 } 11288 }, 11289 11290 11291 reject: function(reason) { 11292 deferred.resolve(createInternalRejectedPromise(reason)); 11293 }, 11294 11295 11296 notify: function(progress) { 11297 if (pending) { 11298 var callbacks = pending; 11299 11300 if (pending.length) { 11301 nextTick(function() { 11302 var callback; 11303 for (var i = 0, ii = callbacks.length; i < ii; i++) { 11304 callback = callbacks[i]; 11305 callback[2](progress); 11306 } 11307 }); 11308 } 11309 } 11310 }, 11311 11312 11313 promise: { 11314 then: function(callback, errback, progressback) { 11315 var result = defer(); 11316 11317 var wrappedCallback = function(value) { 11318 try { 11319 result.resolve((isFunction(callback) ? callback : defaultCallback)(value)); 11320 } catch(e) { 11321 result.reject(e); 11322 exceptionHandler(e); 11323 } 11324 }; 11325 11326 var wrappedErrback = function(reason) { 11327 try { 11328 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11329 } catch(e) { 11330 result.reject(e); 11331 exceptionHandler(e); 11332 } 11333 }; 11334 11335 var wrappedProgressback = function(progress) { 11336 try { 11337 result.notify((isFunction(progressback) ? progressback : defaultCallback)(progress)); 11338 } catch(e) { 11339 exceptionHandler(e); 11340 } 11341 }; 11342 11343 if (pending) { 11344 pending.push([wrappedCallback, wrappedErrback, wrappedProgressback]); 11345 } else { 11346 value.then(wrappedCallback, wrappedErrback, wrappedProgressback); 11347 } 11348 11349 return result.promise; 11350 }, 11351 11352 "catch": function(callback) { 11353 return this.then(null, callback); 11354 }, 11355 11356 "finally": function(callback) { 11357 11358 function makePromise(value, resolved) { 11359 var result = defer(); 11360 if (resolved) { 11361 result.resolve(value); 11362 } else { 11363 result.reject(value); 11364 } 11365 return result.promise; 11366 } 11367 11368 function handleCallback(value, isResolved) { 11369 var callbackOutput = null; 11370 try { 11371 callbackOutput = (callback ||defaultCallback)(); 11372 } catch(e) { 11373 return makePromise(e, false); 11374 } 11375 if (callbackOutput && isFunction(callbackOutput.then)) { 11376 return callbackOutput.then(function() { 11377 return makePromise(value, isResolved); 11378 }, function(error) { 11379 return makePromise(error, false); 11380 }); 11381 } else { 11382 return makePromise(value, isResolved); 11383 } 11384 } 11385 11386 return this.then(function(value) { 11387 return handleCallback(value, true); 11388 }, function(error) { 11389 return handleCallback(error, false); 11390 }); 11391 } 11392 } 11393 }; 11394 11395 return deferred; 11396 }; 11397 11398 11399 var ref = function(value) { 11400 if (value && isFunction(value.then)) return value; 11401 return { 11402 then: function(callback) { 11403 var result = defer(); 11404 nextTick(function() { 11405 result.resolve(callback(value)); 11406 }); 11407 return result.promise; 11408 } 11409 }; 11410 }; 11411 11412 11413 /** 11414 * @ngdoc method 11415 * @name $q#reject 11416 * @function 11417 * 11418 * @description 11419 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 11420 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 11421 * a promise chain, you don't need to worry about it. 11422 * 11423 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 11424 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 11425 * a promise error callback and you want to forward the error to the promise derived from the 11426 * current promise, you have to "rethrow" the error by returning a rejection constructed via 11427 * `reject`. 11428 * 11429 * ```js 11430 * promiseB = promiseA.then(function(result) { 11431 * // success: do something and resolve promiseB 11432 * // with the old or a new result 11433 * return result; 11434 * }, function(reason) { 11435 * // error: handle the error if possible and
11436 * // resolve promiseB with newPromiseOrValue, 11437 * // otherwise forward the rejection to promiseB 11438 * if (canHandle(reason)) { 11439 * // handle the error and recover 11440 * return newPromiseOrValue; 11441 * } 11442 * return $q.reject(reason); 11443 * }); 11444 * ``` 11445 * 11446 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 11447 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 11448 */ 11449 var reject = function(reason) { 11450 var result = defer(); 11451 result.reject(reason); 11452 return result.promise; 11453 }; 11454 11455 var createInternalRejectedPromise = function(reason) { 11456 return { 11457 then: function(callback, errback) { 11458 var result = defer(); 11459 nextTick(function() { 11460 try { 11461 result.resolve((isFunction(errback) ? errback : defaultErrback)(reason)); 11462 } catch(e) { 11463 result.reject(e); 11464 exceptionHandler(e); 11465 } 11466 }); 11467 return result.promise; 11468 } 11469 }; 11470 }; 11471 11472 11473 /** 11474 * @ngdoc method 11475 * @name $q#when 11476 * @function 11477 * 11478 * @description 11479 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 11480 * This is useful when you are dealing with an object that might or might not be a promise, or if 11481 * the promise comes from a source that can't be trusted. 11482 * 11483 * @param {*} value Value or a promise 11484 * @returns {Promise} Returns a promise of the passed value or promise 11485 */ 11486 var when = function(value, callback, errback, progressback) { 11487 var result = defer(), 11488 done; 11489 11490 var wrappedCallback = function(value) { 11491 try { 11492 return (isFunction(callback) ? callback : defaultCallback)(value); 11493 } catch (e) { 11494 exceptionHandler(e); 11495 return reject(e); 11496 } 11497 }; 11498 11499 var wrappedErrback = function(reason) { 11500 try { 11501 return (isFunction(errback) ? errback : defaultErrback)(reason); 11502 } catch (e) { 11503 exceptionHandler(e); 11504 return reject(e); 11505 } 11506 }; 11507 11508 var wrappedProgressback = function(progress) { 11509 try { 11510 return (isFunction(progressback) ? progressback : defaultCallback)(progress); 11511 } catch (e) { 11512 exceptionHandler(e); 11513 } 11514 }; 11515 11516 nextTick(function() { 11517 ref(value).then(function(value) { 11518 if (done) return; 11519 done = true; 11520 result.resolve(ref(value).then(wrappedCallback, wrappedErrback, wrappedProgressback)); 11521 }, function(reason) { 11522 if (done) return; 11523 done = true; 11524 result.resolve(wrappedErrback(reason)); 11525 }, function(progress) { 11526 if (done) return; 11527 result.notify(wrappedProgressback(progress)); 11528 }); 11529 }); 11530 11531 return result.promise; 11532 }; 11533 11534 11535 function defaultCallback(value) { 11536 return value; 11537 } 11538 11539 11540 function defaultErrback(reason) { 11541 return reject(reason); 11542 } 11543 11544 11545 /** 11546 * @ngdoc method 11547 * @name $q#all 11548 * @function 11549 * 11550 * @description 11551 * Combines multiple promises into a single promise that is resolved when all of the input 11552 * promises are resolved. 11553 * 11554 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 11555 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 11556 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 11557 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 11558 * with the same rejection value. 11559 */ 11560 function all(promises) { 11561 var deferred = defer(), 11562 counter = 0, 11563 results = isArray(promises) ? [] : {}; 11564 11565 forEach(promises, function(promise, key) { 11566 counter++; 11567 ref(promise).then(function(value) { 11568 if (results.hasOwnProperty(key)) return; 11569 results[key] = value; 11570 if (!(--counter)) deferred.resolve(results); 11571 }, function(reason) { 11572 if (results.hasOwnProperty(key)) return; 11573 deferred.reject(reason); 11574 }); 11575 });
vendor: 4,330 bytes, lines 11576-11709
11576 11577 if (counter === 0) { 11578 deferred.resolve(results); 11579 } 11580 11581 return deferred.promise; 11582 } 11583 11584 return { 11585 defer: defer, 11586 reject: reject, 11587 when: when, 11588 all: all 11589 }; 11590} 11591 11592function $$RAFProvider(){ //rAF 11593 this.$get = ['$window', '$timeout', function($window, $timeout) { 11594 var requestAnimationFrame = $window.requestAnimationFrame || 11595 $window.webkitRequestAnimationFrame || 11596 $window.mozRequestAnimationFrame; 11597 11598 var cancelAnimationFrame = $window.cancelAnimationFrame || 11599 $window.webkitCancelAnimationFrame || 11600 $window.mozCancelAnimationFrame || 11601 $window.webkitCancelRequestAnimationFrame; 11602 11603 var rafSupported = !!requestAnimationFrame; 11604 var raf = rafSupported 11605 ? function(fn) { 11606 var id = requestAnimationFrame(fn); 11607 return function() { 11608 cancelAnimationFrame(id); 11609 }; 11610 } 11611 : function(fn) { 11612 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 11613 return function() { 11614 $timeout.cancel(timer); 11615 }; 11616 }; 11617 11618 raf.supported = rafSupported; 11619 11620 return raf; 11621 }]; 11622} 11623 11624/** 11625 * DESIGN NOTES 11626 * 11627 * The design decisions behind the scope are heavily favored for speed and memory consumption. 11628 * 11629 * The typical use of scope is to watch the expressions, which most of the time return the same 11630 * value as last time so we optimize the operation. 11631 * 11632 * Closures construction is expensive in terms of speed as well as memory: 11633 * - No closures, instead use prototypical inheritance for API 11634 * - Internal state needs to be stored on scope directly, which means that private state is 11635 * exposed as $$____ properties 11636 * 11637 * Loop operations are optimized by using while(count--) { ... } 11638 * - this means that in order to keep the same order of execution as addition we have to add 11639 * items to the array at the beginning (shift) instead of at the end (push) 11640 * 11641 * Child scopes are created and removed often 11642 * - Using an array would be slow since inserts in middle are expensive so we use linked list 11643 * 11644 * There are few watches then a lot of observers. This is why you don't want the observer to be 11645 * implemented in the same way as watch. Watch requires return of initialization function which 11646 * are expensive to construct. 11647 */ 11648 11649 11650/** 11651 * @ngdoc provider 11652 * @name $rootScopeProvider 11653 * @description 11654 * 11655 * Provider for the $rootScope service. 11656 */ 11657 11658/** 11659 * @ngdoc method 11660 * @name $rootScopeProvider#digestTtl 11661 * @description 11662 * 11663 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 11664 * assuming that the model is unstable. 11665 * 11666 * The current default is 10 iterations. 11667 * 11668 * In complex applications it's possible that the dependencies between `$watch`s will result in 11669 * several digest iterations. However if an application needs more than the default 10 digest 11670 * iterations for its model to stabilize then you should investigate what is causing the model to 11671 * continuously change during the digest. 11672 * 11673 * Increasing the TTL could have performance implications, so you should not change it without 11674 * proper justification. 11675 * 11676 * @param {number} limit The number of digest iterations. 11677 */ 11678 11679 11680/** 11681 * @ngdoc service 11682 * @name $rootScope 11683 * @description 11684 * 11685 * Every application has a single root {@link ng.$rootScope.Scope scope}. 11686 * All other scopes are descendant scopes of the root scope. Scopes provide separation 11687 * between the model and the view, via a mechanism for watching the model for changes. 11688 * They also provide an event emission/broadcast and subscription facility. See the 11689 * {@link guide/scope developer guide on scopes}. 11690 */ 11691function $RootScopeProvider(){ 11692 var TTL = 10; 11693 var $rootScopeMinErr = minErr('$rootScope'); 11694 var lastDirtyWatch = null; 11695 11696 this.digestTtl = function(value) { 11697 if (arguments.length) { 11698 TTL = value; 11699 } 11700 return TTL; 11701 }; 11702 11703 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 11704 function( $injector, $exceptionHandler, $parse, $browser) { 11705 11706 /** 11707 * @ngdoc type 11708 * @name $rootScope.Scope 11709 *
11710 * @description 11711 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 11712 * {@link auto.$injector $injector}. Child scopes are created using the 11713 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 11714 * compiled HTML template is executed.) 11715 * 11716 * Here is a simple scope snippet to show how you can interact with the scope. 11717 * ```html 11718 * <file src="./test/ng/rootScopeSpec.js" tag="docs1" /> 11719 * ``` 11720 * 11721 * # Inheritance 11722 * A scope can inherit from a parent scope, as in this example: 11723 * ```js 11724 var parent = $rootScope; 11725 var child = parent.$new(); 11726 11727 parent.salutation = "Hello"; 11728 child.name = "World"; 11729 expect(child.salutation).toEqual('Hello'); 11730 11731 child.salutation = "Welcome"; 11732 expect(child.salutation).toEqual('Welcome'); 11733 expect(parent.salutation).toEqual('Hello'); 11734 * ``` 11735 * 11736 * 11737 * @param {Object.<string, function()>=} providers Map of service factory which need to be 11738 * provided for the current scope. Defaults to {@link ng}. 11739 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 11740 * append/override services provided by `providers`. This is handy 11741 * when unit-testing and having the need to override a default 11742 * service. 11743 * @returns {Object} Newly created scope. 11744 * 11745 */ 11746 function Scope() { 11747 this.$id = nextUid(); 11748 this.$$phase = this.$parent = this.$$watchers = 11749 this.$$nextSibling = this.$$prevSibling = 11750 this.$$childHead = this.$$childTail = null; 11751 this['this'] = this.$root = this; 11752 this.$$destroyed = false; 11753 this.$$asyncQueue = []; 11754 this.$$postDigestQueue = []; 11755 this.$$listeners = {}; 11756 this.$$listenerCount = {}; 11757 this.$$isolateBindings = {}; 11758 } 11759 11760 /** 11761 * @ngdoc property 11762 * @name $rootScope.Scope#$id 11763 * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for 11764 * debugging. 11765 */ 11766 11767 11768 Scope.prototype = { 11769 constructor: Scope, 11770 /** 11771 * @ngdoc method 11772 * @name $rootScope.Scope#$new 11773 * @function 11774 * 11775 * @description 11776 * Creates a new child {@link ng.$rootScope.Scope scope}. 11777 * 11778 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} and 11779 * {@link ng.$rootScope.Scope#$digest $digest()} events. The scope can be removed from the 11780 * scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 11781 * 11782 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 11783 * desired for the scope and its child scopes to be permanently detached from the parent and 11784 * thus stop participating in model change detection and listener notification by invoking. 11785 * 11786 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 11787 * parent scope. The scope is isolated, as it can not see parent scope properties. 11788 * When creating widgets, it is useful for the widget to not accidentally read parent 11789 * state. 11790 * 11791 * @returns {Object} The newly created child scope. 11792 * 11793 */ 11794 $new: function(isolate) { 11795 var ChildScope, 11796 child; 11797 11798 if (isolate) { 11799 child = new Scope(); 11800 child.$root = this.$root; 11801 // ensure that there is just one async queue per $rootScope and its children 11802 child.$$asyncQueue = this.$$asyncQueue; 11803 child.$$postDigestQueue = this.$$postDigestQueue; 11804 } else { 11805 ChildScope = function() {}; // should be anonymous; This is so that when the minifier munges 11806 // the name it does not become random set of chars. This will then show up as class 11807 // name in the web inspector. 11808 ChildScope.prototype = this; 11809 child = new ChildScope(); 11810 child.$id = nextUid(); 11811 } 11812 child['this'] = child; 11813 child.$$listeners = {}; 11814 child.$$listenerCount = {}; 11815 child.$parent = this;
11816 child.$$watchers = child.$$nextSibling = child.$$childHead = child.$$childTail = null; 11817 child.$$prevSibling = this.$$childTail; 11818 if (this.$$childHead) { 11819 this.$$childTail.$$nextSibling = child; 11820 this.$$childTail = child; 11821 } else { 11822 this.$$childHead = this.$$childTail = child; 11823 } 11824 return child; 11825 }, 11826 11827 /** 11828 * @ngdoc method 11829 * @name $rootScope.Scope#$watch 11830 * @function 11831 * 11832 * @description 11833 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 11834 * 11835 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 11836 * $digest()} and should return the value that will be watched. (Since 11837 * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the 11838 * `watchExpression` can execute multiple times per 11839 * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) 11840 * - The `listener` is called only when the value from the current `watchExpression` and the 11841 * previous call to `watchExpression` are not equal (with the exception of the initial run, 11842 * see below). The inequality is determined according to 11843 * {@link angular.equals} function. To save the value of the object for later comparison, 11844 * the {@link angular.copy} function is used. It also means that watching complex options 11845 * will have adverse memory and performance implications. 11846 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 11847 * This is achieved by rerunning the watchers until no changes are detected. The rerun 11848 * iteration limit is 10 to prevent an infinite loop deadlock. 11849 * 11850 * 11851 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 11852 * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` 11853 * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a 11854 * change is detected, be prepared for multiple calls to your listener.) 11855 * 11856 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 11857 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 11858 * watcher. In rare cases, this is undesirable because the listener is called when the result 11859 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 11860 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 11861 * listener was called due to initialization. 11862 * 11863 * The example below contains an illustration of using a function as your $watch listener 11864 * 11865 * 11866 * # Example 11867 * ```js 11868 // let's assume that scope was dependency injected as the $rootScope 11869 var scope = $rootScope; 11870 scope.name = 'misko'; 11871 scope.counter = 0; 11872 11873 expect(scope.counter).toEqual(0); 11874 scope.$watch('name', function(newValue, oldValue) { 11875 scope.counter = scope.counter + 1; 11876 }); 11877 expect(scope.counter).toEqual(0); 11878 11879 scope.$digest(); 11880 // no variable change 11881 expect(scope.counter).toEqual(0); 11882 11883 scope.name = 'adam'; 11884 scope.$digest(); 11885 expect(scope.counter).toEqual(1); 11886 11887 11888 11889 // Using a listener function 11890 var food; 11891 scope.foodCounter = 0; 11892 expect(scope.foodCounter).toEqual(0); 11893 scope.$watch( 11894 // This is the listener function 11895 function() { return food; }, 11896 // This is the change handler 11897 function(newValue, oldValue) { 11898 if ( newValue !== oldValue ) { 11899 // Only increment the counter if the value changed 11900 scope.foodCounter = scope.foodCounter + 1; 11901 } 11902 } 11903 ); 11904 // No digest has been run so the counter will be zero 11905 expect(scope.foodCounter).toEqual(0); 11906 11907 // Run the digest but since food has not changed count will still be zero 11908 scope.$digest(); 11909 expect(scope.foodCounter).toEqual(0); 11910 11911 // Update food and run digest. Now the counter will increment 11912 food = 'cheeseburger'; 11913 scope.$digest(); 11914 expect(scope.foodCounter).toEqual(1); 11915 11916 * ``` 11917 * 11918 * 11919 * 11920 * @param {(function()|string)} watchExpression Expression that is evaluated on each 11921 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 11922 * a call to the `listener`. 11923 * 11924 * - `string`: Evaluated as {@link guide/expression expression} 11925 * - `function(scope)`: called with current `scope` as a parameter. 11926 * @param {(function()|string)=} listener Callback called whenever the return value of 11927 * the `watchExpression` changes. 11928 * 11929 * - `string`: Evaluated as {@link guide/expression expression} 11930 * - `function(newValue, oldValue, scope)`: called with current and previous values as 11931 * parameters. 11932 * 11933 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of 11934 * comparing for reference equality. 11935 * @returns {function()} Returns a deregistration function for this listener. 11936 */ 11937 $watch: function(watchExp, listener, objectEquality) { 11938 var scope = this, 11939 get = compileToFn(watchExp, 'watch'), 11940 array = scope.$$watchers, 11941 watcher = { 11942 fn: listener, 11943 last: initWatchVal, 11944 get: get, 11945 exp: watchExp, 11946 eq: !!objectEquality 11947 }; 11948 11949 lastDirtyWatch = null; 11950 11951 // in the case user pass string, we need to compile it, do we really need this ? 11952 if (!isFunction(listener)) { 11953 var listenFn = compileToFn(listener || noop, 'listener'); 11954 watcher.fn = function(newVal, oldVal, scope) {listenFn(scope);}; 11955 } 11956 11957 if (typeof watchExp == 'string' && get.constant) { 11958 var originalFn = watcher.fn; 11959 watcher.fn = function(newVal, oldVal, scope) { 11960 originalFn.call(this, newVal, oldVal, scope); 11961 arrayRemove(array, watcher); 11962 }; 11963 } 11964 11965 if (!array) { 11966 array = scope.$$watchers = []; 11967 } 11968 // we use unshift since we use a while loop in $digest for speed. 11969 // the while loop reads in reverse order. 11970 array.unshift(watcher); 11971 11972 return function() { 11973 arrayRemove(array, watcher); 11974 lastDirtyWatch = null; 11975 }; 11976 }, 11977 11978 11979 /** 11980 * @ngdoc method 11981 * @name $rootScope.Scope#$watchCollection 11982 * @function 11983 *
11984 * @description 11985 * Shallow watches the properties of an object and fires whenever any of the properties change 11986 * (for arrays, this implies watching the array items; for object maps, this implies watching 11987 * the properties). If a change is detected, the `listener` callback is fired. 11988 * 11989 * - The `obj` collection is observed via standard $watch operation and is examined on every 11990 * call to $digest() to see if any items have been added, removed, or moved. 11991 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 11992 * adding, removing, and moving items belonging to an object or array. 11993 * 11994 * 11995 * # Example 11996 * ```js 11997 $scope.names = ['igor', 'matias', 'misko', 'james']; 11998 $scope.dataCount = 4; 11999 12000 $scope.$watchCollection('names', function(newNames, oldNames) { 12001 $scope.dataCount = newNames.length; 12002 }); 12003 12004 expect($scope.dataCount).toEqual(4); 12005 $scope.$digest(); 12006 12007 //still at 4 ... no changes 12008 expect($scope.dataCount).toEqual(4); 12009 12010 $scope.names.pop(); 12011 $scope.$digest(); 12012 12013 //now there's been a change 12014 expect($scope.dataCount).toEqual(3); 12015 * ``` 12016 * 12017 * 12018 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 12019 * expression value should evaluate to an object or an array which is observed on each 12020 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 12021 * collection will trigger a call to the `listener`. 12022 * 12023 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 12024 * when a change is detected. 12025 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 12026 * - The `oldCollection` object is a copy of the former collection data. 12027 * Due to performance considerations, the`oldCollection` value is computed only if the 12028 * `listener` function declares two or more arguments. 12029 * - The `scope` argument refers to the current scope. 12030 * 12031 * @returns {function()} Returns a de-registration function for this listener. When the 12032 * de-registration function is executed, the internal watch operation is terminated. 12033 */ 12034 $watchCollection: function(obj, listener) { 12035 var self = this; 12036 // the current value, updated on each dirty-check run 12037 var newValue; 12038 // a shallow copy of the newValue from the last dirty-check run, 12039 // updated to match newValue during dirty-check run 12040 var oldValue; 12041 // a shallow copy of the newValue from when the last change happened 12042 var veryOldValue; 12043 // only track veryOldValue if the listener is asking for it 12044 var trackVeryOldValue = (listener.length > 1); 12045 var changeDetected = 0; 12046 var objGetter = $parse(obj); 12047 var internalArray = []; 12048 var internalObject = {}; 12049 var initRun = true; 12050 var oldLength = 0; 12051 12052 function $watchCollectionWatch() { 12053 newValue = objGetter(self); 12054 var newLength, key; 12055 12056 if (!isObject(newValue)) { // if primitive 12057 if (oldValue !== newValue) { 12058 oldValue = newValue; 12059 changeDetected++; 12060 } 12061 } else if (isArrayLike(newValue)) { 12062 if (oldValue !== internalArray) { 12063 // we are transitioning from something which was not an array into array. 12064 oldValue = internalArray; 12065 oldLength = oldValue.length = 0; 12066 changeDetected++; 12067 } 12068 12069 newLength = newValue.length; 12070 12071 if (oldLength !== newLength) { 12072 // if lengths do not match we need to trigger change notification 12073 changeDetected++; 12074 oldValue.length = oldLength = newLength; 12075 } 12076 // copy the items to oldValue and look for changes. 12077 for (var i = 0; i < newLength; i++) { 12078 var bothNaN = (oldValue[i] !== oldValue[i]) && 12079 (newValue[i] !== newValue[i]);
12080 if (!bothNaN && (oldValue[i] !== newValue[i])) { 12081 changeDetected++; 12082 oldValue[i] = newValue[i]; 12083 } 12084 } 12085 } else { 12086 if (oldValue !== internalObject) { 12087 // we are transitioning from something which was not an object into object. 12088 oldValue = internalObject = {}; 12089 oldLength = 0; 12090 changeDetected++; 12091 } 12092 // copy the items to oldValue and look for changes. 12093 newLength = 0; 12094 for (key in newValue) { 12095 if (newValue.hasOwnProperty(key)) { 12096 newLength++; 12097 if (oldValue.hasOwnProperty(key)) { 12098 if (oldValue[key] !== newValue[key]) { 12099 changeDetected++; 12100 oldValue[key] = newValue[key]; 12101 } 12102 } else { 12103 oldLength++; 12104 oldValue[key] = newValue[key]; 12105 changeDetected++; 12106 } 12107 } 12108 } 12109 if (oldLength > newLength) { 12110 // we used to have more keys, need to find them and destroy them. 12111 changeDetected++; 12112 for(key in oldValue) { 12113 if (oldValue.hasOwnProperty(key) && !newValue.hasOwnProperty(key)) { 12114 oldLength--; 12115 delete oldValue[key]; 12116 } 12117 } 12118 } 12119 } 12120 return changeDetected; 12121 } 12122 12123 function $watchCollectionAction() { 12124 if (initRun) { 12125 initRun = false; 12126 listener(newValue, newValue, self); 12127 } else { 12128 listener(newValue, veryOldValue, self); 12129 } 12130 12131 // make a copy for the next time a collection is changed 12132 if (trackVeryOldValue) { 12133 if (!isObject(newValue)) { 12134 //primitive 12135 veryOldValue = newValue; 12136 } else if (isArrayLike(newValue)) { 12137 veryOldValue = new Array(newValue.length); 12138 for (var i = 0; i < newValue.length; i++) { 12139 veryOldValue[i] = newValue[i]; 12140 } 12141 } else { // if object 12142 veryOldValue = {}; 12143 for (var key in newValue) { 12144 if (hasOwnProperty.call(newValue, key)) { 12145 veryOldValue[key] = newValue[key]; 12146 } 12147 } 12148 } 12149 } 12150 } 12151 12152 return this.$watch($watchCollectionWatch, $watchCollectionAction); 12153 }, 12154 12155 /** 12156 * @ngdoc method 12157 * @name $rootScope.Scope#$digest 12158 * @function 12159 * 12160 * @description 12161 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 12162 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 12163 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 12164 * until no more listeners are firing. This means that it is possible to get into an infinite 12165 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 12166 * iterations exceeds 10. 12167 * 12168 * Usually, you don't call `$digest()` directly in 12169 * {@link ng.directive:ngController controllers} or in 12170 * {@link ng.$compileProvider#directive directives}. 12171 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 12172 * a {@link ng.$compileProvider#directive directives}), which will force a `$digest()`. 12173 * 12174 * If you want to be notified whenever `$digest()` is called, 12175 * you can register a `watchExpression` function with 12176 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 12177 * 12178 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 12179 * 12180 * # Example 12181 * ```js 12182 var scope = ...; 12183 scope.name = 'misko'; 12184 scope.counter = 0; 12185 12186 expect(scope.counter).toEqual(0); 12187 scope.$watch('name', function(newValue, oldValue) { 12188 scope.counter = scope.counter + 1; 12189 }); 12190 expect(scope.counter).toEqual(0); 12191 12192 scope.$digest(); 12193 // no variable change 12194 expect(scope.counter).toEqual(0); 12195 12196 scope.name = 'adam'; 12197 scope.$digest(); 12198 expect(scope.counter).toEqual(1); 12199 * ``` 12200 * 12201 */ 12202 $digest: function() { 12203 var watch, value, last, 12204 watchers, 12205 asyncQueue = this.$$asyncQueue, 12206 postDigestQueue = this.$$postDigestQueue, 12207 length, 12208 dirty, ttl = TTL, 12209 next, current, target = this, 12210 watchLog = [], 12211 logIdx, logMsg, asyncTask; 12212 12213 beginPhase('$digest'); 12214 12215 lastDirtyWatch = null; 12216
12217 do { // "while dirty" loop 12218 dirty = false; 12219 current = target; 12220 12221 while(asyncQueue.length) { 12222 try { 12223 asyncTask = asyncQueue.shift(); 12224 asyncTask.scope.$eval(asyncTask.expression); 12225 } catch (e) { 12226 clearPhase(); 12227 $exceptionHandler(e); 12228 } 12229 lastDirtyWatch = null; 12230 } 12231 12232 traverseScopesLoop: 12233 do { // "traverse the scopes" loop 12234 if ((watchers = current.$$watchers)) { 12235 // process our watches 12236 length = watchers.length; 12237 while (length--) { 12238 try { 12239 watch = watchers[length]; 12240 // Most common watches are on primitives, in which case we can short 12241 // circuit it with === operator, only when === fails do we use .equals 12242 if (watch) { 12243 if ((value = watch.get(current)) !== (last = watch.last) && 12244 !(watch.eq 12245 ? equals(value, last) 12246 : (typeof value == 'number' && typeof last == 'number' 12247 && isNaN(value) && isNaN(last)))) { 12248 dirty = true; 12249 lastDirtyWatch = watch; 12250 watch.last = watch.eq ? copy(value) : value; 12251 watch.fn(value, ((last === initWatchVal) ? value : last), current); 12252 if (ttl < 5) { 12253 logIdx = 4 - ttl; 12254 if (!watchLog[logIdx]) watchLog[logIdx] = []; 12255 logMsg = (isFunction(watch.exp)) 12256 ? 'fn: ' + (watch.exp.name || watch.exp.toString()) 12257 : watch.exp; 12258 logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last); 12259 watchLog[logIdx].push(logMsg); 12260 } 12261 } else if (watch === lastDirtyWatch) { 12262 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 12263 // have already been tested. 12264 dirty = false; 12265 break traverseScopesLoop; 12266 } 12267 } 12268 } catch (e) { 12269 clearPhase(); 12270 $exceptionHandler(e); 12271 } 12272 } 12273 } 12274 12275 // Insanity Warning: scope depth-first traversal 12276 // yes, this code is a bit crazy, but it works and we have tests to prove it! 12277 // this piece should be kept in sync with the traversal in $broadcast 12278 if (!(next = (current.$$childHead || 12279 (current !== target && current.$$nextSibling)))) { 12280 while(current !== target && !(next = current.$$nextSibling)) { 12281 current = current.$parent; 12282 } 12283 } 12284 } while ((current = next)); 12285 12286 // `break traverseScopesLoop;` takes us to here 12287 12288 if((dirty || asyncQueue.length) && !(ttl--)) { 12289 clearPhase(); 12290 throw $rootScopeMinErr('infdig', 12291 '{0} $digest() iterations reached. Aborting!\n' + 12292 'Watchers fired in the last 5 iterations: {1}', 12293 TTL, toJson(watchLog)); 12294 } 12295 12296 } while (dirty || asyncQueue.length); 12297 12298 clearPhase(); 12299 12300 while(postDigestQueue.length) { 12301 try { 12302 postDigestQueue.shift()(); 12303 } catch (e) { 12304 $exceptionHandler(e); 12305 } 12306 } 12307 }, 12308 12309 12310 /** 12311 * @ngdoc event 12312 * @name $rootScope.Scope#$destroy 12313 * @eventType broadcast on scope being destroyed 12314 * 12315 * @description 12316 * Broadcasted when a scope and its children are being destroyed. 12317 * 12318 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 12319 * clean up DOM bindings before an element is removed from the DOM. 12320 */ 12321 12322 /** 12323 * @ngdoc method 12324 * @name $rootScope.Scope#$destroy 12325 * @function 12326 *
12327 * @description 12328 * Removes the current scope (and all of its children) from the parent scope. Removal implies 12329 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 12330 * propagate to the current scope and its children. Removal also implies that the current 12331 * scope is eligible for garbage collection. 12332 * 12333 * The `$destroy()` is usually used by directives such as 12334 * {@link ng.directive:ngRepeat ngRepeat} for managing the 12335 * unrolling of the loop. 12336 * 12337 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 12338 * Application code can register a `$destroy` event handler that will give it a chance to 12339 * perform any necessary cleanup. 12340 * 12341 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 12342 * clean up DOM bindings before an element is removed from the DOM. 12343 */ 12344 $destroy: function() { 12345 // we can't destroy the root scope or a scope that has been already destroyed 12346 if (this.$$destroyed) return; 12347 var parent = this.$parent; 12348 12349 this.$broadcast('$destroy'); 12350 this.$$destroyed = true; 12351 if (this === $rootScope) return; 12352 12353 forEach(this.$$listenerCount, bind(null, decrementListenerCount, this)); 12354 12355 // sever all the references to parent scopes (after this cleanup, the current scope should 12356 // not be retained by any of our references and should be eligible for garbage collection) 12357 if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 12358 if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 12359 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 12360 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 12361 12362 12363 // All of the code below is bogus code that works around V8's memory leak via optimized code 12364 // and inline caches. 12365 // 12366 // see: 12367 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 12368 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 12369 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 12370 12371 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 12372 this.$$childTail = this.$root = null; 12373 12374 // don't reset these to null in case some async task tries to register a listener/watch/task 12375 this.$$listeners = {}; 12376 this.$$watchers = this.$$asyncQueue = this.$$postDigestQueue = []; 12377 12378 // prevent NPEs since these methods have references to properties we nulled out 12379 this.$destroy = this.$digest = this.$apply = noop; 12380 this.$on = this.$watch = function() { return noop; }; 12381 }, 12382 12383 /** 12384 * @ngdoc method 12385 * @name $rootScope.Scope#$eval 12386 * @function 12387 * 12388 * @description 12389 * Executes the `expression` on the current scope and returns the result. Any exceptions in 12390 * the expression are propagated (uncaught). This is useful when evaluating Angular 12391 * expressions. 12392 * 12393 * # Example 12394 * ```js 12395 var scope = ng.$rootScope.Scope(); 12396 scope.a = 1; 12397 scope.b = 2; 12398 12399 expect(scope.$eval('a+b')).toEqual(3); 12400 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 12401 * ``` 12402 * 12403 * @param {(string|function())=} expression An angular expression to be executed. 12404 * 12405 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12406 * - `function(scope)`: execute the function with the current `scope` parameter. 12407 * 12408 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 12409 * @returns {*} The result of evaluating the expression. 12410 */ 12411 $eval: function(expr, locals) { 12412 return $parse(expr)(this, locals); 12413 }, 12414 12415 /** 12416 * @ngdoc method 12417 * @name $rootScope.Scope#$evalAsync 12418 * @function 12419 *
12420 * @description 12421 * Executes the expression on the current scope at a later point in time. 12422 * 12423 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 12424 * that: 12425 * 12426 * - it will execute after the function that scheduled the evaluation (preferably before DOM 12427 * rendering). 12428 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 12429 * `expression` execution. 12430 * 12431 * Any exceptions from the execution of the expression are forwarded to the 12432 * {@link ng.$exceptionHandler $exceptionHandler} service. 12433 * 12434 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 12435 * will be scheduled. However, it is encouraged to always call code that changes the model 12436 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 12437 * 12438 * @param {(string|function())=} expression An angular expression to be executed. 12439 * 12440 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12441 * - `function(scope)`: execute the function with the current `scope` parameter. 12442 * 12443 */ 12444 $evalAsync: function(expr) { 12445 // if we are outside of an $digest loop and this is the first time we are scheduling async 12446 // task also schedule async auto-flush 12447 if (!$rootScope.$$phase && !$rootScope.$$asyncQueue.length) { 12448 $browser.defer(function() { 12449 if ($rootScope.$$asyncQueue.length) { 12450 $rootScope.$digest(); 12451 } 12452 }); 12453 } 12454 12455 this.$$asyncQueue.push({scope: this, expression: expr}); 12456 }, 12457 12458 $$postDigest : function(fn) { 12459 this.$$postDigestQueue.push(fn); 12460 }, 12461 12462 /** 12463 * @ngdoc method 12464 * @name $rootScope.Scope#$apply 12465 * @function 12466 * 12467 * @description 12468 * `$apply()` is used to execute an expression in angular from outside of the angular 12469 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 12470 * Because we are calling into the angular framework we need to perform proper scope life 12471 * cycle of {@link ng.$exceptionHandler exception handling}, 12472 * {@link ng.$rootScope.Scope#$digest executing watches}. 12473 * 12474 * ## Life cycle 12475 * 12476 * # Pseudo-Code of `$apply()` 12477 * ```js 12478 function $apply(expr) { 12479 try { 12480 return $eval(expr); 12481 } catch (e) { 12482 $exceptionHandler(e); 12483 } finally { 12484 $root.$digest(); 12485 } 12486 } 12487 * ``` 12488 * 12489 * 12490 * Scope's `$apply()` method transitions through the following stages: 12491 * 12492 * 1. The {@link guide/expression expression} is executed using the 12493 * {@link ng.$rootScope.Scope#$eval $eval()} method. 12494 * 2. Any exceptions from the execution of the expression are forwarded to the 12495 * {@link ng.$exceptionHandler $exceptionHandler} service. 12496 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 12497 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 12498 * 12499 * 12500 * @param {(string|function())=} exp An angular expression to be executed. 12501 * 12502 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 12503 * - `function(scope)`: execute the function with current `scope` parameter. 12504 * 12505 * @returns {*} The result of evaluating the expression. 12506 */ 12507 $apply: function(expr) { 12508 try { 12509 beginPhase('$apply'); 12510 return this.$eval(expr); 12511 } catch (e) { 12512 $exceptionHandler(e); 12513 } finally { 12514 clearPhase(); 12515 try { 12516 $rootScope.$digest(); 12517 } catch (e) { 12518 $exceptionHandler(e); 12519 throw e; 12520 } 12521 } 12522 }, 12523 12524 /** 12525 * @ngdoc method 12526 * @name $rootScope.Scope#$on 12527 * @function 12528 *
12529 * @description 12530 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 12531 * discussion of event life cycle. 12532 * 12533 * The event listener function format is: `function(event, args...)`. The `event` object 12534 * passed into the listener has the following attributes: 12535 * 12536 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 12537 * `$broadcast`-ed. 12538 * - `currentScope` - `{Scope}`: the current scope which is handling the event. 12539 * - `name` - `{string}`: name of the event. 12540 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 12541 * further event propagation (available only for events that were `$emit`-ed). 12542 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 12543 * to true. 12544 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 12545 * 12546 * @param {string} name Event name to listen on. 12547 * @param {function(event, ...args)} listener Function to call when the event is emitted. 12548 * @returns {function()} Returns a deregistration function for this listener. 12549 */ 12550 $on: function(name, listener) { 12551 var namedListeners = this.$$listeners[name]; 12552 if (!namedListeners) { 12553 this.$$listeners[name] = namedListeners = []; 12554 } 12555 namedListeners.push(listener); 12556 12557 var current = this; 12558 do { 12559 if (!current.$$listenerCount[name]) { 12560 current.$$listenerCount[name] = 0; 12561 } 12562 current.$$listenerCount[name]++; 12563 } while ((current = current.$parent)); 12564 12565 var self = this; 12566 return function() { 12567 namedListeners[indexOf(namedListeners, listener)] = null; 12568 decrementListenerCount(self, 1, name); 12569 }; 12570 }, 12571 12572 12573 /** 12574 * @ngdoc method 12575 * @name $rootScope.Scope#$emit 12576 * @function 12577 * 12578 * @description 12579 * Dispatches an event `name` upwards through the scope hierarchy notifying the 12580 * registered {@link ng.$rootScope.Scope#$on} listeners. 12581 * 12582 * The event life cycle starts at the scope on which `$emit` was called. All 12583 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 12584 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 12585 * registered listeners along the way. The event will stop propagating if one of the listeners 12586 * cancels it. 12587 * 12588 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 12589 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 12590 * 12591 * @param {string} name Event name to emit. 12592 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 12593 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 12594 */ 12595 $emit: function(name, args) { 12596 var empty = [], 12597 namedListeners, 12598 scope = this, 12599 stopPropagation = false, 12600 event = { 12601 name: name, 12602 targetScope: scope, 12603 stopPropagation: function() {stopPropagation = true;}, 12604 preventDefault: function() { 12605 event.defaultPrevented = true; 12606 }, 12607 defaultPrevented: false 12608 }, 12609 listenerArgs = concat([event], arguments, 1), 12610 i, length; 12611 12612 do { 12613 namedListeners = scope.$$listeners[name] || empty; 12614 event.currentScope = scope; 12615 for (i=0, length=namedListeners.length; i<length; i++) { 12616 12617 // if listeners were deregistered, defragment the array 12618 if (!namedListeners[i]) { 12619 namedListeners.splice(i, 1); 12620 i--; 12621 length--; 12622 continue; 12623 } 12624 try { 12625 //allow all listeners attached to the current scope to run 12626 namedListeners[i].apply(null, listenerArgs); 12627 } catch (e) { 12628 $exceptionHandler(e); 12629 } 12630 } 12631 //if any listener on the current scope stops propagation, prevent bubbling 12632 if (stopPropagation) return event; 12633 //traverse upwards 12634 scope = scope.$parent; 12635 } while (scope); 12636 12637 return event; 12638 }, 12639 12640 12641 /** 12642 * @ngdoc method 12643 * @name $rootScope.Scope#$broadcast 12644 * @function 12645 *
12646 * @description 12647 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 12648 * registered {@link ng.$rootScope.Scope#$on} listeners. 12649 * 12650 * The event life cycle starts at the scope on which `$broadcast` was called. All 12651 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 12652 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 12653 * scope and calls all registered listeners along the way. The event cannot be canceled. 12654 * 12655 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 12656 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 12657 * 12658 * @param {string} name Event name to broadcast. 12659 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 12660 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 12661 */ 12662 $broadcast: function(name, args) { 12663 var target = this, 12664 current = target, 12665 next = target, 12666 event = { 12667 name: name, 12668 targetScope: target, 12669 preventDefault: function() { 12670 event.defaultPrevented = true; 12671 }, 12672 defaultPrevented: false 12673 }, 12674 listenerArgs = concat([event], arguments, 1), 12675 listeners, i, length; 12676 12677 //down while you can, then up and next sibling or up and next sibling until back at root 12678 while ((current = next)) { 12679 event.currentScope = current; 12680 listeners = current.$$listeners[name] || []; 12681 for (i=0, length = listeners.length; i<length; i++) { 12682 // if listeners were deregistered, defragment the array 12683 if (!listeners[i]) { 12684 listeners.splice(i, 1); 12685 i--; 12686 length--; 12687 continue; 12688 } 12689 12690 try { 12691 listeners[i].apply(null, listenerArgs); 12692 } catch(e) { 12693 $exceptionHandler(e); 12694 } 12695 } 12696 12697 // Insanity Warning: scope depth-first traversal 12698 // yes, this code is a bit crazy, but it works and we have tests to prove it! 12699 // this piece should be kept in sync with the traversal in $digest 12700 // (though it differs due to having the extra check for $$listenerCount) 12701 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 12702 (current !== target && current.$$nextSibling)))) { 12703 while(current !== target && !(next = current.$$nextSibling)) { 12704 current = current.$parent; 12705 } 12706 } 12707 } 12708 12709 return event; 12710 } 12711 }; 12712 12713 var $rootScope = new Scope(); 12714 12715 return $rootScope; 12716 12717 12718 function beginPhase(phase) { 12719 if ($rootScope.$$phase) { 12720 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 12721 } 12722 12723 $rootScope.$$phase = phase; 12724 } 12725 12726 function clearPhase() { 12727 $rootScope.$$phase = null; 12728 } 12729 12730 function compileToFn(exp, name) { 12731 var fn = $parse(exp); 12732 assertArgFn(fn, name); 12733 return fn; 12734 } 12735 12736 function decrementListenerCount(current, count, name) { 12737 do { 12738 current.$$listenerCount[name] -= count; 12739 12740 if (current.$$listenerCount[name] === 0) { 12741 delete current.$$listenerCount[name]; 12742 } 12743 } while ((current = current.$parent)); 12744 } 12745 12746 /** 12747 * function used as an initial value for watchers. 12748 * because it's unique we can easily tell it apart from other values 12749 */ 12750 function initWatchVal() {} 12751 }]; 12752} 12753 12754/** 12755 * @description 12756 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 12757 */ 12758function $$SanitizeUriProvider() { 12759 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 12760 imgSrcSanitizationWhitelist = /^\s*(https?|ftp|file):|data:image\//; 12761 12762 /** 12763 * @description 12764 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 12765 * urls during a[href] sanitization. 12766 * 12767 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 12768 * 12769 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 12770 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 12771 * regular expression. If a match is found, the original url is
12771written into the dom. Otherwise, 12772 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 12773 * 12774 * @param {RegExp=} regexp New regexp to whitelist urls with. 12775 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 12776 * chaining otherwise. 12777 */ 12778 this.aHrefSanitizationWhitelist = function(regexp) { 12779 if (isDefined(regexp)) { 12780 aHrefSanitizationWhitelist = regexp; 12781 return this; 12782 } 12783 return aHrefSanitizationWhitelist; 12784 }; 12785 12786 12787 /** 12788 * @description 12789 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 12790 * urls during img[src] sanitization. 12791 * 12792 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 12793 * 12794 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 12795 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 12796 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 12797 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 12798 * 12799 * @param {RegExp=} regexp New regexp to whitelist urls with. 12800 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 12801 * chaining otherwise. 12802 */ 12803 this.imgSrcSanitizationWhitelist = function(regexp) { 12804 if (isDefined(regexp)) { 12805 imgSrcSanitizationWhitelist = regexp; 12806 return this; 12807 } 12808 return imgSrcSanitizationWhitelist; 12809 }; 12810 12811 this.$get = function() { 12812 return function sanitizeUri(uri, isImage) { 12813 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 12814 var normalizedVal; 12815 // NOTE: urlResolve() doesn't support IE < 8 so we don't sanitize for that case. 12816 if (!msie || msie >= 8 ) { 12817 normalizedVal = urlResolve(uri).href; 12818 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 12819 return 'unsafe:'+normalizedVal; 12820 } 12821 } 12822 return uri; 12823 }; 12824 }; 12825} 12826 12827var $sceMinErr = minErr('$sce'); 12828 12829var SCE_CONTEXTS = { 12830 HTML: 'html', 12831 CSS: 'css', 12832 URL: 'url', 12833 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 12834 // url. (e.g. ng-include, script src, templateUrl) 12835 RESOURCE_URL: 'resourceUrl', 12836 JS: 'js' 12837}; 12838 12839// Helper functions follow. 12840 12841// Copied from: 12842// http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962 12843// Prereq: s is a string. 12844function escapeForRegexp(s) { 12845 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 12846 replace(/\x08/g, '\\x08'); 12847} 12848 12849 12850function adjustMatcher(matcher) { 12851 if (matcher === 'self') { 12852 return matcher; 12853 } else if (isString(matcher)) { 12854 // Strings match exactly except for 2 wildcards - '*' and '**'. 12855 // '*' matches any character except those from the set ':/.?&'. 12856 // '**' matches any character (like .* in a RegExp). 12857 // More than 2 *'s raises an error as it's ill defined. 12858 if (matcher.indexOf('***') > -1) { 12859 throw $sceMinErr('iwcard', 12860 'Illegal sequence *** in string matcher. String: {0}', matcher); 12861 } 12862 matcher = escapeForRegexp(matcher). 12863 replace('\\*\\*', '.*'). 12864 replace('\\*', '[^:/.?&;]*'); 12865 return new RegExp('^' + matcher + '$'); 12866 } else if (isRegExp(matcher)) { 12867 // The only other type of matcher allowed is a Regexp. 12868 // Match entire URL / disallow partial matches. 12869 // Flags are reset (i.e. no global, ignoreCase or multiline) 12870 return new RegExp('^' + matcher.source + '$'); 12871 } else { 12872 throw $sceMinErr('imatcher', 12873 'Matchers may only be "self", string patterns or RegExp objects'); 12874 } 12875} 12876 12877 12878function adjustMatchers(matchers) { 12879 var adjustedMatchers = []; 12880 if (isDefined(matchers)) { 12881 forEach(matchers, function(matcher) { 12882 adjustedMatchers.push(adjustMatcher(matcher)); 12883 }); 12884 } 12885 return adjustedMatchers; 12886} 12887 12888 12889/** 12890 * @ngdoc service 12891 * @name $sceDelegate 12892 * @function 12893 * 12894 * @description 12895 * 12896 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 12897 * Contextual Escaping (SCE)} services to AngularJS. 12898 * 12899 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 12900 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 12901 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 12902 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 12903 * work because `$sce` delegates to `$sceDelegate` for these operations. 12904 * 12905 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 12906 * 12907 * The default instance of `$sceDelegate` should work out of the box with little pain. While you
12908 * can override it completely to change the behavior of `$sce`, the common case would 12909 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 12910 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 12911 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 12912 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 12913 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 12914 */ 12915 12916/** 12917 * @ngdoc provider 12918 * @name $sceDelegateProvider 12919 * @description 12920 * 12921 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 12922 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 12923 * that the URLs used for sourcing Angular templates are safe. Refer {@link 12924 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 12925 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 12926 * 12927 * For the general details about this service in Angular, read the main page for {@link ng.$sce 12928 * Strict Contextual Escaping (SCE)}. 12929 * 12930 * **Example**: Consider the following case. <a name="example"></a> 12931 * 12932 * - your app is hosted at url `http://myapp.example.com/` 12933 * - but some of your templates are hosted on other domains you control such as 12934 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 12935 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 12936 * 12937 * Here is what a secure configuration for this scenario might look like: 12938 * 12939 * <pre class="prettyprint"> 12940 * angular.module('myApp', []).config(function($sceDelegateProvider) { 12941 * $sceDelegateProvider.resourceUrlWhitelist([ 12942 * // Allow same origin resource loads. 12943 * 'self', 12944 * // Allow loading from our assets domain. Notice the difference between * and **. 12945 * 'http://srv*.assets.example.com/**']); 12946 * 12947 * // The blacklist overrides the whitelist so the open redirect here is blocked. 12948 * $sceDelegateProvider.resourceUrlBlacklist([ 12949 * 'http://myapp.example.com/clickThru**']); 12950 * }); 12951 * </pre> 12952 */ 12953 12954function $SceDelegateProvider() { 12955 this.SCE_CONTEXTS = SCE_CONTEXTS; 12956 12957 // Resource URLs can also be trusted by policy. 12958 var resourceUrlWhitelist = ['self'], 12959 resourceUrlBlacklist = []; 12960 12961 /** 12962 * @ngdoc method 12963 * @name $sceDelegateProvider#resourceUrlWhitelist 12964 * @function 12965 * 12966 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 12967 * provided. This must be an array or null. A snapshot of this array is used so further 12968 * changes to the array are ignored. 12969 * 12970 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 12971 * allowed in this array. 12972 * 12973 * Note: **an empty whitelist array will block all URLs**! 12974 * 12975 * @return {Array} the currently set whitelist array. 12976 * 12977 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 12978 * same origin resource requests. 12979 * 12980 * @description 12981 * Sets/Gets the whitelist of trusted resource URLs. 12982 */ 12983 this.resourceUrlWhitelist = function (value) { 12984 if (arguments.length) { 12985 resourceUrlWhitelist = adjustMatchers(value); 12986 } 12987 return resourceUrlWhitelist; 12988 }; 12989 12990 /** 12991 * @ngdoc method 12992 * @name $sceDelegateProvider#resourceUrlBlacklist 12993 * @function 12994 * 12995 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 12996 * provided. This must be an array or null. A snapshot of this array is used so further 12997 * changes to the array are ignored. 12998 * 12999 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 13000 * allowed in this array. 13001 * 13002 * The typical usage for the blacklist is to **block 13003 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 13004 * these would otherwise be trusted but actually return content from the redirected domain. 13005 *
vendor: 4,741 bytes, lines 13006-13133
13006 * Finally, **the blacklist overrides the whitelist** and has the final say. 13007 * 13008 * @return {Array} the currently set blacklist array. 13009 * 13010 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 13011 * is no blacklist.) 13012 * 13013 * @description 13014 * Sets/Gets the blacklist of trusted resource URLs. 13015 */ 13016 13017 this.resourceUrlBlacklist = function (value) { 13018 if (arguments.length) { 13019 resourceUrlBlacklist = adjustMatchers(value); 13020 } 13021 return resourceUrlBlacklist; 13022 }; 13023 13024 this.$get = ['$injector', function($injector) { 13025 13026 var htmlSanitizer = function htmlSanitizer(html) { 13027 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 13028 }; 13029 13030 if ($injector.has('$sanitize')) { 13031 htmlSanitizer = $injector.get('$sanitize'); 13032 } 13033 13034 13035 function matchUrl(matcher, parsedUrl) { 13036 if (matcher === 'self') { 13037 return urlIsSameOrigin(parsedUrl); 13038 } else { 13039 // definitely a regex. See adjustMatchers() 13040 return !!matcher.exec(parsedUrl.href); 13041 } 13042 } 13043 13044 function isResourceUrlAllowedByPolicy(url) { 13045 var parsedUrl = urlResolve(url.toString()); 13046 var i, n, allowed = false; 13047 // Ensure that at least one item from the whitelist allows this url. 13048 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 13049 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 13050 allowed = true; 13051 break; 13052 } 13053 } 13054 if (allowed) { 13055 // Ensure that no item from the blacklist blocked this url. 13056 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 13057 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 13058 allowed = false; 13059 break; 13060 } 13061 } 13062 } 13063 return allowed; 13064 } 13065 13066 function generateHolderType(Base) { 13067 var holderType = function TrustedValueHolderType(trustedValue) { 13068 this.$$unwrapTrustedValue = function() { 13069 return trustedValue; 13070 }; 13071 }; 13072 if (Base) { 13073 holderType.prototype = new Base(); 13074 } 13075 holderType.prototype.valueOf = function sceValueOf() { 13076 return this.$$unwrapTrustedValue(); 13077 }; 13078 holderType.prototype.toString = function sceToString() { 13079 return this.$$unwrapTrustedValue().toString(); 13080 }; 13081 return holderType; 13082 } 13083 13084 var trustedValueHolderBase = generateHolderType(), 13085 byType = {}; 13086 13087 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 13088 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 13089 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 13090 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 13091 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 13092 13093 /** 13094 * @ngdoc method 13095 * @name $sceDelegate#trustAs 13096 * 13097 * @description 13098 * Returns an object that is trusted by angular for use in specified strict 13099 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 13100 * attribute interpolation, any dom event binding attribute interpolation 13101 * such as for onclick, etc.) that uses the provided value. 13102 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 13103 * 13104 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 13105 * resourceUrl, html, js and css. 13106 * @param {*} value The value that that should be considered trusted/safe. 13107 * @returns {*} A value that can be used to stand in for the provided `value` in places 13108 * where Angular expects a $sce.trustAs() return value. 13109 */ 13110 function trustAs(type, trustedValue) { 13111 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 13112 if (!Constructor) { 13113 throw $sceMinErr('icontext', 13114 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 13115 type, trustedValue); 13116 } 13117 if (trustedValue === null || trustedValue === undefined || trustedValue === '') { 13118 return trustedValue; 13119 } 13120 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 13121 // mutable objects, we ensure here that the value passed in is actually a string. 13122 if (typeof trustedValue !== 'string') { 13123 throw $sceMinErr('itype', 13124 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 13125 type); 13126 } 13127 return new Constructor(trustedValue); 13128 } 13129 13130 /** 13131 * @ngdoc method 13132 * @name $sceDelegate#valueOf 13133 *
13134 * @description 13135 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 13136 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 13137 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 13138 * 13139 * If the passed parameter is not a value that had been returned by {@link 13140 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 13141 * 13142 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 13143 * call or anything else. 13144 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 13145 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 13146 * `value` unchanged. 13147 */ 13148 function valueOf(maybeTrusted) { 13149 if (maybeTrusted instanceof trustedValueHolderBase) { 13150 return maybeTrusted.$$unwrapTrustedValue(); 13151 } else { 13152 return maybeTrusted; 13153 } 13154 } 13155 13156 /** 13157 * @ngdoc method 13158 * @name $sceDelegate#getTrusted 13159 * 13160 * @description 13161 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 13162 * returns the originally supplied value if the queried context type is a supertype of the 13163 * created type. If this condition isn't satisfied, throws an exception. 13164 * 13165 * @param {string} type The kind of context in which this value is to be used. 13166 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 13167 * `$sceDelegate.trustAs`} call. 13168 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 13169 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 13170 */ 13171 function getTrusted(type, maybeTrusted) { 13172 if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { 13173 return maybeTrusted; 13174 } 13175 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 13176 if (constructor && maybeTrusted instanceof constructor) { 13177 return maybeTrusted.$$unwrapTrustedValue(); 13178 } 13179 // If we get here, then we may only take one of two actions. 13180 // 1. sanitize the value for the requested type, or 13181 // 2. throw an exception. 13182 if (type === SCE_CONTEXTS.RESOURCE_URL) { 13183 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 13184 return maybeTrusted; 13185 } else { 13186 throw $sceMinErr('insecurl', 13187 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 13188 maybeTrusted.toString()); 13189 } 13190 } else if (type === SCE_CONTEXTS.HTML) { 13191 return htmlSanitizer(maybeTrusted); 13192 } 13193 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 13194 } 13195 13196 return { trustAs: trustAs, 13197 getTrusted: getTrusted, 13198 valueOf: valueOf }; 13199 }]; 13200} 13201 13202 13203/** 13204 * @ngdoc provider 13205 * @name $sceProvider 13206 * @description 13207 * 13208 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 13209 * - enable/disable Strict Contextual Escaping (SCE) in a module 13210 * - override the default implementation with a custom delegate 13211 * 13212 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 13213 */ 13214 13215/* jshint maxlen: false*/ 13216 13217/** 13218 * @ngdoc service 13219 * @name $sce 13220 * @function 13221 * 13222 * @description 13223 * 13224 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 13225 * 13226 * # Strict Contextual Escaping 13227 * 13228 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 13229 * contexts to result in a value that is marked as safe to use for that context. One example of 13230 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 13231 * to these contexts as privileged or SCE contexts. 13232 * 13233 * As of version 1.2, Angular ships with SCE enabled by default. 13234 * 13235 * Note: When enabled (the default), IE8 in quirks mode is not supported. In this mode, IE8 allows 13236 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 13237 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
13238 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 13239 * to the top of your HTML document. 13240 * 13241 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 13242 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 13243 * 13244 * Here's an example of a binding in a privileged context: 13245 * 13246 * <pre class="prettyprint"> 13247 * <input ng-model="userHtml"> 13248 * <div ng-bind-html="userHtml"> 13249 * </pre> 13250 * 13251 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 13252 * disabled, this application allows the user to render arbitrary HTML into the DIV. 13253 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 13254 * bindings. (HTML is just one example of a context where rendering user controlled input creates 13255 * security vulnerabilities.) 13256 * 13257 * For the case of HTML, you might use a library, either on the client side, or on the server side, 13258 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 13259 * 13260 * How would you ensure that every place that used these types of bindings was bound to a value that 13261 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 13262 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 13263 * properties/fields and forgot to update the binding to the sanitized value? 13264 * 13265 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 13266 * determine that something explicitly says it's safe to use a value for binding in that
13267 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 13268 * for those values that you can easily tell are safe - because they were received from your server, 13269 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 13270 * allowing only the files in a specific directory to do this. Ensuring that the internal API 13271 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 13272 * 13273 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 13274 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 13275 * obtain values that will be accepted by SCE / privileged contexts. 13276 * 13277 * 13278 * ## How does it work? 13279 * 13280 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 13281 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 13282 * ng.$sce#parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 13283 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 13284 * 13285 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 13286 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 13287 * simplified): 13288 * 13289 * <pre class="prettyprint"> 13290 * var ngBindHtmlDirective = ['$sce', function($sce) { 13291 * return function(scope, element, attr) { 13292 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 13293 * element.html(value || ''); 13294 * }); 13295 * }; 13296 * }]; 13297 * </pre> 13298 * 13299 * ## Impact on loading templates 13300 * 13301 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 13302 * `templateUrl`'s specified by {@link guide/directive directives}. 13303 * 13304 * By default, Angular only loads templates from the same domain and protocol as the application 13305 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 13306 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 13307 * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 13308 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 13309 * 13310 * *Please note*: 13311 * The browser's 13312 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 13313 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 13314 * policy apply in addition to this and may further restrict whether the template is successfully 13315 * loaded. This means that without the right CORS policy, loading templates from a different domain 13316 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 13317 * browsers. 13318 * 13319 * ## This feels like too much overhead for the developer? 13320 * 13321 * It's important to remember that SCE only applies to interpolation expressions. 13322 * 13323 * If your expressions are constant literals, they're automatically trusted and you don't need to 13324 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 13325 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 13326 * 13327 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 13328 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 13329 * 13330 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 13331 * templates in `ng-include` from your application's domain without having to even know about SCE. 13332 * It blocks loading templates from other domains or loading templates over http from an https 13333 * served document. You can change these by setting your own custom {@link 13334 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 13335 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 13336 * 13337 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 13338 * application that's secure and can be audited to verify that with much more ease than bolting 13339 * security onto an application later. 13340 * 13341 * <a name="contexts"></a> 13342 * ## What trusted context types are supported? 13343 * 13344 * | Context | Notes | 13345 * |---------------------|----------------| 13346 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. | 13347 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 13348 * | `$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. | 13349 * | `$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. | 13350 * | `$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. | 13351 * 13352 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 13353 * 13354 * Each element in these arrays must be one of the following: 13355 * 13356 * - **'self'** 13357 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 13358 * domain** as the application document using the **same protocol**. 13359 * - **String** (except the special value `'self'`) 13360 * - The string is matched against the full *normalized / absolute URL* of the resource 13361 * being tested (substring matches are not good enough.) 13362 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 13363 * match themselves. 13364 * - `*`: matches zero or more occurrences of any character other than one of the following 6 13365 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use
13366 * in a whitelist. 13367 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 13368 * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. 13369 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 13370 * not have been the intention.) It's usage at the very end of the path is ok. (e.g. 13371 * http://foo.example.com/templates/**). 13372 * - **RegExp** (*see caveat below*) 13373 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 13374 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 13375 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 13376 * have good test coverage.). For instance, the use of `.` in the regex is correct only in a 13377 * small number of cases. A `.` character in the regex used when matching the scheme or a 13378 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 13379 * is highly recommended to use the string patterns and only fall back to regular expressions 13380 * if they as a last resort. 13381 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 13382 * matched against the **entire** *normalized / absolute URL* of the resource being tested 13383 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 13384 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 13385 * - If you are generating your JavaScript from some other templating engine (not 13386 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 13387 * remember to escape your regular expression (and be aware that you might need more than 13388 * one level of escaping depending on your templating engine and the way you interpolated 13389 * the value.) Do make use of your platform's escaping mechanism as it might be good 13390 * enough before coding your own. e.g. Ruby has 13391 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 13392 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 13393 * Javascript lacks a similar built in function for escaping. Take a look at Google 13394 * Closure library's [goog.string.regExpEscape(s)]( 13395 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 13396 * 13397 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 13398 * 13399 * ## Show me an example using SCE. 13400 * 13401 * @example 13402<example module="mySceApp" deps="angular-sanitize.js"> 13403<file name="index.html"> 13404 <div ng-controller="myAppController as myCtrl"> 13405 <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 13406 <b>User comments</b><br> 13407 By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 13408 $sanitize is available. If $sanitize isn't available, this results in an error instead of an 13409 exploit. 13410 <div class="well"> 13411 <div ng-repeat="userComment in myCtrl.userComments"> 13412 <b>{{userComment.name}}</b>: 13413 <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 13414 <br> 13415 </div> 13416 </div> 13417 </div> 13418</file> 13419 13420<file name="script.js"> 13421 var mySceApp = angular.module('mySceApp', ['ngSanitize']); 13422 13423 mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) { 13424 var self = this; 13425 $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 13426 self.userComments = userComments; 13427 }); 13428 self.explicitlyTrustedHtml = $sce.trustAsHtml( 13429 '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 13430 'sanitization."">Hover over this text.</span>'); 13431 }); 13432</file> 13433 13434<file name="test_data.json"> 13435[ 13436 { "name": "Alice", 13437 "htmlComment": 13438 "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 13439 }, 13440 { "name": "Bob",
13441 "htmlComment": "<i>Yes!</i> Am I the only other one?" 13442 } 13443] 13444</file> 13445 13446<file name="protractor.js" type="protractor"> 13447 describe('SCE doc demo', function() { 13448 it('should sanitize untrusted values', function() { 13449 expect(element(by.css('.htmlComment')).getInnerHtml()) 13450 .toBe('<span>Is <i>anyone</i> reading this?</span>'); 13451 }); 13452 13453 it('should NOT sanitize explicitly trusted values', function() { 13454 expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 13455 '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 13456 'sanitization."">Hover over this text.</span>'); 13457 }); 13458 }); 13459</file> 13460</example> 13461 * 13462 * 13463 * 13464 * ## Can I disable SCE completely? 13465 * 13466 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 13467 * for little coding overhead. It will be much harder to take an SCE disabled application and 13468 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 13469 * for cases where you have a lot of existing code that was written before SCE was introduced and 13470 * you're migrating them a module at a time. 13471 * 13472 * That said, here's how you can completely disable SCE: 13473 * 13474 * <pre class="prettyprint"> 13475 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 13476 * // Completely disable SCE. For demonstration purposes only! 13477 * // Do not use in new projects. 13478 * $sceProvider.enabled(false); 13479 * }); 13480 * </pre> 13481 * 13482 */ 13483/* jshint maxlen: 100 */ 13484 13485function $SceProvider() { 13486 var enabled = true; 13487 13488 /** 13489 * @ngdoc method 13490 * @name $sceProvider#enabled 13491 * @function 13492 * 13493 * @param {boolean=} value If provided, then enables/disables SCE. 13494 * @return {boolean} true if SCE is enabled, false otherwise. 13495 * 13496 * @description 13497 * Enables/disables SCE and returns the current value. 13498 */ 13499 this.enabled = function (value) { 13500 if (arguments.length) { 13501 enabled = !!value; 13502 } 13503 return enabled; 13504 }; 13505 13506 13507 /* Design notes on the default implementation for SCE. 13508 * 13509 * The API contract for the SCE delegate 13510 * ------------------------------------- 13511 * The SCE delegate object must provide the following 3 methods: 13512 * 13513 * - trustAs(contextEnum, value) 13514 * This method is used to tell the SCE service that the provided value is OK to use in the 13515 * contexts specified by contextEnum. It must return an object that will be accepted by 13516 * getTrusted() for a compatible contextEnum and return this value. 13517 * 13518 * - valueOf(value) 13519 * For values that were not produced by trustAs(), return them as is. For values that were 13520 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 13521 * trustAs is wrapping the given values into some type, this operation unwraps it when given 13522 * such a value. 13523 * 13524 * - getTrusted(contextEnum, value) 13525 * This function should return the a value that is safe to use in the context specified by 13526 * contextEnum or throw and exception otherwise. 13527 * 13528 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 13529 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 13530 * instance, an implementation could maintain a registry of all trusted objects by context. In 13531 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 13532 * return the same object passed in if it was found in the registry under a compatible context or 13533 * throw an exception otherwise. An implementation might only wrap values some of the time based 13534 * on some criteria. getTrusted() might return a value and not throw an exception for special 13535 * constants or objects even if not wrapped. All such implementations fulfill this contract. 13536 * 13537 * 13538 * A note on the inheritance model for SCE contexts 13539 * ------------------------------------------------ 13540 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 13541 * is purely an implementation details. 13542 * 13543 * The contract is simply this: 13544 * 13545 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 13546 * will also succeed. 13547 * 13548 * Inheritance happens to capture this in a natural way. In some future, we
13549 * may not use inheritance anymore. That is OK because no code outside of 13550 * sce.js and sceSpecs.js would need to be aware of this detail. 13551 */ 13552 13553 this.$get = ['$parse', '$sniffer', '$sceDelegate', function( 13554 $parse, $sniffer, $sceDelegate) { 13555 // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows 13556 // the "expression(javascript expression)" syntax which is insecure. 13557 if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) { 13558 throw $sceMinErr('iequirks', 13559 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' + 13560 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 13561 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 13562 } 13563 13564 var sce = copy(SCE_CONTEXTS); 13565 13566 /** 13567 * @ngdoc method 13568 * @name $sce#isEnabled 13569 * @function 13570 * 13571 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 13572 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 13573 * 13574 * @description 13575 * Returns a boolean indicating if SCE is enabled. 13576 */ 13577 sce.isEnabled = function () { 13578 return enabled; 13579 }; 13580 sce.trustAs = $sceDelegate.trustAs; 13581 sce.getTrusted = $sceDelegate.getTrusted; 13582 sce.valueOf = $sceDelegate.valueOf; 13583 13584 if (!enabled) { 13585 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 13586 sce.valueOf = identity; 13587 } 13588 13589 /** 13590 * @ngdoc method 13591 * @name $sce#parse 13592 * 13593 * @description 13594 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 13595 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 13596 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 13597 * *result*)} 13598 * 13599 * @param {string} type The kind of SCE context in which this result will be used. 13600 * @param {string} expression String expression to compile. 13601 * @returns {function(context, locals)} a function which represents the compiled expression: 13602 * 13603 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13604 * are evaluated against (typically a scope object). 13605 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13606 * `context`. 13607 */ 13608 sce.parseAs = function sceParseAs(type, expr) { 13609 var parsed = $parse(expr); 13610 if (parsed.literal && parsed.constant) { 13611 return parsed; 13612 } else { 13613 return function sceParseAsTrusted(self, locals) { 13614 return sce.getTrusted(type, parsed(self, locals)); 13615 }; 13616 } 13617 }; 13618 13619 /** 13620 * @ngdoc method 13621 * @name $sce#trustAs 13622 * 13623 * @description 13624 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 13625 * returns an object that is trusted by angular for use in specified strict contextual 13626 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 13627 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 13628 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 13629 * escaping. 13630 * 13631 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 13632 * resource_url, html, js and css. 13633 * @param {*} value The value that that should be considered trusted/safe. 13634 * @returns {*} A value that can be used to stand in for the provided `value` in places 13635 * where Angular expects a $sce.trustAs() return value. 13636 */ 13637 13638 /** 13639 * @ngdoc method 13640 * @name $sce#trustAsHtml 13641 * 13642 * @description 13643 * Shorthand method. `$sce.trustAsHtml(value)` â 13644 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 13645 * 13646 * @param {*} value The value to trustAs. 13647 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 13648 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 13649 * only accept expressions that are either literal constants or are the 13650 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 13651 */ 13652 13653 /** 13654 * @ngdoc method 13655 * @name $sce#trustAsUrl 13656 *
13657 * @description 13658 * Shorthand method. `$sce.trustAsUrl(value)` â 13659 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 13660 * 13661 * @param {*} value The value to trustAs. 13662 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 13663 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 13664 * only accept expressions that are either literal constants or are the 13665 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 13666 */ 13667 13668 /** 13669 * @ngdoc method 13670 * @name $sce#trustAsResourceUrl 13671 * 13672 * @description 13673 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 13674 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 13675 * 13676 * @param {*} value The value to trustAs. 13677 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 13678 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 13679 * only accept expressions that are either literal constants or are the return 13680 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 13681 */ 13682 13683 /** 13684 * @ngdoc method 13685 * @name $sce#trustAsJs 13686 * 13687 * @description 13688 * Shorthand method. `$sce.trustAsJs(value)` â 13689 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 13690 * 13691 * @param {*} value The value to trustAs. 13692 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 13693 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 13694 * only accept expressions that are either literal constants or are the 13695 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 13696 */ 13697 13698 /** 13699 * @ngdoc method 13700 * @name $sce#getTrusted 13701 * 13702 * @description 13703 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 13704 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 13705 * originally supplied value if the queried context type is a supertype of the created type. 13706 * If this condition isn't satisfied, throws an exception. 13707 * 13708 * @param {string} type The kind of context in which this value is to be used. 13709 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 13710 * call. 13711 * @returns {*} The value the was originally provided to 13712 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 13713 * Otherwise, throws an exception. 13714 */ 13715 13716 /** 13717 * @ngdoc method 13718 * @name $sce#getTrustedHtml 13719 * 13720 * @description 13721 * Shorthand method. `$sce.getTrustedHtml(value)` â 13722 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 13723 * 13724 * @param {*} value The value to pass to `$sce.getTrusted`. 13725 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 13726 */ 13727 13728 /** 13729 * @ngdoc method 13730 * @name $sce#getTrustedCss 13731 * 13732 * @description 13733 * Shorthand method. `$sce.getTrustedCss(value)` â 13734 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 13735 * 13736 * @param {*} value The value to pass to `$sce.getTrusted`. 13737 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 13738 */ 13739 13740 /** 13741 * @ngdoc method 13742 * @name $sce#getTrustedUrl 13743 * 13744 * @description 13745 * Shorthand method. `$sce.getTrustedUrl(value)` â 13746 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 13747 * 13748 * @param {*} value The value to pass to `$sce.getTrusted`. 13749 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 13750 */ 13751 13752 /** 13753 * @ngdoc method 13754 * @name $sce#getTrustedResourceUrl 13755 * 13756 * @description 13757 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 13758 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 13759 * 13760 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 13761 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 13762 */ 13763 13764 /** 13765 * @ngdoc method 13766 * @name $sce#getTrustedJs 13767 *
13768 * @description 13769 * Shorthand method. `$sce.getTrustedJs(value)` â 13770 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 13771 * 13772 * @param {*} value The value to pass to `$sce.getTrusted`. 13773 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 13774 */ 13775 13776 /** 13777 * @ngdoc method 13778 * @name $sce#parseAsHtml 13779 * 13780 * @description 13781 * Shorthand method. `$sce.parseAsHtml(expression string)` â 13782 * {@link ng.$sce#parse `$sce.parseAs($sce.HTML, value)`} 13783 * 13784 * @param {string} expression String expression to compile. 13785 * @returns {function(context, locals)} a function which represents the compiled expression: 13786 * 13787 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13788 * are evaluated against (typically a scope object). 13789 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13790 * `context`. 13791 */ 13792 13793 /** 13794 * @ngdoc method 13795 * @name $sce#parseAsCss 13796 * 13797 * @description 13798 * Shorthand method. `$sce.parseAsCss(value)` â 13799 * {@link ng.$sce#parse `$sce.parseAs($sce.CSS, value)`} 13800 * 13801 * @param {string} expression String expression to compile. 13802 * @returns {function(context, locals)} a function which represents the compiled expression: 13803 * 13804 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13805 * are evaluated against (typically a scope object). 13806 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13807 * `context`. 13808 */ 13809 13810 /** 13811 * @ngdoc method 13812 * @name $sce#parseAsUrl 13813 * 13814 * @description 13815 * Shorthand method. `$sce.parseAsUrl(value)` â 13816 * {@link ng.$sce#parse `$sce.parseAs($sce.URL, value)`} 13817 * 13818 * @param {string} expression String expression to compile. 13819 * @returns {function(context, locals)} a function which represents the compiled expression: 13820 * 13821 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13822 * are evaluated against (typically a scope object). 13823 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13824 * `context`. 13825 */ 13826 13827 /** 13828 * @ngdoc method 13829 * @name $sce#parseAsResourceUrl 13830 * 13831 * @description 13832 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 13833 * {@link ng.$sce#parse `$sce.parseAs($sce.RESOURCE_URL, value)`} 13834 * 13835 * @param {string} expression String expression to compile. 13836 * @returns {function(context, locals)} a function which represents the compiled expression: 13837 * 13838 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13839 * are evaluated against (typically a scope object). 13840 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13841 * `context`. 13842 */ 13843 13844 /** 13845 * @ngdoc method 13846 * @name $sce#parseAsJs 13847 * 13848 * @description 13849 * Shorthand method. `$sce.parseAsJs(value)` â 13850 * {@link ng.$sce#parse `$sce.parseAs($sce.JS, value)`} 13851 * 13852 * @param {string} expression String expression to compile. 13853 * @returns {function(context, locals)} a function which represents the compiled expression: 13854 * 13855 * * `context` â `{object}` â an object against which any expressions embedded in the strings 13856 * are evaluated against (typically a scope object). 13857 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 13858 * `context`. 13859 */ 13860 13861 // Shorthand delegations. 13862 var parse = sce.parseAs, 13863 getTrusted = sce.getTrusted, 13864 trustAs = sce.trustAs; 13865 13866 forEach(SCE_CONTEXTS, function (enumValue, name) { 13867 var lName = lowercase(name); 13868 sce[camelCase("parse_as_" + lName)] = function (expr) { 13869 return parse(enumValue, expr); 13870 }; 13871 sce[camelCase("get_trusted_" + lName)] = function (value) { 13872 return getTrusted(enumValue, value); 13873 }; 13874 sce[camelCase("trust_as_" + lName)] = function (value) { 13875 return trustAs(enumValue, value); 13876 }; 13877 });
13878 13879 return sce; 13880 }]; 13881} 13882 13883/** 13884 * !!! This is an undocumented "private" service !!! 13885 * 13886 * @name $sniffer 13887 * @requires $window 13888 * @requires $document 13889 * 13890 * @property {boolean} history Does the browser support html5 history api ? 13891 * @property {boolean} hashchange Does the browser support hashchange event ? 13892 * @property {boolean} transitions Does the browser support CSS transition events ? 13893 * @property {boolean} animations Does the browser support CSS animation events ? 13894 * 13895 * @description 13896 * This is very simple implementation of testing browser's features. 13897 */ 13898function $SnifferProvider() { 13899 this.$get = ['$window', '$document', function($window, $document) { 13900 var eventSupport = {}, 13901 android = 13902 int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 13903 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 13904 document = $document[0] || {}, 13905 documentMode = document.documentMode, 13906 vendorPrefix, 13907 vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/, 13908 bodyStyle = document.body && document.body.style, 13909 transitions = false, 13910 animations = false, 13911 match; 13912 13913 if (bodyStyle) { 13914 for(var prop in bodyStyle) { 13915 if(match = vendorRegex.exec(prop)) { 13916 vendorPrefix = match[0]; 13917 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 13918 break; 13919 } 13920 } 13921 13922 if(!vendorPrefix) { 13923 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 13924 } 13925 13926 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 13927 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 13928 13929 if (android && (!transitions||!animations)) { 13930 transitions = isString(document.body.style.webkitTransition); 13931 animations = isString(document.body.style.webkitAnimation); 13932 } 13933 } 13934 13935 13936 return { 13937 // Android has history.pushState, but it does not update location correctly 13938 // so let's not use the history API at all. 13939 // http://code.google.com/p/android/issues/detail?id=17471 13940 // https://github.com/angular/angular.js/issues/904 13941 13942 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 13943 // so let's not use the history API also 13944 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 13945 // jshint -W018 13946 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 13947 // jshint +W018 13948 hashchange: 'onhashchange' in $window && 13949 // IE8 compatible mode lies 13950 (!documentMode || documentMode > 7), 13951 hasEvent: function(event) { 13952 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 13953 // it. In particular the event is not fired when backspace or delete key are pressed or 13954 // when cut operation is performed. 13955 if (event == 'input' && msie == 9) return false; 13956 13957 if (isUndefined(eventSupport[event])) { 13958 var divElm = document.createElement('div'); 13959 eventSupport[event] = 'on' + event in divElm; 13960 } 13961 13962 return eventSupport[event]; 13963 }, 13964 csp: csp(), 13965 vendorPrefix: vendorPrefix, 13966 transitions : transitions, 13967 animations : animations, 13968 android: android, 13969 msie : msie, 13970 msieDocumentMode: documentMode 13971 }; 13972 }]; 13973} 13974 13975function $TimeoutProvider() { 13976 this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler', 13977 function($rootScope, $browser, $q, $exceptionHandler) { 13978 var deferreds = {}; 13979 13980 13981 /** 13982 * @ngdoc service 13983 * @name $timeout 13984 * 13985 * @description 13986 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 13987 * block and delegates any exceptions to 13988 * {@link ng.$exceptionHandler $exceptionHandler} service. 13989 * 13990 * The return value of registering a timeout function is a promise, which will be resolved when 13991 * the timeout is reached and the timeout function is executed. 13992 * 13993 * To cancel a timeout request, call `$timeout.cancel(promise)`. 13994 * 13995 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 13996 * synchronously flush the queue of deferred functions. 13997 * 13998 * @param {function()} fn A function, whose execution should be delayed. 13999 * @param {number=} [delay=0] Delay in milliseconds. 14000 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 14001 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 14002 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 14003 * promise will be resolved with is the return value of the `fn` function. 14004 * 14005 */ 14006 function timeout(fn, delay, invokeApply) { 14007 var deferred = $q.defer(), 14008 promise = deferred.promise, 14009 skipApply = (isDefined(invokeApply) && !invokeApply), 14010 timeoutId; 14011 14012 timeoutId = $browser.defer(function() { 14013 try { 14014 deferred.resolve(fn()); 14015 } catch(e) { 14016 deferred.reject(e); 14017 $exceptionHandler(e); 14018 } 14019 finally { 14020 delete deferreds[promise.$$timeoutId]; 14021 } 14022 14023 if (!skipApply) $rootScope.$apply(); 14024 }, delay); 14025 14026 promise.$$timeoutId = timeoutId; 14027 deferreds[timeoutId] = deferred; 14028 14029 return promise; 14030 } 14031 14032 14033 /** 14034 * @ngdoc method 14035 * @name $timeout#cancel 14036 *
14037 * @description 14038 * Cancels a task associated with the `promise`. As a result of this, the promise will be 14039 * resolved with a rejection. 14040 * 14041 * @param {Promise=} promise Promise returned by the `$timeout` function. 14042 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 14043 * canceled. 14044 */ 14045 timeout.cancel = function(promise) { 14046 if (promise && promise.$$timeoutId in deferreds) { 14047 deferreds[promise.$$timeoutId].reject('canceled'); 14048 delete deferreds[promise.$$timeoutId]; 14049 return $browser.defer.cancel(promise.$$timeoutId); 14050 } 14051 return false; 14052 }; 14053 14054 return timeout; 14055 }]; 14056} 14057 14058// NOTE: The usage of window and document instead of $window and $document here is 14059// deliberate. This service depends on the specific behavior of anchor nodes created by the 14060// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 14061// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 14062// doesn't know about mocked locations and resolves URLs to the real document - which is 14063// exactly the behavior needed here. There is little value is mocking these out for this 14064// service. 14065var urlParsingNode = document.createElement("a"); 14066var originUrl = urlResolve(window.location.href, true); 14067 14068 14069/** 14070 * 14071 * Implementation Notes for non-IE browsers 14072 * ---------------------------------------- 14073 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 14074 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 14075 * URL will be resolved into an absolute URL in the context of the application document. 14076 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 14077 * properties are all populated to reflect the normalized URL. This approach has wide 14078 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 14079 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 14080 * 14081 * Implementation Notes for IE 14082 * --------------------------- 14083 * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other 14084 * browsers. However, the parsed components will not be set if the URL assigned did not specify 14085 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 14086 * work around that by performing the parsing in a 2nd step by taking a previously normalized 14087 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 14088 * properties such as protocol, hostname, port, etc. 14089 * 14090 * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one 14091 * uses the inner HTML approach to assign the URL as part of an HTML snippet - 14092 * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. 14093 * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. 14094 * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that 14095 * method and IE < 8 is unsupported. 14096 * 14097 * References: 14098 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 14099 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 14100 * http://url.spec.whatwg.org/#urlutils 14101 * https://github.com/angular/angular.js/pull/2902 14102 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 14103 * 14104 * @function 14105 * @param {string} url The URL to be parsed. 14106 * @description Normalizes and parses a URL. 14107 * @returns {object} Returns the normalized URL as a dictionary. 14108 * 14109 * | member name | Description | 14110 * |---------------|----------------| 14111 * | href | A normalized version of the provided URL if it was not an absolute URL | 14112 * | protocol | The protocol including the trailing colon | 14113 * | host | The host and port (if the port is non-default) of the normalizedUrl | 14114 * | search | The search params, minus the question mark | 14115 * | hash | The hash string, minus the hash symbol 14116 * | hostname | The hostname 14117 * | port | The port, without ":" 14118 * | pathname | The pathname, beginning with "/" 14119 * 14120 */ 14121function urlResolve(url, base) { 14122 var href = url; 14123 14124 if (msie) { 14125 // Normalize before parse. Refer Implementation Notes on why this is 14126 // done in two steps on IE. 14127 urlParsingNode.setAttribute("href", href); 14128 href = urlParsingNode.href; 14129 } 14130 14131 urlParsingNode.setAttribute('href', href); 14132 14133 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 14134 return { 14135 href: urlParsingNode.href, 14136 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 14137 host: urlParsingNode.host, 14138 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 14139 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 14140 hostname: urlParsingNode.hostname, 14141 port: urlParsingNode.port, 14142 pathname: (urlParsingNode.pathname.charAt(0) === '/') 14143 ? urlParsingNode.pathname 14144 : '/' + urlParsingNode.pathname 14145 }; 14146} 14147 14148/** 14149 * Parse a request URL and determine whether this is a same-origin request as the application document. 14150 * 14151 * @param {string|object} requestUrl The url of the request as a string that will be resolved 14152 * or a parsed URL object. 14153 * @returns {boolean} Whether the request is for the same origin as the application document. 14154 */ 14155function urlIsSameOrigin(requestUrl) { 14156 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 14157 return (parsed.protocol === originUrl.protocol && 14158 parsed.host === originUrl.host); 14159} 14160 14161/** 14162 * @ngdoc service 14163 * @name $window 14164 * 14165 * @description 14166 * A reference to the browser's `window` object. While `window` 14167 * is globally available in JavaScript, it causes testability problems, because 14168 * it is a global variable. In angular we always refer to it through the 14169 * `$window` service, so it may be overridden, removed or mocked for testing. 14170 * 14171 * Expressions, like the one defined for the `ngClick` directive in the example 14172 * below, are evaluated with respect to the current scope. Therefore, there is 14173 * no risk of inadvertently coding in a dependency on a global value in such an 14174 * expression. 14175 * 14176 * @example 14177 <example> 14178 <file name="index.html"> 14179 <script> 14180 function Ctrl($scope, $window) { 14181 $scope.greeting = 'Hello, World!'; 14182 $scope.doGreeting = function(greeting) { 14183 $window.alert(greeting); 14184 }; 14185 } 14186 </script> 14187 <div ng-controller="Ctrl"> 14188 <input type="text" ng-model="greeting" /> 14189 <button ng-click="doGreeting(greeting)">ALERT</button> 14190 </div> 14191 </file> 14192 <file name="protractor.js" type="protractor"> 14193 it('should display the greeting in the input box', function() {
14194 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 14195 // If we click the button it will block the test runner 14196 // element(':button').click(); 14197 }); 14198 </file> 14199 </example> 14200 */ 14201function $WindowProvider(){ 14202 this.$get = valueFn(window); 14203} 14204 14205/** 14206 * @ngdoc provider 14207 * @name $filterProvider 14208 * @description 14209 * 14210 * Filters are just functions which transform input to an output. However filters need to be 14211 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 14212 * annotated with dependencies and is responsible for creating a filter function. 14213 * 14214 * ```js 14215 * // Filter registration 14216 * function MyModule($provide, $filterProvider) { 14217 * // create a service to demonstrate injection (not always needed) 14218 * $provide.value('greet', function(name){ 14219 * return 'Hello ' + name + '!'; 14220 * }); 14221 * 14222 * // register a filter factory which uses the 14223 * // greet service to demonstrate DI. 14224 * $filterProvider.register('greet', function(greet){ 14225 * // return the filter function which uses the greet service 14226 * // to generate salutation 14227 * return function(text) { 14228 * // filters need to be forgiving so check input validity 14229 * return text && greet(text) || text; 14230 * }; 14231 * }); 14232 * } 14233 * ``` 14234 * 14235 * The filter function is registered with the `$injector` under the filter name suffix with 14236 * `Filter`. 14237 * 14238 * ```js 14239 * it('should be the same instance', inject( 14240 * function($filterProvider) { 14241 * $filterProvider.register('reverse', function(){ 14242 * return ...; 14243 * }); 14244 * }, 14245 * function($filter, reverseFilter) { 14246 * expect($filter('reverse')).toBe(reverseFilter); 14247 * }); 14248 * ``` 14249 * 14250 * 14251 * For more information about how angular filters work, and how to create your own filters, see 14252 * {@link guide/filter Filters} in the Angular Developer Guide. 14253 */ 14254/** 14255 * @ngdoc method 14256 * @name $filterProvider#register 14257 * @description 14258 * Register filter factory function. 14259 * 14260 * @param {String} name Name of the filter. 14261 * @param {Function} fn The filter factory function which is injectable. 14262 */ 14263 14264 14265/** 14266 * @ngdoc service 14267 * @name $filter 14268 * @function 14269 * @description 14270 * Filters are used for formatting data displayed to the user. 14271 * 14272 * The general syntax in templates is as follows: 14273 * 14274 * {{ expression [| filter_name[:parameter_value] ... ] }} 14275 * 14276 * @param {String} name Name of the filter function to retrieve 14277 * @return {Function} the filter function 14278 */ 14279$FilterProvider.$inject = ['$provide']; 14280function $FilterProvider($provide) { 14281 var suffix = 'Filter'; 14282 14283 /** 14284 * @ngdoc method 14285 * @name $controllerProvider#register 14286 * @param {string|Object} name Name of the filter function, or an object map of filters where 14287 * the keys are the filter names and the values are the filter factories. 14288 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 14289 * of the registered filter instances. 14290 */ 14291 function register(name, factory) { 14292 if(isObject(name)) { 14293 var filters = {}; 14294 forEach(name, function(filter, key) { 14295 filters[key] = register(key, filter); 14296 }); 14297 return filters; 14298 } else { 14299 return $provide.factory(name + suffix, factory); 14300 } 14301 } 14302 this.register = register; 14303 14304 this.$get = ['$injector', function($injector) { 14305 return function(name) { 14306 return $injector.get(name + suffix); 14307 }; 14308 }]; 14309 14310 //////////////////////////////////////// 14311 14312 /* global 14313 currencyFilter: false, 14314 dateFilter: false, 14315 filterFilter: false, 14316 jsonFilter: false, 14317 limitToFilter: false, 14318 lowercaseFilter: false, 14319 numberFilter: false, 14320 orderByFilter: false, 14321 uppercaseFilter: false, 14322 */ 14323 14324 register('currency', currencyFilter); 14325 register('date', dateFilter); 14326 register('filter', filterFilter); 14327 register('json', jsonFilter); 14328 register('limitTo', limitToFilter); 14329 register('lowercase', lowercaseFilter); 14330 register('number', numberFilter); 14331 register('orderBy', orderByFilter); 14332 register('uppercase', uppercaseFilter); 14333} 14334 14335/** 14336 * @ngdoc filter 14337 * @name filter 14338 * @function 14339 * 14340 * @description 14341 * Selects a subset of items from `array` and returns it as a new array. 14342 * 14343 * @param {Array} array The source array. 14344 * @param {string|Object|function()} expression The predicate to be used for selecting items from 14345 * `array`. 14346 * 14347 * Can be one of: 14348 * 14349 * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against
14350 * the contents of the `array`. All strings or objects with string properties in `array` that contain this string 14351 * will be returned. The predicate can be negated by prefixing the string with `!`. 14352 * 14353 * - `Object`: A pattern object can be used to filter specific properties on objects contained 14354 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 14355 * which have property `name` containing "M" and property `phone` containing "1". A special 14356 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 14357 * property of the object. That's equivalent to the simple substring match with a `string` 14358 * as described above. 14359 * 14360 * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is 14361 * called for each element of `array`. The final result is an array of those elements that 14362 * the predicate returned true for. 14363 * 14364 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 14365 * determining if the expected value (from the filter expression) and actual value (from 14366 * the object in the array) should be considered a match. 14367 * 14368 * Can be one of: 14369 * 14370 * - `function(actual, expected)`: 14371 * The function will be given the object value and the predicate value to compare and 14372 * should return true if the item should be included in filtered result. 14373 * 14374 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`. 14375 * this is essentially strict comparison of expected and actual. 14376 * 14377 * - `false|undefined`: A short hand for a function which will look for a substring match in case 14378 * insensitive way. 14379 * 14380 * @example 14381 <example> 14382 <file name="index.html"> 14383 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 14384 {name:'Mary', phone:'800-BIG-MARY'}, 14385 {name:'Mike', phone:'555-4321'}, 14386 {name:'Adam', phone:'555-5678'}, 14387 {name:'Julie', phone:'555-8765'}, 14388 {name:'Juliette', phone:'555-5678'}]"></div> 14389 14390 Search: <input ng-model="searchText"> 14391 <table id="searchTextResults"> 14392 <tr><th>Name</th><th>Phone</th></tr> 14393 <tr ng-repeat="friend in friends | filter:searchText"> 14394 <td>{{friend.name}}</td> 14395 <td>{{friend.phone}}</td> 14396 </tr> 14397 </table> 14398 <hr> 14399 Any: <input ng-model="search.$"> <br> 14400 Name only <input ng-model="search.name"><br> 14401 Phone only <input ng-model="search.phone"><br> 14402 Equality <input type="checkbox" ng-model="strict"><br> 14403 <table id="searchObjResults"> 14404 <tr><th>Name</th><th>Phone</th></tr> 14405 <tr ng-repeat="friendObj in friends | filter:search:strict"> 14406 <td>{{friendObj.name}}</td> 14407 <td>{{friendObj.phone}}</td> 14408 </tr> 14409 </table> 14410 </file> 14411 <file name="protractor.js" type="protractor"> 14412 var expectFriendNames = function(expectedNames, key) { 14413 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 14414 arr.forEach(function(wd, i) { 14415 expect(wd.getText()).toMatch(expectedNames[i]); 14416 }); 14417 }); 14418 }; 14419 14420 it('should search across all fields when filtering with a string', function() { 14421 var searchText = element(by.model('searchText')); 14422 searchText.clear(); 14423 searchText.sendKeys('m'); 14424 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 14425 14426 searchText.clear(); 14427 searchText.sendKeys('76'); 14428 expectFriendNames(['John', 'Julie'], 'friend'); 14429 }); 14430 14431 it('should search in specific fields when filtering with a predicate object', function() { 14432 var searchAny = element(by.model('search.$')); 14433 searchAny.clear(); 14434 searchAny.sendKeys('i'); 14435 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 14436 }); 14437 it('should use a equal comparison when comparator is true', function() { 14438 var searchName = element(by.model('search.name')); 14439 var strict = element(by.model('strict')); 14440 searchName.clear(); 14441 searchName.sendKeys('Julie');
14442 strict.click(); 14443 expectFriendNames(['Julie'], 'friendObj'); 14444 }); 14445 </file> 14446 </example> 14447 */ 14448function filterFilter() { 14449 return function(array, expression, comparator) { 14450 if (!isArray(array)) return array; 14451 14452 var comparatorType = typeof(comparator), 14453 predicates = []; 14454 14455 predicates.check = function(value) { 14456 for (var j = 0; j < predicates.length; j++) { 14457 if(!predicates[j](value)) { 14458 return false; 14459 } 14460 } 14461 return true; 14462 }; 14463 14464 if (comparatorType !== 'function') { 14465 if (comparatorType === 'boolean' && comparator) { 14466 comparator = function(obj, text) { 14467 return angular.equals(obj, text); 14468 }; 14469 } else { 14470 comparator = function(obj, text) { 14471 if (obj && text && typeof obj === 'object' && typeof text === 'object') { 14472 for (var objKey in obj) { 14473 if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) && 14474 comparator(obj[objKey], text[objKey])) { 14475 return true; 14476 } 14477 } 14478 return false; 14479 } 14480 text = (''+text).toLowerCase(); 14481 return (''+obj).toLowerCase().indexOf(text) > -1; 14482 }; 14483 } 14484 } 14485 14486 var search = function(obj, text){ 14487 if (typeof text == 'string' && text.charAt(0) === '!') { 14488 return !search(obj, text.substr(1)); 14489 } 14490 switch (typeof obj) { 14491 case "boolean": 14492 case "number": 14493 case "string": 14494 return comparator(obj, text); 14495 case "object": 14496 switch (typeof text) { 14497 case "object": 14498 return comparator(obj, text); 14499 default: 14500 for ( var objKey in obj) { 14501 if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) { 14502 return true; 14503 } 14504 } 14505 break; 14506 } 14507 return false; 14508 case "array": 14509 for ( var i = 0; i < obj.length; i++) { 14510 if (search(obj[i], text)) { 14511 return true; 14512 } 14513 } 14514 return false; 14515 default: 14516 return false; 14517 } 14518 }; 14519 switch (typeof expression) { 14520 case "boolean": 14521 case "number": 14522 case "string": 14523 // Set up expression object and fall through 14524 expression = {$:expression}; 14525 // jshint -W086 14526 case "object": 14527 // jshint +W086 14528 for (var key in expression) { 14529 (function(path) { 14530 if (typeof expression[path] == 'undefined') return; 14531 predicates.push(function(value) { 14532 return search(path == '$' ? value : (value && value[path]), expression[path]); 14533 }); 14534 })(key); 14535 } 14536 break; 14537 case 'function': 14538 predicates.push(expression); 14539 break; 14540 default: 14541 return array; 14542 } 14543 var filtered = []; 14544 for ( var j = 0; j < array.length; j++) { 14545 var value = array[j]; 14546 if (predicates.check(value)) { 14547 filtered.push(value); 14548 } 14549 } 14550 return filtered; 14551 }; 14552} 14553 14554/** 14555 * @ngdoc filter 14556 * @name currency 14557 * @function 14558 * 14559 * @description 14560 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 14561 * symbol for current locale is used. 14562 * 14563 * @param {number} amount Input to filter. 14564 * @param {string=} symbol Currency symbol or identifier to be displayed. 14565 * @returns {string} Formatted number. 14566 * 14567 * 14568 * @example 14569 <example> 14570 <file name="index.html"> 14571 <script> 14572 function Ctrl($scope) { 14573 $scope.amount = 1234.56; 14574 } 14575 </script> 14576 <div ng-controller="Ctrl"> 14577 <input type="number" ng-model="amount"> <br> 14578 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 14579 custom currency identifier (USD$): <span>{{amount | currency:"USD$"}}</span> 14580 </div> 14581 </file> 14582 <file name="protractor.js" type="protractor"> 14583 it('should init with 1234.56', function() { 14584 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 14585 expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56'); 14586 }); 14587 it('should update', function() { 14588 if (browser.params.browser == 'safari') { 14589 // Safari does not understand the minus key. See 14590 // https://github.com/angular/protractor/issues/481 14591 return; 14592 }
14593 element(by.model('amount')).clear(); 14594 element(by.model('amount')).sendKeys('-1234'); 14595 expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); 14596 expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)'); 14597 }); 14598 </file> 14599 </example> 14600 */ 14601currencyFilter.$inject = ['$locale']; 14602function currencyFilter($locale) { 14603 var formats = $locale.NUMBER_FORMATS; 14604 return function(amount, currencySymbol){ 14605 if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM; 14606 return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2). 14607 replace(/\u00A4/g, currencySymbol); 14608 }; 14609} 14610 14611/** 14612 * @ngdoc filter 14613 * @name number 14614 * @function 14615 * 14616 * @description 14617 * Formats a number as text. 14618 * 14619 * If the input is not a number an empty string is returned. 14620 * 14621 * @param {number|string} number Number to format. 14622 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 14623 * If this is not provided then the fraction size is computed from the current locale's number 14624 * formatting pattern. In the case of the default locale, it will be 3. 14625 * @returns {string} Number rounded to decimalPlaces and places a â,â after each third digit. 14626 * 14627 * @example 14628 <example> 14629 <file name="index.html"> 14630 <script> 14631 function Ctrl($scope) { 14632 $scope.val = 1234.56789; 14633 } 14634 </script> 14635 <div ng-controller="Ctrl"> 14636 Enter number: <input ng-model='val'><br> 14637 Default formatting: <span id='number-default'>{{val | number}}</span><br> 14638 No fractions: <span>{{val | number:0}}</span><br> 14639 Negative number: <span>{{-val | number:4}}</span> 14640 </div> 14641 </file> 14642 <file name="protractor.js" type="protractor"> 14643 it('should format numbers', function() { 14644 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 14645 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 14646 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 14647 }); 14648 14649 it('should update', function() { 14650 element(by.model('val')).clear(); 14651 element(by.model('val')).sendKeys('3374.333'); 14652 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 14653 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 14654 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 14655 }); 14656 </file> 14657 </example> 14658 */ 14659 14660 14661numberFilter.$inject = ['$locale']; 14662function numberFilter($locale) { 14663 var formats = $locale.NUMBER_FORMATS; 14664 return function(number, fractionSize) { 14665 return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 14666 fractionSize); 14667 }; 14668} 14669 14670var DECIMAL_SEP = '.'; 14671function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 14672 if (number == null || !isFinite(number) || isObject(number)) return ''; 14673 14674 var isNegative = number < 0; 14675 number = Math.abs(number); 14676 var numStr = number + '', 14677 formatedText = '', 14678 parts = []; 14679 14680 var hasExponent = false; 14681 if (numStr.indexOf('e') !== -1) { 14682 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 14683 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 14684 numStr = '0'; 14685 } else { 14686 formatedText = numStr; 14687 hasExponent = true; 14688 } 14689 } 14690 14691 if (!hasExponent) { 14692 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 14693 14694 // determine fractionSize if it is not specified 14695 if (isUndefined(fractionSize)) { 14696 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 14697 } 14698 14699 var pow = Math.pow(10, fractionSize); 14700 number = Math.round(number * pow) / pow; 14701 var fraction = ('' + number).split(DECIMAL_SEP); 14702 var whole = fraction[0]; 14703 fraction = fraction[1] || ''; 14704 14705 var i, pos = 0, 14706 lgroup = pattern.lgSize, 14707 group = pattern.gSize; 14708 14709 if (whole.length >= (lgroup + group)) { 14710 pos = whole.length - lgroup; 14711 for (i = 0; i < pos; i++) { 14712 if ((pos - i)%group === 0 && i !== 0) { 14713 formatedText += groupSep; 14714 } 14715 formatedText += whole.charAt(i); 14716 } 14717 } 14718 14719 for (i = pos; i < whole.length; i++) { 14720 if ((whole.length - i)%lgroup === 0 && i !== 0) { 14721 formatedText += groupSep; 14722 } 14723 formatedText += whole.charAt(i); 14724 } 14725 14726 // format fraction part. 14727 while(fraction.length < fractionSize) { 14728 fraction += '0'; 14729 } 14730 14731 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 14732 } else { 14733 14734 if (fractionSize > 0 && number > -1 && number < 1) { 14735 formatedText = number.toFixed(fractionSize); 14736 } 14737 } 14738 14739 parts.push(isNegative ? pattern.negPre : pattern.posPre); 14740 parts.push(formatedText); 14741 parts.push(isNegative ? pattern.negSuf : pattern.posSuf); 14742 return parts.join(''); 14743} 14744 14745function padNumber(num, digits, trim) { 14746 var neg = ''; 14747 if (num < 0) { 14748 neg = '-'; 14749 num = -num; 14750 } 14751 num = '' + num; 14752 while(num.length < digits) num = '0' + num; 14753 if (trim) 14754 num = num.substr(num.length - digits); 14755 return neg + num; 14756} 14757 14758 14759function dateGetter(name, size, offset, trim) { 14760 offset = offset || 0; 14761 return function(date) { 14762 var value = date['get' + name](); 14763 if (offset > 0 || value > -offset) 14764 value += offset; 14765 if (value === 0 && offset == -12 ) value = 12; 14766 return padNumber(value, size, trim); 14767 }; 14768} 14769 14770function dateStrGetter(name, shortForm) { 14771 return function(date, formats) { 14772 var value = date['get' + name](); 14773 var get = uppercase(shortForm ? ('SHORT' + name) : name); 14774 14775 return formats[get][value]; 14776 }; 14777} 14778 14779function timeZoneGetter(date) { 14780 var zone = -1 * date.getTimezoneOffset(); 14781 var paddedZone = (zone >= 0) ? "+" : ""; 14782 14783 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 14784 padNumber(Math.abs(zone % 60), 2); 14785 14786 return paddedZone; 14787} 14788 14789function ampmGetter(date, formats) { 14790 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 14791} 14792 14793var DATE_FORMATS = { 14794 yyyy: dateGetter('FullYear', 4), 14795 yy: dateGetter('FullYear', 2, 0, true), 14796 y: dateGetter('FullYear', 1), 14797 MMMM: dateStrGetter('Month'), 14798 MMM: dateStrGetter('Month', true), 14799 MM: dateGetter('Month', 2, 1), 14800 M: dateGetter('Month', 1, 1), 14801 dd: dateGetter('Date', 2), 14802 d: dateGetter('Date', 1), 14803 HH: dateGetter('Hours', 2), 14804 H: dateGetter('Hours', 1), 14805 hh: dateGetter('Hours', 2, -12), 14806 h: dateGetter('Hours', 1, -12), 14807 mm: dateGetter('Minutes', 2), 14808 m: dateGetter('Minutes', 1), 14809 ss: dateGetter('Seconds', 2), 14810 s: dateGetter('Seconds', 1), 14811 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 14812 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 14813 sss: dateGetter('Milliseconds', 3), 14814 EEEE: dateStrGetter('Day'), 14815 EEE: dateStrGetter('Day', true), 14816 a: ampmGetter, 14817 Z: timeZoneGetter 14818}; 14819 14820var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/, 14821 NUMBER_STRING = /^\-?\d+$/; 14822 14823/** 14824 * @ngdoc filter 14825 * @name date 14826 * @function 14827 * 14828 * @description 14829 * Formats `date` to a string based on the requested `format`. 14830 * 14831 * `format` string can be composed of the following elements: 14832 * 14833 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 14834 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 14835 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 14836 * * `'MMMM'`: Month in year (January-December) 14837 * * `'MMM'`: Month in year (Jan-Dec) 14838 * * `'MM'`: Month in year, padded (01-12) 14839 * * `'M'`: Month in year (1-12) 14840 * * `'dd'`: Day in month, padded (01-31) 14841 * * `'d'`: Day in month (1-31) 14842 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 14843 * * `'EEE'`: Day in Week, (Sun-Sat) 14844 * * `'HH'`: Hour in day, padded (00-23) 14845 * * `'H'`: Hour in day (0-23) 14846 * * `'hh'`: Hour in am/pm, padded (01-12) 14847 * * `'h'`: Hour in am/pm, (1-12) 14848 * * `'mm'`: Minute in hour, padded (00-59) 14849 * * `'m'`: Minute in hour (0-59) 14850 * * `'ss'`: Second in minute, padded (00-59) 14851 * * `'s'`: Second in minute (0-59) 14852 * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999) 14853 * * `'a'`: am/pm marker 14854 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 14855 * 14856 * `format` string can also be one of the following predefined 14857 * {@link guide/i18n localizable formats}: 14858 * 14859 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 14860 * (e.g. Sep 3, 2010 12:05:08 pm) 14861 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm) 14862 * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale 14863 * (e.g. Friday, September 3, 2010) 14864 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 14865 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 14866 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 14867 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm) 14868 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm) 14869 * 14870 * `format` string can contain literal values. These need to be quoted with single quotes (e.g. 14871 * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence 14872 * (e.g. `"h 'o''clock'"`). 14873 * 14874 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 14875 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its 14876 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 14877 * specified in the string input, the time is considered to be in the local timezone. 14878 * @param {string=} format Formatting rules (see Description). If not specified, 14879 * `mediumDate` is used. 14880 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 14881 * 14882 * @example 14883 <example> 14884 <file name="index.html">
14885 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 14886 <span>{{1288323623006 | date:'medium'}}</span><br> 14887 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 14888 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 14889 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 14890 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 14891 </file> 14892 <file name="protractor.js" type="protractor"> 14893 it('should format date', function() { 14894 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 14895 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 14896 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 14897 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 14898 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 14899 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 14900 }); 14901 </file> 14902 </example> 14903 */ 14904dateFilter.$inject = ['$locale']; 14905function dateFilter($locale) { 14906 14907 14908 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 14909 // 1 2 3 4 5 6 7 8 9 10 11 14910 function jsonStringToDate(string) { 14911 var match; 14912 if (match = string.match(R_ISO8601_STR)) { 14913 var date = new Date(0), 14914 tzHour = 0, 14915 tzMin = 0, 14916 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 14917 timeSetter = match[8] ? date.setUTCHours : date.setHours; 14918 14919 if (match[9]) { 14920 tzHour = int(match[9] + match[10]); 14921 tzMin = int(match[9] + match[11]); 14922 } 14923 dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); 14924 var h = int(match[4]||0) - tzHour; 14925 var m = int(match[5]||0) - tzMin; 14926 var s = int(match[6]||0); 14927 var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000); 14928 timeSetter.call(date, h, m, s, ms); 14929 return date; 14930 } 14931 return string; 14932 } 14933 14934 14935 return function(date, format) { 14936 var text = '', 14937 parts = [], 14938 fn, match; 14939 14940 format = format || 'mediumDate'; 14941 format = $locale.DATETIME_FORMATS[format] || format; 14942 if (isString(date)) { 14943 if (NUMBER_STRING.test(date)) { 14944 date = int(date); 14945 } else { 14946 date = jsonStringToDate(date); 14947 } 14948 } 14949 14950 if (isNumber(date)) { 14951 date = new Date(date); 14952 } 14953 14954 if (!isDate(date)) { 14955 return date; 14956 } 14957 14958 while(format) { 14959 match = DATE_FORMATS_SPLIT.exec(format); 14960 if (match) { 14961 parts = concat(parts, match, 1); 14962 format = parts.pop(); 14963 } else { 14964 parts.push(format); 14965 format = null; 14966 } 14967 } 14968 14969 forEach(parts, function(value){ 14970 fn = DATE_FORMATS[value]; 14971 text += fn ? fn(date, $locale.DATETIME_FORMATS) 14972 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 14973 }); 14974 14975 return text; 14976 }; 14977} 14978 14979 14980/** 14981 * @ngdoc filter 14982 * @name json 14983 * @function 14984 * 14985 * @description 14986 * Allows you to convert a JavaScript object into JSON string. 14987 * 14988 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 14989 * the binding is automatically converted to JSON. 14990 * 14991 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 14992 * @returns {string} JSON string. 14993 * 14994 * 14995 * @example 14996 <example> 14997 <file name="index.html"> 14998 <pre>{{ {'name':'value'} | json }}</pre> 14999 </file> 15000 <file name="protractor.js" type="protractor"> 15001 it('should jsonify filtered objects', function() { 15002 expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/); 15003 }); 15004 </file> 15005 </example> 15006 * 15007 */ 15008function jsonFilter() { 15009 return function(object) { 15010 return toJson(object, true); 15011 }; 15012} 15013 15014 15015/** 15016 * @ngdoc filter 15017 * @name lowercase 15018 * @function 15019 * @description 15020 * Converts string to lowercase. 15021 * @see angular.lowercase 15022 */ 15023var lowercaseFilter = valueFn(lowercase); 15024 15025 15026/** 15027 * @ngdoc filter 15028 * @name uppercase 15029 * @function 15030 * @description 15031 * Converts string to uppercase. 15032 * @see angular.uppercase 15033 */ 15034var uppercaseFilter = valueFn(uppercase); 15035 15036/** 15037 * @ngdoc filter 15038 * @name limitTo 15039 * @function 15040 * 15041 * @description 15042 * Creates a new array or string containing only a specified number of elements. The elements 15043 * are taken from either the beginning or the end of the source array or string, as specified by 15044 * the value and sign (positive or negative) of `limit`. 15045 * 15046 * @param {Array|string} input Source array or string to be limited. 15047 * @param {string|number} limit The length of the returned array or string. If the `limit` number 15048 * is positive, `limit` number of items from the beginning of the source array/string are copied. 15049 * If the number is negative, `limit` number of items from the end of the source array/string 15050 * are copied. The `limit` will be trimmed if it exceeds `array.length` 15051 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 15052 * had less than `limit` elements. 15053 * 15054 * @example 15055 <example> 15056 <file name="index.html"> 15057 <script> 15058 function Ctrl($scope) { 15059 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 15060 $scope.letters = "abcdefghi"; 15061 $scope.numLimit = 3; 15062 $scope.letterLimit = 3; 15063 } 15064 </script> 15065 <div ng-controller="Ctrl"> 15066 Limit {{numbers}} to: <input type="integer" ng-model="numLimit"> 15067 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 15068 Limit {{letters}} to: <input type="integer" ng-model="letterLimit"> 15069 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 15070 </div> 15071 </file> 15072 <file name="protractor.js" type="protractor"> 15073 var numLimitInput = element(by.model('numLimit')); 15074 var letterLimitInput = element(by.model('letterLimit')); 15075 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 15076 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 15077 15078 it('should limit the number array to first three items', function() { 15079 expect(numLimitInput.getAttribute('value')).toBe('3'); 15080 expect(letterLimitInput.getAttribute('value')).toBe('3'); 15081 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 15082 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 15083 }); 15084 15085 it('should update the output when -3 is entered', function() { 15086 numLimitInput.clear(); 15087 numLimitInput.sendKeys('-3'); 15088 letterLimitInput.clear(); 15089 letterLimitInput.sendKeys('-3'); 15090 expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 15091 expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 15092 }); 15093 15094 it('should not exceed the maximum size of input array', function() { 15095 numLimitInput.clear(); 15096 numLimitInput.sendKeys('100'); 15097 letterLimitInput.clear(); 15098 letterLimitInput.sendKeys('100'); 15099 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 15100 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 15101 }); 15102 </file> 15103 </example> 15104 */ 15105function limitToFilter(){ 15106 return function(input, limit) { 15107 if (!isArray(input) && !isString(input)) return input; 15108 15109 limit = int(limit); 15110 15111 if (isString(input)) { 15112 //NaN check on limit 15113 if (limit) { 15114 return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length); 15115 } else { 15116 return ""; 15117 } 15118 } 15119 15120 var out = [], 15121 i, n; 15122 15123 // if abs(limit) exceeds maximum length, trim it 15124 if (limit > input.length) 15125 limit = input.length; 15126 else if (limit < -input.length) 15127 limit = -input.length; 15128 15129 if (limit > 0) { 15130 i = 0; 15131 n = limit; 15132 } else { 15133 i = input.length + limit; 15134 n = input.length; 15135 } 15136 15137 for (; i<n; i++) { 15138 out.push(input[i]); 15139 } 15140 15141 return out; 15142 }; 15143} 15144 15145/** 15146 * @ngdoc filter 15147 * @name orderBy 15148 * @function 15149 * 15150 * @description 15151 * Orders a specified `array` by the `expression` predicate. 15152 * 15153 * @param {Array} array The array to sort. 15154 * @param {function(*)|string|Array.<(function(*)|string)>} expression A predicate to be 15155 * used by the comparator to determine the order of elements. 15156 * 15157 * Can be one of: 15158 * 15159 * - `function`: Getter function. The result of this function will be sorted using the 15160 * `<`, `=`, `>` operator. 15161 * - `string`: An Angular expression which evaluates to an object to order by, such as 'name' 15162 * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control 15163 * ascending or descending sort order (for example, +name or -name). 15164 * - `Array`: An array of function or string predicates. The first predicate in the array 15165 * is used for sorting, but when two items are equivalent, the next predicate is used. 15166 * 15167 * @param {boolean=} reverse Reverse the order of the array. 15168 * @returns {Array} Sorted copy of the source array. 15169 * 15170 * @example 15171 <example> 15172 <file name="index.html"> 15173 <script> 15174 function Ctrl($scope) { 15175 $scope.friends = 15176 [{name:'John', phone:'555-1212', age:10}, 15177 {name:'Mary', phone:'555-9876', age:19}, 15178 {name:'Mike', phone:'555-4321', age:21}, 15179 {name:'Adam', phone:'555-5678', age:35}, 15180 {name:'Julie', phone:'555-8765', age:29}] 15181 $scope.predicate = '-age'; 15182 } 15183 </script> 15184 <div ng-controller="Ctrl"> 15185 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 15186 <hr/> 15187 [ <a href="" ng-click="predicate=''">unsorted</a> ] 15188 <table class="friend"> 15189 <tr> 15190 <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a> 15191 (<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th> 15192 <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> 15193 <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th> 15194 </tr> 15195 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 15196 <td>{{friend.name}}</td> 15197 <td>{{friend.phone}}</td> 15198 <td>{{friend.age}}</td> 15199 </tr> 15200 </table> 15201 </div> 15202 </file> 15203 </example> 15204 */ 15205orderByFilter.$inject = ['$parse']; 15206function orderByFilter($parse){ 15207 return function(array, sortPredicate, reverseOrder) { 15208 if (!isArray(array)) return array; 15209 if (!sortPredicate) return array; 15210 sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate]; 15211 sortPredicate = map(sortPredicate, function(predicate){ 15212 var descending = false, get = predicate || identity; 15213 if (isString(predicate)) { 15214 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 15215 descending = predicate.charAt(0) == '-'; 15216 predicate = predicate.substring(1); 15217 } 15218 get = $parse(predicate); 15219 if (get.constant) { 15220 var key = get(); 15221 return reverseComparator(function(a,b) { 15222 return compare(a[key], b[key]); 15223 }, descending); 15224 } 15225 } 15226 return reverseComparator(function(a,b){ 15227 return compare(get(a),get(b)); 15228 }, descending); 15229 });
15230 var arrayCopy = []; 15231 for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); } 15232 return arrayCopy.sort(reverseComparator(comparator, reverseOrder)); 15233 15234 function comparator(o1, o2){ 15235 for ( var i = 0; i < sortPredicate.length; i++) { 15236 var comp = sortPredicate[i](o1, o2); 15237 if (comp !== 0) return comp; 15238 } 15239 return 0; 15240 } 15241 function reverseComparator(comp, descending) { 15242 return toBoolean(descending) 15243 ? function(a,b){return comp(b,a);} 15244 : comp; 15245 } 15246 function compare(v1, v2){ 15247 var t1 = typeof v1; 15248 var t2 = typeof v2; 15249 if (t1 == t2) { 15250 if (t1 == "string") { 15251 v1 = v1.toLowerCase(); 15252 v2 = v2.toLowerCase(); 15253 } 15254 if (v1 === v2) return 0; 15255 return v1 < v2 ? -1 : 1; 15256 } else { 15257 return t1 < t2 ? -1 : 1; 15258 } 15259 } 15260 }; 15261} 15262 15263function ngDirective(directive) { 15264 if (isFunction(directive)) { 15265 directive = { 15266 link: directive 15267 }; 15268 } 15269 directive.restrict = directive.restrict || 'AC'; 15270 return valueFn(directive); 15271} 15272 15273/** 15274 * @ngdoc directive 15275 * @name a 15276 * @restrict E 15277 * 15278 * @description 15279 * Modifies the default behavior of the html A tag so that the default action is prevented when 15280 * the href attribute is empty. 15281 * 15282 * This change permits the easy creation of action links with the `ngClick` directive 15283 * without changing the location or causing page reloads, e.g.: 15284 * `<a href="" ng-click="list.addItem()">Add Item</a>` 15285 */ 15286var htmlAnchorDirective = valueFn({ 15287 restrict: 'E', 15288 compile: function(element, attr) { 15289 15290 if (msie <= 8) { 15291 15292 // turn <a href ng-click="..">link</a> into a stylable link in IE 15293 // but only if it doesn't have name attribute, in which case it's an anchor 15294 if (!attr.href && !attr.name) { 15295 attr.$set('href', ''); 15296 } 15297 15298 // add a comment node to anchors to workaround IE bug that causes element content to be reset 15299 // to new attribute content if attribute is updated with value containing @ and element also 15300 // contains value with @ 15301 // see issue #1949 15302 element.append(document.createComment('IE fix')); 15303 } 15304 15305 if (!attr.href && !attr.xlinkHref && !attr.name) { 15306 return function(scope, element) { 15307 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 15308 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 15309 'xlink:href' : 'href'; 15310 element.on('click', function(event){ 15311 // if we have no href url, then don't navigate anywhere. 15312 if (!element.attr(href)) { 15313 event.preventDefault(); 15314 } 15315 }); 15316 }; 15317 } 15318 } 15319}); 15320 15321/** 15322 * @ngdoc directive 15323 * @name ngHref 15324 * @restrict A 15325 * @priority 99 15326 * 15327 * @description 15328 * Using Angular markup like `{{hash}}` in an href attribute will 15329 * make the link go to the wrong URL if the user clicks it before 15330 * Angular has a chance to replace the `{{hash}}` markup with its 15331 * value. Until Angular replaces the markup the link will be broken 15332 * and will most likely return a 404 error. 15333 * 15334 * The `ngHref` directive solves this problem. 15335 * 15336 * The wrong way to write it: 15337 * ```html 15338 * <a href="http://www.gravatar.com/avatar/{{hash}}"/> 15339 * ``` 15340 * 15341 * The correct way to write it: 15342 * ```html 15343 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}"/> 15344 * ``` 15345 * 15346 * @element A 15347 * @param {template} ngHref any string which can contain `{{}}` markup. 15348 * 15349 * @example 15350 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 15351 * in links and their different behaviors: 15352 <example> 15353 <file name="index.html"> 15354 <input ng-model="value" /><br /> 15355 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 15356 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 15357 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 15358 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 15359 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 15360 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 15361 </file> 15362 <file name="protractor.js" type="protractor"> 15363 it('should execute ng-click but not reload when href without value', function() {
15364 element(by.id('link-1')).click(); 15365 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 15366 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 15367 }); 15368 15369 it('should execute ng-click but not reload when href empty string', function() { 15370 element(by.id('link-2')).click(); 15371 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 15372 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 15373 }); 15374 15375 it('should execute ng-click and change url when ng-href specified', function() { 15376 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 15377 15378 element(by.id('link-3')).click(); 15379 15380 // At this point, we navigate away from an Angular page, so we need 15381 // to use browser.driver to get the base webdriver. 15382 15383 browser.wait(function() { 15384 return browser.driver.getCurrentUrl().then(function(url) { 15385 return url.match(/\/123$/); 15386 }); 15387 }, 1000, 'page should navigate to /123'); 15388 }); 15389 15390 xit('should execute ng-click but not reload when href empty string and name specified', function() { 15391 element(by.id('link-4')).click(); 15392 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 15393 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 15394 }); 15395 15396 it('should execute ng-click but not reload when no href but name specified', function() { 15397 element(by.id('link-5')).click(); 15398 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 15399 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 15400 }); 15401 15402 it('should only change url when only ng-href', function() { 15403 element(by.model('value')).clear(); 15404 element(by.model('value')).sendKeys('6'); 15405 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 15406 15407 element(by.id('link-6')).click(); 15408 15409 // At this point, we navigate away from an Angular page, so we need 15410 // to use browser.driver to get the base webdriver. 15411 browser.wait(function() { 15412 return browser.driver.getCurrentUrl().then(function(url) { 15413 return url.match(/\/6$/); 15414 }); 15415 }, 1000, 'page should navigate to /6'); 15416 }); 15417 </file> 15418 </example> 15419 */ 15420 15421/** 15422 * @ngdoc directive 15423 * @name ngSrc 15424 * @restrict A 15425 * @priority 99 15426 * 15427 * @description 15428 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 15429 * work right: The browser will fetch from the URL with the literal 15430 * text `{{hash}}` until Angular replaces the expression inside 15431 * `{{hash}}`. The `ngSrc` directive solves this problem. 15432 * 15433 * The buggy way to write it: 15434 * ```html 15435 * <img src="http://www.gravatar.com/avatar/{{hash}}"/> 15436 * ``` 15437 * 15438 * The correct way to write it: 15439 * ```html 15440 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/> 15441 * ``` 15442 * 15443 * @element IMG 15444 * @param {template} ngSrc any string which can contain `{{}}` markup. 15445 */ 15446 15447/** 15448 * @ngdoc directive 15449 * @name ngSrcset 15450 * @restrict A 15451 * @priority 99 15452 * 15453 * @description 15454 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 15455 * work right: The browser will fetch from the URL with the literal 15456 * text `{{hash}}` until Angular replaces the expression inside 15457 * `{{hash}}`. The `ngSrcset` directive solves this problem. 15458 * 15459 * The buggy way to write it: 15460 * ```html 15461 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 15462 * ``` 15463 * 15464 * The correct way to write it: 15465 * ```html 15466 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 15467 * ``` 15468 * 15469 * @element IMG 15470 * @param {template} ngSrcset any string which can contain `{{}}` markup. 15471 */ 15472 15473/** 15474 * @ngdoc directive 15475 * @name ngDisabled 15476 * @restrict A 15477 * @priority 100 15478 * 15479 * @description 15480 * 15481 * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: 15482 * ```html 15483 * <div ng-init="scope = { isDisabled: false }"> 15484 * <button disabled="{{scope.isDisabled}}">Disabled</button> 15485 * </div> 15486 * ``` 15487 * 15488 * The HTML specification does not require browsers to preserve the values of boolean attributes 15489 * such as disabled. (Their presence means true and their absence means false.) 15490 * If we put an Angular interpolation expression into such an attribute then the 15491 * binding information would be lost when the browser removes the attribute. 15492 * The `ngDisabled` directive solves this problem for the `disabled` attribute. 15493 * This complementary directive is not removed by the browser and so provides 15494 * a permanent reliable place to store the binding information. 15495 * 15496 * @example 15497 <example> 15498 <file name="index.html"> 15499 Click me to toggle: <input type="checkbox" ng-model="checked"><br/> 15500 <button ng-model="button" ng-disabled="checked">Button</button> 15501 </file> 15502 <file name="protractor.js" type="protractor"> 15503 it('should toggle button', function() { 15504 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
15505 element(by.model('checked')).click(); 15506 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 15507 }); 15508 </file> 15509 </example> 15510 * 15511 * @element INPUT 15512 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 15513 * then special attribute "disabled" will be set on the element 15514 */ 15515 15516 15517/** 15518 * @ngdoc directive 15519 * @name ngChecked 15520 * @restrict A 15521 * @priority 100 15522 * 15523 * @description 15524 * The HTML specification does not require browsers to preserve the values of boolean attributes 15525 * such as checked. (Their presence means true and their absence means false.) 15526 * If we put an Angular interpolation expression into such an attribute then the 15527 * binding information would be lost when the browser removes the attribute. 15528 * The `ngChecked` directive solves this problem for the `checked` attribute. 15529 * This complementary directive is not removed by the browser and so provides 15530 * a permanent reliable place to store the binding information. 15531 * @example 15532 <example> 15533 <file name="index.html"> 15534 Check me to check both: <input type="checkbox" ng-model="master"><br/> 15535 <input id="checkSlave" type="checkbox" ng-checked="master"> 15536 </file> 15537 <file name="protractor.js" type="protractor"> 15538 it('should check both checkBoxes', function() { 15539 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 15540 element(by.model('master')).click(); 15541 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 15542 }); 15543 </file> 15544 </example> 15545 * 15546 * @element INPUT 15547 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 15548 * then special attribute "checked" will be set on the element 15549 */ 15550 15551 15552/** 15553 * @ngdoc directive 15554 * @name ngReadonly 15555 * @restrict A 15556 * @priority 100 15557 * 15558 * @description 15559 * The HTML specification does not require browsers to preserve the values of boolean attributes 15560 * such as readonly. (Their presence means true and their absence means false.) 15561 * If we put an Angular interpolation expression into such an attribute then the 15562 * binding information would be lost when the browser removes the attribute. 15563 * The `ngReadonly` directive solves this problem for the `readonly` attribute. 15564 * This complementary directive is not removed by the browser and so provides 15565 * a permanent reliable place to store the binding information. 15566 * @example 15567 <example> 15568 <file name="index.html"> 15569 Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/> 15570 <input type="text" ng-readonly="checked" value="I'm Angular"/> 15571 </file> 15572 <file name="protractor.js" type="protractor"> 15573 it('should toggle readonly attr', function() { 15574 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 15575 element(by.model('checked')).click(); 15576 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 15577 }); 15578 </file> 15579 </example> 15580 * 15581 * @element INPUT 15582 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 15583 * then special attribute "readonly" will be set on the element 15584 */ 15585 15586 15587/** 15588 * @ngdoc directive 15589 * @name ngSelected 15590 * @restrict A 15591 * @priority 100 15592 * 15593 * @description 15594 * The HTML specification does not require browsers to preserve the values of boolean attributes 15595 * such as selected. (Their presence means true and their absence means false.) 15596 * If we put an Angular interpolation expression into such an attribute then the 15597 * binding information would be lost when the browser removes the attribute. 15598 * The `ngSelected` directive solves this problem for the `selected` attribute. 15599 * This complementary directive is not removed by the browser and so provides 15600 * a permanent reliable place to store the binding information. 15601 * 15602 * @example 15603 <example> 15604 <file name="index.html"> 15605 Check me to select: <input type="checkbox" ng-model="selected"><br/> 15606 <select> 15607 <option>Hello!</option> 15608 <option id="greet" ng-selected="selected">Greetings!</option> 15609 </select> 15610 </file> 15611 <file name="protractor.js" type="protractor"> 15612 it('should select Greetings!', function() { 15613 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
15614 element(by.model('selected')).click(); 15615 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 15616 }); 15617 </file> 15618 </example> 15619 * 15620 * @element OPTION 15621 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 15622 * then special attribute "selected" will be set on the element 15623 */ 15624 15625/** 15626 * @ngdoc directive 15627 * @name ngOpen 15628 * @restrict A 15629 * @priority 100 15630 * 15631 * @description 15632 * The HTML specification does not require browsers to preserve the values of boolean attributes 15633 * such as open. (Their presence means true and their absence means false.) 15634 * If we put an Angular interpolation expression into such an attribute then the 15635 * binding information would be lost when the browser removes the attribute. 15636 * The `ngOpen` directive solves this problem for the `open` attribute. 15637 * This complementary directive is not removed by the browser and so provides 15638 * a permanent reliable place to store the binding information. 15639 * @example 15640 <example> 15641 <file name="index.html"> 15642 Check me check multiple: <input type="checkbox" ng-model="open"><br/> 15643 <details id="details" ng-open="open"> 15644 <summary>Show/Hide me</summary> 15645 </details> 15646 </file> 15647 <file name="protractor.js" type="protractor"> 15648 it('should toggle open', function() { 15649 expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); 15650 element(by.model('open')).click(); 15651 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 15652 }); 15653 </file> 15654 </example> 15655 * 15656 * @element DETAILS 15657 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 15658 * then special attribute "open" will be set on the element 15659 */ 15660 15661var ngAttributeAliasDirectives = {}; 15662 15663 15664// boolean attrs are evaluated 15665forEach(BOOLEAN_ATTR, function(propName, attrName) { 15666 // binding to multiple is not supported 15667 if (propName == "multiple") return; 15668 15669 var normalized = directiveNormalize('ng-' + attrName); 15670 ngAttributeAliasDirectives[normalized] = function() { 15671 return { 15672 priority: 100, 15673 link: function(scope, element, attr) { 15674 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 15675 attr.$set(attrName, !!value); 15676 }); 15677 } 15678 }; 15679 }; 15680}); 15681 15682 15683// ng-src, ng-srcset, ng-href are interpolated 15684forEach(['src', 'srcset', 'href'], function(attrName) { 15685 var normalized = directiveNormalize('ng-' + attrName); 15686 ngAttributeAliasDirectives[normalized] = function() { 15687 return { 15688 priority: 99, // it needs to run after the attributes are interpolated 15689 link: function(scope, element, attr) { 15690 var propName = attrName, 15691 name = attrName; 15692 15693 if (attrName === 'href' && 15694 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 15695 name = 'xlinkHref'; 15696 attr.$attr[name] = 'xlink:href'; 15697 propName = null; 15698 } 15699 15700 attr.$observe(normalized, function(value) { 15701 if (!value) 15702 return; 15703 15704 attr.$set(name, value); 15705 15706 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 15707 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 15708 // to set the property as well to achieve the desired effect. 15709 // we use attr[attrName] value since $set can sanitize the url. 15710 if (msie && propName) element.prop(propName, attr[name]); 15711 }); 15712 } 15713 }; 15714 }; 15715}); 15716 15717/* global -nullFormCtrl */ 15718var nullFormCtrl = { 15719 $addControl: noop, 15720 $removeControl: noop, 15721 $setValidity: noop, 15722 $setDirty: noop, 15723 $setPristine: noop 15724}; 15725 15726/** 15727 * @ngdoc type 15728 * @name form.FormController 15729 * 15730 * @property {boolean} $pristine True if user has not interacted with the form yet. 15731 * @property {boolean} $dirty True if user has already interacted with the form. 15732 * @property {boolean} $valid True if all of the containing forms and controls are valid. 15733 * @property {boolean}
15733 $invalid True if at least one containing control or form is invalid. 15734 * 15735 * @property {Object} $error Is an object hash, containing references to all invalid controls or 15736 * forms, where: 15737 * 15738 * - keys are validation tokens (error names), 15739 * - values are arrays of controls or forms that are invalid for given error name. 15740 * 15741 * 15742 * Built-in validation tokens: 15743 * 15744 * - `email` 15745 * - `max` 15746 * - `maxlength` 15747 * - `min` 15748 * - `minlength` 15749 * - `number` 15750 * - `pattern` 15751 * - `required` 15752 * - `url` 15753 * 15754 * @description 15755 * `FormController` keeps track of all its controls and nested forms as well as state of them, 15756 * such as being valid/invalid or dirty/pristine. 15757 * 15758 * Each {@link ng.directive:form form} directive creates an instance 15759 * of `FormController`. 15760 * 15761 */ 15762//asks for $scope to fool the BC controller module 15763FormController.$inject = ['$element', '$attrs', '$scope', '$animate']; 15764function FormController(element, attrs, $scope, $animate) { 15765 var form = this, 15766 parentForm = element.parent().controller('form') || nullFormCtrl, 15767 invalidCount = 0, // used to easily determine if we are valid 15768 errors = form.$error = {}, 15769 controls = []; 15770 15771 // init state 15772 form.$name = attrs.name || attrs.ngForm; 15773 form.$dirty = false; 15774 form.$pristine = true; 15775 form.$valid = true; 15776 form.$invalid = false; 15777 15778 parentForm.$addControl(form); 15779 15780 // Setup initial state of the control 15781 element.addClass(PRISTINE_CLASS); 15782 toggleValidCss(true); 15783 15784 // convenience method for easy toggling of classes 15785 function toggleValidCss(isValid, validationErrorKey) { 15786 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 15787 $animate.removeClass(element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); 15788 $animate.addClass(element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 15789 } 15790 15791 /** 15792 * @ngdoc method 15793 * @name form.FormController#$addControl 15794 * 15795 * @description 15796 * Register a control with the form. 15797 * 15798 * Input elements using ngModelController do this automatically when they are linked. 15799 */ 15800 form.$addControl = function(control) { 15801 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 15802 // and not added to the scope. Now we throw an error. 15803 assertNotHasOwnProperty(control.$name, 'input'); 15804 controls.push(control); 15805 15806 if (control.$name) { 15807 form[control.$name] = control; 15808 } 15809 }; 15810 15811 /** 15812 * @ngdoc method 15813 * @name form.FormController#$removeControl 15814 * 15815 * @description 15816 * Deregister a control from the form. 15817 * 15818 * Input elements using ngModelController do this automatically when they are destroyed. 15819 */ 15820 form.$removeControl = function(control) { 15821 if (control.$name && form[control.$name] === control) { 15822 delete form[control.$name]; 15823 } 15824 forEach(errors, function(queue, validationToken) { 15825 form.$setValidity(validationToken, true, control); 15826 }); 15827 15828 arrayRemove(controls, control); 15829 }; 15830 15831 /** 15832 * @ngdoc method 15833 * @name form.FormController#$setValidity 15834 * 15835 * @description 15836 * Sets the validity of a form control. 15837 * 15838 * This method will also propagate to parent forms. 15839 */ 15840 form.$setValidity = function(validationToken, isValid, control) { 15841 var queue = errors[validationToken]; 15842 15843 if (isValid) { 15844 if (queue) { 15845 arrayRemove(queue, control); 15846 if (!queue.length) { 15847 invalidCount--; 15848 if (!invalidCount) { 15849 toggleValidCss(isValid); 15850 form.$valid = true; 15851 form.$invalid = false; 15852 } 15853 errors[validationToken] = false; 15854 toggleValidCss(true, validationToken); 15855 parentForm.$setValidity(validationToken, true, form); 15856 } 15857 } 15858 15859 } else { 15860 if (!invalidCount) { 15861 toggleValidCss(isValid); 15862 } 15863 if (queue) { 15864 if (includes(queue, control)) return; 15865 } else { 15866 errors[validationToken] = queue = []; 15867 invalidCount++;
15868 toggleValidCss(false, validationToken); 15869 parentForm.$setValidity(validationToken, false, form); 15870 } 15871 queue.push(control); 15872 15873 form.$valid = false; 15874 form.$invalid = true; 15875 } 15876 }; 15877 15878 /** 15879 * @ngdoc method 15880 * @name form.FormController#$setDirty 15881 * 15882 * @description 15883 * Sets the form to a dirty state. 15884 * 15885 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 15886 * state (ng-dirty class). This method will also propagate to parent forms. 15887 */ 15888 form.$setDirty = function() { 15889 $animate.removeClass(element, PRISTINE_CLASS); 15890 $animate.addClass(element, DIRTY_CLASS); 15891 form.$dirty = true; 15892 form.$pristine = false; 15893 parentForm.$setDirty(); 15894 }; 15895 15896 /** 15897 * @ngdoc method 15898 * @name form.FormController#$setPristine 15899 * 15900 * @description 15901 * Sets the form to its pristine state. 15902 * 15903 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 15904 * state (ng-pristine class). This method will also propagate to all the controls contained 15905 * in this form. 15906 * 15907 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 15908 * saving or resetting it. 15909 */ 15910 form.$setPristine = function () { 15911 $animate.removeClass(element, DIRTY_CLASS); 15912 $animate.addClass(element, PRISTINE_CLASS); 15913 form.$dirty = false; 15914 form.$pristine = true; 15915 forEach(controls, function(control) { 15916 control.$setPristine(); 15917 }); 15918 }; 15919} 15920 15921 15922/** 15923 * @ngdoc directive 15924 * @name ngForm 15925 * @restrict EAC 15926 * 15927 * @description 15928 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 15929 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 15930 * sub-group of controls needs to be determined. 15931 * 15932 * Note: the purpose of `ngForm` is to group controls, 15933 * but not to be a replacement for the `<form>` tag with all of its capabilities 15934 * (e.g. posting to the server, ...). 15935 * 15936 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 15937 * related scope, under this name. 15938 * 15939 */ 15940 15941 /** 15942 * @ngdoc directive 15943 * @name form 15944 * @restrict E 15945 * 15946 * @description 15947 * Directive that instantiates 15948 * {@link form.FormController FormController}. 15949 * 15950 * If the `name` attribute is specified, the form controller is published onto the current scope under 15951 * this name. 15952 * 15953 * # Alias: {@link ng.directive:ngForm `ngForm`} 15954 * 15955 * In Angular forms can be nested. This means that the outer form is valid when all of the child 15956 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 15957 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to 15958 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when 15959 * using Angular validation directives in forms that are dynamically generated using the 15960 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` 15961 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an 15962 * `ngForm` directive and nest these in an outer `form` element. 15963 * 15964 * 15965 * # CSS classes 15966 * - `ng-valid` is set if the form is valid. 15967 * - `ng-invalid` is set if the form is invalid. 15968 * - `ng-pristine` is set if the form is pristine. 15969 * - `ng-dirty` is set if the form is dirty. 15970 * 15971 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 15972 * 15973 * 15974 * # Submitting a form and preventing the default action 15975 * 15976 * Since the role of forms in client-side Angular applications is different than in classical 15977 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 15978 * page reload that sends the data to the server. Instead some javascript logic should be triggered 15979 * to handle the form submission in an application-specific way. 15980 * 15981 * For this reason, Angular prevents the default action (form submission to the server) unless the 15982 * `<form>` element has an `action` attribute specified. 15983 * 15984 * You can use one of the following two ways to specify what javascript method should be called when 15985 * a form is submitted: 15986 * 15987 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 15988 * - {@link ng.directive:ngClick ngClick}
15988 directive on the first 15989 * button or input field of type submit (input[type=submit]) 15990 * 15991 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 15992 * or {@link ng.directive:ngClick ngClick} directives. 15993 * This is because of the following form submission rules in the HTML specification: 15994 * 15995 * - If a form has only one input field then hitting enter in this field triggers form submit 15996 * (`ngSubmit`) 15997 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 15998 * doesn't trigger submit 15999 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 16000 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 16001 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 16002 * 16003 * @param {string=} name Name of the form. If specified, the form controller will be published into 16004 * related scope, under this name. 16005 * 16006 * ## Animation Hooks 16007 * 16008 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 16009 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 16010 * other validations that are performed within the form. Animations in ngForm are similar to how 16011 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 16012 * as JS animations. 16013 * 16014 * The following example shows a simple way to utilize CSS transitions to style a form element 16015 * that has been rendered as invalid after it has been validated: 16016 * 16017 * <pre> 16018 * //be sure to include ngAnimate as a module to hook into more 16019 * //advanced animations 16020 * .my-form { 16021 * transition:0.5s linear all; 16022 * background: white; 16023 * } 16024 * .my-form.ng-invalid { 16025 * background: red; 16026 * color:white; 16027 * } 16028 * </pre> 16029 * 16030 * @example 16031 <example deps="angular-animate.js" animations="true" fixBase="true"> 16032 <file name="index.html"> 16033 <script> 16034 function Ctrl($scope) { 16035 $scope.userType = 'guest'; 16036 } 16037 </script> 16038 <style> 16039 .my-form { 16040 -webkit-transition:all linear 0.5s; 16041 transition:all linear 0.5s; 16042 background: transparent; 16043 } 16044 .my-form.ng-invalid { 16045 background: red; 16046 } 16047 </style> 16048 <form name="myForm" ng-controller="Ctrl" class="my-form"> 16049 userType: <input name="input" ng-model="userType" required> 16050 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 16051 <tt>userType = {{userType}}</tt><br> 16052 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br> 16053 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br> 16054 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 16055 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 16056 </form> 16057 </file> 16058 <file name="protractor.js" type="protractor"> 16059 it('should initialize to model', function() { 16060 var userType = element(by.binding('userType')); 16061 var valid = element(by.binding('myForm.input.$valid')); 16062 16063 expect(userType.getText()).toContain('guest'); 16064 expect(valid.getText()).toContain('true'); 16065 }); 16066 16067 it('should be invalid if empty', function() { 16068 var userType = element(by.binding('userType')); 16069 var valid = element(by.binding('myForm.input.$valid')); 16070 var userInput = element(by.model('userType')); 16071 16072 userInput.clear(); 16073 userInput.sendKeys(''); 16074 16075 expect(userType.getText()).toEqual('userType ='); 16076 expect(valid.getText()).toContain('false'); 16077 }); 16078 </file> 16079 </example> 16080 * 16081 */ 16082var formDirectiveFactory = function(isNgForm) { 16083 return ['$timeout', function($timeout) { 16084 var formDirective = { 16085 name: 'form', 16086 restrict: isNgForm ? 'EAC' : 'E', 16087 controller: FormController, 16088 compile: function() { 16089 return { 16090 pre: function(scope, formElement, attr, controller) { 16091 if (!attr.action) { 16092 // we can't use jq events because if a form is destroyed during submission the default 16093 // action is not prevented. see #1238 16094 // 16095 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 16096 // page reload if the form was destroyed by submission of the form via a click handler 16097 // on a button in the form. Looks like an IE9 specific bug. 16098 var preventDefaultListener = function(event) { 16099 event.preventDefault 16100 ? event.preventDefault() 16101 : event.returnValue = false; // IE 16102 }; 16103 16104 addEventListenerFn(formElement[0], 'submit', preventDefaultListener); 16105 16106 // unregister the preventDefault listener so that we don't not leak memory but in a 16107 // way that will achieve the prevention of the default action. 16108 formElement.on('$destroy', function() { 16109 $timeout(function() { 16110 removeEventListenerFn(formElement[0], 'submit', preventDefaultListener); 16111 }, 0, false); 16112 }); 16113 } 16114 16115 var parentFormCtrl = formElement.parent().controller('form'), 16116 alias = attr.name || attr.ngForm; 16117 16118 if (alias) { 16119 setter(scope, alias, controller, alias); 16120 } 16121 if (parentFormCtrl) { 16122 formElement.on('$destroy', function() { 16123 parentFormCtrl.$removeControl(controller); 16124 if (alias) { 16125 setter(scope, alias, undefined, alias); 16126 } 16127 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 16128 }); 16129 } 16130 } 16131 }; 16132 } 16133 }; 16134 16135 return formDirective; 16136 }]; 16137}; 16138 16139var formDirective = formDirectiveFactory(); 16140var ngFormDirective = formDirectiveFactory(true); 16141 16142/* global 16143 16144 -VALID_CLASS, 16145 -INVALID_CLASS, 16146 -PRISTINE_CLASS, 16147 -DIRTY_CLASS 16148*/ 16149 16150var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 16151var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i;
16152var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; 16153 16154var inputType = { 16155 16156 /** 16157 * @ngdoc input 16158 * @name input[text] 16159 * 16160 * @description 16161 * Standard HTML text input with angular data binding. 16162 * 16163 * @param {string} ngModel Assignable angular expression to data-bind to. 16164 * @param {string=} name Property name of the form under which the control is published. 16165 * @param {string=} required Adds `required` validation error key if the value is not entered. 16166 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16167 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16168 * `required` when you want to data-bind to the `required` attribute. 16169 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16170 * minlength. 16171 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16172 * maxlength. 16173 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16174 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16175 * patterns defined as scope expressions. 16176 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16177 * interaction with the input element. 16178 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 16179 * 16180 * @example 16181 <example name="text-input-directive"> 16182 <file name="index.html"> 16183 <script> 16184 function Ctrl($scope) { 16185 $scope.text = 'guest'; 16186 $scope.word = /^\s*\w*\s*$/; 16187 } 16188 </script> 16189 <form name="myForm" ng-controller="Ctrl"> 16190 Single word: <input type="text" name="input" ng-model="text" 16191 ng-pattern="word" required ng-trim="false"> 16192 <span class="error" ng-show="myForm.input.$error.required"> 16193 Required!</span> 16194 <span class="error" ng-show="myForm.input.$error.pattern"> 16195 Single word only!</span> 16196 16197 <tt>text = {{text}}</tt><br/> 16198 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16199 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16200 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16201 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16202 </form> 16203 </file> 16204 <file name="protractor.js" type="protractor"> 16205 var text = element(by.binding('text')); 16206 var valid = element(by.binding('myForm.input.$valid')); 16207 var input = element(by.model('text')); 16208 16209 it('should initialize to model', function() { 16210 expect(text.getText()).toContain('guest'); 16211 expect(valid.getText()).toContain('true'); 16212 }); 16213 16214 it('should be invalid if empty', function() { 16215 input.clear(); 16216 input.sendKeys(''); 16217 16218 expect(text.getText()).toEqual('text ='); 16219 expect(valid.getText()).toContain('false'); 16220 }); 16221 16222 it('should be invalid if multi word', function() { 16223 input.clear(); 16224 input.sendKeys('hello world'); 16225 16226 expect(valid.getText()).toContain('false'); 16227 }); 16228 </file> 16229 </example> 16230 */ 16231 'text': textInputType, 16232 16233 16234 /** 16235 * @ngdoc input 16236 * @name input[number] 16237 * 16238 * @description 16239 * Text input with number validation and transformation. Sets the `number` validation 16240 * error if not a valid number. 16241 * 16242 * @param {string} ngModel Assignable angular expression to data-bind to. 16243 * @param {string=} name Property name of the form under which the control is published. 16244 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 16245 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 16246 * @param {string=} required Sets `required` validation error key if the value is not entered. 16247 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16248 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16249 * `required` when you want to data-bind to the `required` attribute. 16250 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16251 * minlength. 16252 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16253 * maxlength. 16254 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16255 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16256 * patterns defined as scope expressions. 16257 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16258 * interaction with the input element. 16259 * 16260 * @example 16261 <example name="number-input-directive"> 16262 <file name="index.html"> 16263 <script> 16264 function Ctrl($scope) { 16265 $scope.value = 12; 16266 } 16267 </script> 16268 <form name="myForm" ng-controller="Ctrl"> 16269 Number: <input type="number" name="input" ng-model="value" 16270 min="0" max="99" required>
16271 <span class="error" ng-show="myForm.input.$error.required"> 16272 Required!</span> 16273 <span class="error" ng-show="myForm.input.$error.number"> 16274 Not valid number!</span> 16275 <tt>value = {{value}}</tt><br/> 16276 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16277 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16278 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16279 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16280 </form> 16281 </file> 16282 <file name="protractor.js" type="protractor"> 16283 var value = element(by.binding('value')); 16284 var valid = element(by.binding('myForm.input.$valid')); 16285 var input = element(by.model('value')); 16286 16287 it('should initialize to model', function() { 16288 expect(value.getText()).toContain('12'); 16289 expect(valid.getText()).toContain('true'); 16290 }); 16291 16292 it('should be invalid if empty', function() { 16293 input.clear(); 16294 input.sendKeys(''); 16295 expect(value.getText()).toEqual('value ='); 16296 expect(valid.getText()).toContain('false'); 16297 }); 16298 16299 it('should be invalid if over max', function() { 16300 input.clear(); 16301 input.sendKeys('123'); 16302 expect(value.getText()).toEqual('value ='); 16303 expect(valid.getText()).toContain('false'); 16304 }); 16305 </file> 16306 </example> 16307 */ 16308 'number': numberInputType, 16309 16310 16311 /** 16312 * @ngdoc input 16313 * @name input[url] 16314 * 16315 * @description 16316 * Text input with URL validation. Sets the `url` validation error key if the content is not a 16317 * valid URL. 16318 * 16319 * @param {string} ngModel Assignable angular expression to data-bind to. 16320 * @param {string=} name Property name of the form under which the control is published. 16321 * @param {string=} required Sets `required` validation error key if the value is not entered. 16322 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16323 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16324 * `required` when you want to data-bind to the `required` attribute. 16325 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16326 * minlength. 16327 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16328 * maxlength. 16329 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16330 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16331 * patterns defined as scope expressions. 16332 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16333 * interaction with the input element. 16334 * 16335 * @example 16336 <example name="url-input-directive"> 16337 <file name="index.html"> 16338 <script> 16339 function Ctrl($scope) { 16340 $scope.text = 'http://google.com'; 16341 } 16342 </script> 16343 <form name="myForm" ng-controller="Ctrl"> 16344 URL: <input type="url" name="input" ng-model="text" required> 16345 <span class="error" ng-show="myForm.input.$error.required"> 16346 Required!</span> 16347 <span class="error" ng-show="myForm.input.$error.url"> 16348 Not valid url!</span> 16349 <tt>text = {{text}}</tt><br/> 16350 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16351 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16352 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16353 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16354 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 16355 </form> 16356 </file> 16357 <file name="protractor.js" type="protractor"> 16358 var text = element(by.binding('text')); 16359 var valid = element(by.binding('myForm.input.$valid')); 16360 var input = element(by.model('text'));
16361 16362 it('should initialize to model', function() { 16363 expect(text.getText()).toContain('http://google.com'); 16364 expect(valid.getText()).toContain('true'); 16365 }); 16366 16367 it('should be invalid if empty', function() { 16368 input.clear(); 16369 input.sendKeys(''); 16370 16371 expect(text.getText()).toEqual('text ='); 16372 expect(valid.getText()).toContain('false'); 16373 }); 16374 16375 it('should be invalid if not url', function() { 16376 input.clear(); 16377 input.sendKeys('box'); 16378 16379 expect(valid.getText()).toContain('false'); 16380 }); 16381 </file> 16382 </example> 16383 */ 16384 'url': urlInputType, 16385 16386 16387 /** 16388 * @ngdoc input 16389 * @name input[email] 16390 * 16391 * @description 16392 * Text input with email validation. Sets the `email` validation error key if not a valid email 16393 * address. 16394 * 16395 * @param {string} ngModel Assignable angular expression to data-bind to. 16396 * @param {string=} name Property name of the form under which the control is published. 16397 * @param {string=} required Sets `required` validation error key if the value is not entered. 16398 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16399 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16400 * `required` when you want to data-bind to the `required` attribute. 16401 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16402 * minlength. 16403 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16404 * maxlength. 16405 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16406 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16407 * patterns defined as scope expressions. 16408 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16409 * interaction with the input element. 16410 * 16411 * @example 16412 <example name="email-input-directive"> 16413 <file name="index.html"> 16414 <script> 16415 function Ctrl($scope) { 16416 $scope.text = '[email protected]'; 16417 } 16418 </script> 16419 <form name="myForm" ng-controller="Ctrl"> 16420 Email: <input type="email" name="input" ng-model="text" required> 16421 <span class="error" ng-show="myForm.input.$error.required"> 16422 Required!</span> 16423 <span class="error" ng-show="myForm.input.$error.email"> 16424 Not valid email!</span> 16425 <tt>text = {{text}}</tt><br/> 16426 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 16427 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 16428 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 16429 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 16430 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 16431 </form> 16432 </file> 16433 <file name="protractor.js" type="protractor"> 16434 var text = element(by.binding('text')); 16435 var valid = element(by.binding('myForm.input.$valid')); 16436 var input = element(by.model('text')); 16437 16438 it('should initialize to model', function() { 16439 expect(text.getText()).toContain('[email protected]'); 16440 expect(valid.getText()).toContain('true'); 16441 }); 16442 16443 it('should be invalid if empty', function() { 16444 input.clear(); 16445 input.sendKeys(''); 16446 expect(text.getText()).toEqual('text ='); 16447 expect(valid.getText()).toContain('false'); 16448 }); 16449 16450 it('should be invalid if not email', function() { 16451 input.clear(); 16452 input.sendKeys('xxx'); 16453 16454 expect(valid.getText()).toContain('false'); 16455 }); 16456 </file> 16457 </example> 16458 */ 16459 'email': emailInputType, 16460 16461 16462 /** 16463 * @ngdoc input 16464 * @name input[radio] 16465 * 16466 * @description 16467 * HTML radio button. 16468 * 16469 * @param {string} ngModel Assignable angular expression to data-bind to. 16470 * @param {string} value The value to which the expression should be set when selected. 16471 * @param {string=} name Property name of the form under which the control is published. 16472 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16473 * interaction with the input element. 16474 * @param {string} ngValue Angular expression which sets the value to which the expression should 16475 * be set when selected. 16476 * 16477 * @example 16478 <example name="radio-input-directive"> 16479 <file name="index.html"> 16480 <script> 16481 function Ctrl($scope) { 16482 $scope.color = 'blue'; 16483 $scope.specialValue = { 16484 "id": "12345", 16485 "value": "green" 16486 }; 16487 } 16488 </script> 16489 <form name="myForm" ng-controller="Ctrl"> 16490 <input type="radio" ng-model="color" value="red"> Red <br/> 16491 <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/> 16492 <input type="radio" ng-model="color" value="blue"> Blue <br/> 16493 <tt>color = {{color | json}}</tt><br/> 16494 </form> 16495 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 16496 </file> 16497 <file name="protractor.js" type="protractor"> 16498 it('should change state', function() { 16499 var color = element(by.binding('color')); 16500 16501 expect(color.getText()).toContain('blue'); 16502 16503 element.all(by.model('color')).get(0).click(); 16504 16505 expect(color.getText()).toContain('red'); 16506 }); 16507 </file> 16508 </example> 16509 */ 16510 'radio': radioInputType, 16511 16512 16513 /** 16514 * @ngdoc input 16515 * @name input[checkbox] 16516 * 16517 * @description 16518 * HTML checkbox. 16519 * 16520 * @param {string} ngModel Assignable angular expression to data-bind to. 16521 * @param {string=} name Property name of the form under which the control is published. 16522 * @param {string=} ngTrueValue The value to which the expression should be set when selected. 16523 * @param {string=} ngFalseValue The value to which the expression should be set when not selected. 16524 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16525 * interaction with the input element. 16526 * 16527 * @example 16528 <example name="checkbox-input-directive"> 16529 <file name="index.html"> 16530 <script> 16531 function Ctrl($scope) { 16532 $scope.value1 = true; 16533 $scope.value2 = 'YES' 16534 } 16535 </script> 16536 <form name="myForm" ng-controller="Ctrl"> 16537 Value1: <input type="checkbox" ng-model="value1"> <br/> 16538 Value2: <input type="checkbox" ng-model="value2" 16539 ng-true-value="YES" ng-false-value="NO"> <br/> 16540 <tt>value1 = {{value1}}</tt><br/> 16541 <tt>value2 = {{value2}}</tt><br/> 16542 </form> 16543 </file> 16544 <file name="protractor.js" type="protractor"> 16545 it('should change state', function() { 16546 var value1 = element(by.binding('value1')); 16547 var value2 = element(by.binding('value2')); 16548 16549 expect(value1.getText()).toContain('true'); 16550 expect(value2.getText()).toContain('YES'); 16551
16552 element(by.model('value1')).click(); 16553 element(by.model('value2')).click(); 16554 16555 expect(value1.getText()).toContain('false'); 16556 expect(value2.getText()).toContain('NO'); 16557 }); 16558 </file> 16559 </example> 16560 */ 16561 'checkbox': checkboxInputType, 16562 16563 'hidden': noop, 16564 'button': noop, 16565 'submit': noop, 16566 'reset': noop, 16567 'file': noop 16568}; 16569 16570// A helper function to call $setValidity and return the value / undefined, 16571// a pattern that is repeated a lot in the input validation logic. 16572function validate(ctrl, validatorName, validity, value){ 16573 ctrl.$setValidity(validatorName, validity); 16574 return validity ? value : undefined; 16575} 16576 16577 16578function addNativeHtml5Validators(ctrl, validatorName, element) { 16579 var validity = element.prop('validity'); 16580 if (isObject(validity)) { 16581 var validator = function(value) { 16582 // Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can 16583 // perform the required validation) 16584 if (!ctrl.$error[validatorName] && (validity.badInput || validity.customError || 16585 validity.typeMismatch) && !validity.valueMissing) { 16586 ctrl.$setValidity(validatorName, false); 16587 return; 16588 } 16589 return value; 16590 }; 16591 ctrl.$parsers.push(validator); 16592 } 16593} 16594 16595function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16596 var validity = element.prop('validity'); 16597 // In composition mode, users are still inputing intermediate text buffer, 16598 // hold the listener until composition is done. 16599 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 16600 if (!$sniffer.android) { 16601 var composing = false; 16602 16603 element.on('compositionstart', function(data) { 16604 composing = true; 16605 }); 16606 16607 element.on('compositionend', function() { 16608 composing = false; 16609 listener(); 16610 }); 16611 } 16612 16613 var listener = function() { 16614 if (composing) return; 16615 var value = element.val(); 16616 16617 // By default we will trim the value 16618 // If the attribute ng-trim exists we will avoid trimming 16619 // e.g. <input ng-model="foo" ng-trim="false"> 16620 if (toBoolean(attr.ngTrim || 'T')) { 16621 value = trim(value); 16622 } 16623 16624 if (ctrl.$viewValue !== value || 16625 // If the value is still empty/falsy, and there is no `required` error, run validators 16626 // again. This enables HTML5 constraint validation errors to affect Angular validation 16627 // even when the first character entered causes an error. 16628 (validity && value === '' && !validity.valueMissing)) { 16629 if (scope.$$phase) { 16630 ctrl.$setViewValue(value); 16631 } else { 16632 scope.$apply(function() { 16633 ctrl.$setViewValue(value); 16634 }); 16635 } 16636 } 16637 }; 16638 16639 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 16640 // input event on backspace, delete or cut 16641 if ($sniffer.hasEvent('input')) { 16642 element.on('input', listener); 16643 } else { 16644 var timeout; 16645 16646 var deferListener = function() { 16647 if (!timeout) { 16648 timeout = $browser.defer(function() { 16649 listener(); 16650 timeout = null; 16651 }); 16652 } 16653 }; 16654 16655 element.on('keydown', function(event) { 16656 var key = event.keyCode; 16657 16658 // ignore 16659 // command modifiers arrows 16660 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 16661 16662 deferListener(); 16663 }); 16664 16665 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 16666 if ($sniffer.hasEvent('paste')) { 16667 element.on('paste cut', deferListener); 16668 } 16669 } 16670 16671 // if user paste into input using mouse on older browser 16672 // or form autocomplete on newer browser, we need "change" event to catch it 16673 element.on('change', listener); 16674 16675 ctrl.$render = function() { 16676 element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); 16677 }; 16678 16679 // pattern validator 16680 var pattern = attr.ngPattern, 16681 patternValidator, 16682 match; 16683 16684 if (pattern) {
16685 var validateRegex = function(regexp, value) { 16686 return validate(ctrl, 'pattern', ctrl.$isEmpty(value) || regexp.test(value), value); 16687 }; 16688 match = pattern.match(/^\/(.*)\/([gim]*)$/); 16689 if (match) { 16690 pattern = new RegExp(match[1], match[2]); 16691 patternValidator = function(value) { 16692 return validateRegex(pattern, value); 16693 }; 16694 } else { 16695 patternValidator = function(value) { 16696 var patternObj = scope.$eval(pattern); 16697 16698 if (!patternObj || !patternObj.test) { 16699 throw minErr('ngPattern')('noregexp', 16700 'Expected {0} to be a RegExp but was {1}. Element: {2}', pattern, 16701 patternObj, startingTag(element)); 16702 } 16703 return validateRegex(patternObj, value); 16704 }; 16705 } 16706 16707 ctrl.$formatters.push(patternValidator); 16708 ctrl.$parsers.push(patternValidator); 16709 } 16710 16711 // min length validator 16712 if (attr.ngMinlength) { 16713 var minlength = int(attr.ngMinlength); 16714 var minLengthValidator = function(value) { 16715 return validate(ctrl, 'minlength', ctrl.$isEmpty(value) || value.length >= minlength, value); 16716 }; 16717 16718 ctrl.$parsers.push(minLengthValidator); 16719 ctrl.$formatters.push(minLengthValidator); 16720 } 16721 16722 // max length validator 16723 if (attr.ngMaxlength) { 16724 var maxlength = int(attr.ngMaxlength); 16725 var maxLengthValidator = function(value) { 16726 return validate(ctrl, 'maxlength', ctrl.$isEmpty(value) || value.length <= maxlength, value); 16727 }; 16728 16729 ctrl.$parsers.push(maxLengthValidator); 16730 ctrl.$formatters.push(maxLengthValidator); 16731 } 16732} 16733 16734function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16735 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 16736 16737 ctrl.$parsers.push(function(value) { 16738 var empty = ctrl.$isEmpty(value); 16739 if (empty || NUMBER_REGEXP.test(value)) { 16740 ctrl.$setValidity('number', true); 16741 return value === '' ? null : (empty ? value : parseFloat(value)); 16742 } else { 16743 ctrl.$setValidity('number', false); 16744 return undefined; 16745 } 16746 }); 16747 16748 addNativeHtml5Validators(ctrl, 'number', element); 16749 16750 ctrl.$formatters.push(function(value) { 16751 return ctrl.$isEmpty(value) ? '' : '' + value; 16752 }); 16753 16754 if (attr.min) { 16755 var minValidator = function(value) { 16756 var min = parseFloat(attr.min); 16757 return validate(ctrl, 'min', ctrl.$isEmpty(value) || value >= min, value); 16758 }; 16759 16760 ctrl.$parsers.push(minValidator); 16761 ctrl.$formatters.push(minValidator); 16762 } 16763 16764 if (attr.max) { 16765 var maxValidator = function(value) { 16766 var max = parseFloat(attr.max); 16767 return validate(ctrl, 'max', ctrl.$isEmpty(value) || value <= max, value); 16768 }; 16769 16770 ctrl.$parsers.push(maxValidator); 16771 ctrl.$formatters.push(maxValidator); 16772 } 16773 16774 ctrl.$formatters.push(function(value) { 16775 return validate(ctrl, 'number', ctrl.$isEmpty(value) || isNumber(value), value); 16776 }); 16777} 16778 16779function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16780 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 16781 16782 var urlValidator = function(value) { 16783 return validate(ctrl, 'url', ctrl.$isEmpty(value) || URL_REGEXP.test(value), value); 16784 }; 16785 16786 ctrl.$formatters.push(urlValidator); 16787 ctrl.$parsers.push(urlValidator); 16788} 16789 16790function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 16791 textInputType(scope, element, attr, ctrl, $sniffer, $browser); 16792 16793 var emailValidator = function(value) { 16794 return validate(ctrl, 'email', ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value), value); 16795 }; 16796 16797 ctrl.$formatters.push(emailValidator); 16798 ctrl.$parsers.push(emailValidator); 16799} 16800 16801function radioInputType(scope, element, attr, ctrl) { 16802 // make the name unique, if not defined 16803 if (isUndefined(attr.name)) { 16804 element.attr('name', nextUid()); 16805 } 16806 16807 element.on('click', function() { 16808 if (element[0].checked) { 16809 scope.$apply(function() { 16810 ctrl.$setViewValue(attr.value); 16811 }); 16812 } 16813 }); 16814 16815 ctrl.$render = function() { 16816 var value = attr.value; 16817 element[0].checked = (value == ctrl.$viewValue); 16818 }; 16819 16820 attr.$observe('value', ctrl.$render); 16821} 16822 16823function checkboxInputType(scope, element, attr, ctrl) { 16824 var trueValue = attr.ngTrueValue, 16825 falseValue = attr.ngFalseValue; 16826 16827 if (!isString(trueValue)) trueValue = true; 16828 if (!isString(falseValue)) falseValue = false; 16829 16830 element.on('click', function() { 16831 scope.$apply(function() { 16832 ctrl.$setViewValue(element[0].checked); 16833 });
16834 }); 16835 16836 ctrl.$render = function() { 16837 element[0].checked = ctrl.$viewValue; 16838 }; 16839 16840 // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. 16841 ctrl.$isEmpty = function(value) { 16842 return value !== trueValue; 16843 }; 16844 16845 ctrl.$formatters.push(function(value) { 16846 return value === trueValue; 16847 }); 16848 16849 ctrl.$parsers.push(function(value) { 16850 return value ? trueValue : falseValue; 16851 }); 16852} 16853 16854 16855/** 16856 * @ngdoc directive 16857 * @name textarea 16858 * @restrict E 16859 * 16860 * @description 16861 * HTML textarea element control with angular data-binding. The data-binding and validation 16862 * properties of this element are exactly the same as those of the 16863 * {@link ng.directive:input input element}. 16864 * 16865 * @param {string} ngModel Assignable angular expression to data-bind to. 16866 * @param {string=} name Property name of the form under which the control is published. 16867 * @param {string=} required Sets `required` validation error key if the value is not entered. 16868 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 16869 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 16870 * `required` when you want to data-bind to the `required` attribute. 16871 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16872 * minlength. 16873 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16874 * maxlength. 16875 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16876 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16877 * patterns defined as scope expressions. 16878 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16879 * interaction with the input element. 16880 */ 16881 16882 16883/** 16884 * @ngdoc directive 16885 * @name input 16886 * @restrict E 16887 * 16888 * @description 16889 * HTML input element control with angular data-binding. Input control follows HTML5 input types 16890 * and polyfills the HTML5 validation behavior for older browsers. 16891 * 16892 * @param {string} ngModel Assignable angular expression to data-bind to. 16893 * @param {string=} name Property name of the form under which the control is published. 16894 * @param {string=} required Sets `required` validation error key if the value is not entered. 16895 * @param {boolean=} ngRequired Sets `required` attribute if set to true 16896 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 16897 * minlength. 16898 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 16899 * maxlength. 16900 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 16901 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 16902 * patterns defined as scope expressions. 16903 * @param {string=} ngChange Angular expression to be executed when input changes due to user 16904 * interaction with the input element. 16905 * 16906 * @example 16907 <example name="input-directive"> 16908 <file name="index.html"> 16909 <script> 16910 function Ctrl($scope) { 16911 $scope.user = {name: 'guest', last: 'visitor'}; 16912 } 16913 </script> 16914 <div ng-controller="Ctrl"> 16915 <form name="myForm"> 16916 User name: <input type="text" name="userName" ng-model="user.name" required> 16917 <span class="error" ng-show="myForm.userName.$error.required"> 16918 Required!</span><br> 16919 Last name: <input type="text" name="lastName" ng-model="user.last" 16920 ng-minlength="3" ng-maxlength="10"> 16921 <span class="error" ng-show="myForm.lastName.$error.minlength"> 16922 Too short!</span> 16923 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 16924 Too long!</span><br> 16925 </form> 16926 <hr> 16927 <tt>user = {{user}}</tt><br/> 16928 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br> 16929 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br> 16930 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br> 16931 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br> 16932 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 16933 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 16934 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br> 16935 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br> 16936 </div> 16937 </file> 16938 <file name="protractor.js" type="protractor"> 16939 var user = element(by.binding('{{user}}')); 16940 var userNameValid = element(by.binding('myForm.userName.$valid')); 16941 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 16942 var lastNameError = element(by.binding('myForm.lastName.$error')); 16943 var formValid = element(by.binding('myForm.$valid'));
16944 var userNameInput = element(by.model('user.name')); 16945 var userLastInput = element(by.model('user.last')); 16946 16947 it('should initialize to model', function() { 16948 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 16949 expect(userNameValid.getText()).toContain('true'); 16950 expect(formValid.getText()).toContain('true'); 16951 }); 16952 16953 it('should be invalid if empty when required', function() { 16954 userNameInput.clear(); 16955 userNameInput.sendKeys(''); 16956 16957 expect(user.getText()).toContain('{"last":"visitor"}'); 16958 expect(userNameValid.getText()).toContain('false'); 16959 expect(formValid.getText()).toContain('false'); 16960 }); 16961 16962 it('should be valid if empty when min length is set', function() { 16963 userLastInput.clear(); 16964 userLastInput.sendKeys(''); 16965 16966 expect(user.getText()).toContain('{"name":"guest","last":""}'); 16967 expect(lastNameValid.getText()).toContain('true'); 16968 expect(formValid.getText()).toContain('true'); 16969 }); 16970 16971 it('should be invalid if less than required min length', function() { 16972 userLastInput.clear(); 16973 userLastInput.sendKeys('xx'); 16974
16975 expect(user.getText()).toContain('{"name":"guest"}'); 16976 expect(lastNameValid.getText()).toContain('false'); 16977 expect(lastNameError.getText()).toContain('minlength'); 16978 expect(formValid.getText()).toContain('false'); 16979 }); 16980 16981 it('should be invalid if longer than max length', function() { 16982 userLastInput.clear(); 16983 userLastInput.sendKeys('some ridiculously long name'); 16984 16985 expect(user.getText()).toContain('{"name":"guest"}'); 16986 expect(lastNameValid.getText()).toContain('false'); 16987 expect(lastNameError.getText()).toContain('maxlength'); 16988 expect(formValid.getText()).toContain('false'); 16989 }); 16990 </file> 16991 </example> 16992 */ 16993var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) { 16994 return { 16995 restrict: 'E', 16996 require: '?ngModel', 16997 link: function(scope, element, attr, ctrl) { 16998 if (ctrl) { 16999 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer, 17000 $browser); 17001 } 17002 } 17003 }; 17004}]; 17005 17006var VALID_CLASS = 'ng-valid', 17007 INVALID_CLASS = 'ng-invalid', 17008 PRISTINE_CLASS = 'ng-pristine', 17009 DIRTY_CLASS = 'ng-dirty'; 17010 17011/** 17012 * @ngdoc type 17013 * @name ngModel.NgModelController 17014 * 17015 * @property {string} $viewValue Actual string value in the view. 17016 * @property {*} $modelValue The value in the model, that the control is bound to. 17017 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 17018 the control reads value from the DOM. Each function is called, in turn, passing the value 17019 through to the next. The last return value is used to populate the model. 17020 Used to sanitize / convert the value as well as validation. For validation, 17021 the parsers should update the validity state using 17022 {@link ngModel.NgModelController#$setValidity $setValidity()}, 17023 and return `undefined` for invalid values. 17024 17025 * 17026 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 17027 the model value changes. Each function is called, in turn, passing the value through to the 17028 next. Used to format / convert values for display in the control and validation. 17029 * ```js 17030 * function formatter(value) { 17031 * if (value) { 17032 * return value.toUpperCase(); 17033 * } 17034 * } 17035 * ngModel.$formatters.push(formatter); 17036 * ``` 17037 * 17038 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 17039 * view value has changed. It is called with no arguments, and its return value is ignored. 17040 * This can be used in place of additional $watches against the model value. 17041 * 17042 * @property {Object} $error An object hash with all errors as keys. 17043 * 17044 * @property {boolean} $pristine True if user has not interacted with the control yet. 17045 * @property {boolean} $dirty True if user has already interacted with the control. 17046 * @property {boolean} $valid True if there is no error. 17047 * @property {boolean} $invalid True if at least one error on the control. 17048 * 17049 * @description 17050 * 17051 * `NgModelController` provides API for the `ng-model` directive. The controller contains 17052 * services for data-binding, validation, CSS updates, and value formatting and parsing. It 17053 * purposefully does not contain any logic which deals with DOM rendering or listening to 17054 * DOM events. Such DOM related logic should be provided by other directives which make use of 17055 * `NgModelController` for data-binding. 17056 * 17057 * ## Custom Control Example 17058 * This example shows how to use `NgModelController` with a custom control to achieve 17059 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 17060 * collaborate together to achieve the desired result. 17061 * 17062 * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element 17063 * contents be edited in place by the user. This will not work on older browsers. 17064 * 17065 * <example name="NgModelController" module="customControl"> 17066 <file name="style.css"> 17067 [contenteditable] { 17068 border: 1px solid black; 17069 background-color: white; 17070 min-height: 20px; 17071 } 17072 17073 .ng-invalid { 17074 border: 1px solid red; 17075 } 17076 17077 </file> 17078 <file name="script.js">
17079 angular.module('customControl', []). 17080 directive('contenteditable', function() { 17081 return { 17082 restrict: 'A', // only activate on element attribute 17083 require: '?ngModel', // get a hold of NgModelController 17084 link: function(scope, element, attrs, ngModel) { 17085 if(!ngModel) return; // do nothing if no ng-model 17086 17087 // Specify how UI should be updated 17088 ngModel.$render = function() { 17089 element.html(ngModel.$viewValue || ''); 17090 }; 17091 17092 // Listen for change events to enable binding 17093 element.on('blur keyup change', function() { 17094 scope.$apply(read); 17095 }); 17096 read(); // initialize 17097 17098 // Write data to the model 17099 function read() { 17100 var html = element.html(); 17101 // When we clear the content editable the browser leaves a <br> behind 17102 // If strip-br attribute is provided then we strip this out 17103 if( attrs.stripBr && html == '<br>' ) { 17104 html = ''; 17105 } 17106 ngModel.$setViewValue(html); 17107 } 17108 } 17109 }; 17110 }); 17111 </file> 17112 <file name="index.html"> 17113 <form name="myForm"> 17114 <div contenteditable 17115 name="myWidget" ng-model="userContent" 17116 strip-br="true" 17117 required>Change me!</div> 17118 <span ng-show="myForm.myWidget.$error.required">Required!</span> 17119 <hr> 17120 <textarea ng-model="userContent"></textarea> 17121 </form> 17122 </file> 17123 <file name="protractor.js" type="protractor"> 17124 it('should data-bind and become invalid', function() { 17125 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 17126 // SafariDriver can't handle contenteditable 17127 // and Firefox driver can't clear contenteditables very well 17128 return; 17129 } 17130 var contentEditable = element(by.css('[contenteditable]')); 17131 var content = 'Change me!'; 17132 17133 expect(contentEditable.getText()).toEqual(content); 17134 17135 contentEditable.clear(); 17136 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 17137 expect(contentEditable.getText()).toEqual(''); 17138 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 17139 }); 17140 </file> 17141 * </example> 17142 * 17143 * 17144 */ 17145var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', 17146 function($scope, $exceptionHandler, $attr, $element, $parse, $animate) { 17147 this.$viewValue = Number.NaN; 17148 this.$modelValue = Number.NaN; 17149 this.$parsers = []; 17150 this.$formatters = []; 17151 this.$viewChangeListeners = []; 17152 this.$pristine = true; 17153 this.$dirty = false; 17154 this.$valid = true; 17155 this.$invalid = false; 17156 this.$name = $attr.name; 17157 17158 var ngModelGet = $parse($attr.ngModel), 17159 ngModelSet = ngModelGet.assign; 17160 17161 if (!ngModelSet) { 17162 throw minErr('ngModel')('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 17163 $attr.ngModel, startingTag($element)); 17164 } 17165 17166 /** 17167 * @ngdoc method 17168 * @name ngModel.NgModelController#$render 17169 * 17170 * @description 17171 * Called when the view needs to be updated. It is expected that the user of the ng-model 17172 * directive will implement this method. 17173 */ 17174 this.$render = noop; 17175 17176 /** 17177 * @ngdoc method 17178 * @name ngModel.NgModelController#$isEmpty 17179 * 17180 * @description 17181 * This is called when we need to determine if the value of the input is empty. 17182 * 17183 * For instance, the required directive does this to work out if the input has data or not. 17184 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 17185 * 17186 * You can override this for input directives whose concept of being empty is different to the 17187 * default. The `checkboxInputType` directive does this because in its case a value of `false` 17188 * implies empty. 17189 * 17190 * @param {*} value Reference to check. 17191 * @returns {boolean} True if `value` is empty. 17192 */ 17193 this.$isEmpty = function(value) { 17194 return isUndefined(value) || value === '' || value === null || value !== value; 17195 }; 17196 17197 var parentForm = $element.inheritedData('$formController') || nullFormCtrl, 17198 invalidCount = 0, // used to easily determine if we are valid 17199 $error = this.$error = {};
17199 // keep invalid keys here 17200 17201 17202 // Setup initial state of the control 17203 $element.addClass(PRISTINE_CLASS); 17204 toggleValidCss(true); 17205 17206 // convenience method for easy toggling of classes 17207 function toggleValidCss(isValid, validationErrorKey) { 17208 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 17209 $animate.removeClass($element, (isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey); 17210 $animate.addClass($element, (isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey); 17211 } 17212 17213 /** 17214 * @ngdoc method 17215 * @name ngModel.NgModelController#$setValidity 17216 * 17217 * @description 17218 * Change the validity state, and notifies the form when the control changes validity. (i.e. it 17219 * does not notify form if given validator is already marked as invalid). 17220 * 17221 * This method should be called by validators - i.e. the parser or formatter functions. 17222 * 17223 * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign 17224 * to `$error[validationErrorKey]=isValid` so that it is available for data-binding. 17225 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 17226 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 17227 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 17228 * @param {boolean} isValid Whether the current state is valid (true) or invalid (false). 17229 */ 17230 this.$setValidity = function(validationErrorKey, isValid) { 17231 // Purposeful use of ! here to cast isValid to boolean in case it is undefined 17232 // jshint -W018 17233 if ($error[validationErrorKey] === !isValid) return; 17234 // jshint +W018 17235 17236 if (isValid) { 17237 if ($error[validationErrorKey]) invalidCount--; 17238 if (!invalidCount) { 17239 toggleValidCss(true); 17240 this.$valid = true; 17241 this.$invalid = false; 17242 } 17243 } else { 17244 toggleValidCss(false); 17245 this.$invalid = true; 17246 this.$valid = false; 17247 invalidCount++; 17248 } 17249 17250 $error[validationErrorKey] = !isValid; 17251 toggleValidCss(isValid, validationErrorKey); 17252 17253 parentForm.$setValidity(validationErrorKey, isValid, this); 17254 }; 17255 17256 /** 17257 * @ngdoc method 17258 * @name ngModel.NgModelController#$setPristine 17259 * 17260 * @description 17261 * Sets the control to its pristine state. 17262 * 17263 * This method can be called to remove the 'ng-dirty' class and set the control to its pristine 17264 * state (ng-pristine class). 17265 */ 17266 this.$setPristine = function () { 17267 this.$dirty = false; 17268 this.$pristine = true; 17269 $animate.removeClass($element, DIRTY_CLASS); 17270 $animate.addClass($element, PRISTINE_CLASS); 17271 }; 17272 17273 /** 17274 * @ngdoc method 17275 * @name ngModel.NgModelController#$setViewValue 17276 * 17277 * @description 17278 * Update the view value. 17279 * 17280 * This method should be called when the view value changes, typically from within a DOM event handler. 17281 * For example {@link ng.directive:input input} and 17282 * {@link ng.directive:select select} directives call it. 17283 * 17284 * It will update the $viewValue, then pass this value through each of the functions in `$parsers`, 17285 * which includes any validators. The value that comes out of this `$parsers` pipeline, be applied to 17286 * `$modelValue` and the **expression** specified in the `ng-model` attribute. 17287 * 17288 * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. 17289 * 17290 * Note that calling this function does not trigger a `$digest`. 17291 * 17292 * @param {string} value Value from the view. 17293 */ 17294 this.$setViewValue = function(value) { 17295 this.$viewValue = value; 17296 17297 // change to dirty 17298 if (this.$pristine) { 17299 this.$dirty = true; 17300 this.$pristine = false; 17301 $animate.removeClass($element, PRISTINE_CLASS); 17302 $animate.addClass($element, DIRTY_CLASS); 17303 parentForm.$setDirty(); 17304 } 17305 17306 forEach(this.$parsers, function(fn) { 17307 value = fn(value); 17308 }); 17309 17310 if (this.$modelValue !== value) { 17311 this.$modelValue = value; 17312 ngModelSet($scope, value);
17313 forEach(this.$viewChangeListeners, function(listener) { 17314 try { 17315 listener(); 17316 } catch(e) { 17317 $exceptionHandler(e); 17318 } 17319 }); 17320 } 17321 }; 17322 17323 // model -> value 17324 var ctrl = this; 17325 17326 $scope.$watch(function ngModelWatch() { 17327 var value = ngModelGet($scope); 17328 17329 // if scope model value and ngModel value are out of sync 17330 if (ctrl.$modelValue !== value) { 17331 17332 var formatters = ctrl.$formatters, 17333 idx = formatters.length; 17334 17335 ctrl.$modelValue = value; 17336 while(idx--) { 17337 value = formatters[idx](value); 17338 } 17339 17340 if (ctrl.$viewValue !== value) { 17341 ctrl.$viewValue = value; 17342 ctrl.$render(); 17343 } 17344 } 17345 17346 return value; 17347 }); 17348}]; 17349 17350 17351/** 17352 * @ngdoc directive 17353 * @name ngModel 17354 * 17355 * @element input 17356 * 17357 * @description 17358 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 17359 * property on the scope using {@link ngModel.NgModelController NgModelController}, 17360 * which is created and exposed by this directive. 17361 * 17362 * `ngModel` is responsible for: 17363 * 17364 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 17365 * require. 17366 * - Providing validation behavior (i.e. required, number, email, url). 17367 * - Keeping the state of the control (valid/invalid, dirty/pristine, validation errors). 17368 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`) including animations. 17369 * - Registering the control with its parent {@link ng.directive:form form}. 17370 * 17371 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 17372 * current scope. If the property doesn't already exist on this scope, it will be created 17373 * implicitly and added to the scope. 17374 * 17375 * For best practices on using `ngModel`, see: 17376 * 17377 * - [https://github.com/angular/angular.js/wiki/Understanding-Scopes] 17378 * 17379 * For basic examples, how to use `ngModel`, see: 17380 * 17381 * - {@link ng.directive:input input} 17382 * - {@link input[text] text} 17383 * - {@link input[checkbox] checkbox} 17384 * - {@link input[radio] radio} 17385 * - {@link input[number] number} 17386 * - {@link input[email] email} 17387 * - {@link input[url] url} 17388 * - {@link ng.directive:select select} 17389 * - {@link ng.directive:textarea textarea} 17390 * 17391 * # CSS classes 17392 * The following CSS classes are added and removed on the associated input/select/textarea element 17393 * depending on the validity of the model. 17394 * 17395 * - `ng-valid` is set if the model is valid. 17396 * - `ng-invalid` is set if the model is invalid. 17397 * - `ng-pristine` is set if the model is pristine. 17398 * - `ng-dirty` is set if the model is dirty. 17399 * 17400 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 17401 * 17402 * ## Animation Hooks 17403 * 17404 * Animations within models are triggered when any of the associated CSS classes are added and removed 17405 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, 17406 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 17407 * The animations that are triggered within ngModel are similar to how they work in ngClass and 17408 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 17409 * 17410 * The following example shows a simple way to utilize CSS transitions to style an input element 17411 * that has been rendered as invalid after it has been validated: 17412 * 17413 * <pre> 17414 * //be sure to include ngAnimate as a module to hook into more 17415 * //advanced animations 17416 * .my-input { 17417 * transition:0.5s linear all; 17418 * background: white; 17419 * } 17420 * .my-input.ng-invalid { 17421 * background: red; 17422 * color:white; 17423 * } 17424 * </pre> 17425 * 17426 * @example 17427 * <example deps="angular-animate.js" animations="true" fixBase="true"> 17428 <file name="index.html"> 17429 <script> 17430 function Ctrl($scope) { 17431 $scope.val = '1'; 17432 } 17433 </script> 17434 <style> 17435 .my-input { 17436 -webkit-transition:all linear 0.5s; 17437 transition:all linear 0.5s; 17438 background: transparent; 17439 } 17440 .my-input.ng-invalid { 17441 color:white; 17442 background: red; 17443 } 17444 </style> 17445 Update input to see transitions when valid/invalid. 17446 Integer is a valid value. 17447 <form name="testForm" ng-controller="Ctrl"> 17448 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" /> 17449 </form> 17450 </file> 17451 * </example> 17452 */ 17453var ngModelDirective = function() { 17454 return { 17455 require: ['ngModel', '^?form'], 17456 controller: NgModelController, 17457 link: function(scope, element, attr, ctrls) { 17458 // notify others, especially parent forms 17459 17460 var modelCtrl = ctrls[0], 17461 formCtrl = ctrls[1] || nullFormCtrl; 17462 17463 formCtrl.$addControl(modelCtrl); 17464 17465 scope.$on('$destroy', function() { 17466 formCtrl.$removeControl(modelCtrl); 17467 }); 17468 } 17469 }; 17470};
17471 17472 17473/** 17474 * @ngdoc directive 17475 * @name ngChange 17476 * 17477 * @description 17478 * Evaluate the given expression when the user changes the input. 17479 * The expression is evaluated immediately, unlike the JavaScript onchange event 17480 * which only triggers at the end of a change (usually, when the user leaves the 17481 * form element or presses the return key). 17482 * The expression is not evaluated when the value change is coming from the model. 17483 * 17484 * Note, this directive requires `ngModel` to be present. 17485 * 17486 * @element input 17487 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 17488 * in input value. 17489 * 17490 * @example 17491 * <example name="ngChange-directive"> 17492 * <file name="index.html"> 17493 * <script> 17494 * function Controller($scope) { 17495 * $scope.counter = 0; 17496 * $scope.change = function() { 17497 * $scope.counter++; 17498 * }; 17499 * } 17500 * </script> 17501 * <div ng-controller="Controller"> 17502 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 17503 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 17504 * <label for="ng-change-example2">Confirmed</label><br /> 17505 * <tt>debug = {{confirmed}}</tt><br/> 17506 * <tt>counter = {{counter}}</tt><br/> 17507 * </div> 17508 * </file> 17509 * <file name="protractor.js" type="protractor"> 17510 * var counter = element(by.binding('counter')); 17511 * var debug = element(by.binding('confirmed')); 17512 * 17513 * it('should evaluate the expression if changing from view', function() { 17514 * expect(counter.getText()).toContain('0'); 17515 * 17516 * element(by.id('ng-change-example1')).click(); 17517 * 17518 * expect(counter.getText()).toContain('1'); 17519 * expect(debug.getText()).toContain('true'); 17520 * }); 17521 * 17522 * it('should not evaluate the expression if changing from model', function() { 17523 * element(by.id('ng-change-example2')).click(); 17524 17525 * expect(counter.getText()).toContain('0'); 17526 * expect(debug.getText()).toContain('true'); 17527 * }); 17528 * </file> 17529 * </example> 17530 */ 17531var ngChangeDirective = valueFn({ 17532 require: 'ngModel', 17533 link: function(scope, element, attr, ctrl) { 17534 ctrl.$viewChangeListeners.push(function() { 17535 scope.$eval(attr.ngChange); 17536 }); 17537 } 17538}); 17539 17540 17541var requiredDirective = function() { 17542 return { 17543 require: '?ngModel', 17544 link: function(scope, elm, attr, ctrl) { 17545 if (!ctrl) return; 17546 attr.required = true; // force truthy in case we are on non input element 17547 17548 var validator = function(value) { 17549 if (attr.required && ctrl.$isEmpty(value)) { 17550 ctrl.$setValidity('required', false); 17551 return; 17552 } else { 17553 ctrl.$setValidity('required', true); 17554 return value; 17555 } 17556 }; 17557 17558 ctrl.$formatters.push(validator); 17559 ctrl.$parsers.unshift(validator); 17560 17561 attr.$observe('required', function() { 17562 validator(ctrl.$viewValue); 17563 }); 17564 } 17565 }; 17566}; 17567 17568 17569/** 17570 * @ngdoc directive 17571 * @name ngList 17572 * 17573 * @description 17574 * Text input that converts between a delimited string and an array of strings. The delimiter 17575 * can be a fixed string (by default a comma) or a regular expression. 17576 * 17577 * @element input 17578 * @param {string=} ngList optional delimiter that should be used to split the value. If 17579 * specified in form `/something/` then the value will be converted into a regular expression. 17580 * 17581 * @example 17582 <example name="ngList-directive"> 17583 <file name="index.html"> 17584 <script> 17585 function Ctrl($scope) { 17586 $scope.names = ['igor', 'misko', 'vojta']; 17587 } 17588 </script> 17589 <form name="myForm" ng-controller="Ctrl"> 17590 List: <input name="namesInput" ng-model="names" ng-list required> 17591 <span class="error" ng-show="myForm.namesInput.$error.required"> 17592 Required!</span> 17593 <br> 17594 <tt>names = {{names}}</tt><br/> 17595 <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 17596 <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 17597 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 17598 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 17599 </form> 17600 </file> 17601 <file name="protractor.js" type="protractor"> 17602 var listInput = element(by.model('names')); 17603 var names = element(by.binding('{{names}}'));
17604 var valid = element(by.binding('myForm.namesInput.$valid')); 17605 var error = element(by.css('span.error')); 17606 17607 it('should initialize to model', function() { 17608 expect(names.getText()).toContain('["igor","misko","vojta"]'); 17609 expect(valid.getText()).toContain('true'); 17610 expect(error.getCssValue('display')).toBe('none'); 17611 }); 17612 17613 it('should be invalid if empty', function() { 17614 listInput.clear(); 17615 listInput.sendKeys(''); 17616 17617 expect(names.getText()).toContain(''); 17618 expect(valid.getText()).toContain('false'); 17619 expect(error.getCssValue('display')).not.toBe('none'); }); 17620 </file> 17621 </example> 17622 */ 17623var ngListDirective = function() { 17624 return { 17625 require: 'ngModel', 17626 link: function(scope, element, attr, ctrl) { 17627 var match = /\/(.*)\//.exec(attr.ngList), 17628 separator = match && new RegExp(match[1]) || attr.ngList || ','; 17629 17630 var parse = function(viewValue) { 17631 // If the viewValue is invalid (say required but empty) it will be `undefined` 17632 if (isUndefined(viewValue)) return; 17633 17634 var list = []; 17635 17636 if (viewValue) { 17637 forEach(viewValue.split(separator), function(value) { 17638 if (value) list.push(trim(value)); 17639 }); 17640 } 17641 17642 return list; 17643 }; 17644 17645 ctrl.$parsers.push(parse); 17646 ctrl.$formatters.push(function(value) { 17647 if (isArray(value)) { 17648 return value.join(', '); 17649 } 17650 17651 return undefined; 17652 }); 17653 17654 // Override the standard $isEmpty because an empty array means the input is empty. 17655 ctrl.$isEmpty = function(value) { 17656 return !value || !value.length; 17657 }; 17658 } 17659 }; 17660}; 17661 17662 17663var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 17664/** 17665 * @ngdoc directive 17666 * @name ngValue 17667 * 17668 * @description 17669 * Binds the given expression to the value of `input[select]` or `input[radio]`, so 17670 * that when the element is selected, the `ngModel` of that element is set to the 17671 * bound value. 17672 * 17673 * `ngValue` is useful when dynamically generating lists of radio buttons using `ng-repeat`, as 17674 * shown below. 17675 * 17676 * @element input 17677 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 17678 * of the `input` element 17679 * 17680 * @example 17681 <example name="ngValue-directive"> 17682 <file name="index.html"> 17683 <script> 17684 function Ctrl($scope) { 17685 $scope.names = ['pizza', 'unicorns', 'robots']; 17686 $scope.my = { favorite: 'unicorns' }; 17687 } 17688 </script> 17689 <form ng-controller="Ctrl"> 17690 <h2>Which is your favorite?</h2> 17691 <label ng-repeat="name in names" for="{{name}}"> 17692 {{name}} 17693 <input type="radio" 17694 ng-model="my.favorite" 17695 ng-value="name" 17696 id="{{name}}" 17697 name="favorite"> 17698 </label> 17699 <div>You chose {{my.favorite}}</div> 17700 </form> 17701 </file> 17702 <file name="protractor.js" type="protractor"> 17703 var favorite = element(by.binding('my.favorite')); 17704 17705 it('should initialize to model', function() { 17706 expect(favorite.getText()).toContain('unicorns'); 17707 }); 17708 it('should bind the values to the inputs', function() { 17709 element.all(by.model('my.favorite')).get(0).click(); 17710 expect(favorite.getText()).toContain('pizza'); 17711 }); 17712 </file> 17713 </example> 17714 */ 17715var ngValueDirective = function() { 17716 return { 17717 priority: 100, 17718 compile: function(tpl, tplAttr) { 17719 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 17720 return function ngValueConstantLink(scope, elm, attr) { 17721 attr.$set('value', scope.$eval(attr.ngValue)); 17722 }; 17723 } else { 17724 return function ngValueLink(scope, elm, attr) { 17725 scope.$watch(attr.ngValue, function valueWatchAction(value) { 17726 attr.$set('value', value); 17727 }); 17728 }; 17729 } 17730 } 17731 }; 17732};
17733 17734/** 17735 * @ngdoc directive 17736 * @name ngBind 17737 * @restrict AC 17738 * 17739 * @description 17740 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 17741 * with the value of a given expression, and to update the text content when the value of that 17742 * expression changes. 17743 * 17744 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 17745 * `{{ expression }}` which is similar but less verbose. 17746 * 17747 * It is preferable to use `ngBind` instead of `{{ expression }}` when a template is momentarily 17748 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 17749 * element attribute, it makes the bindings invisible to the user while the page is loading. 17750 * 17751 * An alternative solution to this problem would be using the 17752 * {@link ng.directive:ngCloak ngCloak} directive. 17753 * 17754 * 17755 * @element ANY 17756 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 17757 * 17758 * @example 17759 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 17760 <example> 17761 <file name="index.html"> 17762 <script> 17763 function Ctrl($scope) { 17764 $scope.name = 'Whirled'; 17765 } 17766 </script> 17767 <div ng-controller="Ctrl"> 17768 Enter name: <input type="text" ng-model="name"><br> 17769 Hello <span ng-bind="name"></span>! 17770 </div> 17771 </file> 17772 <file name="protractor.js" type="protractor"> 17773 it('should check ng-bind', function() { 17774 var nameInput = element(by.model('name')); 17775 17776 expect(element(by.binding('name')).getText()).toBe('Whirled'); 17777 nameInput.clear(); 17778 nameInput.sendKeys('world'); 17779 expect(element(by.binding('name')).getText()).toBe('world'); 17780 }); 17781 </file> 17782 </example> 17783 */ 17784var ngBindDirective = ngDirective(function(scope, element, attr) { 17785 element.addClass('ng-binding').data('$binding', attr.ngBind); 17786 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 17787 // We are purposefully using == here rather than === because we want to 17788 // catch when value is "null or undefined" 17789 // jshint -W041 17790 element.text(value == undefined ? '' : value); 17791 }); 17792}); 17793 17794 17795/** 17796 * @ngdoc directive 17797 * @name ngBindTemplate 17798 * 17799 * @description 17800 * The `ngBindTemplate` directive specifies that the element 17801 * text content should be replaced with the interpolation of the template 17802 * in the `ngBindTemplate` attribute. 17803 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 17804 * expressions. This directive is needed since some HTML elements 17805 * (such as TITLE and OPTION) cannot contain SPAN elements. 17806 * 17807 * @element ANY 17808 * @param {string} ngBindTemplate template of form 17809 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 17810 * 17811 * @example 17812 * Try it here: enter text in text box and watch the greeting change. 17813 <example> 17814 <file name="index.html"> 17815 <script> 17816 function Ctrl($scope) { 17817 $scope.salutation = 'Hello'; 17818 $scope.name = 'World'; 17819 } 17820 </script> 17821 <div ng-controller="Ctrl"> 17822 Salutation: <input type="text" ng-model="salutation"><br> 17823 Name: <input type="text" ng-model="name"><br> 17824 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 17825 </div> 17826 </file> 17827 <file name="protractor.js" type="protractor"> 17828 it('should check ng-bind', function() { 17829 var salutationElem = element(by.binding('salutation')); 17830 var salutationInput = element(by.model('salutation')); 17831 var nameInput = element(by.model('name')); 17832 17833 expect(salutationElem.getText()).toBe('Hello World!'); 17834 17835 salutationInput.clear(); 17836 salutationInput.sendKeys('Greetings'); 17837 nameInput.clear(); 17838 nameInput.sendKeys('user'); 17839 17840 expect(salutationElem.getText()).toBe('Greetings user!'); 17841 }); 17842 </file> 17843 </example> 17844 */ 17845var ngBindTemplateDirective = ['$interpolate', function($interpolate) { 17846 return function(scope, element, attr) { 17847 // TODO: move this to scenario runner 17848 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 17849 element.addClass('ng-binding').data('$binding', interpolateFn); 17850 attr.$observe('ngBindTemplate', function(value) { 17851 element.text(value); 17852 });
17853 }; 17854}]; 17855 17856 17857/** 17858 * @ngdoc directive 17859 * @name ngBindHtml 17860 * 17861 * @description 17862 * Creates a binding that will innerHTML the result of evaluating the `expression` into the current 17863 * element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link 17864 * ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` 17865 * is available, for example, by including {@link ngSanitize} in your module's dependencies (not in 17866 * core Angular.) You may also bypass sanitization for values you know are safe. To do so, bind to 17867 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 17868 * under {@link ng.$sce#Example Strict Contextual Escaping (SCE)}. 17869 * 17870 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 17871 * will have an exception (instead of an exploit.) 17872 * 17873 * @element ANY 17874 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 17875 * 17876 * @example 17877 Try it here: enter text in text box and watch the greeting change. 17878 17879 <example module="ngBindHtmlExample" deps="angular-sanitize.js"> 17880 <file name="index.html"> 17881 <div ng-controller="ngBindHtmlCtrl"> 17882 <p ng-bind-html="myHTML"></p> 17883 </div> 17884 </file> 17885 17886 <file name="script.js"> 17887 angular.module('ngBindHtmlExample', ['ngSanitize']) 17888 17889 .controller('ngBindHtmlCtrl', ['$scope', function ngBindHtmlCtrl($scope) { 17890 $scope.myHTML = 17891 'I am an <code>HTML</code>string with <a href="#">links!</a> and other <em>stuff</em>'; 17892 }]); 17893 </file> 17894 17895 <file name="protractor.js" type="protractor"> 17896 it('should check ng-bind-html', function() { 17897 expect(element(by.binding('myHTML')).getText()).toBe( 17898 'I am an HTMLstring with links! and other stuff'); 17899 }); 17900 </file> 17901 </example> 17902 */ 17903var ngBindHtmlDirective = ['$sce', '$parse', function($sce, $parse) { 17904 return function(scope, element, attr) { 17905 element.addClass('ng-binding').data('$binding', attr.ngBindHtml); 17906 17907 var parsed = $parse(attr.ngBindHtml); 17908 function getStringValue() { return (parsed(scope) || '').toString(); } 17909 17910 scope.$watch(getStringValue, function ngBindHtmlWatchAction(value) { 17911 element.html($sce.getTrustedHtml(parsed(scope)) || ''); 17912 }); 17913 }; 17914}]; 17915 17916function classDirective(name, selector) { 17917 name = 'ngClass' + name; 17918 return ['$animate', function($animate) { 17919 return { 17920 restrict: 'AC', 17921 link: function(scope, element, attr) { 17922 var oldVal; 17923 17924 scope.$watch(attr[name], ngClassWatchAction, true); 17925 17926 attr.$observe('class', function(value) { 17927 ngClassWatchAction(scope.$eval(attr[name])); 17928 }); 17929 17930 17931 if (name !== 'ngClass') { 17932 scope.$watch('$index', function($index, old$index) { 17933 // jshint bitwise: false 17934 var mod = $index & 1; 17935 if (mod !== old$index & 1) { 17936 var classes = arrayClasses(scope.$eval(attr[name])); 17937 mod === selector ? 17938 addClasses(classes) : 17939 removeClasses(classes); 17940 } 17941 }); 17942 } 17943 17944 function addClasses(classes) { 17945 var newClasses = digestClassCounts(classes, 1); 17946 attr.$addClass(newClasses); 17947 } 17948 17949 function removeClasses(classes) { 17950 var newClasses = digestClassCounts(classes, -1); 17951 attr.$removeClass(newClasses); 17952 } 17953 17954 function digestClassCounts (classes, count) { 17955 var classCounts = element.data('$classCounts') || {}; 17956 var classesToUpdate = []; 17957 forEach(classes, function (className) { 17958 if (count > 0 || classCounts[className]) { 17959 classCounts[className] = (classCounts[className] || 0) + count; 17960 if (classCounts[className] === +(count > 0)) { 17961 classesToUpdate.push(className); 17962 } 17963 } 17964 }); 17965 element.data('$classCounts', classCounts); 17966 return classesToUpdate.join(' '); 17967 } 17968
17969 function updateClasses (oldClasses, newClasses) { 17970 var toAdd = arrayDifference(newClasses, oldClasses); 17971 var toRemove = arrayDifference(oldClasses, newClasses); 17972 toRemove = digestClassCounts(toRemove, -1); 17973 toAdd = digestClassCounts(toAdd, 1); 17974 17975 if (toAdd.length === 0) { 17976 $animate.removeClass(element, toRemove); 17977 } else if (toRemove.length === 0) { 17978 $animate.addClass(element, toAdd); 17979 } else { 17980 $animate.setClass(element, toAdd, toRemove); 17981 } 17982 } 17983 17984 function ngClassWatchAction(newVal) { 17985 if (selector === true || scope.$index % 2 === selector) { 17986 var newClasses = arrayClasses(newVal || []); 17987 if (!oldVal) { 17988 addClasses(newClasses); 17989 } else if (!equals(newVal,oldVal)) { 17990 var oldClasses = arrayClasses(oldVal); 17991 updateClasses(oldClasses, newClasses); 17992 } 17993 } 17994 oldVal = copy(newVal); 17995 } 17996 } 17997 }; 17998 17999 function arrayDifference(tokens1, tokens2) { 18000 var values = []; 18001 18002 outer: 18003 for(var i = 0; i < tokens1.length; i++) { 18004 var token = tokens1[i]; 18005 for(var j = 0; j < tokens2.length; j++) { 18006 if(token == tokens2[j]) continue outer; 18007 } 18008 values.push(token); 18009 } 18010 return values; 18011 } 18012 18013 function arrayClasses (classVal) { 18014 if (isArray(classVal)) { 18015 return classVal; 18016 } else if (isString(classVal)) { 18017 return classVal.split(' '); 18018 } else if (isObject(classVal)) { 18019 var classes = [], i = 0; 18020 forEach(classVal, function(v, k) { 18021 if (v) { 18022 classes.push(k); 18023 } 18024 }); 18025 return classes; 18026 } 18027 return classVal; 18028 } 18029 }]; 18030} 18031 18032/** 18033 * @ngdoc directive 18034 * @name ngClass 18035 * @restrict AC 18036 * 18037 * @description 18038 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 18039 * an expression that represents all classes to be added. 18040 * 18041 * The directive operates in three different ways, depending on which of three types the expression 18042 * evaluates to: 18043 * 18044 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 18045 * names. 18046 * 18047 * 2. If the expression evaluates to an array, each element of the array should be a string that is 18048 * one or more space-delimited class names. 18049 * 18050 * 3. If the expression evaluates to an object, then for each key-value pair of the 18051 * object with a truthy value the corresponding key is used as a class name. 18052 * 18053 * The directive won't add duplicate classes if a particular class was already set. 18054 * 18055 * When the expression changes, the previously added classes are removed and only then the 18056 * new classes are added. 18057 * 18058 * @animations 18059 * add - happens just before the class is applied to the element 18060 * remove - happens just before the class is removed from the element 18061 * 18062 * @element ANY 18063 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 18064 * of the evaluation can be a string representing space delimited class 18065 * names, an array, or a map of class names to boolean values. In the case of a map, the 18066 * names of the properties whose values are truthy will be added as css classes to the 18067 * element. 18068 * 18069 * @example Example that demonstrates basic bindings via ngClass directive. 18070 <example> 18071 <file name="index.html"> 18072 <p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p> 18073 <input type="checkbox" ng-model="deleted"> deleted (apply "strike" class)<br> 18074 <input type="checkbox" ng-model="important"> important (apply "bold" class)<br> 18075 <input type="checkbox" ng-model="error"> error (apply "red" class) 18076 <hr> 18077 <p ng-class="style">Using String Syntax</p> 18078 <input type="text" ng-model="style" placeholder="Type: bold strike red"> 18079 <hr> 18080 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 18081 <input ng-model="style1" placeholder="Type: bold, strike or red"><br> 18082 <input ng-model="style2" placeholder="Type: bold, strike or red"><br> 18083 <input ng-model="style3" placeholder="Type: bold, strike or red"><br> 18084 </file> 18085 <file name="style.css"> 18086 .strike { 18087 text-decoration: line-through; 18088 } 18089 .bold { 18090 font-weight: bold; 18091 } 18092 .red { 18093 color: red; 18094 } 18095 </file> 18096 <file name="protractor.js" type="protractor"> 18097 var ps = element.all(by.css('p')); 18098 18099 it('should let you toggle the class', function() { 18100 18101 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 18102 expect(ps.first().getAttribute('class')).not.toMatch(/red/); 18103
18104 element(by.model('important')).click(); 18105 expect(ps.first().getAttribute('class')).toMatch(/bold/); 18106 18107 element(by.model('error')).click(); 18108 expect(ps.first().getAttribute('class')).toMatch(/red/); 18109 }); 18110 18111 it('should let you toggle string example', function() { 18112 expect(ps.get(1).getAttribute('class')).toBe(''); 18113 element(by.model('style')).clear(); 18114 element(by.model('style')).sendKeys('red'); 18115 expect(ps.get(1).getAttribute('class')).toBe('red'); 18116 }); 18117 18118 it('array example should have 3 classes', function() { 18119 expect(ps.last().getAttribute('class')).toBe(''); 18120 element(by.model('style1')).sendKeys('bold'); 18121 element(by.model('style2')).sendKeys('strike'); 18122 element(by.model('style3')).sendKeys('red'); 18123 expect(ps.last().getAttribute('class')).toBe('bold strike red'); 18124 }); 18125 </file> 18126 </example> 18127 18128 ## Animations 18129 18130 The example below demonstrates how to perform animations using ngClass. 18131 18132 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 18133 <file name="index.html"> 18134 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 18135 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 18136 <br> 18137 <span class="base-class" ng-class="myVar">Sample Text</span> 18138 </file> 18139 <file name="style.css"> 18140 .base-class { 18141 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18142 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 18143 } 18144 18145 .base-class.my-class { 18146 color: red; 18147 font-size:3em; 18148 } 18149 </file> 18150 <file name="protractor.js" type="protractor"> 18151 it('should check ng-class', function() { 18152 expect(element(by.css('.base-class')).getAttribute('class')).not. 18153 toMatch(/my-class/); 18154 18155 element(by.id('setbtn')).click(); 18156 18157 expect(element(by.css('.base-class')).getAttribute('class')). 18158 toMatch(/my-class/); 18159 18160 element(by.id('clearbtn')).click(); 18161 18162 expect(element(by.css('.base-class')).getAttribute('class')).not. 18163 toMatch(/my-class/); 18164 }); 18165 </file> 18166 </example> 18167 18168 18169 ## ngClass and pre-existing CSS3 Transitions/Animations 18170 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 18171 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 18172 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 18173 to view the step by step details of {@link ngAnimate.$animate#addclass $animate.addClass} and 18174 {@link ngAnimate.$animate#removeclass $animate.removeClass}. 18175 */ 18176var ngClassDirective = classDirective('', true); 18177 18178/** 18179 * @ngdoc directive 18180 * @name ngClassOdd 18181 * @restrict AC 18182 * 18183 * @description 18184 * The `ngClassOdd` and `ngClassEven` directives work exactly as 18185 * {@link ng.directive:ngClass ngClass}, except they work in 18186 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 18187 * 18188 * This directive can be applied only within the scope of an 18189 * {@link ng.directive:ngRepeat ngRepeat}. 18190 * 18191 * @element ANY 18192 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 18193 * of the evaluation can be a string representing space delimited class names or an array. 18194 * 18195 * @example 18196 <example> 18197 <file name="index.html"> 18198 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 18199 <li ng-repeat="name in names"> 18200 <span ng-class-odd="'odd'" ng-class-even="'even'"> 18201 {{name}} 18202 </span> 18203 </li> 18204 </ol> 18205 </file> 18206 <file name="style.css"> 18207 .odd { 18208 color: red; 18209 } 18210 .even { 18211 color: blue; 18212 } 18213 </file> 18214 <file name="protractor.js" type="protractor"> 18215 it('should check ng-class-odd and ng-class-even', function() { 18216 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 18217 toMatch(/odd/); 18218 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 18219 toMatch(/even/); 18220 }); 18221 </file> 18222 </example> 18223 */ 18224var ngClassOddDirective = classDirective('Odd', 0); 18225 18226/** 18227 * @ngdoc directive 18228 * @name ngClassEven 18229 * @restrict AC 18230 * 18231 * @description 18232 * The `ngClassOdd` and `ngClassEven` directives work exactly as 18233 * {@link ng.directive:ngClass ngClass}, except they work in 18234 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 18235 * 18236 * This directive can be applied only within the scope of an 18237 * {@link ng.directive:ngRepeat ngRepeat}. 18238 * 18239 * @element ANY 18240 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 18241 * result of the evaluation can be a string representing space delimited class names or an array. 18242 * 18243 * @example 18244 <example> 18245 <file name="index.html"> 18246 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 18247 <li ng-repeat="name in names">
18248 <span ng-class-odd="'odd'" ng-class-even="'even'"> 18249 {{name}} 18250 </span> 18251 </li> 18252 </ol> 18253 </file> 18254 <file name="style.css"> 18255 .odd { 18256 color: red; 18257 } 18258 .even { 18259 color: blue; 18260 } 18261 </file> 18262 <file name="protractor.js" type="protractor"> 18263 it('should check ng-class-odd and ng-class-even', function() { 18264 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 18265 toMatch(/odd/); 18266 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 18267 toMatch(/even/); 18268 }); 18269 </file> 18270 </example> 18271 */ 18272var ngClassEvenDirective = classDirective('Even', 1); 18273 18274/** 18275 * @ngdoc directive 18276 * @name ngCloak 18277 * @restrict AC 18278 * 18279 * @description 18280 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 18281 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 18282 * directive to avoid the undesirable flicker effect caused by the html template display. 18283 * 18284 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 18285 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 18286 * of the browser view. 18287 * 18288 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 18289 * `angular.min.js`. 18290 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 18291 * 18292 * ```css 18293 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 18294 * display: none !important; 18295 * } 18296 * ``` 18297 * 18298 * When this css rule is loaded by the browser, all html elements (including their children) that 18299 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 18300 * during the compilation of the template it deletes the `ngCloak` element attribute, making 18301 * the compiled element visible. 18302 * 18303 * For the best result, the `angular.js` script must be loaded in the head section of the html 18304 * document; alternatively, the css rule above must be included in the external stylesheet of the 18305 * application. 18306 * 18307 * Legacy browsers, like IE7, do not provide attribute selector support (a
18307dded in CSS 2.1) so they 18308 * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css 18309 * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. 18310 * 18311 * @element ANY 18312 * 18313 * @example 18314 <example> 18315 <file name="index.html"> 18316 <div id="template1" ng-cloak>{{ 'hello' }}</div> 18317 <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div> 18318 </file> 18319 <file name="protractor.js" type="protractor"> 18320 it('should remove the template directive and css class', function() { 18321 expect($('#template1').getAttribute('ng-cloak')). 18322 toBeNull(); 18323 expect($('#template2').getAttribute('ng-cloak')). 18324 toBeNull(); 18325 }); 18326 </file> 18327 </example> 18328 * 18329 */ 18330var ngCloakDirective = ngDirective({ 18331 compile: function(element, attr) { 18332 attr.$set('ngCloak', undefined); 18333 element.removeClass('ng-cloak'); 18334 } 18335}); 18336 18337/** 18338 * @ngdoc directive 18339 * @name ngController 18340 * 18341 * @description 18342 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 18343 * supports the principles behind the Model-View-Controller design pattern. 18344 * 18345 * MVC components in angular: 18346 * 18347 * * Model â The Model is scope properties; scopes are attached to the DOM where scope properties 18348 * are accessed through bindings. 18349 * * View â The template (HTML with data bindings) that is rendered into the View. 18350 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 18351 * logic behind the application to decorate the scope with functions and values 18352 * 18353 * Note that you can also attach controllers to the DOM by declaring it in a route definition 18354 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 18355 * again using `ng-controller` in the template itself. This will cause the controller to be attached 18356 * and executed twice. 18357 * 18358 * @element ANY 18359 * @scope 18360 * @param {expression} ngController Name of a globally accessible constructor function or an 18361 * {@link guide/expression expression} that on the current scope evaluates to a 18362 * constructor function. The controller instance can be published into a scope property 18363 * by specifying `as propertyName`. 18364 * 18365 * @example 18366 * Here is a simple form for editing user contact information. Adding, removing, clearing, and 18367 * greeting are methods declared on the controller (see source tab). These methods can 18368 * easily be called from the angular markup. Notice that the scope becomes the `this` for the 18369 * controller's instance. This allows for easy access to the view data from the controller. Also 18370 * notice that any changes to the data are automatically reflected in the View without the need 18371 * for a manual update. The example is shown in two different declaration styles you may use 18372 * according to preference. 18373 <example> 18374 <file name="index.html"> 18375 <script> 18376 function SettingsController1() { 18377 this.name = "John Smith"; 18378 this.contacts = [ 18379 {type: 'phone', value: '408 555 1212'}, 18380 {type: 'email', value: '[email protected]'} ]; 18381 }; 18382 18383 SettingsController1.prototype.greet = function() { 18384 alert(this.name); 18385 }; 18386 18387 SettingsController1.prototype.addContact = function() { 18388 this.contacts.push({type: 'email', value: '[email protected]'}); 18389 }; 18390 18391 SettingsController1.prototype.removeContact = function(contactToRemove) { 18392 var index = this.contacts.indexOf(contactToRemove); 18393 this.contacts.splice(index, 1); 18394 }; 18395 18396 SettingsController1.prototype.clearContact = function(contact) { 18397 contact.type = 'phone'; 18398 contact.value = ''; 18399 }; 18400 </script> 18401 <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 18402 Name: <input type="text" ng-model="settings.name"/> 18403 [ <a href="" ng-click="settings.greet()">greet</a> ]<br/> 18404 Contact: 18405 <ul> 18406 <li ng-repeat="contact in settings.contacts"> 18407 <select ng-model="contact.type"> 18408 <option>phone</option> 18409 <option>email</option> 18410 </select> 18411 <input type="text" ng-model="contact.value"/> 18412 [ <a href="" ng-click="settings.clearContact(contact)">clear</a> 18413 | <a href="" ng-click="settings.removeContact(contact)">X</a> ] 18414 </li> 18415 <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li> 18416 </ul> 18417 </div> 18418 </file> 18419 <file name="protractor.js" type="protractor"> 18420 it('should check controller as', function() { 18421 var container = element(by.id('ctrl-as-exmpl')); 18422 18423 expect(container.findElement(by.model('settings.name')) 18424 .getAttribute('value')).toBe('John Smith'); 18425 18426 var firstRepeat = 18427 container.findElement(by.repeater('contact in settings.contacts').row(0)); 18428 var secondRepeat = 18429 container.findElement(by.repeater('contact in settings.contacts').row(1)); 18430 18431 expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18432 .toBe('408 555 1212'); 18433 expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18434 .toBe('[email protected]'); 18435 18436 firstRepeat.findElement(by.linkText('clear')).click(); 18437 18438 expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18439 .toBe(''); 18440 18441 container.findElement(by.linkText('add')).click(); 18442 18443 expect(container.findElement(by.repeater('contact in settings.contacts').row(2)) 18444 .findElement(by.model('contact.value')) 18445 .getAttribute('value')) 18446 .toBe('[email protected]'); 18447 }); 18448 </file> 18449 </example> 18450 <example> 18451 <file name="index.html"> 18452 <script> 18453 function SettingsController2($scope) { 18454 $scope.name = "John Smith"; 18455 $scope.contacts = [ 18456 {type:'phone', value:'408 555 1212'}, 18457 {type:'email', value:'[email protected]'} ]; 18458 18459 $scope.greet = function() { 18460 alert(this.name); 18461 }; 18462 18463 $scope.addContact = function() { 18464 this.contacts.push({type:'email', value:'[email protected]'}); 18465 }; 18466 18467 $scope.removeContact = function(contactToRemove) { 18468 var index = this.contacts.indexOf(contactToRemove); 18469 this.contacts.splice(index, 1); 18470 }; 18471 18472 $scope.clearContact = function(contact) { 18473 contact.type = 'phone'; 18474 contact.value = ''; 18475 }; 18476 } 18477 </script> 18478 <div id="ctrl-exmpl" ng-controller="SettingsController2"> 18479 Name: <input type="text" ng-model="name"/> 18480 [ <a href="" ng-click="greet()">greet</a> ]<br/> 18481 Contact: 18482 <ul> 18483 <li ng-repeat="contact in contacts"> 18484 <select ng-model="contact.type"> 18485 <option>phone</option> 18486 <option>email</option> 18487 </select> 18488 <input type="text" ng-model="contact.value"/> 18489 [ <a href="" ng-click="clearContact(contact)">clear</a> 18490 | <a href="" ng-click="removeContact(contact)">X</a> ] 18491 </li> 18492 <li>[ <a href="" ng-click="addContact()">add</a> ]</li> 18493 </ul> 18494 </div> 18495 </file> 18496 <file name="protractor.js" type="protractor"> 18497 it('should check controller', function() { 18498 var container = element(by.id('ctrl-exmpl')); 18499 18500 expect(container.findElement(by.model('name')) 18501 .getAttribute('value')).toBe('John Smith'); 18502 18503 var firstRepeat = 18504 container.findElement(by.repeater('contact in contacts').row(0)); 18505 var secondRepeat = 18506 container.findElement(by.repeater('contact in contacts').row(1)); 18507 18508 expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18509 .toBe('408 555 1212'); 18510 expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18511 .toBe('[email protected]'); 18512 18513 firstRepeat.findElement(by.linkText('clear')).click(); 18514 18515 expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value')) 18516 .toBe(''); 18517 18518 container.findElement(by.linkText('add')).click(); 18519 18520 expect(container.findElement(by.repeater('contact in contacts').row(2)) 18521 .findElement(by.model('contact.value')) 18522 .getAttribute('value')) 18523 .toBe('[email protected]'); 18524 }); 18525 </file> 18526 </example> 18527 18528 */ 18529var ngControllerDirective = [function() { 18530 return { 18531 scope: true, 18532 controller: '@', 18533 priority: 500 18534 }; 18535}]; 18536 18537/** 18538 * @ngdoc directive 18539 * @name ngCsp 18540 * 18541 * @element html 18542 * @description 18543 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. 18544 * 18545 * This is necessary when developing things like Google Chrome Extensions. 18546 * 18547 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). 18548 * For us to be compatible, we just need to implement the "getterFn" in $parse without violating 18549 * any of these restrictions. 18550 * 18551 * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` 18552 * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will
18553 * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will 18554 * be raised. 18555 * 18556 * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically 18557 * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). 18558 * To make those directives work in CSP mode, include the `angular-csp.css` manually. 18559 * 18560 * In order to use this feature put the `ngCsp` directive on the root element of the application. 18561 * 18562 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 18563 * 18564 * @example 18565 * This example shows how to apply the `ngCsp` directive to the `html` tag. 18566 ```html 18567 <!doctype html> 18568 <html ng-app ng-csp> 18569 ... 18570 ... 18571 </html> 18572 ``` 18573 */ 18574 18575// ngCsp is not implemented as a proper directive any more, because we need it be processed while we bootstrap 18576// the system (before $parse is instantiated), for this reason we just have a csp() fn that looks for ng-csp attribute 18577// anywhere in the current doc 18578 18579/** 18580 * @ngdoc directive 18581 * @name ngClick 18582 * 18583 * @description 18584 * The ngClick directive allows you to specify custom behavior when 18585 * an element is clicked. 18586 * 18587 * @element ANY 18588 * @priority 0 18589 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 18590 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 18591 * 18592 * @example 18593 <example> 18594 <file name="index.html"> 18595 <button ng-click="count = count + 1" ng-init="count=0"> 18596 Increment 18597 </button> 18598 count: {{count}} 18599 </file> 18600 <file name="protractor.js" type="protractor"> 18601 it('should check ng-click', function() { 18602 expect(element(by.binding('count')).getText()).toMatch('0'); 18603 element(by.css('button')).click(); 18604 expect(element(by.binding('count')).getText()).toMatch('1'); 18605 }); 18606 </file> 18607 </example> 18608 */ 18609/* 18610 * A directive that allows creation of custom onclick handlers that are defined as angular 18611 * expressions and are compiled and executed within the current scope. 18612 * 18613 * Events that are handled via these handler are always configured not to propagate further. 18614 */ 18615var ngEventDirectives = {}; 18616forEach( 18617 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 18618 function(name) { 18619 var directiveName = directiveNormalize('ng-' + name); 18620 ngEventDirectives[directiveName] = ['$parse', function($parse) { 18621 return { 18622 compile: function($element, attr) { 18623 var fn = $parse(attr[directiveName]); 18624 return function(scope, element, attr) { 18625 element.on(lowercase(name), function(event) { 18626 scope.$apply(function() { 18627 fn(scope, {$event:event}); 18628 }); 18629 }); 18630 }; 18631 } 18632 }; 18633 }]; 18634 } 18635); 18636 18637/** 18638 * @ngdoc directive 18639 * @name ngDblclick 18640 * 18641 * @description 18642 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 18643 * 18644 * @element ANY 18645 * @priority 0 18646 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 18647 * a dblclick. (The Event object is available as `$event`) 18648 * 18649 * @example 18650 <example> 18651 <file name="index.html"> 18652 <button ng-dblclick="count = count + 1" ng-init="count=0"> 18653 Increment (on double click) 18654 </button> 18655 count: {{count}} 18656 </file> 18657 </example> 18658 */ 18659 18660 18661/** 18662 * @ngdoc directive 18663 * @name ngMousedown 18664 * 18665 * @description 18666 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 18667 * 18668 * @element ANY 18669 * @priority 0 18670 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 18671 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 18672 * 18673 * @example 18674 <example> 18675 <file name="index.html"> 18676 <button ng-mousedown="count = count + 1" ng-init="count=0"> 18677 Increment (on mouse down) 18678 </button> 18679 count: {{count}} 18680 </file> 18681 </example> 18682 */ 18683 18684 18685/** 18686 * @ngdoc directive 18687 * @name ngMouseup 18688 * 18689 * @description 18690 * Specify custom behavior on mouseup event. 18691 * 18692 * @element ANY 18693 * @priority 0 18694 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 18695 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 18696 * 18697 * @example 18698 <example> 18699 <file name="index.html"> 18700 <button ng-mouseup="count = count + 1" ng-init="count=0"> 18701 Increment (on mouse up) 18702 </button> 18703 count: {{count}} 18704 </file> 18705 </example> 18706 */ 18707 18708/** 18709 * @ngdoc directive 18710 * @name ngMouseover 18711 * 18712 * @description 18713 * Specify custom behavior on mouseover event. 18714 * 18715 * @element ANY 18716 * @priority 0 18717 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 18718 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 18719 * 18720 * @example 18721 <example> 18722 <file name="index.html"> 18723 <button ng-mouseover="count = count + 1" ng-init="count=0"> 18724 Increment (when mouse is over) 18725 </button> 18726 count: {{count}} 18727 </file> 18728 </example> 18729 */ 18730 18731 18732/** 18733 * @ngdoc directive 18734 * @name ngMouseenter 18735 * 18736 * @description 18737 * Specify custom behavior on mouseenter event. 18738 * 18739 * @element ANY 18740 * @priority 0 18741 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 18742 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 18743 * 18744 * @example 18745 <example> 18746 <file name="index.html"> 18747 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 18748 Increment (when mouse enters) 18749 </button> 18750 count: {{count}} 18751 </file> 18752 </example> 18753 */ 18754 18755 18756/** 18757 * @ngdoc directive 18758 * @name ngMouseleave 18759 * 18760 * @description 18761 * Specify custom behavior on mouseleave event. 18762 * 18763 * @element ANY 18764 * @priority 0 18765 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 18766 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 18767 * 18768 * @example 18769 <example> 18770 <file name="index.html"> 18771 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 18772 Increment (when mouse leaves) 18773 </button> 18774 count: {{count}} 18775 </file> 18776 </example> 18777 */ 18778 18779 18780/** 18781 * @ngdoc directive 18782 * @name ngMousemove 18783 * 18784 * @description 18785 * Specify custom behavior on mousemove event. 18786 * 18787 * @element ANY 18788 * @priority 0 18789 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 18790 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 18791 * 18792 * @example 18793 <example> 18794 <file name="index.html"> 18795 <button ng-mousemove="count = count + 1" ng-init="count=0"> 18796 Increment (when mouse moves) 18797 </button> 18798 count: {{count}} 18799 </file> 18800 </example> 18801 */ 18802 18803 18804/** 18805 * @ngdoc directive 18806 * @name ngKeydown 18807 * 18808 * @description 18809 * Specify custom behavior on keydown event. 18810 * 18811 * @element ANY 18812 * @priority 0 18813 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 18814 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 18815 * 18816 * @example 18817 <example> 18818 <file name="index.html"> 18819 <input ng-keydown="count = count + 1" ng-init="count=0"> 18820 key down count: {{count}} 18821 </file> 18822 </example> 18823 */ 18824 18825 18826/** 18827 * @ngdoc directive 18828 * @name ngKeyup 18829 * 18830 * @description 18831 * Specify custom behavior on keyup event. 18832 * 18833 * @element ANY 18834 * @priority 0 18835 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 18836 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 18837 * 18838 * @example 18839 <example> 18840 <file name="index.html"> 18841 <input ng-keyup="count = count + 1" ng-init="count=0"> 18842 key up count: {{count}} 18843 </file> 18844 </example> 18845 */ 18846 18847 18848/** 18849 * @ngdoc directive 18850 * @name ngKeypress 18851 * 18852 * @description 18853 * Specify custom behavior on keypress event. 18854 * 18855 * @element ANY 18856 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 18857 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 18858 * and can be interrogated for keyCode, altKey, etc.) 18859 * 18860 * @example 18861 <example> 18862 <file name="index.html"> 18863 <input ng-keypress="count = count + 1" ng-init="count=0"> 18864 key press count: {{count}} 18865 </file> 18866 </example> 18867 */ 18868 18869 18870/** 18871 * @ngdoc directive 18872 * @name ngSubmit 18873 * 18874 * @description 18875 * Enables binding angular expressions to onsubmit events. 18876 * 18877 * Additionally it prevents the default action (which for form means
18877sending the request to the 18878 * server and reloading the current page), but only if the form does not contain `action`, 18879 * `data-action`, or `x-action` attributes. 18880 * 18881 * @element form 18882 * @priority 0 18883 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 18884 * ({@link guide/expression#-event- Event object is available as `$event`}) 18885 * 18886 * @example 18887 <example> 18888 <file name="index.html"> 18889 <script> 18890 function Ctrl($scope) { 18891 $scope.list = []; 18892 $scope.text = 'hello'; 18893 $scope.submit = function() { 18894 if ($scope.text) { 18895 $scope.list.push(this.text); 18896 $scope.text = ''; 18897 } 18898 }; 18899 } 18900 </script> 18901 <form ng-submit="submit()" ng-controller="Ctrl"> 18902 Enter text and hit enter: 18903 <input type="text" ng-model="text" name="text" /> 18904 <input type="submit" id="submit" value="Submit" /> 18905 <pre>list={{list}}</pre> 18906 </form> 18907 </file> 18908 <file name="protractor.js" type="protractor"> 18909 it('should check ng-submit', function() { 18910 expect(element(by.binding('list')).getText()).toBe('list=[]'); 18911 element(by.css('#submit')).click(); 18912 expect(element(by.binding('list')).getText()).toContain('hello'); 18913 expect(element(by.input('text')).getAttribute('value')).toBe(''); 18914 }); 18915 it('should ignore empty strings', function() { 18916 expect(element(by.binding('list')).getText()).toBe('list=[]'); 18917 element(by.css('#submit')).click(); 18918 element(by.css('#submit')).click(); 18919 expect(element(by.binding('list')).getText()).toContain('hello'); 18920 }); 18921 </file> 18922 </example> 18923 */ 18924 18925/** 18926 * @ngdoc directive 18927 * @name ngFocus 18928 * 18929 * @description 18930 * Specify custom behavior on focus event. 18931 * 18932 * @element window, input, select, textarea, a 18933 * @priority 0 18934 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 18935 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 18936 * 18937 * @example 18938 * See {@link ng.directive:ngClick ngClick} 18939 */ 18940 18941/** 18942 * @ngdoc directive 18943 * @name ngBlur 18944 * 18945 * @description 18946 * Specify custom behavior on blur event. 18947 * 18948 * @element window, input, select, textarea, a 18949 * @priority 0 18950 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 18951 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 18952 * 18953 * @example 18954 * See {@link ng.directive:ngClick ngClick} 18955 */ 18956 18957/** 18958 * @ngdoc directive 18959 * @name ngCopy 18960 * 18961 * @description 18962 * Specify custom behavior on copy event. 18963 * 18964 * @element window, input, select, textarea, a 18965 * @priority 0 18966 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 18967 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 18968 * 18969 * @example 18970 <example> 18971 <file name="index.html"> 18972 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 18973 copied: {{copied}} 18974 </file> 18975 </example> 18976 */ 18977 18978/** 18979 * @ngdoc directive 18980 * @name ngCut 18981 * 18982 * @description 18983 * Specify custom behavior on cut event. 18984 * 18985 * @element window, input, select, textarea, a 18986 * @priority 0 18987 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 18988 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 18989 * 18990 * @example 18991 <example> 18992 <file name="index.html"> 18993 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 18994 cut: {{cut}} 18995 </file> 18996 </example> 18997 */ 18998 18999/** 19000 * @ngdoc directive 19001 * @name ngPaste 19002 * 19003 * @description 19004 * Specify custom behavior on paste event. 19005 * 19006 * @element window, input, select, textarea, a 19007 * @priority 0 19008 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 19009 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 19010 * 19011 * @example 19012 <example> 19013 <file name="index.html"> 19014 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 19015 pasted: {{paste}} 19016 </file> 19017 </example> 19018 */ 19019 19020/** 19021 * @ngdoc directive 19022 * @name ngIf 19023 * @restrict A 19024 * 19025 * @description 19026 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 19027 * {expression}. If the expression assigned to `ngIf` evaluates to a false 19028 * value then the element is removed from the DOM, otherwise a clone of the 19029 * element is reinserted into the DOM. 19030 * 19031 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 19032 * element in the DOM rather than changing its visibility via the `display` css property. A common 19033 * case when this difference is significant is when using css selectors that rely on an element's 19034 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
19034s. 19035 * 19036 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 19037 * is created when the element is restored. The scope created within `ngIf` inherits from 19038 * its parent scope using 19039 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance). 19040 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 19041 * a javascript primitive defined in the parent scope. In this case any modifications made to the 19042 * variable within the child scope will override (hide) the value in the parent scope. 19043 * 19044 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 19045 * is if an element's class attribute is directly modified after it's compiled, using something like 19046 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 19047 * the added class will be lost because the original compiled state is used to regenerate the element. 19048 * 19049 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 19050 * and `leave` effects. 19051 * 19052 * @animations 19053 * enter - happens just after the ngIf contents change and a new DOM element is created and injected into the ngIf container 19054 * leave - happens just before the ngIf contents are removed from the DOM 19055 * 19056 * @element ANY 19057 * @scope 19058 * @priority 600 19059 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 19060 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 19061 * element is added to the DOM tree. 19062 * 19063 * @example 19064 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 19065 <file name="index.html"> 19066 Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/> 19067 Show when checked: 19068 <span ng-if="checked" class="animate-if"> 19069 I'm removed when the checkbox is unchecked. 19070 </span> 19071 </file> 19072 <file name="animations.css"> 19073 .animate-if { 19074 background:white; 19075 border:1px solid black; 19076 padding:10px; 19077 } 19078 19079 .animate-if.ng-enter, .animate-if.ng-leave { 19080 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19081 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19082 } 19083 19084 .animate-if.ng-enter, 19085 .animate-if.ng-leave.ng-leave-active { 19086 opacity:0; 19087 } 19088 19089 .animate-if.ng-leave, 19090 .animate-if.ng-enter.ng-enter-active { 19091 opacity:1; 19092 } 19093 </file> 19094 </example> 19095 */ 19096var ngIfDirective = ['$animate', function($animate) { 19097 return { 19098 transclude: 'element', 19099 priority: 600, 19100 terminal: true, 19101 restrict: 'A', 19102 $$tlb: true, 19103 link: function ($scope, $element, $attr, ctrl, $transclude) { 19104 var block, childScope, previousElements; 19105 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 19106 19107 if (toBoolean(value)) { 19108 if (!childScope) { 19109 childScope = $scope.$new(); 19110 $transclude(childScope, function (clone) { 19111 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 19112 // Note: We only need the first/last node of the cloned nodes. 19113 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 19114 // by a directive with templateUrl when it's template arrives. 19115 block = { 19116 clone: clone 19117 }; 19118 $animate.enter(clone, $element.parent(), $element); 19119 }); 19120 } 19121 } else { 19122 if(previousElements) { 19123 previousElements.remove(); 19124 previousElements = null; 19125 } 19126 if(childScope) { 19127 childScope.$destroy(); 19128 childScope = null; 19129 } 19130 if(block) { 19131 previousElements = getBlockElements(block.clone); 19132 $animate.leave(previousElements, function() { 19133 previousElements = null; 19134 }); 19135 block = null; 19136 } 19137 } 19138 }); 19139 } 19140 }; 19141}]; 19142 19143/** 19144 * @ngdoc directive 19145 * @name ngInclude 19146 * @restrict ECA 19147 * 19148 * @description 19149 * Fetches, compiles and includes an external HTML fragment. 19150 * 19151 * By default, the template URL is restricted to the same domain and protocol as the 19152 * application document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 19153 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 19154 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 19155 * [wrap them](ng.$sce#trustAsResourceUrl) as trusted values. Refer to Angular's {@link 19156 * ng.$sce Strict Contextual Escaping}. 19157 * 19158 * In addition, the browser's 19159 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 19160 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 19161 * policy may further restrict whether the template is successfully loaded. 19162 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 19163 * access on some browsers. 19164 * 19165 * @animations 19166 * enter - animation is used to bring new content into the browser. 19167 * leave - animation is used to animate existing content away. 19168 * 19169 * The enter and leave animation occur concurrently. 19170 * 19171 * @scope 19172 * @priority 400 19173 * 19174 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 19175 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 19176 * @param {string=} onload Expression to evaluate when a new partial is loaded. 19177 * 19178 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 19179 * $anchorScroll} to scroll the viewport after the content is loaded. 19180 * 19181 * - If the attribute is not set, disable scrolling. 19182 * - If the attribute is set without value, enable scrolling. 19183 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 19184 * 19185 * @example 19186 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 19187 <file name="index.html"> 19188 <div ng-controller="Ctrl"> 19189 <select ng-model="template" ng-options="t.name for t in templates"> 19190 <option value="">(blank)</option> 19191 </select> 19192 url of the template: <tt>{{template.url}}</tt> 19193 <hr/> 19194 <div class="slide-animate-container"> 19195 <div class="slide-animate" ng-include="template.url"></div> 19196 </div> 19197 </div> 19198 </file> 19199 <file name="script.js"> 19200 function Ctrl($scope) { 19201 $scope.templates = 19202 [ { name: 'template1.html', url: 'template1.html'}, 19203 { name: 'template2.html', url: 'template2.html'} ]; 19204 $scope.template = $scope.templates[0]; 19205 } 19206 </file> 19207 <file name="template1.html"> 19208 Content of template1.html 19209 </file> 19210 <file name="template2.html"> 19211 Content of template2.html 19212 </file> 19213 <file name="animations.css"> 19214 .slide-animate-container { 19215 position:relative; 19216 background:white; 19217 border:1px solid black; 19218 height:40px; 19219 overflow:hidden; 19220 } 19221 19222 .slide-animate { 19223 padding:10px; 19224 } 19225 19226 .slide-animate.ng-enter, .slide-animate.ng-leave { 19227 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19228 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 19229 19230 position:absolute; 19231 top:0; 19232 left:0; 19233 right:0; 19234 bottom:0; 19235 display:block; 19236 padding:10px; 19237 } 19238 19239 .slide-animate.ng-enter { 19240 top:-50px; 19241 } 19242 .slide-animate.ng-enter.ng-enter-active { 19243 top:0; 19244 } 19245 19246 .slide-animate.ng-leave { 19247 top:0; 19248 } 19249 .slide-animate.ng-leave.ng-leave-active { 19250 top:50px; 19251 } 19252 </file> 19253 <file name="protractor.js" type="protractor"> 19254 var templateSelect = element(by.model('template'));
19255 var includeElem = element(by.css('[ng-include]')); 19256 19257 it('should load template1.html', function() { 19258 expect(includeElem.getText()).toMatch(/Content of template1.html/); 19259 }); 19260 19261 it('should load template2.html', function() { 19262 if (browser.params.browser == 'firefox') { 19263 // Firefox can't handle using selects 19264 // See https://github.com/angular/protractor/issues/480 19265 return; 19266 } 19267 templateSelect.click(); 19268 templateSelect.element.all(by.css('option')).get(2).click(); 19269 expect(includeElem.getText()).toMatch(/Content of template2.html/); 19270 }); 19271 19272 it('should change to blank', function() { 19273 if (browser.params.browser == 'firefox') { 19274 // Firefox can't handle using selects 19275 return; 19276 } 19277 templateSelect.click(); 19278 templateSelect.element.all(by.css('option')).get(0).click(); 19279 expect(includeElem.isPresent()).toBe(false); 19280 }); 19281 </file> 19282 </example> 19283 */ 19284 19285 19286/** 19287 * @ngdoc event 19288 * @name ngInclude#$includeContentRequested 19289 * @eventType emit on the scope ngInclude was declared in 19290 * @description 19291 * Emitted every time the ngInclude content is requested. 19292 */ 19293 19294 19295/** 19296 * @ngdoc event 19297 * @name ngInclude#$includeContentLoaded 19298 * @eventType emit on the current ngInclude scope 19299 * @description 19300 * Emitted every time the ngInclude content is reloaded. 19301 */ 19302var ngIncludeDirective = ['$http', '$templateCache', '$anchorScroll', '$animate', '$sce', 19303 function($http, $templateCache, $anchorScroll, $animate, $sce) { 19304 return { 19305 restrict: 'ECA', 19306 priority: 400, 19307 terminal: true, 19308 transclude: 'element', 19309 controller: angular.noop, 19310 compile: function(element, attr) { 19311 var srcExp = attr.ngInclude || attr.src, 19312 onloadExp = attr.onload || '', 19313 autoScrollExp = attr.autoscroll; 19314 19315 return function(scope, $element, $attr, ctrl, $transclude) { 19316 var changeCounter = 0, 19317 currentScope, 19318 previousElement, 19319 currentElement; 19320 19321 var cleanupLastIncludeContent = function() { 19322 if(previousElement) { 19323 previousElement.remove(); 19324 previousElement = null; 19325 } 19326 if(currentScope) { 19327 currentScope.$destroy(); 19328 currentScope = null; 19329 } 19330 if(currentElement) { 19331 $animate.leave(currentElement, function() { 19332 previousElement = null; 19333 }); 19334 previousElement = currentElement; 19335 currentElement = null; 19336 } 19337 }; 19338 19339 scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { 19340 var afterAnimation = function() { 19341 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 19342 $anchorScroll(); 19343 } 19344 }; 19345 var thisChangeId = ++changeCounter; 19346 19347 if (src) { 19348 $http.get(src, {cache: $templateCache}).success(function(response) { 19349 if (thisChangeId !== changeCounter) return; 19350 var newScope = scope.$new(); 19351 ctrl.template = response; 19352 19353 // Note: This will also link all children of ng-include that were contained in the original 19354 // html. If that content contains controllers, ... they could pollute/change the scope. 19355 // However, using ng-include on an element with additional content does not make sense... 19356 // Note: We can't remove them in the cloneAttchFn of $transclude as that 19357 // function is called before linking the content, which would apply child 19358 // directives to non existing elements. 19359 var clone = $transclude(newScope, function(clone) { 19360 cleanupLastIncludeContent(); 19361 $animate.enter(clone, null, $element, afterAnimation); 19362 }); 19363 19364 currentScope = newScope; 19365 currentElement = clone; 19366 19367 currentScope.$emit('$includeContentLoaded'); 19368 scope.$eval(onloadExp); 19369 }).error(function() { 19370 if (thisChangeId === changeCounter) cleanupLastIncludeContent(); 19371 }); 19372 scope.$emit('$includeContentRequested'); 19373 } else { 19374 cleanupLastIncludeContent(); 19375 ctrl.template = null; 19376 } 19377 }); 19378 }; 19379 } 19380 }; 19381}]; 19382 19383// This directive is called during the $transclude call of the first `ngInclude` directive. 19384// It will replace and compile the content of the element with the loaded template. 19385// We need this directive so that the element content is already filled when 19386// the link function of another directive on the same element as ngInclude 19387// is called. 19388var ngIncludeFillContentDirective = ['$compile', 19389 function($compile) { 19390 return { 19391 restrict: 'ECA', 19392 priority: -400, 19393 require: 'ngInclude', 19394 link: function(scope, $element, $attr, ctrl) { 19395 $element.html(ctrl.template); 19396 $compile($element.contents())(scope); 19397 } 19398 }; 19399 }]; 19400 19401/** 19402 * @ngdoc directive 19403 * @name ngInit 19404 * @restrict AC 19405 * 19406 * @description 19407 * The `ngInit` directive allows you to evaluate an expression in the 19408 * current scope. 19409 * 19410 * <div class="alert alert-error"> 19411 * The only appropriate use of `ngInit` is for aliasing special properties of 19412 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you 19413 * should use {@link guide/controller controllers} rather than `ngInit` 19414 * to initialize values on a scope. 19415 * </div> 19416 * <div class="alert alert-warning"> 19417 * **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
19418 * sure you have parenthesis for correct precedence: 19419 * <pre class="prettyprint"> 19420 * <div ng-init="test1 = (data | orderBy:'name')"></div> 19421 * </pre> 19422 * </div> 19423 * 19424 * @priority 450 19425 * 19426 * @element ANY 19427 * @param {expression} ngInit {@link guide/expression Expression} to eval. 19428 * 19429 * @example 19430 <example> 19431 <file name="index.html"> 19432 <script> 19433 function Ctrl($scope) { 19434 $scope.list = [['a', 'b'], ['c', 'd']]; 19435 } 19436 </script> 19437 <div ng-controller="Ctrl"> 19438 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 19439 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 19440 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 19441 </div> 19442 </div> 19443 </div> 19444 </file> 19445 <file name="protractor.js" type="protractor"> 19446 it('should alias index positions', function() { 19447 var elements = element.all(by.css('.example-init')); 19448 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 19449 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 19450 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 19451 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 19452 }); 19453 </file> 19454 </example> 19455 */ 19456var ngInitDirective = ngDirective({ 19457 priority: 450, 19458 compile: function() { 19459 return { 19460 pre: function(scope, element, attrs) { 19461 scope.$eval(attrs.ngInit); 19462 } 19463 }; 19464 } 19465}); 19466 19467/** 19468 * @ngdoc directive 19469 * @name ngNonBindable 19470 * @restrict AC 19471 * @priority 1000 19472 * 19473 * @description 19474 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 19475 * DOM element. This is useful if the element contains what appears to be Angular directives and 19476 * bindings but which should be ignored by Angular. This could be the case if you have a site that 19477 * displays snippets of code, for instance. 19478 * 19479 * @element ANY 19480 * 19481 * @example 19482 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 19483 * but the one wrapped in `ngNonBindable` is left alone. 19484 * 19485 * @example 19486 <example> 19487 <file name="index.html"> 19488 <div>Normal: {{1 + 2}}</div> 19489 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 19490 </file> 19491 <file name="protractor.js" type="protractor"> 19492 it('should check ng-non-bindable', function() { 19493 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 19494 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 19495 }); 19496 </file> 19497 </example> 19498 */ 19499var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 19500 19501/** 19502 * @ngdoc directive 19503 * @name ngPluralize 19504 * @restrict EA 19505 * 19506 * @description 19507 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 19508 * These rules are bundled with angular.js, but can be overridden 19509 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 19510 * by specifying the mappings between 19511 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 19512 * and the strings to be displayed. 19513 * 19514 * # Plural categories and explicit number rules 19515 * There are two 19516 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 19517 * in Angular's default en-US locale: "one" and "other". 19518 * 19519 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 19520 * any number that is not 1), an explicit number rule can only match one number. For example, the 19521 * explicit number rule for "3" matches the number 3. There are examples of plural categories 19522 * and explicit number rules throughout the rest of this documentation. 19523 * 19524 * # Configuring ngPluralize 19525 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 19526 * You can also provide an optional attribute, `offset`. 19527 * 19528 * The value of the `count` attribute can be either a string or an {@link guide/expression 19529 * Angular expression}; these are evaluated on the current scope for its bound value. 19530 * 19531 * The `when` attribute specifies the mappings between plural categories and the actual 19532 * string to be displayed. The value of the attribute should be a JSON object. 19533 * 19534 * The following example shows how to configure ngPluralize: 19535 * 19536 * ```html 19537 * <ng-pluralize count="personCount"
19538 when="{'0': 'Nobody is viewing.', 19539 * 'one': '1 person is viewing.', 19540 * 'other': '{} people are viewing.'}"> 19541 * </ng-pluralize> 19542 *``` 19543 * 19544 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 19545 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 19546 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 19547 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 19548 * show "a dozen people are viewing". 19549 * 19550 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 19551 * into pluralized strings. In the previous example, Angular will replace `{}` with 19552 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 19553 * for <span ng-non-bindable>{{numberExpression}}</span>. 19554 * 19555 * # Configuring ngPluralize with offset 19556 * The `offset` attribute allows further customization of pluralized text, which can result in 19557 * a better user experience. For example, instead of the message "4 people are viewing this document", 19558 * you might display "John, Kate and 2 others are viewing this document". 19559 * The offset attribute allows you to offset a number by any desired value. 19560 * Let's take a look at an example: 19561 * 19562 * ```html 19563 * <ng-pluralize count="personCount" offset=2 19564 * when="{'0': 'Nobody is viewing.', 19565 * '1': '{{person1}} is viewing.', 19566 * '2': '{{person1}} and {{person2}} are viewing.', 19567 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 19568 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 19569 * </ng-pluralize> 19570 * ``` 19571 * 19572 * Notice that we are still using two plural categories(one, other), but we added 19573 * three explicit number rules 0, 1 and 2. 19574 * When one person, perhaps John, views the document, "John is viewing" will be shown. 19575 * When three people view the document, no explicit number rule is found, so 19576 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 19577 * In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" 19578 * is shown. 19579 * 19580 * Note that when you specify offsets, you must provide explicit number rules for 19581 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 19582 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 19583 * plural categories "one" and "other". 19584 * 19585 * @param {string|expression} count The variable to be bound to. 19586 * @param {string} when The mapping between plural category to its corresponding strings. 19587 * @param {number=} offset Offset to deduct from the total number. 19588 * 19589 * @example 19590 <example> 19591 <file name="index.html"> 19592 <script> 19593 function Ctrl($scope) { 19594 $scope.person1 = 'Igor'; 19595 $scope.person2 = 'Misko'; 19596 $scope.personCount = 1; 19597 } 19598 </script> 19599 <div ng-controller="Ctrl"> 19600 Person 1:<input type="text" ng-model="person1" value="Igor" /><br/> 19601 Person 2:<input type="text" ng-model="person2" value="Misko" /><br/> 19602 Number of People:<input type="text" ng-model="personCount" value="1" /><br/> 19603 19604 <!--- Example with simple pluralization rules for en locale ---> 19605 Without Offset: 19606 <ng-pluralize count="personCount" 19607 when="{'0': 'Nobody is viewing.', 19608 'one': '1 person is viewing.', 19609 'other': '{} people are viewing.'}"> 19610 </ng-pluralize><br> 19611 19612 <!--- Example with offset ---> 19613 With Offset(2): 19614 <ng-pluralize count="personCount" offset=2 19615 when="{'0': 'Nobody is viewing.', 19616 '1': '{{person1}} is viewing.', 19617 '2': '{{person1}} and {{person2}} are viewing.', 19618 'one': '{{person1}}, {{person2}} and one other person are viewing.',
19619 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 19620 </ng-pluralize> 19621 </div> 19622 </file> 19623 <file name="protractor.js" type="protractor"> 19624 it('should show correct pluralized string', function() { 19625 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 19626 var withOffset = element.all(by.css('ng-pluralize')).get(1); 19627 var countInput = element(by.model('personCount')); 19628 19629 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 19630 expect(withOffset.getText()).toEqual('Igor is viewing.'); 19631 19632 countInput.clear(); 19633 countInput.sendKeys('0'); 19634 19635 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 19636 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 19637 19638 countInput.clear(); 19639 countInput.sendKeys('2'); 19640 19641 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 19642 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 19643 19644 countInput.clear(); 19645 countInput.sendKeys('3'); 19646 19647 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 19648 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 19649 19650 countInput.clear(); 19651 countInput.sendKeys('4'); 19652 19653 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 19654 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 19655 }); 19656 it('should show data-bound names', function() { 19657 var withOffset = element.all(by.css('ng-pluralize')).get(1); 19658 var personCount = element(by.model('personCount')); 19659 var person1 = element(by.model('person1')); 19660 var person2 = element(by.model('person2')); 19661 personCount.clear(); 19662 personCount.sendKeys('4'); 19663 person1.clear(); 19664 person1.sendKeys('Di'); 19665 person2.clear(); 19666 person2.sendKeys('Vojta'); 19667 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 19668 }); 19669 </file> 19670 </example> 19671 */ 19672var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { 19673 var BRACE = /{}/g; 19674 return { 19675 restrict: 'EA', 19676 link: function(scope, element, attr) { 19677 var numberExp = attr.count, 19678 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 19679 offset = attr.offset || 0, 19680 whens = scope.$eval(whenExp) || {}, 19681 whensExpFns = {}, 19682 startSymbol = $interpolate.startSymbol(), 19683 endSymbol = $interpolate.endSymbol(), 19684 isWhen = /^when(Minus)?(.+)$/; 19685 19686 forEach(attr, function(expression, attributeName) { 19687 if (isWhen.test(attributeName)) { 19688 whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = 19689 element.attr(attr.$attr[attributeName]); 19690 } 19691 }); 19692 forEach(whens, function(expression, key) { 19693 whensExpFns[key] = 19694 $interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' + 19695 offset + endSymbol)); 19696 }); 19697 19698 scope.$watch(function ngPluralizeWatch() { 19699 var value = parseFloat(scope.$eval(numberExp)); 19700 19701 if (!isNaN(value)) { 19702 //if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, 19703 //check it against pluralization rules in $locale service 19704 if (!(value in whens)) value = $locale.pluralCat(value - offset); 19705 return whensExpFns[value](scope, element, true); 19706 } else { 19707 return ''; 19708 } 19709 }, function ngPluralizeWatchAction(newVal) { 19710 element.text(newVal); 19711 }); 19712 } 19713 }; 19714}]; 19715 19716/** 19717 * @ngdoc directive 19718 * @name ngRepeat 19719 * 19720 * @description 19721 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 19722 * instance gets its own scope, where the given loop variable is set to the current collection item, 19723 * and `$index` is set to the item index or key. 19724 * 19725 * Special properties are exposed on the local scope of each template instance, including: 19726 * 19727 * | Variable | Type | Details | 19728 * |-----------|-----------------|-----------------------------------------------------------------------------| 19729 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 19730 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 19731 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 19732 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 19733 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 19734 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 19735 *
19736 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 19737 * This may be useful when, for instance, nesting ngRepeats. 19738 * 19739 * # Special repeat start and end points 19740 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 19741 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 19742 * 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) 19743 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 19744 * 19745 * The example below makes use of this feature: 19746 * ```html 19747 * <header ng-repeat-start="item in items"> 19748 * Header {{ item }} 19749 * </header> 19750 * <div class="body"> 19751 * Body {{ item }} 19752 * </div> 19753 * <footer ng-repeat-end> 19754 * Footer {{ item }} 19755 * </footer> 19756 * ``` 19757 * 19758 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 19759 * ```html 19760 * <header> 19761 * Header A 19762 * </header> 19763 * <div class="body"> 19764 * Body A 19765 * </div> 19766 * <footer> 19767 * Footer A 19768 * </footer> 19769 * <header> 19770 * Header B 19771 * </header> 19772 * <div class="body"> 19773 * Body B 19774 * </div> 19775 * <footer> 19776 * Footer B 19777 * </footer> 19778 * ``` 19779 * 19780 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 19781 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 19782 * 19783 * @animations 19784 * **.enter** - when a new item is added to the list or when an item is revealed after a filter 19785 * 19786 * **.leave** - when an item is removed from the list or when an item is filtered out 19787 * 19788 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 19789 * 19790 * @element ANY 19791 * @scope 19792 * @priority 1000 19793 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 19794 * formats are currently supported: 19795 * 19796 * * `variable in expression` â where variable is the user defined loop variable and `expression` 19797 * is a scope expression giving the collection to enumerate. 19798 * 19799 * For example: `album in artist.albums`. 19800 * 19801 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 19802 * and `expression` is the scope expression giving the collection to enumerate. 19803 * 19804 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 19805 * 19806 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking function 19807 * which can be used to associate the objects in the collection with the DOM elements. If no tracking function 19808 * is specified the ng-repeat associates elements by identity in the collection. It is an error to have 19809 * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are 19810 * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, 19811 * before specifying a tracking expression. 19812 * 19813 * For example: `item in items` is equivalent to `item in items track by $id(item)'. This implies that the DOM elements 19814 * will be associated by item identity in the array. 19815 * 19816 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 19817 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 19818 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 19819 * element in the same way in the DOM. 19820 * 19821 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 19822 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 19823 * property is same. 19824 * 19825 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 19826 * to items in conjunction with a tracking expression. 19827 * 19828 * @example 19829 * This example initializes the scope to a list of names and 19830 * then uses `ngRepeat` to display every person: 19831 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 19832 <file name="index.html"> 19833 <div ng-init="friends = [ 19834 {name:'John', age:25, gender:'boy'}, 19835 {name:'Jessie', age:30, gender:'girl'}, 19836 {name:'Johanna', age:28, gender:'girl'}, 19837 {name:'Joy', age:15, gender:'girl'}, 19838 {name:'Mary', age:28, gender:'girl'}, 19839 {name:'Peter', age:95, gender:'boy'}, 19840 {name:'Sebastian', age:50, gender:'boy'}, 19841 {name:'Erika', age:27, gender:'girl'}, 19842 {name:'Patrick', age:40, gender:'boy'}, 19843 {name:'Samantha', age:60, gender:'girl'} 19844 ]"> 19845 I have {{friends.length}} friends. They are: 19846 <input type="search" ng-model="q" placeholder="filter friends..." /> 19847 <ul class="example-animate-container"> 19848 <li class="animate-repeat" ng-repeat="friend in friends | filter:q"> 19849 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 19850 </li> 19851 </ul> 19852 </div> 19853 </file> 19854 <file name="animations.css"> 19855 .example-animate-container { 19856 background:white; 19857 border:1px solid black; 19858 list-style:none; 19859 margin:0; 19860 padding:0 10px; 19861 } 19862 19863 .animate-repeat { 19864 line-height:40px; 19865 list-style:none; 19866 box-sizing:border-box; 19867 } 19868 19869 .animate-repeat.ng-move, 19870 .animate-repeat.ng-enter, 19871 .animate-repeat.ng-leave { 19872 -webkit-transition:all linear 0.5s; 19873 transition:all linear 0.5s; 19874 } 19875 19876 .animate-repeat.ng-leave.ng-leave-active, 19877 .animate-repeat.ng-move, 19878 .animate-repeat.ng-enter { 19879 opacity:0; 19880 max-height:0; 19881 } 19882 19883 .animate-repeat.ng-leave,
19884 .animate-repeat.ng-move.ng-move-active, 19885 .animate-repeat.ng-enter.ng-enter-active { 19886 opacity:1; 19887 max-height:40px; 19888 } 19889 </file> 19890 <file name="protractor.js" type="protractor"> 19891 var friends = element.all(by.repeater('friend in friends')); 19892 19893 it('should render initial data set', function() { 19894 expect(friends.count()).toBe(10); 19895 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 19896 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 19897 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 19898 expect(element(by.binding('friends.length')).getText()) 19899 .toMatch("I have 10 friends. They are:"); 19900 }); 19901 19902 it('should update repeater when filter predicate changes', function() { 19903 expect(friends.count()).toBe(10); 19904 19905 element(by.model('q')).sendKeys('ma'); 19906 19907 expect(friends.count()).toBe(2); 19908 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 19909 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 19910 }); 19911 </file> 19912 </example> 19913 */ 19914var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 19915 var NG_REMOVED = '$$NG_REMOVED'; 19916 var ngRepeatMinErr = minErr('ngRepeat'); 19917 return { 19918 transclude: 'element', 19919 priority: 1000, 19920 terminal: true, 19921 $$tlb: true, 19922 link: function($scope, $element, $attr, ctrl, $transclude){ 19923 var expression = $attr.ngRepeat; 19924 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/), 19925 trackByExp, trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn, 19926 lhs, rhs, valueIdentifier, keyIdentifier, 19927 hashFnLocals = {$id: hashKey}; 19928 19929 if (!match) { 19930 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 19931 expression); 19932 } 19933 19934 lhs = match[1]; 19935 rhs = match[2]; 19936 trackByExp = match[3]; 19937 19938 if (trackByExp) { 19939 trackByExpGetter = $parse(trackByExp); 19940 trackByIdExpFn = function(key, value, index) { 19941 // assign key, value, and $index to the locals so that they can be used in hash functions 19942 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 19943 hashFnLocals[valueIdentifier] = value; 19944 hashFnLocals.$index = index; 19945 return trackByExpGetter($scope, hashFnLocals); 19946 }; 19947 } else { 19948 trackByIdArrayFn = function(key, value) { 19949 return hashKey(value); 19950 }; 19951 trackByIdObjFn = function(key) { 19952 return key; 19953 }; 19954 } 19955 19956 match = lhs.match(/^(?:([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\))$/); 19957 if (!match) { 19958 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 19959 lhs); 19960 } 19961 valueIdentifier = match[3] || match[1]; 19962 keyIdentifier = match[2]; 19963 19964 // Store a list of elements from previous run. This is a hash where key is the item from the 19965 // iterator, and the value is objects with following properties. 19966 // - scope: bound scope 19967 // - element: previous element. 19968 // - index: position 19969 var lastBlockMap = {}; 19970 19971 //watch props 19972 $scope.$watchCollection(rhs, function ngRepeatAction(collection){ 19973 var index, length, 19974 previousNode = $element[0], // current position of the node 19975 nextNode, 19976 // Same as lastBlockMap but it has the current state. It will become the 19977 // lastBlockMap on the next iteration. 19978 nextBlockMap = {}, 19979 arrayLength, 19980 childScope, 19981 key, value, // key/value of iteration 19982 trackById, 19983 trackByIdFn, 19984 collectionKeys, 19985 block, // last object information {scope, element, id} 19986 nextBlockOrder = [], 19987 elementsToRemove; 19988 19989 19990 if (isArrayLike(collection)) { 19991 collectionKeys = collection; 19992 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 19993 } else { 19994 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 19995 // if object, extract keys, sort them and use to determine order of iteration over obj props 19996 collectionKeys = []; 19997 for (key in collection) { 19998 if (collection.hasOwnProperty(key) && key.charAt(0) != '$') { 19999 collectionKeys.push(key); 20000 } 20001 } 20002 collectionKeys.sort(); 20003 } 20004 20005 arrayLength = collectionKeys.length; 20006 20007 // locate existing items 20008 length = nextBlockOrder.length = collectionKeys.length; 20009 for(index = 0; index < length; index++) { 20010 key = (collection === collectionKeys) ? index : collectionKeys[index]; 20011 value = collection[key]; 20012 trackById = trackByIdFn(key, value, index); 20013 assertNotHasOwnProperty(trackById, '`track by` id'); 20014 if(lastBlockMap.hasOwnProperty(trackById)) { 20015 block = lastBlockMap[trackById]; 20016 delete lastBlockMap[trackById]; 20017 nextBlockMap[trackById] = block; 20018 nextBlockOrder[index] = block; 20019 } else if (nextBlockMap.hasOwnProperty(trackById)) { 20020 // restore lastBlockMap
20021 forEach(nextBlockOrder, function(block) { 20022 if (block && block.scope) lastBlockMap[block.id] = block; 20023 }); 20024 // This is a duplicate and we need to throw an error 20025 throw ngRepeatMinErr('dupes', "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}", 20026 expression, trackById); 20027 } else { 20028 // new never before seen block 20029 nextBlockOrder[index] = { id: trackById }; 20030 nextBlockMap[trackById] = false; 20031 } 20032 } 20033 20034 // remove existing items 20035 for (key in lastBlockMap) { 20036 // lastBlockMap is our own object so we don't need to use special hasOwnPropertyFn 20037 if (lastBlockMap.hasOwnProperty(key)) { 20038 block = lastBlockMap[key]; 20039 elementsToRemove = getBlockElements(block.clone); 20040 $animate.leave(elementsToRemove); 20041 forEach(elementsToRemove, function(element) { element[NG_REMOVED] = true; }); 20042 block.scope.$destroy(); 20043 } 20044 } 20045 20046 // we are not using forEach for perf reasons (trying to avoid #call) 20047 for (index = 0, length = collectionKeys.length; index < length; index++) { 20048 key = (collection === collectionKeys) ? index : collectionKeys[index]; 20049 value = collection[key]; 20050 block = nextBlockOrder[index]; 20051 if (nextBlockOrder[index - 1]) previousNode = getBlockEnd(nextBlockOrder[index - 1]); 20052 20053 if (block.scope) { 20054 // if we have already seen this object, then we need to reuse the 20055 // associated scope/element 20056 childScope = block.scope; 20057 20058 nextNode = previousNode; 20059 do { 20060 nextNode = nextNode.nextSibling; 20061 } while(nextNode && nextNode[NG_REMOVED]); 20062 20063 if (getBlockStart(block) != nextNode) { 20064 // existing item which got moved 20065 $animate.move(getBlockElements(block.clone), null, jqLite(previousNode)); 20066 } 20067 previousNode = getBlockEnd(block); 20068 } else { 20069 // new item which we don't know about 20070 childScope = $scope.$new(); 20071 } 20072 20073 childScope[valueIdentifier] = value; 20074 if (keyIdentifier) childScope[keyIdentifier] = key; 20075 childScope.$index = index; 20076 childScope.$first = (index === 0); 20077 childScope.$last = (index === (arrayLength - 1)); 20078 childScope.$middle = !(childScope.$first || childScope.$last); 20079 // jshint bitwise: false 20080 childScope.$odd = !(childScope.$even = (index&1) === 0); 20081 // jshint bitwise: true 20082 20083 if (!block.scope) { 20084 $transclude(childScope, function(clone) { 20085 clone[clone.length++] = document.createComment(' end ngRepeat: ' + expression + ' '); 20086 $animate.enter(clone, null, jqLite(previousNode)); 20087 previousNode = clone; 20088 block.scope = childScope; 20089 // Note: We only need the first/last node of the cloned nodes. 20090 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 20091 // by a directive with templateUrl when it's template arrives. 20092 block.clone = clone; 20093 nextBlockMap[block.id] = block; 20094 }); 20095 } 20096 } 20097 lastBlockMap = nextBlockMap; 20098 }); 20099 } 20100 }; 20101 20102 function getBlockStart(block) { 20103 return block.clone[0]; 20104 } 20105 20106 function getBlockEnd(block) { 20107 return block.clone[block.clone.length - 1]; 20108 } 20109}]; 20110 20111/** 20112 * @ngdoc directive 20113 * @name ngShow 20114 * 20115 * @description 20116 * The `ngShow` directive shows or hides the given HTML element based on the expression 20117 * provided to the ngShow attribute. The element is shown or hidden by removing or adding 20118 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 20119 * in AngularJS and sets the display style to none (using an !important flag). 20120 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 20121 * 20122 * ```html 20123 * <!-- when $scope.myValue is truthy (element is visible) --> 20124 * <div ng-show="myValue"></div> 20125 * 20126 * <!-- when $scope.myValue is falsy (element is hidden) --> 20127 * <div ng-show="myValue" class="ng-hide"></div> 20128 * ``` 20129 * 20130 * When the ngShow expression evaluates to false then the ng-hide CSS class is added to the class attribute 20131 * on the element causing it to become hidden. When true, the ng-hide CSS class is removed 20132 * from the element causing the element not to appear hidden. 20133 * 20134 * ## Why is !important used? 20135 * 20136 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 20137 * can be easily overridden by heavier selectors. For example, something as simple 20138 * as changing the display style on a HTML list item would make hidden elements appear visible.
20139 * This also becomes a bigger issue when dealing with CSS frameworks. 20140 * 20141 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 20142 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 20143 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 20144 * 20145 * ### Overriding .ng-hide 20146 * 20147 * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by 20148 * restating the styles for the .ng-hide class in CSS: 20149 * ```css 20150 * .ng-hide { 20151 * //!annotate CSS Specificity|Not to worry, this will override the AngularJS default... 20152 * display:block!important; 20153 * 20154 * //this is just another form of hiding an element 20155 * position:absolute; 20156 * top:-9999px; 20157 * left:-9999px; 20158 * } 20159 * ``` 20160 * 20161 * Just remember to include the important flag so the CSS override will function. 20162 * 20163 * <div class="alert alert-warning"> 20164 * **Note:** Here is a list of values that ngShow will consider as a falsy value (case insensitive):<br /> 20165 * "f" / "0" / "false" / "no" / "n" / "[]" 20166 * </div> 20167 * 20168 * ## A note about animations with ngShow 20169 * 20170 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 20171 * is true and false. This system works like the animation system present with ngClass except that 20172 * you must also include the !important flag to override the display property 20173 * so that you can perform an animation when the element is hidden during the time of the animation. 20174 * 20175 * ```css 20176 * // 20177 * //a working example can be found at the bottom of this page 20178 * // 20179 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 20180 * transition:0.5s linear all; 20181 * display:block!important; 20182 * } 20183 * 20184 * .my-element.ng-hide-add { ... } 20185 * .my-element.ng-hide-add.ng-hide-add-active { ... } 20186 * .my-element.ng-hide-remove { ... } 20187 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 20188 * ``` 20189 * 20190 * @animations 20191 * addClass: .ng-hide - happens after the ngShow expression evaluates to a truthy value and the just before contents are set to visible 20192 * removeClass: .ng-hide - happens after the ngShow expression evaluates to a non truthy value and just before the contents are set to hidden 20193 * 20194 * @element ANY 20195 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 20196 * then the element is shown or hidden respectively. 20197 * 20198 * @example 20199 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20200 <file name="index.html"> 20201 Click me: <input type="checkbox" ng-model="checked"><br/> 20202 <div> 20203 Show: 20204 <div class="check-element animate-show" ng-show="checked"> 20205 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 20206 </div> 20207 </div> 20208 <div> 20209 Hide: 20210 <div class="check-element animate-show" ng-hide="checked"> 20211 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 20212 </div> 20213 </div> 20214 </file> 20215 <file name="glyphicons.css"> 20216 @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); 20217 </file> 20218 <file name="animations.css"> 20219 .animate-show { 20220 -webkit-transition:all linear 0.5s; 20221 transition:all linear 0.5s; 20222 line-height:20px; 20223 opacity:1; 20224 padding:10px; 20225 border:1px solid black; 20226 background:white; 20227 } 20228 20229 .animate-show.ng-hide-add, 20230 .animate-show.ng-hide-remove { 20231 display:block!important; 20232 } 20233 20234 .animate-show.ng-hide { 20235 line-height:0; 20236 opacity:0; 20237 padding:0 10px; 20238 } 20239 20240 .check-element { 20241 padding:10px; 20242 border:1px solid black; 20243 background:white; 20244 } 20245 </file> 20246 <file name="protractor.js" type="protractor"> 20247 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 20248 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 20249 20250 it('should check ng-show / ng-hide', function() { 20251 expect(thumbsUp.isDisplayed()).toBeFalsy(); 20252 expect(thumbsDown.isDisplayed()).toBeTruthy(); 20253
20254 element(by.model('checked')).click(); 20255 20256 expect(thumbsUp.isDisplayed()).toBeTruthy(); 20257 expect(thumbsDown.isDisplayed()).toBeFalsy(); 20258 }); 20259 </file> 20260 </example> 20261 */ 20262var ngShowDirective = ['$animate', function($animate) { 20263 return function(scope, element, attr) { 20264 scope.$watch(attr.ngShow, function ngShowWatchAction(value){ 20265 $animate[toBoolean(value) ? 'removeClass' : 'addClass'](element, 'ng-hide'); 20266 }); 20267 }; 20268}]; 20269 20270 20271/** 20272 * @ngdoc directive 20273 * @name ngHide 20274 * 20275 * @description 20276 * The `ngHide` directive shows or hides the given HTML element based on the expression 20277 * provided to the ngHide attribute. The element is shown or hidden by removing or adding 20278 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 20279 * in AngularJS and sets the display style to none (using an !important flag). 20280 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 20281 * 20282 * ```html 20283 * <!-- when $scope.myValue is truthy (element is hidden) --> 20284 * <div ng-hide="myValue"></div> 20285 * 20286 * <!-- when $scope.myValue is falsy (element is visible) --> 20287 * <div ng-hide="myValue" class="ng-hide"></div> 20288 * ``` 20289 * 20290 * When the ngHide expression evaluates to true then the .ng-hide CSS class is added to the class attribute 20291 * on the element causing it to become hidden. When false, the ng-hide CSS class is removed 20292 * from the element causing the element not to appear hidden. 20293 * 20294 * ## Why is !important used? 20295 * 20296 * You may be wondering why !important is used for the .ng-hide CSS class. This is because the `.ng-hide` selector 20297 * can be easily overridden by heavier selectors. For example, something as simple 20298 * as changing the display style on a HTML list item would make hidden elements appear visible. 20299 * This also becomes a bigger issue when dealing with CSS frameworks. 20300 * 20301 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 20302 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 20303 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 20304 * 20305 * ### Overriding .ng-hide 20306 * 20307 * If you wish to change the hide behavior with ngShow/ngHide then this can be achieved by 20308 * restating the styles for the .ng-hide class in CSS: 20309 * ```css 20310 * .ng-hide { 20311 * //!annotate CSS Specificity|Not to worry, this will override the AngularJS default... 20312 * display:block!important; 20313 * 20314 * //this is just another form of hiding an element 20315 * position:absolute; 20316 * top:-9999px; 20317 * left:-9999px; 20318 * } 20319 * ``` 20320 * 20321 * Just remember to include the important flag so the CSS override will function. 20322 * 20323 * <div class="alert alert-warning"> 20324 * **Note:** Here is a list of values that ngHide will consider as a falsy value (case insensitive):<br /> 20325 * "f" / "0" / "false" / "no" / "n" / "[]" 20326 * </div> 20327 * 20328 * ## A note about animations with ngHide 20329 * 20330 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 20331 * is true and false. This system works like the animation system present with ngClass, except that 20332 * you must also include the !important flag to override the display property so 20333 * that you can perform an animation when the element is hidden during the time of the animation. 20334 * 20335 * ```css 20336 * // 20337 * //a working example can be found at the bottom of this page 20338 * // 20339 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 20340 * transition:0.5s linear all; 20341 * display:block!important; 20342 * } 20343 * 20344 * .my-element.ng-hide-add { ... } 20345 * .my-element.ng-hide-add.ng-hide-add-active { ... } 20346 * .my-element.ng-hide-remove { ... } 20347 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 20348 * ``` 20349 * 20350 * @animations 20351 * removeClass: .ng-hide - happens after the ngHide expression evaluates to a truthy value and just before the contents are set to hidden 20352 * addClass: .ng-hide - happens after the ngHide expression evaluates to a non truthy value and just before the contents are set to visible 20353 * 20354 * @element ANY 20355 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 20356 * the element is shown or hidden respectively. 20357 * 20358 * @example 20359 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20360 <file name="index.html"> 20361 Click me: <input type="checkbox" ng-model="checked"><br/> 20362 <div> 20363 Show: 20364 <div class="check-element animate-hide" ng-show="checked">
20365 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 20366 </div> 20367 </div> 20368 <div> 20369 Hide: 20370 <div class="check-element animate-hide" ng-hide="checked"> 20371 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 20372 </div> 20373 </div> 20374 </file> 20375 <file name="glyphicons.css"> 20376 @import url(//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-glyphicons.css); 20377 </file> 20378 <file name="animations.css"> 20379 .animate-hide { 20380 -webkit-transition:all linear 0.5s; 20381 transition:all linear 0.5s; 20382 line-height:20px; 20383 opacity:1; 20384 padding:10px; 20385 border:1px solid black; 20386 background:white; 20387 } 20388 20389 .animate-hide.ng-hide-add, 20390 .animate-hide.ng-hide-remove { 20391 display:block!important; 20392 } 20393 20394 .animate-hide.ng-hide { 20395 line-height:0; 20396 opacity:0; 20397 padding:0 10px; 20398 } 20399 20400 .check-element { 20401 padding:10px; 20402 border:1px solid black; 20403 background:white; 20404 } 20405 </file> 20406 <file name="protractor.js" type="protractor"> 20407 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 20408 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 20409 20410 it('should check ng-show / ng-hide', function() { 20411 expect(thumbsUp.isDisplayed()).toBeFalsy(); 20412 expect(thumbsDown.isDisplayed()).toBeTruthy(); 20413 20414 element(by.model('checked')).click(); 20415 20416 expect(thumbsUp.isDisplayed()).toBeTruthy(); 20417 expect(thumbsDown.isDisplayed()).toBeFalsy(); 20418 }); 20419 </file> 20420 </example> 20421 */ 20422var ngHideDirective = ['$animate', function($animate) { 20423 return function(scope, element, attr) { 20424 scope.$watch(attr.ngHide, function ngHideWatchAction(value){ 20425 $animate[toBoolean(value) ? 'addClass' : 'removeClass'](element, 'ng-hide'); 20426 }); 20427 }; 20428}]; 20429 20430/** 20431 * @ngdoc directive 20432 * @name ngStyle 20433 * @restrict AC 20434 * 20435 * @description 20436 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 20437 * 20438 * @element ANY 20439 * @param {expression} ngStyle {@link guide/expression Expression} which evals to an 20440 * object whose keys are CSS style names and values are corresponding values for those CSS 20441 * keys. 20442 * 20443 * @example 20444 <example> 20445 <file name="index.html"> 20446 <input type="button" value="set" ng-click="myStyle={color:'red'}"> 20447 <input type="button" value="clear" ng-click="myStyle={}"> 20448 <br/> 20449 <span ng-style="myStyle">Sample Text</span> 20450 <pre>myStyle={{myStyle}}</pre> 20451 </file> 20452 <file name="style.css"> 20453 span { 20454 color: black; 20455 } 20456 </file> 20457 <file name="protractor.js" type="protractor"> 20458 var colorSpan = element(by.css('span')); 20459 20460 it('should check ng-style', function() { 20461 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 20462 element(by.css('input[value=set]')).click(); 20463 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 20464 element(by.css('input[value=clear]')).click(); 20465 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 20466 }); 20467 </file> 20468 </example> 20469 */ 20470var ngStyleDirective = ngDirective(function(scope, element, attr) { 20471 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 20472 if (oldStyles && (newStyles !== oldStyles)) { 20473 forEach(oldStyles, function(val, style) { element.css(style, '');}); 20474 } 20475 if (newStyles) element.css(newStyles); 20476 }, true); 20477}); 20478 20479/** 20480 * @ngdoc directive 20481 * @name ngSwitch 20482 * @restrict EA 20483 * 20484 * @description 20485 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 20486 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 20487 * as specified in the template. 20488 * 20489 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 20490 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 20491 * matches the value obtained from the evaluated expression. In other words, you define a container element 20492 * (where you place the directive), place an expression on the **`on="..."` attribute** 20493 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 20494 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 20495 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 20496 * attribute is displayed. 20497 * 20498 * <div class="alert alert-info"> 20499 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 20500 * as literal string values to match against. 20501 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
20501omeVal"` not against the 20502 * value of the expression `$scope.someVal`. 20503 * </div> 20504 20505 * @animations 20506 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 20507 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 20508 * 20509 * @usage 20510 * <ANY ng-switch="expression"> 20511 * <ANY ng-switch-when="matchValue1">...</ANY> 20512 * <ANY ng-switch-when="matchValue2">...</ANY> 20513 * <ANY ng-switch-default>...</ANY> 20514 * </ANY> 20515 * 20516 * 20517 * @scope 20518 * @priority 800 20519 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>. 20520 * On child elements add: 20521 * 20522 * * `ngSwitchWhen`: the case statement to match against. If match then this 20523 * case will be displayed. If the same match appears multiple times, all the 20524 * elements will be displayed. 20525 * * `ngSwitchDefault`: the default case when no other case match. If there 20526 * are multiple default cases, all of them will be displayed when no other 20527 * case match. 20528 * 20529 * 20530 * @example 20531 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20532 <file name="index.html"> 20533 <div ng-controller="Ctrl"> 20534 <select ng-model="selection" ng-options="item for item in items"> 20535 </select> 20536 <tt>selection={{selection}}</tt> 20537 <hr/> 20538 <div class="animate-switch-container" 20539 ng-switch on="selection"> 20540 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 20541 <div class="animate-switch" ng-switch-when="home">Home Span</div> 20542 <div class="animate-switch" ng-switch-default>default</div> 20543 </div> 20544 </div> 20545 </file> 20546 <file name="script.js"> 20547 function Ctrl($scope) { 20548 $scope.items = ['settings', 'home', 'other']; 20549 $scope.selection = $scope.items[0]; 20550 } 20551 </file> 20552 <file name="animations.css"> 20553 .animate-switch-container { 20554 position:relative; 20555 background:white; 20556 border:1px solid black; 20557 height:40px; 20558 overflow:hidden; 20559 } 20560 20561 .animate-switch { 20562 padding:10px; 20563 } 20564 20565 .animate-switch.ng-animate { 20566 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 20567 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 20568 20569 position:absolute; 20570 top:0; 20571 left:0; 20572 right:0; 20573 bottom:0; 20574 } 20575 20576 .animate-switch.ng-leave.ng-leave-active, 20577 .animate-switch.ng-enter { 20578 top:-50px; 20579 } 20580 .animate-switch.ng-leave, 20581 .animate-switch.ng-enter.ng-enter-active { 20582 top:0; 20583 } 20584 </file> 20585 <file name="protractor.js" type="protractor"> 20586 var switchElem = element(by.css('[ng-switch]')); 20587 var select = element(by.model('selection')); 20588 20589 it('should start in settings', function() { 20590 expect(switchElem.getText()).toMatch(/Settings Div/); 20591 }); 20592 it('should change to home', function() { 20593 select.element.all(by.css('option')).get(1).click(); 20594 expect(switchElem.getText()).toMatch(/Home Span/); 20595 }); 20596 it('should select default', function() { 20597 select.element.all(by.css('option')).get(2).click(); 20598 expect(switchElem.getText()).toMatch(/default/); 20599 }); 20600 </file> 20601 </example> 20602 */ 20603var ngSwitchDirective = ['$animate', function($animate) { 20604 return { 20605 restrict: 'EA', 20606 require: 'ngSwitch', 20607 20608 // asks for $scope to fool the BC controller module 20609 controller: ['$scope', function ngSwitchController() { 20610 this.cases = {}; 20611 }], 20612 link: function(scope, element, attr, ngSwitchController) { 20613 var watchExpr = attr.ngSwitch || attr.on, 20614 selectedTranscludes, 20615 selectedElements, 20616 previousElements, 20617 selectedScopes = []; 20618 20619 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 20620 var i, ii = selectedScopes.length; 20621 if(ii > 0) { 20622 if(previousElements) { 20623 for (i = 0; i < ii; i++) { 20624 previousElements[i].remove(); 20625 } 20626 previousElements = null; 20627 } 20628 20629 previousElements = []; 20630 for (i= 0; i<ii; i++) { 20631 var selected = selectedElements[i]; 20632 selectedScopes[i].$destroy(); 20633 previousElements[i] = selected; 20634 $animate.leave(selected, function() { 20635 previousElements.splice(i, 1); 20636 if(previousElements.length === 0) { 20637 previousElements = null; 20638 } 20639 }); 20640 } 20641 } 20642 20643 selectedElements = []; 20644 selectedScopes = []; 20645 20646 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 20647 scope.$eval(attr.change);
20648 forEach(selectedTranscludes, function(selectedTransclude) { 20649 var selectedScope = scope.$new(); 20650 selectedScopes.push(selectedScope); 20651 selectedTransclude.transclude(selectedScope, function(caseElement) { 20652 var anchor = selectedTransclude.element; 20653 20654 selectedElements.push(caseElement); 20655 $animate.enter(caseElement, anchor.parent(), anchor); 20656 }); 20657 }); 20658 } 20659 }); 20660 } 20661 }; 20662}]; 20663 20664var ngSwitchWhenDirective = ngDirective({ 20665 transclude: 'element', 20666 priority: 800, 20667 require: '^ngSwitch', 20668 link: function(scope, element, attrs, ctrl, $transclude) { 20669 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 20670 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 20671 } 20672}); 20673 20674var ngSwitchDefaultDirective = ngDirective({ 20675 transclude: 'element', 20676 priority: 800, 20677 require: '^ngSwitch', 20678 link: function(scope, element, attr, ctrl, $transclude) { 20679 ctrl.cases['?'] = (ctrl.cases['?'] || []); 20680 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 20681 } 20682}); 20683 20684/** 20685 * @ngdoc directive 20686 * @name ngTransclude 20687 * @restrict AC 20688 * 20689 * @description 20690 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 20691 * 20692 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 20693 * 20694 * @element ANY 20695 * 20696 * @example 20697 <example module="transclude"> 20698 <file name="index.html"> 20699 <script> 20700 function Ctrl($scope) { 20701 $scope.title = 'Lorem Ipsum'; 20702 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 20703 } 20704 20705 angular.module('transclude', []) 20706 .directive('pane', function(){ 20707 return { 20708 restrict: 'E', 20709 transclude: true, 20710 scope: { title:'@' }, 20711 template: '<div style="border: 1px solid black;">' + 20712 '<div style="background-color: gray">{{title}}</div>' + 20713 '<div ng-transclude></div>' + 20714 '</div>' 20715 }; 20716 }); 20717 </script> 20718 <div ng-controller="Ctrl"> 20719 <input ng-model="title"><br> 20720 <textarea ng-model="text"></textarea> <br/> 20721 <pane title="{{title}}">{{text}}</pane> 20722 </div> 20723 </file> 20724 <file name="protractor.js" type="protractor"> 20725 it('should have transcluded', function() { 20726 var titleElement = element(by.model('title')); 20727 titleElement.clear(); 20728 titleElement.sendKeys('TITLE'); 20729 var textElement = element(by.model('text')); 20730 textElement.clear(); 20731 textElement.sendKeys('TEXT'); 20732 expect(element(by.binding('title')).getText()).toEqual('TITLE'); 20733 expect(element(by.binding('text')).getText()).toEqual('TEXT'); 20734 }); 20735 </file> 20736 </example> 20737 * 20738 */ 20739var ngTranscludeDirective = ngDirective({ 20740 link: function($scope, $element, $attrs, controller, $transclude) { 20741 if (!$transclude) { 20742 throw minErr('ngTransclude')('orphan', 20743 'Illegal use of ngTransclude directive in the template! ' + 20744 'No parent directive that requires a transclusion found. ' + 20745 'Element: {0}', 20746 startingTag($element)); 20747 } 20748 20749 $transclude(function(clone) { 20750 $element.empty(); 20751 $element.append(clone); 20752 }); 20753 } 20754}); 20755 20756/** 20757 * @ngdoc directive 20758 * @name script 20759 * @restrict E 20760 * 20761 * @description 20762 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 20763 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 20764 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 20765 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 20766 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 20767 * 20768 * @param {string} type Must be set to `'text/ng-template'`. 20769 * @param {string} id Cache name of the template. 20770 * 20771 * @example 20772 <example> 20773 <file name="index.html"> 20774 <script type="text/ng-template" id="/tpl.html"> 20775 Content of the template. 20776 </script> 20777 20778 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 20779 <div id="tpl-content" ng-include src="currentTpl"></div> 20780 </file> 20781 <file name="protractor.js" type="protractor"> 20782 it('should load template defined inside script tag', function() {
20783 element(by.css('#tpl-link')).click(); 20784 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 20785 }); 20786 </file> 20787 </example> 20788 */ 20789var scriptDirective = ['$templateCache', function($templateCache) { 20790 return { 20791 restrict: 'E', 20792 terminal: true, 20793 compile: function(element, attr) { 20794 if (attr.type == 'text/ng-template') { 20795 var templateUrl = attr.id, 20796 // IE is not consistent, in scripts we have to read .text but in other nodes we have to read .textContent 20797 text = element[0].text; 20798 20799 $templateCache.put(templateUrl, text); 20800 } 20801 } 20802 }; 20803}]; 20804 20805var ngOptionsMinErr = minErr('ngOptions'); 20806/** 20807 * @ngdoc directive 20808 * @name select 20809 * @restrict E 20810 * 20811 * @description 20812 * HTML `SELECT` element with angular data-binding. 20813 * 20814 * # `ngOptions` 20815 * 20816 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 20817 * elements for the `<select>` element using the array or object obtained by evaluating the 20818 * `ngOptions` comprehension_expression. 20819 * 20820 * When an item in the `<select>` menu is selected, the array element or object property 20821 * represented by the selected option will be bound to the model identified by the `ngModel` 20822 * directive. 20823 * 20824 * <div class="alert alert-warning"> 20825 * **Note:** `ngModel` compares by reference, not value. This is important when binding to an 20826 * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). 20827 * </div> 20828 * 20829 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 20830 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 20831 * option. See example below for demonstration. 20832 * 20833 * <div class="alert alert-warning"> 20834 * **Note:** `ngOptions` provides an iterator facility for the `<option>` element which should be used instead 20835 * of {@link ng.directive:ngRepeat ngRepeat} when you want the 20836 * `select` model to be bound to a non-string value. This is because an option element can only 20837 * be bound to string values at present. 20838 * </div> 20839 * 20840 * @param {string} ngModel Assignable angular expression to data-bind to. 20841 * @param {string=} name Property name of the form under which the control is published. 20842 * @param {string=} required The control is considered valid only if value is entered. 20843 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20844 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20845 * `required` when you want to data-bind to the `required` attribute. 20846 * @param {comprehension_expression=} ngOptions in one of the following forms: 20847 * 20848 * * for array data sources: 20849 * * `label` **`for`** `value` **`in`** `array` 20850 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 20851 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 20852 * * `select` **`as`** `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 20853 * * for object data sources: 20854 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 20855 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 20856 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 20857 * * `select` **`as`** `label` **`group by`** `group` 20858 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 20859 * 20860 * Where: 20861 * 20862 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 20863 * * `value`: local variable which will refer to each item in the `array` or each property value 20864 * of `object` during iteration. 20865 * * `key`: local variable which will refer to a property name in `object` during iteration. 20866 * * `label`: The result of this expression will be the label for `<option>` element. The 20867 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 20868 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 20869 * element. If not specified, `select` expression will default to `value`. 20870 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 20871 * DOM element. 20872 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 20873 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 20874 * `value` variable (e.g. `value.propertyName`). 20875 * 20876 * @example 20877 <example> 20878 <file name="index.html"> 20879 <script> 20880 function MyCntrl($scope) { 20881 $scope.colors = [ 20882 {name:'black', shade:'dark'}, 20883 {name:'white', shade:'light'}, 20884 {name:'red', shade:'dark'}, 20885 {name:'blue', shade:'dark'}, 20886 {name:'yellow', shade:'light'} 20887 ]; 20888 $scope.color = $scope.colors[2]; // red 20889 } 20890 </script> 20891 <div ng-controller="MyCntrl"> 20892 <ul> 20893 <li ng-repeat="color in colors"> 20894 Name: <input ng-model="color.name"> 20895 [<a href ng-click="colors.splice($index, 1)">X</a>] 20896 </li> 20897 <li> 20898 [<a href ng-click="colors.push({})">add</a>] 20899 </li> 20900 </ul> 20901 <hr/> 20902 Color (null not allowed): 20903 <select ng-model="color" ng-options="c.name for c in colors"></select><br> 20904 20905 Color (null allowed):
20906 <span class="nullable"> 20907 <select ng-model="color" ng-options="c.name for c in colors"> 20908 <option value="">-- choose color --</option> 20909 </select> 20910 </span><br/> 20911 20912 Color grouped by shade: 20913 <select ng-model="color" ng-options="c.name group by c.shade for c in colors"> 20914 </select><br/> 20915 20916 20917 Select <a href ng-click="color={name:'not in list'}">bogus</a>.<br> 20918 <hr/> 20919 Currently selected: {{ {selected_color:color} }} 20920 <div style="border:solid 1px black; height:20px" 20921 ng-style="{'background-color':color.name}"> 20922 </div> 20923 </div> 20924 </file> 20925 <file name="protractor.js" type="protractor"> 20926 it('should check ng-options', function() { 20927 expect(element(by.binding('{selected_color:color}')).getText()).toMatch('red'); 20928 element.all(by.select('color')).first().click(); 20929 element.all(by.css('select[ng-model="color"] option')).first().click(); 20930 expect(element(by.binding('{selected_color:color}')).getText()).toMatch('black'); 20931 element(by.css('.nullable select[ng-model="color"]')).click(); 20932 element.all(by.css('.nullable select[ng-model="color"] option')).first().click(); 20933 expect(element(by.binding('{selected_color:color}')).getText()).toMatch('null'); 20934 }); 20935 </file> 20936 </example> 20937 */ 20938 20939var ngOptionsDirective = valueFn({ terminal: true }); 20940// jshint maxlen: false 20941var selectDirective = ['$compile', '$parse', function($compile, $parse) { 20942 //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 20943 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]+?))?$/, 20944 nullModelCtrl = {$setViewValue: noop}; 20945// jshint maxlen: 100 20946 20947 return { 20948 restrict: 'E', 20949 require: ['select', '?ngModel'], 20950 controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 20951 var self = this, 20952 optionsMap = {}, 20953 ngModelCtrl = nullModelCtrl, 20954 nullOption, 20955 unknownOption; 20956 20957 20958 self.databound = $attrs.ngModel; 20959 20960 20961 self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { 20962 ngModelCtrl = ngModelCtrl_; 20963 nullOption = nullOption_; 20964 unknownOption = unknownOption_; 20965 }; 20966 20967 20968 self.addOption = function(value) { 20969 assertNotHasOwnProperty(value, '"option value"'); 20970 optionsMap[value] = true; 20971 20972 if (ngModelCtrl.$viewValue == value) { 20973 $element.val(value); 20974 if (unknownOption.parent()) unknownOption.remove(); 20975 } 20976 }; 20977 20978 20979 self.removeOption = function(value) { 20980 if (this.hasOption(value)) { 20981 delete optionsMap[value]; 20982 if (ngModelCtrl.$viewValue == value) { 20983 this.renderUnknownOption(value); 20984 } 20985 } 20986 }; 20987 20988 20989 self.renderUnknownOption = function(val) { 20990 var unknownVal = '? ' + hashKey(val) + ' ?'; 20991 unknownOption.val(unknownVal); 20992 $element.prepend(unknownOption); 20993 $element.val(unknownVal); 20994 unknownOption.prop('selected', true); // needed for IE 20995 }; 20996 20997 20998 self.hasOption = function(value) { 20999 return optionsMap.hasOwnProperty(value); 21000 }; 21001 21002 $scope.$on('$destroy', function() { 21003 // disable unknown option so that we don't do work when the whole select is being destroyed 21004 self.renderUnknownOption = noop; 21005 }); 21006 }], 21007 21008 link: function(scope, element, attr, ctrls) { 21009 // if ngModel is not defined, we don't need to do anything 21010 if (!ctrls[1]) return; 21011 21012 var selectCtrl = ctrls[0], 21013 ngModelCtrl = ctrls[1], 21014 multiple = attr.multiple, 21015 optionsExp = attr.ngOptions, 21016 nullOption = false, // if false, user will not be able to select it (used by ngOptions) 21017 emptyOption, 21018 // we can't just jqLite('<option>') since jqLite is not smart enough 21019 // to create it in <select> and IE barfs otherwise. 21020 optionTemplate = jqLite(document.createElement('option')), 21021 optGroupTemplate =jqLite(document.createElement('optgroup')), 21022 unknownOption = optionTemplate.clone(); 21023 21024 // find "null" option 21025 for(var i = 0, children = element.children(), ii = children.length; i < ii; i++) { 21026 if (children[i].value === '') { 21027 emptyOption = nullOption = children.eq(i); 21028 break; 21029 } 21030 } 21031 21032 selectCtrl.init(ngModelCtrl, nullOption, unknownOption); 21033 21034 // required validator 21035 if (multiple) { 21036 ngModelCtrl.$isEmpty = function(value) { 21037 return !value || value.length === 0; 21038 }; 21039 } 21040 21041 if (optionsExp) setupAsOptions(scope, element, ngModelCtrl); 21042 else if (multiple) setupAsMultiple(scope, element, ngModelCtrl); 21043 else setupAsSingle(scope, element, ngModelCtrl, selectCtrl); 21044 21045
21046 //////////////////////////// 21047 21048 21049 21050 function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) { 21051 ngModelCtrl.$render = function() { 21052 var viewValue = ngModelCtrl.$viewValue; 21053 21054 if (selectCtrl.hasOption(viewValue)) { 21055 if (unknownOption.parent()) unknownOption.remove(); 21056 selectElement.val(viewValue); 21057 if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy 21058 } else { 21059 if (isUndefined(viewValue) && emptyOption) { 21060 selectElement.val(''); 21061 } else { 21062 selectCtrl.renderUnknownOption(viewValue); 21063 } 21064 } 21065 }; 21066 21067 selectElement.on('change', function() { 21068 scope.$apply(function() { 21069 if (unknownOption.parent()) unknownOption.remove(); 21070 ngModelCtrl.$setViewValue(selectElement.val()); 21071 }); 21072 }); 21073 } 21074 21075 function setupAsMultiple(scope, selectElement, ctrl) { 21076 var lastView; 21077 ctrl.$render = function() { 21078 var items = new HashMap(ctrl.$viewValue); 21079 forEach(selectElement.find('option'), function(option) { 21080 option.selected = isDefined(items.get(option.value)); 21081 }); 21082 }; 21083 21084 // we have to do it on each watch since ngModel watches reference, but 21085 // we need to work of an array, so we need to see if anything was inserted/removed 21086 scope.$watch(function selectMultipleWatch() { 21087 if (!equals(lastView, ctrl.$viewValue)) { 21088 lastView = copy(ctrl.$viewValue); 21089 ctrl.$render(); 21090 } 21091 }); 21092 21093 selectElement.on('change', function() { 21094 scope.$apply(function() { 21095 var array = []; 21096 forEach(selectElement.find('option'), function(option) { 21097 if (option.selected) { 21098 array.push(option.value); 21099 } 21100 }); 21101 ctrl.$setViewValue(array); 21102 }); 21103 }); 21104 } 21105 21106 function setupAsOptions(scope, selectElement, ctrl) { 21107 var match; 21108 21109 if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) { 21110 throw ngOptionsMinErr('iexp', 21111 "Expected expression in form of " + 21112 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 21113 " but got '{0}'. Element: {1}", 21114 optionsExp, startingTag(selectElement)); 21115 } 21116 21117 var displayFn = $parse(match[2] || match[1]), 21118 valueName = match[4] || match[6], 21119 keyName = match[5], 21120 groupByFn = $parse(match[3] || ''), 21121 valueFn = $parse(match[2] ? match[1] : valueName), 21122 valuesFn = $parse(match[7]), 21123 track = match[8], 21124 trackFn = track ? $parse(match[8]) : null, 21125 // This is an array of array of existing option groups in DOM. 21126 // We try to reuse these if possible 21127 // - optionGroupsCache[0] is the options with no option group 21128 // - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element 21129 optionGroupsCache = [[{element: selectElement, label:''}]]; 21130 21131 if (nullOption) { 21132 // compile the element since there might be bindings in it 21133 $compile(nullOption)(scope); 21134 21135 // remove the class, which is added automatically because we recompile the element and it 21136 // becomes the compilation root 21137 nullOption.removeClass('ng-scope'); 21138 21139 // we need to remove it before calling selectElement.empty() because otherwise IE will 21140 // remove the label from the element. wtf? 21141 nullOption.remove(); 21142 } 21143 21144 // clear contents, we'll add what's needed based on the model 21145 selectElement.empty(); 21146 21147 selectElement.on('change', function() { 21148 scope.$apply(function() { 21149 var optionGroup, 21150 collection = valuesFn(scope) || [], 21151 locals = {}, 21152 key, value, optionElement, index, groupIndex, length, groupLength, trackIndex; 21153 21154 if (multiple) { 21155 value = []; 21156 for (groupIndex = 0, groupLength = optionGroupsCache.length; 21157 groupIndex < groupLength; 21158 groupIndex++) { 21159 // list of options for that group. (first item has the parent) 21160 optionGroup = optionGroupsCache[groupIndex]; 21161 21162 for(index = 1, length = optionGroup.length; index < length; index++) { 21163 if ((optionElement = optionGroup[index].element)[0].selected) { 21164 key = optionElement.val(); 21165 if (keyName) locals[keyName] = key; 21166 if (trackFn) { 21167 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 21168 locals[valueName] = collection[trackIndex]; 21169 if (trackFn(scope, locals) == key) break; 21170 } 21171 } else { 21172 locals[valueName] = collection[key]; 21173 } 21174 value.push(valueFn(scope, locals)); 21175 } 21176 } 21177 } 21178 } else { 21179 key = selectElement.val(); 21180 if (key == '?') { 21181 value = undefined; 21182 } else if (key === ''){
21183 value = null; 21184 } else { 21185 if (trackFn) { 21186 for (trackIndex = 0; trackIndex < collection.length; trackIndex++) { 21187 locals[valueName] = collection[trackIndex]; 21188 if (trackFn(scope, locals) == key) { 21189 value = valueFn(scope, locals); 21190 break; 21191 } 21192 } 21193 } else { 21194 locals[valueName] = collection[key]; 21195 if (keyName) locals[keyName] = key; 21196 value = valueFn(scope, locals); 21197 } 21198 } 21199 // Update the null option's selected property here so $render cleans it up correctly 21200 if (optionGroupsCache[0].length > 1) { 21201 if (optionGroupsCache[0][1].id !== key) { 21202 optionGroupsCache[0][1].selected = false; 21203 } 21204 } 21205 } 21206 ctrl.$setViewValue(value); 21207 }); 21208 }); 21209 21210 ctrl.$render = render; 21211 21212 // TODO(vojta): can't we optimize this ? 21213 scope.$watch(render); 21214 21215 function render() { 21216 // Temporary location for the option groups before we render them 21217 var optionGroups = {'':[]}, 21218 optionGroupNames = [''], 21219 optionGroupName, 21220 optionGroup, 21221 option, 21222 existingParent, existingOptions, existingOption, 21223 modelValue = ctrl.$modelValue, 21224 values = valuesFn(scope) || [], 21225 keys = keyName ? sortedKeys(values) : values, 21226 key, 21227 groupLength, length, 21228 groupIndex, index, 21229 locals = {}, 21230 selected, 21231 selectedSet = false, // nothing is selected yet 21232 lastElement, 21233 element, 21234 label; 21235 21236 if (multiple) { 21237 if (trackFn && isArray(modelValue)) { 21238 selectedSet = new HashMap([]); 21239 for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) { 21240 locals[valueName] = modelValue[trackIndex]; 21241 selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]); 21242 } 21243 } else { 21244 selectedSet = new HashMap(modelValue); 21245 } 21246 } 21247 21248 // We now build up the list of options we need (we merge later) 21249 for (index = 0; length = keys.length, index < length; index++) { 21250 21251 key = index; 21252 if (keyName) { 21253 key = keys[index]; 21254 if ( key.charAt(0) === '$' ) continue; 21255 locals[keyName] = key; 21256 } 21257 21258 locals[valueName] = values[key]; 21259 21260 optionGroupName = groupByFn(scope, locals) || ''; 21261 if (!(optionGroup = optionGroups[optionGroupName])) { 21262 optionGroup = optionGroups[optionGroupName] = []; 21263 optionGroupNames.push(optionGroupName); 21264 } 21265 if (multiple) { 21266 selected = isDefined( 21267 selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals)) 21268 ); 21269 } else { 21270 if (trackFn) { 21271 var modelCast = {}; 21272 modelCast[valueName] = modelValue; 21273 selected = trackFn(scope, modelCast) === trackFn(scope, locals); 21274 } else { 21275 selected = modelValue === valueFn(scope, locals); 21276 } 21277 selectedSet = selectedSet || selected; // see if at least one item is selected 21278 } 21279 label = displayFn(scope, locals); // what will be seen by the user 21280 21281 // doing displayFn(scope, locals) || '' overwrites zero values 21282 label = isDefined(label) ? label : ''; 21283 optionGroup.push({ 21284 // either the index into array or key from object 21285 id: trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index), 21286 label: label, 21287 selected: selected // determine if we should be selected 21288 }); 21289 } 21290 if (!multiple) { 21291 if (nullOption || modelValue === null) { 21292 // insert null option if we have a placeholder, or the model is null 21293 optionGroups[''].unshift({id:'', label:'', selected:!selectedSet}); 21294 } else if (!selectedSet) { 21295 // option could not be found, we have to insert the undefined item 21296 optionGroups[''].unshift({id:'?', label:'', selected:true}); 21297 } 21298 } 21299 21300 // Now we need to update the list of DOM nodes to match the optionGroups we computed above 21301 for (groupIndex = 0, groupLength = optionGroupNames.length; 21302 groupIndex < groupLength; 21303 groupIndex++) { 21304 // current option group name or '' if no group 21305 optionGroupName = optionGroupNames[groupIndex]; 21306 21307 // list of options for that group. (first item has the parent) 21308 optionGroup = optionGroups[optionGroupName]; 21309 21310 if (optionGroupsCache.length <= groupIndex) { 21311 // we need to grow the optionGroups 21312 existingParent = { 21313 element: optGroupTemplate.clone().attr('label', optionGroupName), 21314 label: optionGroup.label 21315 }; 21316 existingOptions = [existingParent]; 21317 optionGroupsCache.push(existingOptions); 21318 selectElement.append(existingParent.element); 21319 } else { 21320 existingOptions = optionGroupsCache[groupIndex]; 21321 existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element 21322 21323 // update the OPTGROUP label if not the same. 21324 if (existingParent.label != optionGroupName) { 21325 existingParent.element.attr('label', existingParent.label = optionGroupName); 21326 } 21327 } 21328 21329 lastElement = null; // start at the beginning 21330 for(index = 0, length = optionGroup.length; index < length; index++) { 21331 option = optionGroup[index]; 21332 if ((existingOption = existingOptions[index+1])) { 21333 // reuse elements 21334 lastElement = existingOption.element; 21335 if (existingOption.label !== option.label) { 21336 lastElement.text(existingOption.label = option.label); 21337 } 21338 if (existingOption.id !== option.id) { 21339 lastElement.val(existingOption.id = option.id); 21340 } 21341 // lastElement.prop('selected') provided by jQuery has side-effects 21342 if (existingOption.selected !== option.selected) {
21343 lastElement.prop('selected', (existingOption.selected = option.selected)); 21344 } 21345 } else { 21346 // grow elements 21347 21348 // if it's a null option 21349 if (option.id === '' && nullOption) { 21350 // put back the pre-compiled element 21351 element = nullOption; 21352 } else { 21353 // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but 21354 // in this version of jQuery on some browser the .text() returns a string 21355 // rather then the element. 21356 (element = optionTemplate.clone()) 21357 .val(option.id) 21358 .attr('selected', option.selected) 21359 .text(option.label); 21360 } 21361 21362 existingOptions.push(existingOption = { 21363 element: element, 21364 label: option.label, 21365 id: option.id, 21366 selected: option.selected 21367 }); 21368 if (lastElement) { 21369 lastElement.after(element); 21370 } else { 21371 existingParent.element.append(element); 21372 } 21373 lastElement = element; 21374 } 21375 } 21376 // remove any excessive OPTIONs in a group 21377 index++; // increment since the existingOptions[0] is parent element not OPTION 21378 while(existingOptions.length > index) { 21379 existingOptions.pop().element.remove(); 21380 } 21381 } 21382 // remove any excessive OPTGROUPs from select 21383 while(optionGroupsCache.length > groupIndex) { 21384 optionGroupsCache.pop()[0].element.remove(); 21385 } 21386 } 21387 } 21388 } 21389 }; 21390}]; 21391 21392var optionDirective = ['$interpolate', function($interpolate) { 21393 var nullSelectCtrl = { 21394 addOption: noop, 21395 removeOption: noop 21396 }; 21397 21398 return { 21399 restrict: 'E', 21400 priority: 100, 21401 compile: function(element, attr) { 21402 if (isUndefined(attr.value)) { 21403 var interpolateFn = $interpolate(element.text(), true); 21404 if (!interpolateFn) { 21405 attr.$set('value', element.text()); 21406 } 21407 } 21408 21409 return function (scope, element, attr) { 21410 var selectCtrlName = '$selectController', 21411 parent = element.parent(), 21412 selectCtrl = parent.data(selectCtrlName) || 21413 parent.parent().data(selectCtrlName); // in case we are in optgroup 21414 21415 if (selectCtrl && selectCtrl.databound) { 21416 // For some reason Opera defaults to true and if not overridden this messes up the repeater. 21417 // We don't want the view to drive the initialization of the model anyway. 21418 element.prop('selected', false); 21419 } else { 21420 selectCtrl = nullSelectCtrl; 21421 } 21422 21423 if (interpolateFn) { 21424 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 21425 attr.$set('value', newVal); 21426 if (newVal !== oldVal) selectCtrl.removeOption(oldVal); 21427 selectCtrl.addOption(newVal); 21428 }); 21429 } else { 21430 selectCtrl.addOption(attr.value); 21431 } 21432 21433 element.on('$destroy', function() { 21434 selectCtrl.removeOption(attr.value); 21435 }); 21436 }; 21437 } 21438 }; 21439}]; 21440 21441var styleDirective = valueFn({ 21442 restrict: 'E', 21443 terminal: true 21444}); 21445 21446 if (window.angular.bootstrap) { 21447 //AngularJS is already loaded, so we can return here... 21448 console.log('WARNING: Tried to load angular more than once.'); 21449 return; 21450 } 21451 21452 //try to bind to jquery now so that one can write angular.element().read() 21453 //but we will rebind on bootstrap again. 21454 bindJQuery(); 21455 21456 publishExternalAPI(angular); 21457 21458 jqLite(document).ready(function() { 21459 angularInit(document, bootstrap); 21460 }); 21461 21462})(window, document); 21463 21464!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.