1/** 2 * @license AngularJS v1.3.12 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 * @param {function} ErrorConstructor Custom error constructor to be instantiated when returning 34 * error from returned function, for cases when a particular type of error is useful. 35 * @returns {function(code:string, template:string, ...templateArgs): Error} minErr instance 36 */ 37 38function minErr(module, ErrorConstructor) { 39 ErrorConstructor = ErrorConstructor || Error; 40 return function() { 41 var code = arguments[0], 42 prefix = '[' + (module ? module + ':' : '') + code + '] ', 43 template = arguments[1], 44 templateArgs = arguments, 45 46 message, i; 47 48 message = prefix + template.replace(/\{\d+\}/g, function(match) { 49 var index = +match.slice(1, -1), arg; 50 51 if (index + 2 < templateArgs.length) { 52 return toDebugString(templateArgs[index + 2]); 53 } 54 return match; 55 }); 56 57 message = message + '\nhttp://errors.angularjs.org/1.3.12/' + 58 (module ? module + '/' : '') + code; 59 for (i = 2; i < arguments.length; i++) { 60 message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' + 61 encodeURIComponent(toDebugString(arguments[i])); 62 } 63 return new ErrorConstructor(message); 64 }; 65} 66 67/* We need to tell jshint what variables are being exported */ 68/* global angular: true, 69 msie: true, 70 jqLite: true, 71 jQuery: true, 72 slice: true, 73 splice: true, 74 push: true, 75 toString: true, 76 ngMinErr: true, 77 angularModule: true, 78 uid: true, 79 REGEX_STRING_REGEXP: true, 80 VALIDITY_STATE_PROPERTY: true, 81 82 lowercase: true, 83 uppercase: true, 84 manualLowercase: true, 85 manualUppercase: true, 86 nodeName_: true, 87 isArrayLike: true, 88 forEach: true, 89 sortedKeys: true, 90 forEachSorted: true, 91 reverseParams: true, 92 nextUid: true, 93 setHashKey: true, 94 extend: true, 95 int: true, 96 inherit: true, 97 noop: true, 98 identity: true, 99 valueFn: true, 100 isUndefined: true, 101 isDefined: true, 102 isObject: true, 103 isString: true, 104 isNumber: true, 105 isDate: true, 106 isArray: true, 107 isFunction: true, 108 isRegExp: true, 109 isWindow: true, 110 isScope: true, 111 isFile: true, 112 isFormData: true, 113 isBlob: true, 114 isBoolean: true, 115 isPromiseLike: true, 116 trim: true, 117 escapeForRegexp: true, 118 isElement: true, 119 makeMap: true, 120 includes: true, 121 arrayRemove: true, 122 copy: true, 123 shallowCopy: true, 124 equals: true, 125 csp: true, 126 concat: true, 127 sliceArgs: true, 128 bind: true, 129 toJsonReplacer: true, 130 toJson: true, 131 fromJson: true, 132 startingTag: true, 133 tryDecodeURIComponent: true, 134 parseKeyValue: true, 135 toKeyValue: true, 136 encodeUriSegment: true, 137 encodeUriQuery: true, 138 angularInit: true, 139 bootstrap: true, 140 getTestability: true, 141 snake_case: true, 142 bindJQuery: true, 143 assertArg: true, 144 assertArgFn: true, 145 assertNotHasOwnProperty: true, 146 getter: true, 147 getBlockNodes: true, 148 hasOwnProperty: true, 149 createMap: true, 150 151 NODE_TYPE_ELEMENT: true, 152 NODE_TYPE_TEXT: true, 153 NODE_TYPE_COMMENT: true, 154 NODE_TYPE_DOCUMENT: true, 155 NODE_TYPE_DOCUMENT_FRAGMENT: true, 156*/ 157
158//////////////////////////////////// 159 160/** 161 * @ngdoc module 162 * @name ng 163 * @module ng 164 * @description 165 * 166 * # ng (core module) 167 * The ng module is loaded by default when an AngularJS application is started. The module itself 168 * contains the essential components for an AngularJS application to function. The table below 169 * lists a high level breakdown of each of the services/factories, filters, directives and testing 170 * components available within this core module. 171 * 172 * <div doc-module-components="ng"></div> 173 */ 174 175var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; 176 177// The name of a form control's ValidityState property. 178// This is used so that it's possible for internal tests to create mock ValidityStates. 179var VALIDITY_STATE_PROPERTY = 'validity'; 180 181/** 182 * @ngdoc function 183 * @name angular.lowercase 184 * @module ng 185 * @kind function 186 * 187 * @description Converts the specified string to lowercase. 188 * @param {string} string String to be converted to lowercase. 189 * @returns {string} Lowercased string. 190 */ 191var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; 192var hasOwnProperty = Object.prototype.hasOwnProperty; 193 194/** 195 * @ngdoc function 196 * @name angular.uppercase 197 * @module ng 198 * @kind function 199 * 200 * @description Converts the specified string to uppercase. 201 * @param {string} string String to be converted to uppercase. 202 * @returns {string} Uppercased string. 203 */ 204var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; 205 206 207var manualLowercase = function(s) { 208 /* jshint bitwise: false */ 209 return isString(s) 210 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 211 : s; 212}; 213var manualUppercase = function(s) { 214 /* jshint bitwise: false */ 215 return isString(s) 216 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 217 : s; 218}; 219 220 221// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 222// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 223// with correct but slower alternatives. 224if ('i' !== 'I'.toLowerCase()) { 225 lowercase = manualLowercase; 226 uppercase = manualUppercase; 227} 228 229 230var 231 msie, // holds major version number for IE, or NaN if UA is not IE. 232 jqLite, // delay binding since jQuery could be loaded after us. 233 jQuery, // delay binding 234 slice = [].slice, 235 splice = [].splice, 236 push = [].push, 237 toString = Object.prototype.toString, 238 ngMinErr = minErr('ng'), 239 240 /** @name angular */ 241 angular = window.angular || (window.angular = {}), 242 angularModule, 243 uid = 0; 244 245/** 246 * documentMode is an IE-only property 247 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 248 */ 249msie = document.documentMode; 250 251 252/** 253 * @private 254 * @param {*} obj 255 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 256 * String ...) 257 */ 258function isArrayLike(obj) { 259 if (obj == null || isWindow(obj)) { 260 return false; 261 } 262 263 var length = obj.length; 264 265 if (obj.nodeType === NODE_TYPE_ELEMENT && length) { 266 return true; 267 } 268 269 return isString(obj) || isArray(obj) || length === 0 || 270 typeof length === 'number' && length > 0 && (length - 1) in obj; 271} 272 273/** 274 * @ngdoc function 275 * @name angular.forEach 276 * @module ng 277 * @kind function 278 * 279 * @description 280 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 281 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 282 * is the value of an object property or an array element, `key` is the object property key or 283 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 284 * 285 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 286 * using the `hasOwnProperty` method. 287 * 288 * Unlike ES262's 289 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 290 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 291 * return the value provided. 292 * 293 ```js 294 var values = {name: 'misko', gender: 'male'}; 295 var log = []; 296 angular.forEach(values, function(value, key) { 297 this.push(key + ': ' + value); 298 }, log); 299 expect(log).toEqual(['name: misko', 'gender: male']); 300 ``` 301 * 302 * @param {Object|Array} obj Object to iterate over. 303 * @param {Function} iterator Iterator function. 304 * @param {Object=} context Object to become context (`this`) for the iterator function. 305 * @returns {Object|Array} Reference to `obj`. 306 */ 307 308function forEach(obj, iterator, context) { 309 var key, length; 310 if (obj) { 311 if (isFunction(obj)) { 312 for (key in obj) { 313 // Need to check if hasOwnProperty exists, 314 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 315 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 316 iterator.call(context, obj[key], key, obj); 317 } 318 } 319 } else if (isArray(obj) || isArrayLike(obj)) { 320 var isPrimitive = typeof obj !== 'object'; 321 for (key = 0, length = obj.length; key < length; key++) { 322 if (isPrimitive || key in obj) { 323 iterator.call(context, obj[key], key, obj); 324 } 325 } 326 }
vendor: 8,671 bytes, lines 326-720
326 else if (obj.forEach && obj.forEach !== forEach) { 327 obj.forEach(iterator, context, obj); 328 } else { 329 for (key in obj) { 330 if (obj.hasOwnProperty(key)) { 331 iterator.call(context, obj[key], key, obj); 332 } 333 } 334 } 335 } 336 return obj; 337} 338 339function sortedKeys(obj) { 340 return Object.keys(obj).sort(); 341} 342 343function forEachSorted(obj, iterator, context) { 344 var keys = sortedKeys(obj); 345 for (var i = 0; i < keys.length; i++) { 346 iterator.call(context, obj[keys[i]], keys[i]); 347 } 348 return keys; 349} 350 351 352/** 353 * when using forEach the params are value, key, but it is often useful to have key, value. 354 * @param {function(string, *)} iteratorFn 355 * @returns {function(*, string)} 356 */ 357function reverseParams(iteratorFn) { 358 return function(value, key) { iteratorFn(key, value); }; 359} 360 361/** 362 * A consistent way of creating unique IDs in angular. 363 * 364 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before 365 * we hit number precision issues in JavaScript. 366 * 367 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M 368 * 369 * @returns {number} an unique alpha-numeric string 370 */ 371function nextUid() { 372 return ++uid; 373} 374 375 376/** 377 * Set or clear the hashkey for an object. 378 * @param obj object 379 * @param h the hashkey (!truthy to delete the hashkey) 380 */ 381function setHashKey(obj, h) { 382 if (h) { 383 obj.$$hashKey = h; 384 } else { 385 delete obj.$$hashKey; 386 } 387} 388 389/** 390 * @ngdoc function 391 * @name angular.extend 392 * @module ng 393 * @kind function 394 * 395 * @description 396 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 397 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 398 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. 399 * Note: Keep in mind that `angular.extend` does not support recursive merge (deep copy). 400 * 401 * @param {Object} dst Destination object. 402 * @param {...Object} src Source object(s). 403 * @returns {Object} Reference to `dst`. 404 */ 405function extend(dst) { 406 var h = dst.$$hashKey; 407 408 for (var i = 1, ii = arguments.length; i < ii; i++) { 409 var obj = arguments[i]; 410 if (obj) { 411 var keys = Object.keys(obj); 412 for (var j = 0, jj = keys.length; j < jj; j++) { 413 var key = keys[j]; 414 dst[key] = obj[key]; 415 } 416 } 417 } 418 419 setHashKey(dst, h); 420 return dst; 421} 422 423function int(str) { 424 return parseInt(str, 10); 425} 426 427 428function inherit(parent, extra) { 429 return extend(Object.create(parent), extra); 430} 431 432/** 433 * @ngdoc function 434 * @name angular.noop 435 * @module ng 436 * @kind function 437 * 438 * @description 439 * A function that performs no operations. This function can be useful when writing code in the 440 * functional style. 441 ```js 442 function foo(callback) { 443 var result = calculateResult(); 444 (callback || angular.noop)(result); 445 } 446 ``` 447 */ 448function noop() {} 449noop.$inject = []; 450 451 452/** 453 * @ngdoc function 454 * @name angular.identity 455 * @module ng 456 * @kind function 457 * 458 * @description 459 * A function that returns its first argument. This function is useful when writing code in the 460 * functional style. 461 * 462 ```js 463 function transformer(transformationFn, value) { 464 return (transformationFn || angular.identity)(value); 465 }; 466 ``` 467 * @param {*} value to be returned. 468 * @returns {*} the value passed in. 469 */ 470function identity($) {return $;} 471identity.$inject = []; 472 473 474function valueFn(value) {return function() {return value;};} 475 476/** 477 * @ngdoc function 478 * @name angular.isUndefined 479 * @module ng 480 * @kind function 481 * 482 * @description 483 * Determines if a reference is undefined. 484 * 485 * @param {*} value Reference to check. 486 * @returns {boolean} True if `value` is undefined. 487 */ 488function isUndefined(value) {return typeof value === 'undefined';} 489 490 491/** 492 * @ngdoc function 493 * @name angular.isDefined 494 * @module ng 495 * @kind function 496 * 497 * @description 498 * Determines if a reference is defined. 499 * 500 * @param {*} value Reference to check. 501 * @returns {boolean} True if `value` is defined. 502 */ 503function isDefined(value) {return typeof value !== 'undefined';} 504 505 506/** 507 * @ngdoc function 508 * @name angular.isObject 509 * @module ng 510 * @kind function 511 * 512 * @description 513 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 514 * considered to be objects. Note that JavaScript arrays are objects. 515 * 516 * @param {*} value Reference to check. 517 * @returns {boolean} True if `value` is an `Object` but not `null`. 518 */ 519function isObject(value) { 520 // http://jsperf.com/isobject4 521 return value !== null && typeof value === 'object'; 522} 523 524 525/** 526 * @ngdoc function 527 * @name angular.isString 528 * @module ng 529 * @kind function 530 * 531 * @description 532 * Determines if a reference is a `String`. 533 * 534 * @param {*} value Reference to check. 535 * @returns {boolean} True if `value` is a `String`. 536 */ 537function isString(value) {return typeof value === 'string';} 538 539 540/** 541 * @ngdoc function 542 * @name angular.isNumber 543 * @module ng 544 * @kind function 545 * 546 * @description 547 * Determines if a reference is a `Number`. 548 * 549 * @param {*} value Reference to check. 550 * @returns {boolean} True if `value` is a `Number`. 551 */ 552function isNumber(value) {return typeof value === 'number';} 553 554 555/** 556 * @ngdoc function 557 * @name angular.isDate 558 * @module ng 559 * @kind function 560 * 561 * @description 562 * Determines if a value is a date. 563 * 564 * @param {*} value Reference to check. 565 * @returns {boolean} True if `value` is a `Date`. 566 */ 567function isDate(value) { 568 return toString.call(value) === '[object Date]'; 569} 570 571 572/** 573 * @ngdoc function 574 * @name angular.isArray 575 * @module ng 576 * @kind function 577 * 578 * @description 579 * Determines if a reference is an `Array`. 580 * 581 * @param {*} value Reference to check. 582 * @returns {boolean} True if `value` is an `Array`. 583 */ 584var isArray = Array.isArray; 585 586/** 587 * @ngdoc function 588 * @name angular.isFunction 589 * @module ng 590 * @kind function 591 * 592 * @description 593 * Determines if a reference is a `Function`. 594 * 595 * @param {*} value Reference to check. 596 * @returns {boolean} True if `value` is a `Function`. 597 */ 598function isFunction(value) {return typeof value === 'function';} 599 600 601/** 602 * Determines if a value is a regular expression object. 603 * 604 * @private 605 * @param {*} value Reference to check. 606 * @returns {boolean} True if `value` is a `RegExp`. 607 */ 608function isRegExp(value) { 609 return toString.call(value) === '[object RegExp]'; 610} 611 612 613/** 614 * Checks if `obj` is a window object. 615 * 616 * @private 617 * @param {*} obj Object to check 618 * @returns {boolean} True if `obj` is a window obj. 619 */ 620function isWindow(obj) { 621 return obj && obj.window === obj; 622} 623 624 625function isScope(obj) { 626 return obj && obj.$evalAsync && obj.$watch; 627} 628 629 630function isFile(obj) { 631 return toString.call(obj) === '[object File]'; 632} 633 634 635function isFormData(obj) { 636 return toString.call(obj) === '[object FormData]'; 637} 638 639 640function isBlob(obj) { 641 return toString.call(obj) === '[object Blob]'; 642} 643 644 645function isBoolean(value) { 646 return typeof value === 'boolean'; 647} 648 649 650function isPromiseLike(obj) { 651 return obj && isFunction(obj.then); 652} 653 654 655var trim = function(value) { 656 return isString(value) ? value.trim() : value; 657}; 658 659// Copied from: 660// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 661// Prereq: s is a string. 662var escapeForRegexp = function(s) { 663 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 664 replace(/\x08/g, '\\x08'); 665}; 666 667 668/** 669 * @ngdoc function 670 * @name angular.isElement 671 * @module ng 672 * @kind function 673 * 674 * @description 675 * Determines if a reference is a DOM element (or wrapped jQuery element). 676 * 677 * @param {*} value Reference to check. 678 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 679 */ 680function isElement(node) { 681 return !!(node && 682 (node.nodeName // we are a direct element 683 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 684} 685 686/** 687 * @param str 'key1,key2,...' 688 * @returns {object} in the form of {key1:true, key2:true, ...} 689 */ 690function makeMap(str) { 691 var obj = {}, items = str.split(","), i; 692 for (i = 0; i < items.length; i++) 693 obj[items[i]] = true; 694 return obj; 695} 696 697 698function nodeName_(element) { 699 return lowercase(element.nodeName || (element[0] && element[0].nodeName)); 700} 701 702function includes(array, obj) { 703 return Array.prototype.indexOf.call(array, obj) != -1; 704} 705 706function arrayRemove(array, value) { 707 var index = array.indexOf(value); 708 if (index >= 0) 709 array.splice(index, 1); 710 return value; 711} 712 713/** 714 * @ngdoc function 715 * @name angular.copy 716 * @module ng 717 * @kind function 718 * 719 * @description 720 * Creates a deep copy of `source`, which should be an object or an array.
721 * 722 * * If no destination is supplied, a copy of the object or array is created. 723 * * If a destination is provided, all of its elements (for arrays) or properties (for objects) 724 * are deleted and then all elements/properties from the source are copied to it. 725 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 726 * * If `source` is identical to 'destination' an exception will be thrown. 727 * 728 * @param {*} source The source that will be used to make a copy. 729 * Can be any type, including primitives, `null`, and `undefined`. 730 * @param {(Object|Array)=} destination Destination into which the source is copied. If 731 * provided, must be of the same type as `source`. 732 * @returns {*} The copy or updated `destination`, if `destination` was specified. 733 * 734 * @example 735 <example module="copyExample"> 736 <file name="index.html"> 737 <div ng-controller="ExampleController"> 738 <form novalidate class="simple-form"> 739 Name: <input type="text" ng-model="user.name" /><br /> 740 E-mail: <input type="email" ng-model="user.email" /><br /> 741 Gender: <input type="radio" ng-model="user.gender" value="male" />male 742 <input type="radio" ng-model="user.gender" value="female" />female<br /> 743 <button ng-click="reset()">RESET</button> 744 <button ng-click="update(user)">SAVE</button> 745 </form> 746 <pre>form = {{user | json}}</pre> 747 <pre>master = {{master | json}}</pre> 748 </div> 749 750 <script> 751 angular.module('copyExample', []) 752 .controller('ExampleController', ['$scope', function($scope) { 753 $scope.master= {}; 754 755 $scope.update = function(user) { 756 // Example with 1 argument 757 $scope.master= angular.copy(user); 758 }; 759 760 $scope.reset = function() { 761 // Example with 2 arguments 762 angular.copy($scope.master, $scope.user); 763 }; 764 765 $scope.reset(); 766 }]); 767 </script> 768 </file> 769 </example> 770 */ 771function copy(source, destination, stackSource, stackDest) { 772 if (isWindow(source) || isScope(source)) { 773 throw ngMinErr('cpws', 774 "Can't copy! Making copies of Window or Scope instances is not supported."); 775 } 776 777 if (!destination) { 778 destination = source; 779 if (source) { 780 if (isArray(source)) { 781 destination = copy(source, [], stackSource, stackDest); 782 } else if (isDate(source)) { 783 destination = new Date(source.getTime()); 784 } else if (isRegExp(source)) { 785 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); 786 destination.lastIndex = source.lastIndex; 787 } else if (isObject(source)) { 788 var emptyObject = Object.create(Object.getPrototypeOf(source)); 789 destination = copy(source, emptyObject, stackSource, stackDest); 790 } 791 } 792 } else { 793 if (source === destination) throw ngMinErr('cpi', 794 "Can't copy! Source and destination are identical."); 795 796 stackSource = stackSource || []; 797 stackDest = stackDest || []; 798 799 if (isObject(source)) { 800 var index = stackSource.indexOf(source); 801 if (index !== -1) return stackDest[index]; 802 803 stackSource.push(source); 804 stackDest.push(destination); 805 } 806 807 var result; 808 if (isArray(source)) { 809 destination.length = 0; 810 for (var i = 0; i < source.length; i++) { 811 result = copy(source[i], null, stackSource, stackDest); 812 if (isObject(source[i])) { 813 stackSource.push(source[i]); 814 stackDest.push(result); 815 } 816 destination.push(result); 817 } 818 } else { 819 var h = destination.$$hashKey; 820 if (isArray(destination)) { 821 destination.length = 0; 822 } else { 823 forEach(destination, function(value, key) { 824 delete destination[key]; 825 }); 826 } 827 for (var key in source) { 828 if (source.hasOwnProperty(key)) { 829 result = copy(source[key], null, stackSource, stackDest); 830 if (isObject(source[key])) { 831 stackSource.push(source[key]); 832 stackDest.push(result); 833 } 834 destination[key] = result; 835 } 836 } 837 setHashKey(destination,h); 838 } 839 840 } 841 return destination; 842} 843 844/** 845 * Creates a shallow copy of an object, an array or a primitive. 846 * 847 * Assumes that there are no proto properties for objects. 848 */ 849function shallowCopy(src, dst) { 850 if (isArray(src)) { 851 dst = dst || []; 852 853 for (var i = 0, ii = src.length; i < ii; i++) { 854 dst[i] = src[i]; 855 } 856 } else if (isObject(src)) { 857 dst = dst || {}; 858 859 for (var key in src) { 860 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 861 dst[key] = src[key]; 862 } 863 } 864 } 865 866 return dst || src; 867} 868 869 870/** 871 * @ngdoc function 872 * @name angular.equals 873 * @module ng 874 * @kind function 875 * 876 * @description 877 * Determines if two objects or two values are equivalent. Supports value types, regular 878 * expressions, arrays and objects. 879 * 880 * Two objects or values are considered equivalent if at least one of the following is true: 881 * 882 * * Both objects or values pass `===` comparison. 883 * * Both objects or values are of the same type and all of their properties are equal by 884 * comparing them with `angular.equals`. 885 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 886 * * Both values represent the same regular expression (In JavaScript,
887 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 888 * representation matches). 889 * 890 * During a property comparison, properties of `function` type and properties with names 891 * that begin with `$` are ignored. 892 * 893 * Scope and DOMWindow objects are being compared only by identify (`===`). 894 * 895 * @param {*} o1 Object or value to compare. 896 * @param {*} o2 Object or value to compare. 897 * @returns {boolean} True if arguments are equal. 898 */ 899function equals(o1, o2) { 900 if (o1 === o2) return true; 901 if (o1 === null || o2 === null) return false; 902 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 903 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 904 if (t1 == t2) { 905 if (t1 == 'object') { 906 if (isArray(o1)) { 907 if (!isArray(o2)) return false; 908 if ((length = o1.length) == o2.length) { 909 for (key = 0; key < length; key++) { 910 if (!equals(o1[key], o2[key])) return false; 911 } 912 return true; 913 } 914 } else if (isDate(o1)) { 915 if (!isDate(o2)) return false; 916 return equals(o1.getTime(), o2.getTime()); 917 } else if (isRegExp(o1) && isRegExp(o2)) { 918 return o1.toString() == o2.toString(); 919 } else { 920 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false; 921 keySet = {}; 922 for (key in o1) { 923 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 924 if (!equals(o1[key], o2[key])) return false; 925 keySet[key] = true; 926 } 927 for (key in o2) { 928 if (!keySet.hasOwnProperty(key) && 929 key.charAt(0) !== '$' && 930 o2[key] !== undefined && 931 !isFunction(o2[key])) return false; 932 } 933 return true; 934 } 935 } 936 } 937 return false; 938} 939 940var csp = function() { 941 if (isDefined(csp.isActive_)) return csp.isActive_; 942 943 var active = !!(document.querySelector('[ng-csp]') || 944 document.querySelector('[data-ng-csp]')); 945 946 if (!active) { 947 try { 948 /* jshint -W031, -W054 */ 949 new Function(''); 950 /* jshint +W031, +W054 */ 951 } catch (e) { 952 active = true; 953 } 954 } 955 956 return (csp.isActive_ = active); 957}; 958 959 960 961function concat(array1, array2, index) { 962 return array1.concat(slice.call(array2, index)); 963} 964 965function sliceArgs(args, startIndex) { 966 return slice.call(args, startIndex || 0); 967} 968 969 970/* jshint -W101 */ 971/** 972 * @ngdoc function 973 * @name angular.bind 974 * @module ng 975 * @kind function 976 * 977 * @description 978 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 979 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 980 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 981 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 982 * 983 * @param {Object} self Context which `fn` should be evaluated in. 984 * @param {function()} fn Function to be bound. 985 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 986 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 987 */ 988/* jshint +W101 */ 989function bind(self, fn) { 990 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 991 if (isFunction(fn) && !(fn instanceof RegExp)) { 992 return curryArgs.length 993 ? function() { 994 return arguments.length 995 ? fn.apply(self, concat(curryArgs, arguments, 0)) 996 : fn.apply(self, curryArgs); 997 } 998 : function() { 999 return arguments.length 1000 ? fn.apply(self, arguments) 1001 : fn.call(self); 1002 }; 1003 } else { 1004 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1005 return fn; 1006 } 1007} 1008 1009 1010function toJsonReplacer(key, value) { 1011 var val = value; 1012 1013 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 1014 val = undefined; 1015 } else if (isWindow(value)) { 1016 val = '$WINDOW'; 1017 } else if (value && document === value) { 1018 val = '$DOCUMENT'; 1019 } else if (isScope(value)) { 1020 val = '$SCOPE'; 1021 } 1022 1023 return val; 1024} 1025 1026 1027/** 1028 * @ngdoc function 1029 * @name angular.toJson 1030 * @module ng 1031 * @kind function 1032 * 1033 * @description 1034 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 1035 * stripped since angular uses this notation internally. 1036 * 1037 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1038 * @param {boolean|number=}
1038 pretty If set to true, the JSON output will contain newlines and whitespace. 1039 * If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2). 1040 * @returns {string|undefined} JSON-ified string representing `obj`. 1041 */ 1042function toJson(obj, pretty) { 1043 if (typeof obj === 'undefined') return undefined; 1044 if (!isNumber(pretty)) { 1045 pretty = pretty ? 2 : null; 1046 } 1047 return JSON.stringify(obj, toJsonReplacer, pretty); 1048} 1049 1050 1051/** 1052 * @ngdoc function 1053 * @name angular.fromJson 1054 * @module ng 1055 * @kind function 1056 * 1057 * @description 1058 * Deserializes a JSON string. 1059 * 1060 * @param {string} json JSON string to deserialize. 1061 * @returns {Object|Array|string|number} Deserialized JSON string. 1062 */ 1063function fromJson(json) { 1064 return isString(json) 1065 ? JSON.parse(json) 1066 : json; 1067} 1068 1069 1070/** 1071 * @returns {string} Returns the string representation of the element. 1072 */ 1073function startingTag(element) { 1074 element = jqLite(element).clone(); 1075 try { 1076 // turns out IE does not let you set .html() on elements which 1077 // are not allowed to have children. So we just ignore it. 1078 element.empty(); 1079 } catch (e) {} 1080 var elemHtml = jqLite('<div>').append(element).html(); 1081 try { 1082 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 1083 elemHtml. 1084 match(/^(<[^>]+>)/)[1]. 1085 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1086 } catch (e) { 1087 return lowercase(elemHtml); 1088 } 1089 1090} 1091 1092 1093///////////////////////////////////////////////// 1094 1095/** 1096 * Tries to decode the URI component without throwing an exception. 1097 * 1098 * @private 1099 * @param str value potential URI component to check. 1100 * @returns {boolean} True if `value` can be decoded 1101 * with the decodeURIComponent function. 1102 */ 1103function tryDecodeURIComponent(value) { 1104 try { 1105 return decodeURIComponent(value); 1106 } catch (e) { 1107 // Ignore any invalid uri component 1108 } 1109} 1110 1111 1112/** 1113 * Parses an escaped url query string into key-value pairs. 1114 * @returns {Object.<string,boolean|Array>} 1115 */ 1116function parseKeyValue(/**string*/keyValue) { 1117 var obj = {}, key_value, key; 1118 forEach((keyValue || "").split('&'), function(keyValue) { 1119 if (keyValue) { 1120 key_value = keyValue.replace(/\+/g,'%20').split('='); 1121 key = tryDecodeURIComponent(key_value[0]); 1122 if (isDefined(key)) { 1123 var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; 1124 if (!hasOwnProperty.call(obj, key)) { 1125 obj[key] = val; 1126 } else if (isArray(obj[key])) { 1127 obj[key].push(val); 1128 } else { 1129 obj[key] = [obj[key],val]; 1130 } 1131 } 1132 } 1133 }); 1134 return obj; 1135} 1136 1137function toKeyValue(obj) { 1138 var parts = []; 1139 forEach(obj, function(value, key) { 1140 if (isArray(value)) { 1141 forEach(value, function(arrayValue) { 1142 parts.push(encodeUriQuery(key, true) + 1143 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1144 }); 1145 } else { 1146 parts.push(encodeUriQuery(key, true) + 1147 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1148 } 1149 }); 1150 return parts.length ? parts.join('&') : ''; 1151} 1152 1153 1154/** 1155 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1156 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1157 * segments: 1158 * segment = *pchar 1159 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1160 * pct-encoded = "%" HEXDIG HEXDIG 1161 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1162 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1163 * / "*" / "+" / "," / ";" / "=" 1164 */ 1165function encodeUriSegment(val) { 1166 return encodeUriQuery(val, true). 1167 replace(/%26/gi, '&'). 1168 replace(/%3D/gi, '='). 1169 replace(/%2B/gi, '+'); 1170} 1171 1172 1173/** 1174 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1175 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1176 * encoded per http://tools.ietf.org/html/rfc3986: 1177 * query = *( pchar / "/" / "?" ) 1178 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1179 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1180 * pct-encoded = "%" HEXDIG HEXDIG 1181 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1182 * / "*" / "+" / "," / ";" / "=" 1183 */ 1184function encodeUriQuery(val, pctEncodeSpaces) { 1185 return encodeURIComponent(val). 1186 replace(/%40/gi, '@'). 1187 replace(/%3A/gi, ':'). 1188 replace(/%24/g, '$'). 1189 replace(/%2C/gi, ','). 1190 replace(/%3B/gi, ';'). 1191 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1192} 1193 1194var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 1195 1196function getNgAttribute(element, ngAttr) { 1197 var attr, i, ii = ngAttrPrefixes.length; 1198 element = jqLite(element); 1199 for (i = 0; i < ii; ++i) { 1200 attr = ngAttrPrefixes[i] + ngAttr; 1201 if (isString(attr = element.attr(attr))) { 1202 return attr; 1203 } 1204 } 1205 return null; 1206} 1207 1208/** 1209 * @ngdoc directive 1210 * @name ngApp 1211 * @module ng 1212 * 1213 * @element ANY 1214 * @param {angular.Module} ngApp an optional application 1215 * {@link angular.module module} name to load. 1216 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 1217 * created in "strict-di" mode. This means that the application will fail to invoke functions which 1218 * do not use explicit function annotation (and are thus unsuitable for minification), as de
1218scribed 1219 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 1220 * tracking down the root of these bugs. 1221 * 1222 * @description 1223 * 1224 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1225 * designates the **root element** of the application and is typically placed near the root element 1226 * of the page - e.g. on the `<body>` or `<html>` tags. 1227 * 1228 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` 1229 * found in the document will be used to define the root element to auto-bootstrap as an 1230 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1231 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1232 * 1233 * You can specify an **AngularJS module** to be used as the root module for the application. This 1234 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 1235 * should contain the application code needed or have dependencies on other modules that will 1236 * contain the code. See {@link angular.module} for more information. 1237 * 1238 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1239 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1240 * would not be resolved to `3`. 1241 * 1242 * `ngApp` is the easiest, and most common way to bootstrap an application. 1243 * 1244 <example module="ngAppDemo"> 1245 <file name="index.html"> 1246 <div ng-controller="ngAppDemoController"> 1247 I can add: {{a}} + {{b}} = {{ a+b }} 1248 </div> 1249 </file> 1250 <file name="script.js"> 1251 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1252 $scope.a = 1; 1253 $scope.b = 2; 1254 }); 1255 </file> 1256 </example> 1257 * 1258 * Using `ngStrictDi`, you would see something like this: 1259 * 1260 <example ng-app-included="true"> 1261 <file name="index.html"> 1262 <div ng-app="ngAppStrictDemo" ng-strict-di> 1263 <div ng-controller="GoodController1"> 1264 I can add: {{a}} + {{b}} = {{ a+b }} 1265 1266 <p>This renders because the controller does not fail to 1267 instantiate, by using explicit annotation style (see 1268 script.js for details) 1269 </p> 1270 </div> 1271 1272 <div ng-controller="GoodController2"> 1273 Name: <input ng-model="name"><br /> 1274 Hello, {{name}}! 1275 1276 <p>This renders because the controller does not fail to 1277 instantiate, by using explicit annotation style 1278 (see script.js for details) 1279 </p> 1280 </div> 1281 1282 <div ng-controller="BadController"> 1283 I can add: {{a}} + {{b}} = {{ a+b }} 1284 1285 <p>The controller could not be instantiated, due to relying 1286 on automatic function annotations (which are disabled in 1287 strict mode). As such, the content of this section is not 1288 interpolated, and there should be an error in your web console. 1289 </p> 1290 </div> 1291 </div> 1292 </file> 1293 <file name="script.js"> 1294 angular.module('ngAppStrictDemo', []) 1295 // BadController will fail to instantiate, due to relying on automatic function annotation, 1296 // rather than an explicit annotation 1297 .controller('BadController', function($scope) { 1298 $scope.a = 1; 1299 $scope.b = 2; 1300 }) 1301 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 1302 // due to using explicit annotations using the array style and $inject property, respectively. 1303 .controller('GoodController1', ['$scope', function($scope) { 1304 $scope.a = 1; 1305 $scope.b = 2; 1306 }]) 1307 .controller('GoodController2', GoodController2); 1308 function GoodController2($scope) { 1309 $scope.name = "World"; 1310 } 1311 GoodController2.$inject = ['$scope']; 1312 </file> 1313 <file name="style.css"> 1314 div[ng-controller] { 1315 margin-bottom: 1em; 1316 -webkit-border-radius: 4px; 1317 border-radius: 4px; 1318 border: 1px solid; 1319 padding: .5em; 1320 } 1321 div[ng-controller^=Good] { 1322 border-color: #d6e9c6; 1323 background-color: #dff0d8; 1324 color: #3c763d; 1325 } 1326 div[ng-controller^=Bad] { 1327 border-color: #ebccd1; 1328 background-color: #f2dede; 1329 color: #a94442; 1330 margin-bottom: 0; 1331 } 1332 </file> 1333 </example> 1334 */ 1335function angularInit(element, bootstrap) { 1336 var appElement, 1337 module, 1338 config = {}; 1339 1340 // The element `element` has priority over any other element 1341 forEach(ngAttrPrefixes, function(prefix) { 1342 var name = prefix + 'app'; 1343 1344 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 1345 appElement = element; 1346 module = element.getAttribute(name); 1347 } 1348 }); 1349 forEach(ngAttrPrefixes, function(prefix) { 1350 var name = prefix + 'app'; 1351 var candidate; 1352 1353 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 1354 appElement = candidate; 1355 module = candidate.getAttribute(name); 1356 } 1357 }); 1358 if (appElement) { 1359 config.strictDi = getNgAttribute(appElement, "strict-di") !== null; 1360 bootstrap(appElement, module ? [module] : [], config); 1361 } 1362} 1363 1364/** 1365 * @ngdoc function 1366 * @name angular.bootstrap 1367 * @module ng 1368 * @description 1369 * Use this function to manually start up angular application. 1370 * 1371 * See: {@link guide/bootstrap Bootstrap} 1372 *
1373 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually. 1374 * They must use {@link ng.directive:ngApp ngApp}. 1375 * 1376 * Angular will detect if it has been loaded into the browser more than once and only allow the 1377 * first loaded script to be bootstrapped and will report a warning to the browser console for 1378 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1379 * multiple instances of Angular try to work on the DOM. 1380 * 1381 * ```html 1382 * <!doctype html> 1383 * <html> 1384 * <body> 1385 * <div ng-controller="WelcomeController"> 1386 * {{greeting}} 1387 * </div> 1388 * 1389 * <script src="angular.js"></script> 1390 * <script> 1391 * var app = angular.module('demo', []) 1392 * .controller('WelcomeController', function($scope) { 1393 * $scope.greeting = 'Welcome!'; 1394 * }); 1395 * angular.bootstrap(document, ['demo']); 1396 * </script> 1397 * </body> 1398 * </html> 1399 * ``` 1400 * 1401 * @param {DOMElement} element DOM element which is the root of angular application. 1402 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1403 * Each item in the array should be the name of a predefined module or a (DI annotated) 1404 * function that will be invoked by the injector as a `config` block. 1405 * See: {@link angular.module modules} 1406 * @param {Object=} config an object for defining configuration options for the application. The 1407 * following keys are supported: 1408 * 1409 * * `strictDi` - disable automatic function annotation for the application. This is meant to 1410 * assist in finding bugs which break minified code. Defaults to `false`. 1411 * 1412 * @returns {auto.$injector} Returns the newly created injector for this app. 1413 */ 1414function bootstrap(element, modules, config) { 1415 if (!isObject(config)) config = {}; 1416 var defaultConfig = { 1417 strictDi: false 1418 }; 1419 config = extend(defaultConfig, config); 1420 var doBootstrap = function() { 1421 element = jqLite(element); 1422 1423 if (element.injector()) { 1424 var tag = (element[0] === document) ? 'document' : startingTag(element); 1425 //Encode angle brackets to prevent input from being sanitized to empty string #8683 1426 throw ngMinErr( 1427 'btstrpd', 1428 "App Already Bootstrapped with this Element '{0}'", 1429 tag.replace(/</,'<').replace(/>/,'>')); 1430 } 1431 1432 modules = modules || []; 1433 modules.unshift(['$provide', function($provide) { 1434 $provide.value('$rootElement', element); 1435 }]); 1436 1437 if (config.debugInfoEnabled) { 1438 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 1439 modules.push(['$compileProvider', function($compileProvider) { 1440 $compileProvider.debugInfoEnabled(true); 1441 }]); 1442 } 1443 1444 modules.unshift('ng'); 1445 var injector = createInjector(modules, config.strictDi); 1446 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 1447 function bootstrapApply(scope, element, compile, injector) { 1448 scope.$apply(function() { 1449 element.data('$injector', injector); 1450 compile(element)(scope); 1451 }); 1452 }] 1453 ); 1454 return injector; 1455 }; 1456 1457 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 1458 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1459 1460 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 1461 config.debugInfoEnabled = true; 1462 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 1463 } 1464 1465 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1466 return doBootstrap(); 1467 } 1468 1469 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1470 angular.resumeBootstrap = function(extraModules) { 1471 forEach(extraModules, function(module) { 1472 modules.push(module); 1473 }); 1474 return doBootstrap(); 1475 }; 1476 1477 if (isFunction(angular.resumeDeferredBootstrap)) { 1478 angular.resumeDeferredBootstrap(); 1479 } 1480} 1481 1482/** 1483 * @ngdoc function 1484 * @name angular.reloadWithDebugInfo 1485 * @module ng 1486 * @description 1487 * Use this function to reload the current application with debug information turned on. 1488 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 1489 * 1490 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 1491 */ 1492function reloadWithDebugInfo() { 1493 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 1494 window.location.reload(); 1495} 1496 1497/** 1498 * @name angular.getTestability 1499 * @module ng 1500 * @description 1501 * Get the testability service for the instance of Angular on the given 1502 * element. 1503 * @param {DOMElement} element DOM element which is the root of angular application. 1504 */ 1505function getTestability(rootElement) { 1506 var injector = angular.element(rootElement).injector(); 1507 if (!injector) { 1508 throw ngMinErr('test', 1509 'no injector found for element argument to getTestability'); 1510 } 1511 return injector.get('$$testability'); 1512} 1513 1514var SNAKE_CASE_REGEXP = /[A-Z]/g; 1515function snake_case(name, separator) { 1516 separator = separator || '_'; 1517 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1518 return (pos ? separator : '') + letter.toLowerCase(); 1519 }); 1520} 1521 1522var bindJQueryFired = false;
1523var skipDestroyOnNextJQueryCleanData; 1524function bindJQuery() { 1525 var originalCleanData; 1526 1527 if (bindJQueryFired) { 1528 return; 1529 } 1530 1531 // bind to jQuery if present; 1532 jQuery = window.jQuery; 1533 // Use jQuery if it exists with proper functionality, otherwise default to us. 1534 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. 1535 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older 1536 // versions. It will not work for sure with jQuery <1.7, though. 1537 if (jQuery && jQuery.fn.on) { 1538 jqLite = jQuery; 1539 extend(jQuery.fn, { 1540 scope: JQLitePrototype.scope, 1541 isolateScope: JQLitePrototype.isolateScope, 1542 controller: JQLitePrototype.controller, 1543 injector: JQLitePrototype.injector, 1544 inheritedData: JQLitePrototype.inheritedData 1545 }); 1546 1547 // All nodes removed from the DOM via various jQuery APIs like .remove() 1548 // are passed through jQuery.cleanData. Monkey-patch this method to fire 1549 // the $destroy event on all removed nodes. 1550 originalCleanData = jQuery.cleanData; 1551 jQuery.cleanData = function(elems) { 1552 var events; 1553 if (!skipDestroyOnNextJQueryCleanData) { 1554 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 1555 events = jQuery._data(elem, "events"); 1556 if (events && events.$destroy) { 1557 jQuery(elem).triggerHandler('$destroy'); 1558 } 1559 } 1560 } else { 1561 skipDestroyOnNextJQueryCleanData = false; 1562 } 1563 originalCleanData(elems); 1564 }; 1565 } else { 1566 jqLite = JQLite; 1567 } 1568 1569 angular.element = jqLite; 1570 1571 // Prevent double-proxying. 1572 bindJQueryFired = true; 1573} 1574 1575/** 1576 * throw error if the argument is falsy. 1577 */ 1578function assertArg(arg, name, reason) { 1579 if (!arg) { 1580 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1581 } 1582 return arg; 1583} 1584 1585function assertArgFn(arg, name, acceptArrayAnnotation) { 1586 if (acceptArrayAnnotation && isArray(arg)) { 1587 arg = arg[arg.length - 1]; 1588 } 1589 1590 assertArg(isFunction(arg), name, 'not a function, got ' + 1591 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1592 return arg; 1593} 1594 1595/** 1596 * throw error if the name given is hasOwnProperty 1597 * @param {String} name the name to test 1598 * @param {String} context the context in which the name is used, such as module or directive 1599 */ 1600function assertNotHasOwnProperty(name, context) { 1601 if (name === 'hasOwnProperty') { 1602 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1603 } 1604} 1605 1606/** 1607 * Return the value accessible from the object by path. Any undefined traversals are ignored 1608 * @param {Object} obj starting object 1609 * @param {String} path path to traverse 1610 * @param {boolean} [bindFnToScope=true] 1611 * @returns {Object} value as accessible by path 1612 */ 1613//TODO(misko): this function needs to be removed 1614function getter(obj, path, bindFnToScope) { 1615 if (!path) return obj; 1616 var keys = path.split('.'); 1617 var key; 1618 var lastInstance = obj; 1619 var len = keys.length; 1620 1621 for (var i = 0; i < len; i++) { 1622 key = keys[i]; 1623 if (obj) { 1624 obj = (lastInstance = obj)[key]; 1625 } 1626 } 1627 if (!bindFnToScope && isFunction(obj)) { 1628 return bind(lastInstance, obj); 1629 } 1630 return obj; 1631} 1632 1633/** 1634 * Return the DOM siblings between the first and last node in the given array. 1635 * @param {Array} array like object 1636 * @returns {jqLite} jqLite collection containing the nodes 1637 */ 1638function getBlockNodes(nodes) { 1639 // TODO(perf): just check if all items in `nodes` are siblings and if they are return the original 1640 // collection, otherwise update the original collection. 1641 var node = nodes[0]; 1642 var endNode = nodes[nodes.length - 1]; 1643 var blockNodes = [node]; 1644 1645 do { 1646 node = node.nextSibling; 1647 if (!node) break; 1648 blockNodes.push(node); 1649 } while (node !== endNode); 1650 1651 return jqLite(blockNodes); 1652} 1653 1654 1655/** 1656 * Creates a new object without a prototype. This object is useful for lookup without having to 1657 * guard against prototypically inherited properties via hasOwnProperty. 1658 * 1659 * Related micro-benchmarks: 1660 * - http://jsperf.com/object-create2 1661 * - http://jsperf.com/proto-map-lookup/2 1662 * - http://jsperf.com/for-in-vs-object-keys2 1663 * 1664 * @returns {Object} 1665 */ 1666function createMap() { 1667 return Object.create(null); 1668} 1669 1670var NODE_TYPE_ELEMENT = 1; 1671var NODE_TYPE_TEXT = 3; 1672var NODE_TYPE_COMMENT = 8; 1673var NODE_TYPE_DOCUMENT = 9; 1674var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 1675 1676/** 1677 * @ngdoc type 1678 * @name angular.Module 1679 * @module ng 1680 * @description 1681 * 1682 * Interface for configuring angular {@link angular.module modules}. 1683 */ 1684 1685function setupModuleLoader(window) { 1686 1687 var $injectorMinErr = minErr('$injector'); 1688 var ngMinErr = minErr('ng'); 1689 1690 function ensure(obj, name, factory) {
vendor: 4,138 bytes, lines 1691-1805
1691 return obj[name] || (obj[name] = factory()); 1692 } 1693 1694 var angular = ensure(window, 'angular', Object); 1695 1696 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1697 angular.$$minErr = angular.$$minErr || minErr; 1698 1699 return ensure(angular, 'module', function() { 1700 /** @type {Object.<string, angular.Module>} */ 1701 var modules = {}; 1702 1703 /** 1704 * @ngdoc function 1705 * @name angular.module 1706 * @module ng 1707 * @description 1708 * 1709 * The `angular.module` is a global place for creating, registering and retrieving Angular 1710 * modules. 1711 * All modules (angular core or 3rd party) that should be available to an application must be 1712 * registered using this mechanism. 1713 * 1714 * When passed two or more arguments, a new module is created. If passed only one argument, an 1715 * existing module (the name passed as the first argument to `module`) is retrieved. 1716 * 1717 * 1718 * # Module 1719 * 1720 * A module is a collection of services, directives, controllers, filters, and configuration information. 1721 * `angular.module` is used to configure the {@link auto.$injector $injector}. 1722 * 1723 * ```js 1724 * // Create a new module 1725 * var myModule = angular.module('myModule', []); 1726 * 1727 * // register a new service 1728 * myModule.value('appName', 'MyCoolApp'); 1729 * 1730 * // configure existing services inside initialization blocks. 1731 * myModule.config(['$locationProvider', function($locationProvider) { 1732 * // Configure existing providers 1733 * $locationProvider.hashPrefix('!'); 1734 * }]); 1735 * ``` 1736 * 1737 * Then you can create an injector and load your modules like this: 1738 * 1739 * ```js 1740 * var injector = angular.injector(['ng', 'myModule']) 1741 * ``` 1742 * 1743 * However it's more likely that you'll just use 1744 * {@link ng.directive:ngApp ngApp} or 1745 * {@link angular.bootstrap} to simplify this process for you. 1746 * 1747 * @param {!string} name The name of the module to create or retrieve. 1748 * @param {!Array.<string>=} requires If specified then new module is being created. If 1749 * unspecified then the module is being retrieved for further configuration. 1750 * @param {Function=} configFn Optional configuration function for the module. Same as 1751 * {@link angular.Module#config Module#config()}. 1752 * @returns {module} new module with the {@link angular.Module} api. 1753 */ 1754 return function module(name, requires, configFn) { 1755 var assertNotHasOwnProperty = function(name, context) { 1756 if (name === 'hasOwnProperty') { 1757 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 1758 } 1759 }; 1760 1761 assertNotHasOwnProperty(name, 'module'); 1762 if (requires && modules.hasOwnProperty(name)) { 1763 modules[name] = null; 1764 } 1765 return ensure(modules, name, function() { 1766 if (!requires) { 1767 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 1768 "the module name or forgot to load it. If registering a module ensure that you " + 1769 "specify the dependencies as the second argument.", name); 1770 } 1771 1772 /** @type {!Array.<Array.<*>>} */ 1773 var invokeQueue = []; 1774 1775 /** @type {!Array.<Function>} */ 1776 var configBlocks = []; 1777 1778 /** @type {!Array.<Function>} */ 1779 var runBlocks = []; 1780 1781 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 1782 1783 /** @type {angular.Module} */ 1784 var moduleInstance = { 1785 // Private state 1786 _invokeQueue: invokeQueue, 1787 _configBlocks: configBlocks, 1788 _runBlocks: runBlocks, 1789 1790 /** 1791 * @ngdoc property 1792 * @name angular.Module#requires 1793 * @module ng 1794 * 1795 * @description 1796 * Holds the list of modules which the injector will load before the current module is 1797 * loaded. 1798 */ 1799 requires: requires, 1800 1801 /** 1802 * @ngdoc property 1803 * @name angular.Module#name 1804 * @module ng 1805 *
1806 * @description 1807 * Name of the module. 1808 */ 1809 name: name, 1810 1811 1812 /** 1813 * @ngdoc method 1814 * @name angular.Module#provider 1815 * @module ng 1816 * @param {string} name service name 1817 * @param {Function} providerType Construction function for creating new instance of the 1818 * service. 1819 * @description 1820 * See {@link auto.$provide#provider $provide.provider()}. 1821 */ 1822 provider: invokeLater('$provide', 'provider'), 1823 1824 /** 1825 * @ngdoc method 1826 * @name angular.Module#factory 1827 * @module ng 1828 * @param {string} name service name 1829 * @param {Function} providerFunction Function for creating new instance of the service. 1830 * @description 1831 * See {@link auto.$provide#factory $provide.factory()}. 1832 */ 1833 factory: invokeLater('$provide', 'factory'), 1834 1835 /** 1836 * @ngdoc method 1837 * @name angular.Module#service 1838 * @module ng 1839 * @param {string} name service name 1840 * @param {Function} constructor A constructor function that will be instantiated. 1841 * @description 1842 * See {@link auto.$provide#service $provide.service()}. 1843 */ 1844 service: invokeLater('$provide', 'service'), 1845 1846 /** 1847 * @ngdoc method 1848 * @name angular.Module#value 1849 * @module ng 1850 * @param {string} name service name 1851 * @param {*} object Service instance object. 1852 * @description 1853 * See {@link auto.$provide#value $provide.value()}. 1854 */ 1855 value: invokeLater('$provide', 'value'), 1856 1857 /** 1858 * @ngdoc method 1859 * @name angular.Module#constant 1860 * @module ng 1861 * @param {string} name constant name 1862 * @param {*} object Constant value. 1863 * @description 1864 * Because the constant are fixed, they get applied before other provide methods. 1865 * See {@link auto.$provide#constant $provide.constant()}. 1866 */ 1867 constant: invokeLater('$provide', 'constant', 'unshift'), 1868 1869 /** 1870 * @ngdoc method 1871 * @name angular.Module#animation 1872 * @module ng 1873 * @param {string} name animation name 1874 * @param {Function} animationFactory Factory function for creating new instance of an 1875 * animation. 1876 * @description 1877 * 1878 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 1879 * 1880 * 1881 * Defines an animation hook that can be later used with 1882 * {@link ngAnimate.$animate $animate} service and directives that use this service. 1883 * 1884 * ```js 1885 * module.animation('.animation-name', function($inject1, $inject2) { 1886 * return { 1887 * eventName : function(element, done) { 1888 * //code to run the animation 1889 * //once complete, then run done() 1890 * return function cancellationFunction(element) { 1891 * //code to cancel the animation 1892 * } 1893 * } 1894 * } 1895 * }) 1896 * ``` 1897 * 1898 * See {@link ng.$animateProvider#register $animateProvider.register()} and 1899 * {@link ngAnimate ngAnimate module} for more information. 1900 */ 1901 animation: invokeLater('$animateProvider', 'register'), 1902 1903 /** 1904 * @ngdoc method 1905 * @name angular.Module#filter 1906 * @module ng 1907 * @param {string} name Filter name. 1908 * @param {Function} filterFactory Factory function for creating new instance of filter. 1909 * @description 1910 * See {@link ng.$filterProvider#register $filterProvider.register()}. 1911 */ 1912 filter: invokeLater('$filterProvider', 'register'), 1913 1914 /** 1915 * @ngdoc method 1916 * @name angular.Module#controller 1917 * @module ng 1918 * @param {string|Object} name Controller name, or an object map of controllers where the 1919 * keys are the names and the values are the constructors. 1920 * @param {Function} constructor Controller constructor function.
vendor: 4,201 bytes, lines 1921-2078
1921 * @description 1922 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 1923 */ 1924 controller: invokeLater('$controllerProvider', 'register'), 1925 1926 /** 1927 * @ngdoc method 1928 * @name angular.Module#directive 1929 * @module ng 1930 * @param {string|Object} name Directive name, or an object map of directives where the 1931 * keys are the names and the values are the factories. 1932 * @param {Function} directiveFactory Factory function for creating new instance of 1933 * directives. 1934 * @description 1935 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 1936 */ 1937 directive: invokeLater('$compileProvider', 'directive'), 1938 1939 /** 1940 * @ngdoc method 1941 * @name angular.Module#config 1942 * @module ng 1943 * @param {Function} configFn Execute this function on module load. Useful for service 1944 * configuration. 1945 * @description 1946 * Use this method to register work which needs to be performed on module loading. 1947 * For more about how to configure services, see 1948 * {@link providers#provider-recipe Provider Recipe}. 1949 */ 1950 config: config, 1951 1952 /** 1953 * @ngdoc method 1954 * @name angular.Module#run 1955 * @module ng 1956 * @param {Function} initializationFn Execute this function after injector creation. 1957 * Useful for application initialization. 1958 * @description 1959 * Use this method to register work which should be performed when the injector is done 1960 * loading all modules. 1961 */ 1962 run: function(block) { 1963 runBlocks.push(block); 1964 return this; 1965 } 1966 }; 1967 1968 if (configFn) { 1969 config(configFn); 1970 } 1971 1972 return moduleInstance; 1973 1974 /** 1975 * @param {string} provider 1976 * @param {string} method 1977 * @param {String=} insertMethod 1978 * @returns {angular.Module} 1979 */ 1980 function invokeLater(provider, method, insertMethod, queue) { 1981 if (!queue) queue = invokeQueue; 1982 return function() { 1983 queue[insertMethod || 'push']([provider, method, arguments]); 1984 return moduleInstance; 1985 }; 1986 } 1987 }); 1988 }; 1989 }); 1990 1991} 1992 1993/* global: toDebugString: true */ 1994 1995function serializeObject(obj) { 1996 var seen = []; 1997 1998 return JSON.stringify(obj, function(key, val) { 1999 val = toJsonReplacer(key, val); 2000 if (isObject(val)) { 2001 2002 if (seen.indexOf(val) >= 0) return '<<already seen>>'; 2003 2004 seen.push(val); 2005 } 2006 return val; 2007 }); 2008} 2009 2010function toDebugString(obj) { 2011 if (typeof obj === 'function') { 2012 return obj.toString().replace(/ \{[\s\S]*$/, ''); 2013 } else if (typeof obj === 'undefined') { 2014 return 'undefined'; 2015 } else if (typeof obj !== 'string') { 2016 return serializeObject(obj); 2017 } 2018 return obj; 2019} 2020 2021/* global angularModule: true, 2022 version: true, 2023 2024 $LocaleProvider, 2025 $CompileProvider, 2026 2027 htmlAnchorDirective, 2028 inputDirective, 2029 inputDirective, 2030 formDirective, 2031 scriptDirective, 2032 selectDirective, 2033 styleDirective, 2034 optionDirective, 2035 ngBindDirective, 2036 ngBindHtmlDirective, 2037 ngBindTemplateDirective, 2038 ngClassDirective, 2039 ngClassEvenDirective, 2040 ngClassOddDirective, 2041 ngCspDirective, 2042 ngCloakDirective, 2043 ngControllerDirective, 2044 ngFormDirective, 2045 ngHideDirective, 2046 ngIfDirective, 2047 ngIncludeDirective, 2048 ngIncludeFillContentDirective, 2049 ngInitDirective, 2050 ngNonBindableDirective, 2051 ngPluralizeDirective, 2052 ngRepeatDirective, 2053 ngShowDirective, 2054 ngStyleDirective, 2055 ngSwitchDirective, 2056 ngSwitchWhenDirective, 2057 ngSwitchDefaultDirective, 2058 ngOptionsDirective, 2059 ngTranscludeDirective, 2060 ngModelDirective, 2061 ngListDirective, 2062 ngChangeDirective, 2063 patternDirective, 2064 patternDirective, 2065 requiredDirective, 2066 requiredDirective, 2067 minlengthDirective, 2068 minlengthDirective, 2069 maxlengthDirective, 2070 maxlengthDirective, 2071 ngValueDirective, 2072 ngModelOptionsDirective, 2073 ngAttributeAliasDirectives, 2074 ngEventDirectives, 2075 2076 $AnchorScrollProvider, 2077 $AnimateProvider, 2078 $BrowserProvider,
2079 $CacheFactoryProvider, 2080 $ControllerProvider, 2081 $DocumentProvider, 2082 $ExceptionHandlerProvider, 2083 $FilterProvider, 2084 $InterpolateProvider, 2085 $IntervalProvider, 2086 $HttpProvider, 2087 $HttpBackendProvider, 2088 $LocationProvider, 2089 $LogProvider, 2090 $ParseProvider, 2091 $RootScopeProvider, 2092 $QProvider, 2093 $$QProvider, 2094 $$SanitizeUriProvider, 2095 $SceProvider, 2096 $SceDelegateProvider, 2097 $SnifferProvider, 2098 $TemplateCacheProvider, 2099 $TemplateRequestProvider, 2100 $$TestabilityProvider, 2101 $TimeoutProvider, 2102 $$RAFProvider, 2103 $$AsyncCallbackProvider, 2104 $WindowProvider, 2105 $$jqLiteProvider 2106*/ 2107 2108 2109/** 2110 * @ngdoc object 2111 * @name angular.version 2112 * @module ng 2113 * @description 2114 * An object that contains information about the current AngularJS version. This object has the 2115 * following properties: 2116 * 2117 * - `full` â `{string}` â Full version string, such as "0.9.18". 2118 * - `major` â `{number}` â Major version number, such as "0". 2119 * - `minor` â `{number}` â Minor version number, such as "9". 2120 * - `dot` â `{number}` â Dot version number, such as "18". 2121 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 2122 */ 2123var version = { 2124 full: '1.3.12', // all of these placeholder strings will be replaced by grunt's 2125 major: 1, // package task 2126 minor: 3, 2127 dot: 12, 2128 codeName: 'outlandish-knitting' 2129}; 2130 2131 2132function publishExternalAPI(angular) { 2133 extend(angular, { 2134 'bootstrap': bootstrap, 2135 'copy': copy, 2136 'extend': extend, 2137 'equals': equals, 2138 'element': jqLite, 2139 'forEach': forEach, 2140 'injector': createInjector, 2141 'noop': noop, 2142 'bind': bind, 2143 'toJson': toJson, 2144 'fromJson': fromJson, 2145 'identity': identity, 2146 'isUndefined': isUndefined, 2147 'isDefined': isDefined, 2148 'isString': isString, 2149 'isFunction': isFunction, 2150 'isObject': isObject, 2151 'isNumber': isNumber, 2152 'isElement': isElement, 2153 'isArray': isArray, 2154 'version': version, 2155 'isDate': isDate, 2156 'lowercase': lowercase, 2157 'uppercase': uppercase, 2158 'callbacks': {counter: 0}, 2159 'getTestability': getTestability, 2160 '$$minErr': minErr, 2161 '$$csp': csp, 2162 'reloadWithDebugInfo': reloadWithDebugInfo 2163 }); 2164 2165 angularModule = setupModuleLoader(window); 2166 try { 2167 angularModule('ngLocale'); 2168 } catch (e) { 2169 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 2170 } 2171 2172 angularModule('ng', ['ngLocale'], ['$provide', 2173 function ngModule($provide) { 2174 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2175 $provide.provider({ 2176 $$sanitizeUri: $$SanitizeUriProvider 2177 }); 2178 $provide.provider('$compile', $CompileProvider). 2179 directive({ 2180 a: htmlAnchorDirective, 2181 input: inputDirective, 2182 textarea: inputDirective, 2183 form: formDirective, 2184 script: scriptDirective, 2185 select: selectDirective, 2186 style: styleDirective, 2187 option: optionDirective, 2188 ngBind: ngBindDirective, 2189 ngBindHtml: ngBindHtmlDirective, 2190 ngBindTemplate: ngBindTemplateDirective, 2191 ngClass: ngClassDirective, 2192 ngClassEven: ngClassEvenDirective, 2193 ngClassOdd: ngClassOddDirective, 2194 ngCloak: ngCloakDirective, 2195 ngController: ngControllerDirective, 2196 ngForm: ngFormDirective, 2197 ngHide: ngHideDirective, 2198 ngIf: ngIfDirective, 2199 ngInclude: ngIncludeDirective, 2200 ngInit: ngInitDirective, 2201 ngNonBindable: ngNonBindableDirective, 2202 ngPluralize: ngPluralizeDirective, 2203 ngRepeat: ngRepeatDirective, 2204 ngShow: ngShowDirective, 2205 ngStyle: ngStyleDirective, 2206 ngSwitch: ngSwitchDirective, 2207 ngSwitchWhen: ngSwitchWhenDirective, 2208 ngSwitchDefault: ngSwitchDefaultDirective, 2209 ngOptions: ngOptionsDirective, 2210 ngTransclude: ngTranscludeDirective, 2211 ngModel: ngModelDirective, 2212 ngList: ngListDirective, 2213 ngChange: ngChangeDirective, 2214 pattern: patternDirective, 2215 ngPattern: patternDirective, 2216 required: requiredDirective, 2217 ngRequired: requiredDirective, 2218 minlength: minlengthDirective, 2219 ngMinlength: minlengthDirective, 2220 maxlength: maxlengthDirective, 2221 ngMaxlength: maxlengthDirective, 2222 ngValue: ngValueDirective, 2223 ngModelOptions: ngModelOptionsDirective 2224 }). 2225 directive({ 2226 ngInclude: ngIncludeFillContentDirective 2227 }). 2228 directive(ngAttributeAliasDirectives). 2229 directive(ngEventDirectives); 2230 $provide.provider({ 2231 $anchorScroll: $AnchorScrollProvider, 2232 $animate: $AnimateProvider, 2233 $browser: $BrowserProvider,
2234 $cacheFactory: $CacheFactoryProvider, 2235 $controller: $ControllerProvider, 2236 $document: $DocumentProvider, 2237 $exceptionHandler: $ExceptionHandlerProvider, 2238 $filter: $FilterProvider, 2239 $interpolate: $InterpolateProvider, 2240 $interval: $IntervalProvider, 2241 $http: $HttpProvider, 2242 $httpBackend: $HttpBackendProvider, 2243 $location: $LocationProvider, 2244 $log: $LogProvider, 2245 $parse: $ParseProvider, 2246 $rootScope: $RootScopeProvider, 2247 $q: $QProvider, 2248 $$q: $$QProvider, 2249 $sce: $SceProvider, 2250 $sceDelegate: $SceDelegateProvider, 2251 $sniffer: $SnifferProvider, 2252 $templateCache: $TemplateCacheProvider, 2253 $templateRequest: $TemplateRequestProvider, 2254 $$testability: $$TestabilityProvider, 2255 $timeout: $TimeoutProvider, 2256 $window: $WindowProvider, 2257 $$rAF: $$RAFProvider, 2258 $$asyncCallback: $$AsyncCallbackProvider, 2259 $$jqLite: $$jqLiteProvider 2260 }); 2261 } 2262 ]); 2263} 2264 2265/* global JQLitePrototype: true, 2266 addEventListenerFn: true, 2267 removeEventListenerFn: true, 2268 BOOLEAN_ATTR: true, 2269 ALIASED_ATTR: true, 2270*/ 2271 2272////////////////////////////////// 2273//JQLite 2274////////////////////////////////// 2275 2276/** 2277 * @ngdoc function 2278 * @name angular.element 2279 * @module ng 2280 * @kind function 2281 * 2282 * @description 2283 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2284 * 2285 * If jQuery is available, `angular.element` is an alias for the 2286 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2287 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." 2288 * 2289 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows 2290 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most 2291 * commonly needed functionality with the goal of having a very small footprint.</div> 2292 * 2293 * To use jQuery, simply load it before `DOMContentLoaded` event fired. 2294 * 2295 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or 2296 * jqLite; they are never raw DOM references.</div> 2297 * 2298 * ## Angular's jqLite 2299 * jqLite provides only the following jQuery methods: 2300 * 2301 * - [`addClass()`](http://api.jquery.com/addClass/) 2302 * - [`after()`](http://api.jquery.com/after/) 2303 * - [`append()`](http://api.jquery.com/append/) 2304 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 2305 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2306 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2307 * - [`clone()`](http://api.jquery.com/clone/) 2308 * - [`contents()`](http://api.jquery.com/contents/) 2309 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()` 2310 * - [`data()`](http://api.jquery.com/data/) 2311 * - [`detach()`](http://api.jquery.com/detach/) 2312 * - [`empty()`](http://api.jquery.com/empty/) 2313 * - [`eq()`](http://api.jquery.com/eq/) 2314 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2315 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2316 * - [`html()`](http://api.jquery.com/html/) 2317 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2318 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2319 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors 2320 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2321 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2322 * - [`prepend()`](http://api.jquery.com/prepend/) 2323 * - [`prop()`](http://api.jquery.com/prop/) 2324 * - [`ready()`](http://api.jquery.com/ready/) 2325 * - [`remove()`](http://api.jquery.com/remove/) 2326 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2327 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2328 * - [`removeData()`](http://api.jquery.com/removeData/) 2329 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2330 * - [`text()`](http://api.jquery.com/text/) 2331 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2332 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2333 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces 2334 * - [`val()`](http://api.jquery.com/val/) 2335 * - [`wrap()`](http://api.jquery.com/wrap/) 2336 * 2337 * ## jQuery/jqLite Extras 2338 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2339 * 2340 * ### Events 2341 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
2341pis and fires this event 2342 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2343 * element before it is removed. 2344 * 2345 * ### Methods 2346 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2347 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2348 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2349 * `'ngModel'`). 2350 * - `injector()` - retrieves the injector of the current element or its parent. 2351 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2352 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 2353 * be enabled. 2354 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2355 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 2356 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2357 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 2358 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2359 * parent element is reached. 2360 * 2361 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2362 * @returns {Object} jQuery object. 2363 */ 2364 2365JQLite.expando = 'ng339'; 2366 2367var jqCache = JQLite.cache = {}, 2368 jqId = 1, 2369 addEventListenerFn = function(element, type, fn) { 2370 element.addEventListener(type, fn, false); 2371 }, 2372 removeEventListenerFn = function(element, type, fn) { 2373 element.removeEventListener(type, fn, false); 2374 }; 2375 2376/* 2377 * !!! This is an undocumented "private" function !!! 2378 */ 2379JQLite._data = function(node) { 2380 //jQuery always returns an object on cache miss 2381 return this.cache[node[this.expando]] || {}; 2382}; 2383 2384function jqNextId() { return ++jqId; } 2385 2386 2387var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2388var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2389var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; 2390var jqLiteMinErr = minErr('jqLite'); 2391 2392/** 2393 * Converts snake_case to camelCase. 2394 * Also there is special case for Moz prefix starting with upper case letter. 2395 * @param name Name to normalize 2396 */ 2397function camelCase(name) { 2398 return name. 2399 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2400 return offset ? letter.toUpperCase() : letter; 2401 }). 2402 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2403} 2404 2405var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; 2406var HTML_REGEXP = /<|&#?\w+;/; 2407var TAG_NAME_REGEXP = /<([\w:]+)/; 2408var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; 2409 2410var wrapMap = { 2411 'option': [1, '<select multiple="multiple">', '</select>'], 2412 2413 'thead': [1, '<table>', '</table>'], 2414 'col': [2, '<table><colgroup>', '</colgroup></table>'], 2415 'tr': [2, '<table><tbody>', '</tbody></table>'], 2416 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2417 '_default': [0, "", ""] 2418}; 2419 2420wrapMap.optgroup = wrapMap.option; 2421wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2422wrapMap.th = wrapMap.td; 2423 2424 2425function jqLiteIsTextNode(html) { 2426 return !HTML_REGEXP.test(html); 2427} 2428 2429function jqLiteAcceptsData(node) { 2430 // The window object can accept data but has no nodeType 2431 // Otherwise we are only interested in elements (1) and documents (9) 2432 var nodeType = node.nodeType; 2433 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 2434} 2435 2436function jqLiteBuildFragment(html, context) { 2437 var tmp, tag, wrap, 2438 fragment = context.createDocumentFragment(), 2439 nodes = [], i; 2440 2441 if (jqLiteIsTextNode(html)) { 2442 // Convert non-html into a text node 2443 nodes.push(context.createTextNode(html)); 2444 } else { 2445 // Convert html into DOM nodes 2446 tmp = tmp || fragment.appendChild(context.createElement("div")); 2447 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2448 wrap = wrapMap[tag] || wrapMap._default; 2449 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2450 2451 // Descend through wrappers to the right content 2452 i = wrap[0]; 2453 while (i--) { 2454 tmp = tmp.lastChild; 2455 } 2456 2457 nodes = concat(nodes, tmp.childNodes); 2458 2459 tmp = fragment.firstChild; 2460 tmp.textContent = ""; 2461 } 2462 2463 // Remove wrapper from fragment 2464 fragment.textContent = ""; 2465 fragment.innerHTML = ""; // Clear inner HTML 2466 forEach(nodes, function(node) { 2467 fragment.appendChild(node); 2468 }); 2469 2470 return fragment; 2471} 2472 2473function jqLiteParseHTML(html, context) { 2474 context = context || document; 2475 var parsed; 2476 2477 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2478 return [context.createElement(parsed[1])]; 2479 } 2480 2481 if ((parsed = jqLiteBuildFragment(html, context))) { 2482 return parsed.childNodes; 2483 } 2484 2485 return []; 2486} 2487
vendor: 1,065 bytes, lines 2488-2529
2488///////////////////////////////////////////// 2489function JQLite(element) { 2490 if (element instanceof JQLite) { 2491 return element; 2492 } 2493 2494 var argIsString; 2495 2496 if (isString(element)) { 2497 element = trim(element); 2498 argIsString = true; 2499 } 2500 if (!(this instanceof JQLite)) { 2501 if (argIsString && element.charAt(0) != '<') { 2502 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2503 } 2504 return new JQLite(element); 2505 } 2506 2507 if (argIsString) { 2508 jqLiteAddNodes(this, jqLiteParseHTML(element)); 2509 } else { 2510 jqLiteAddNodes(this, element); 2511 } 2512} 2513 2514function jqLiteClone(element) { 2515 return element.cloneNode(true); 2516} 2517 2518function jqLiteDealoc(element, onlyDescendants) { 2519 if (!onlyDescendants) jqLiteRemoveData(element); 2520 2521 if (element.querySelectorAll) { 2522 var descendants = element.querySelectorAll('*'); 2523 for (var i = 0, l = descendants.length; i < l; i++) { 2524 jqLiteRemoveData(descendants[i]); 2525 } 2526 } 2527} 2528 2529function jqLiteOff(element, type, fn, unsupported) {
2530 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2531 2532 var expandoStore = jqLiteExpandoStore(element); 2533 var events = expandoStore && expandoStore.events; 2534 var handle = expandoStore && expandoStore.handle; 2535 2536 if (!handle) return; //no listeners registered 2537 2538 if (!type) { 2539 for (type in events) { 2540 if (type !== '$destroy') { 2541 removeEventListenerFn(element, type, handle); 2542 } 2543 delete events[type]; 2544 } 2545 } else { 2546 forEach(type.split(' '), function(type) { 2547 if (isDefined(fn)) { 2548 var listenerFns = events[type]; 2549 arrayRemove(listenerFns || [], fn); 2550 if (listenerFns && listenerFns.length > 0) { 2551 return; 2552 } 2553 } 2554 2555 removeEventListenerFn(element, type, handle); 2556 delete events[type]; 2557 }); 2558 } 2559} 2560 2561function jqLiteRemoveData(element, name) { 2562 var expandoId = element.ng339; 2563 var expandoStore = expandoId && jqCache[expandoId]; 2564 2565 if (expandoStore) { 2566 if (name) { 2567 delete expandoStore.data[name]; 2568 return; 2569 } 2570 2571 if (expandoStore.handle) { 2572 if (expandoStore.events.$destroy) { 2573 expandoStore.handle({}, '$destroy'); 2574 } 2575 jqLiteOff(element); 2576 } 2577 delete jqCache[expandoId]; 2578 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 2579 } 2580} 2581 2582 2583function jqLiteExpandoStore(element, createIfNecessary) { 2584 var expandoId = element.ng339, 2585 expandoStore = expandoId && jqCache[expandoId]; 2586 2587 if (createIfNecessary && !expandoStore) { 2588 element.ng339 = expandoId = jqNextId(); 2589 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 2590 } 2591 2592 return expandoStore; 2593} 2594 2595 2596function jqLiteData(element, key, value) { 2597 if (jqLiteAcceptsData(element)) { 2598 2599 var isSimpleSetter = isDefined(value); 2600 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 2601 var massGetter = !key; 2602 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 2603 var data = expandoStore && expandoStore.data; 2604 2605 if (isSimpleSetter) { // data('key', value) 2606 data[key] = value; 2607 } else { 2608 if (massGetter) { // data() 2609 return data; 2610 } else { 2611 if (isSimpleGetter) { // data('key') 2612 // don't force creation of expandoStore if it doesn't exist yet 2613 return data && data[key]; 2614 } else { // mass-setter: data({key1: val1, key2: val2}) 2615 extend(data, key); 2616 } 2617 } 2618 } 2619 } 2620} 2621 2622function jqLiteHasClass(element, selector) { 2623 if (!element.getAttribute) return false; 2624 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2625 indexOf(" " + selector + " ") > -1); 2626} 2627 2628function jqLiteRemoveClass(element, cssClasses) { 2629 if (cssClasses && element.setAttribute) { 2630 forEach(cssClasses.split(' '), function(cssClass) { 2631 element.setAttribute('class', trim( 2632 (" " + (element.getAttribute('class') || '') + " ") 2633 .replace(/[\n\t]/g, " ") 2634 .replace(" " + trim(cssClass) + " ", " ")) 2635 ); 2636 }); 2637 } 2638} 2639 2640function jqLiteAddClass(element, cssClasses) { 2641 if (cssClasses && element.setAttribute) { 2642 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 2643 .replace(/[\n\t]/g, " "); 2644 2645 forEach(cssClasses.split(' '), function(cssClass) { 2646 cssClass = trim(cssClass); 2647 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 2648 existingClasses += cssClass + ' '; 2649 } 2650 }); 2651 2652 element.setAttribute('class', trim(existingClasses)); 2653 } 2654} 2655 2656 2657function jqLiteAddNodes(root, elements) { 2658 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 2659 2660 if (elements) { 2661 2662 // if a Node (the most common case) 2663 if (elements.nodeType) { 2664 root[root.length++] = elements; 2665 } else { 2666 var length = elements.length; 2667 2668 // if an Array or NodeList and not a Window 2669 if (typeof length === 'number' && elements.window !== elements) { 2670 if (length) { 2671 for (var i = 0; i < length; i++) { 2672 root[root.length++] = elements[i]; 2673 } 2674 } 2675 } else { 2676 root[root.length++] = elements; 2677 } 2678 } 2679 } 2680} 2681 2682 2683function jqLiteController(element, name) { 2684 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 2685} 2686 2687function jqLiteInheritedData(element, name, value) { 2688 // if element is the document object work with the html element instead 2689 // this makes $(document).scope() possible 2690 if (element.nodeType == NODE_TYPE_DOCUMENT) { 2691 element = element.documentElement; 2692 } 2693 var names = isArray(name) ? name : [name]; 2694 2695 while (element) { 2696 for (var i = 0, ii = names.length; i < ii; i++) { 2697 if ((value = jqLite.data(element, names[i])) !== undefined) return value; 2698 } 2699 2700 // If dealing with a document fragment node with a host element, and no parent, use the host 2701 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 2702 // to lookup parent controllers. 2703 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 2704 } 2705} 2706 2707function jqLiteEmpty(element) { 2708 jqLiteDealoc(element, true); 2709 while (element.firstChild) { 2710 element.removeChild(element.firstChild); 2711 } 2712} 2713 2714function jqLiteRemove(element, keepData) { 2715 if (!keepData) jqLiteDealoc(element); 2716 var parent = element.parentNode; 2717 if (parent) parent.removeChild(element); 2718} 2719 2720 2721function jqLiteDocumentLoaded(action, win) { 2722 win = win || window; 2723 if (win.document.readyState === 'complete') { 2724 // Force the action to be run async for consistent behaviour 2725 // from the action's point of view 2726 // i.e. it will definitely not be in a $apply 2727 win.setTimeout(action); 2728 } else { 2729 // No need to unbind this handler as load is only ever called once 2730 jqLite(win).on('load', action); 2731 } 2732} 2733
2734////////////////////////////////////////// 2735// Functions which are declared directly. 2736////////////////////////////////////////// 2737var JQLitePrototype = JQLite.prototype = { 2738 ready: function(fn) { 2739 var fired = false; 2740 2741 function trigger() { 2742 if (fired) return; 2743 fired = true; 2744 fn(); 2745 } 2746 2747 // check if document is already loaded 2748 if (document.readyState === 'complete') { 2749 setTimeout(trigger); 2750 } else { 2751 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 2752 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 2753 // jshint -W064 2754 JQLite(window).on('load', trigger); // fallback to window.onload for others 2755 // jshint +W064 2756 } 2757 }, 2758 toString: function() { 2759 var value = []; 2760 forEach(this, function(e) { value.push('' + e);}); 2761 return '[' + value.join(', ') + ']'; 2762 }, 2763 2764 eq: function(index) { 2765 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 2766 }, 2767 2768 length: 0, 2769 push: push, 2770 sort: [].sort, 2771 splice: [].splice 2772}; 2773 2774////////////////////////////////////////// 2775// Functions iterating getter/setters. 2776// these functions return self on setter and 2777// value on get. 2778////////////////////////////////////////// 2779var BOOLEAN_ATTR = {}; 2780forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 2781 BOOLEAN_ATTR[lowercase(value)] = value; 2782}); 2783var BOOLEAN_ELEMENTS = {}; 2784forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 2785 BOOLEAN_ELEMENTS[value] = true; 2786}); 2787var ALIASED_ATTR = { 2788 'ngMinlength': 'minlength', 2789 'ngMaxlength': 'maxlength', 2790 'ngMin': 'min', 2791 'ngMax': 'max', 2792 'ngPattern': 'pattern' 2793}; 2794 2795function getBooleanAttrName(element, name) { 2796 // check dom last since we will most likely fail on name 2797 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 2798 2799 // booleanAttr is here twice to minimize DOM access 2800 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 2801} 2802 2803function getAliasedAttrName(element, name) { 2804 var nodeName = element.nodeName; 2805 return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name]; 2806} 2807 2808forEach({ 2809 data: jqLiteData, 2810 removeData: jqLiteRemoveData 2811}, function(fn, name) { 2812 JQLite[name] = fn; 2813}); 2814 2815forEach({ 2816 data: jqLiteData, 2817 inheritedData: jqLiteInheritedData, 2818 2819 scope: function(element) { 2820 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2821 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 2822 }, 2823 2824 isolateScope: function(element) { 2825 // Can't use jqLiteData here directly so we stay compatible with jQuery! 2826 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 2827 }, 2828 2829 controller: jqLiteController, 2830 2831 injector: function(element) { 2832 return jqLiteInheritedData(element, '$injector'); 2833 }, 2834 2835 removeAttr: function(element, name) { 2836 element.removeAttribute(name); 2837 }, 2838 2839 hasClass: jqLiteHasClass, 2840 2841 css: function(element, name, value) { 2842 name = camelCase(name); 2843 2844 if (isDefined(value)) { 2845 element.style[name] = value; 2846 } else { 2847 return element.style[name]; 2848 } 2849 }, 2850 2851 attr: function(element, name, value) { 2852 var lowercasedName = lowercase(name); 2853 if (BOOLEAN_ATTR[lowercasedName]) { 2854 if (isDefined(value)) { 2855 if (!!value) { 2856 element[name] = true; 2857 element.setAttribute(name, lowercasedName); 2858 } else { 2859 element[name] = false; 2860 element.removeAttribute(lowercasedName); 2861 } 2862 } else { 2863 return (element[name] || 2864 (element.attributes.getNamedItem(name) || noop).specified) 2865 ? lowercasedName 2866 : undefined; 2867 } 2868 } else if (isDefined(value)) { 2869 element.setAttribute(name, value); 2870 } else if (element.getAttribute) { 2871 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 2872 // some elements (e.g. Document) don't have get attribute, so return undefined 2873 var ret = element.getAttribute(name, 2); 2874 // normalize non-existing attributes to undefined (as jQuery) 2875 return ret === null ? undefined : ret; 2876 } 2877 }, 2878 2879 prop: function(element, name, value) { 2880 if (isDefined(value)) { 2881 element[name] = value; 2882 } else { 2883 return element[name]; 2884 } 2885 }, 2886 2887 text: (function() { 2888 getText.$dv = ''; 2889 return getText; 2890 2891 function getText(element, value) { 2892 if (isUndefined(value)) { 2893 var nodeType = element.nodeType; 2894 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 2895 } 2896 element.textContent = value; 2897 } 2898 })(), 2899 2900 val: function(element, value) { 2901 if (isUndefined(value)) { 2902 if (element.multiple && nodeName_(element) === 'select') { 2903 var result = [];
vendor: 3,900 bytes, lines 2904-3033
2904 forEach(element.options, function(option) { 2905 if (option.selected) { 2906 result.push(option.value || option.text); 2907 } 2908 }); 2909 return result.length === 0 ? null : result; 2910 } 2911 return element.value; 2912 } 2913 element.value = value; 2914 }, 2915 2916 html: function(element, value) { 2917 if (isUndefined(value)) { 2918 return element.innerHTML; 2919 } 2920 jqLiteDealoc(element, true); 2921 element.innerHTML = value; 2922 }, 2923 2924 empty: jqLiteEmpty 2925}, function(fn, name) { 2926 /** 2927 * Properties: writes return selection, reads return first value 2928 */ 2929 JQLite.prototype[name] = function(arg1, arg2) { 2930 var i, key; 2931 var nodeCount = this.length; 2932 2933 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 2934 // in a way that survives minification. 2935 // jqLiteEmpty takes no arguments but is a setter. 2936 if (fn !== jqLiteEmpty && 2937 (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { 2938 if (isObject(arg1)) { 2939 2940 // we are a write, but the object properties are the key/values 2941 for (i = 0; i < nodeCount; i++) { 2942 if (fn === jqLiteData) { 2943 // data() takes the whole object in jQuery 2944 fn(this[i], arg1); 2945 } else { 2946 for (key in arg1) { 2947 fn(this[i], key, arg1[key]); 2948 } 2949 } 2950 } 2951 // return self for chaining 2952 return this; 2953 } else { 2954 // we are a read, so read the first child. 2955 // TODO: do we still need this? 2956 var value = fn.$dv; 2957 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 2958 var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount; 2959 for (var j = 0; j < jj; j++) { 2960 var nodeValue = fn(this[j], arg1, arg2); 2961 value = value ? value + nodeValue : nodeValue; 2962 } 2963 return value; 2964 } 2965 } else { 2966 // we are a write, so apply to all children 2967 for (i = 0; i < nodeCount; i++) { 2968 fn(this[i], arg1, arg2); 2969 } 2970 // return self for chaining 2971 return this; 2972 } 2973 }; 2974}); 2975 2976function createEventHandler(element, events) { 2977 var eventHandler = function(event, type) { 2978 // jQuery specific api 2979 event.isDefaultPrevented = function() { 2980 return event.defaultPrevented; 2981 }; 2982 2983 var eventFns = events[type || event.type]; 2984 var eventFnsLength = eventFns ? eventFns.length : 0; 2985 2986 if (!eventFnsLength) return; 2987 2988 if (isUndefined(event.immediatePropagationStopped)) { 2989 var originalStopImmediatePropagation = event.stopImmediatePropagation; 2990 event.stopImmediatePropagation = function() { 2991 event.immediatePropagationStopped = true; 2992 2993 if (event.stopPropagation) { 2994 event.stopPropagation(); 2995 } 2996 2997 if (originalStopImmediatePropagation) { 2998 originalStopImmediatePropagation.call(event); 2999 } 3000 }; 3001 } 3002 3003 event.isImmediatePropagationStopped = function() { 3004 return event.immediatePropagationStopped === true; 3005 }; 3006 3007 // Copy event handlers in case event handlers array is modified during execution. 3008 if ((eventFnsLength > 1)) { 3009 eventFns = shallowCopy(eventFns); 3010 } 3011 3012 for (var i = 0; i < eventFnsLength; i++) { 3013 if (!event.isImmediatePropagationStopped()) { 3014 eventFns[i].call(element, event); 3015 } 3016 } 3017 }; 3018 3019 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 3020 // events on `element` 3021 eventHandler.elem = element; 3022 return eventHandler; 3023} 3024 3025////////////////////////////////////////// 3026// Functions iterating traversal. 3027// These functions chain results into a single 3028// selector. 3029////////////////////////////////////////// 3030forEach({ 3031 removeData: jqLiteRemoveData, 3032 3033 on: function jqLiteOn(element, type, fn, unsupported) {
3034 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 3035 3036 // Do not add event handlers to non-elements because they will not be cleaned up. 3037 if (!jqLiteAcceptsData(element)) { 3038 return; 3039 } 3040 3041 var expandoStore = jqLiteExpandoStore(element, true); 3042 var events = expandoStore.events; 3043 var handle = expandoStore.handle; 3044 3045 if (!handle) { 3046 handle = expandoStore.handle = createEventHandler(element, events); 3047 } 3048 3049 // http://jsperf.com/string-indexof-vs-split 3050 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 3051 var i = types.length; 3052 3053 while (i--) { 3054 type = types[i]; 3055 var eventFns = events[type]; 3056 3057 if (!eventFns) { 3058 events[type] = []; 3059 3060 if (type === 'mouseenter' || type === 'mouseleave') { 3061 // Refer to jQuery's implementation of mouseenter & mouseleave 3062 // Read about mouseenter and mouseleave: 3063 // http://www.quirksmode.org/js/events_mouse.html#link8 3064 3065 jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) { 3066 var target = this, related = event.relatedTarget; 3067 // For mousenter/leave call the handler if related is outside the target. 3068 // NB: No relatedTarget if the mouse left/entered the browser window 3069 if (!related || (related !== target && !target.contains(related))) { 3070 handle(event, type); 3071 } 3072 }); 3073 3074 } else { 3075 if (type !== '$destroy') { 3076 addEventListenerFn(element, type, handle); 3077 } 3078 } 3079 eventFns = events[type]; 3080 } 3081 eventFns.push(fn); 3082 } 3083 }, 3084 3085 off: jqLiteOff, 3086 3087 one: function(element, type, fn) { 3088 element = jqLite(element); 3089 3090 //add the listener twice so that when it is called 3091 //you can remove the original function and still be 3092 //able to call element.off(ev, fn) normally 3093 element.on(type, function onFn() { 3094 element.off(type, fn); 3095 element.off(type, onFn); 3096 }); 3097 element.on(type, fn); 3098 }, 3099 3100 replaceWith: function(element, replaceNode) { 3101 var index, parent = element.parentNode; 3102 jqLiteDealoc(element); 3103 forEach(new JQLite(replaceNode), function(node) { 3104 if (index) { 3105 parent.insertBefore(node, index.nextSibling); 3106 } else { 3107 parent.replaceChild(node, element); 3108 } 3109 index = node; 3110 }); 3111 }, 3112 3113 children: function(element) { 3114 var children = []; 3115 forEach(element.childNodes, function(element) { 3116 if (element.nodeType === NODE_TYPE_ELEMENT) 3117 children.push(element); 3118 }); 3119 return children; 3120 }, 3121 3122 contents: function(element) { 3123 return element.contentDocument || element.childNodes || []; 3124 }, 3125 3126 append: function(element, node) { 3127 var nodeType = element.nodeType; 3128 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 3129 3130 node = new JQLite(node); 3131 3132 for (var i = 0, ii = node.length; i < ii; i++) { 3133 var child = node[i]; 3134 element.appendChild(child); 3135 } 3136 }, 3137 3138 prepend: function(element, node) { 3139 if (element.nodeType === NODE_TYPE_ELEMENT) { 3140 var index = element.firstChild; 3141 forEach(new JQLite(node), function(child) { 3142 element.insertBefore(child, index); 3143 }); 3144 } 3145 }, 3146 3147 wrap: function(element, wrapNode) { 3148 wrapNode = jqLite(wrapNode).eq(0).clone()[0]; 3149 var parent = element.parentNode; 3150 if (parent) { 3151 parent.replaceChild(wrapNode, element); 3152 } 3153 wrapNode.appendChild(element); 3154 }, 3155 3156 remove: jqLiteRemove, 3157 3158 detach: function(element) { 3159 jqLiteRemove(element, true); 3160 }, 3161 3162 after: function(element, newElement) { 3163 var index = element, parent = element.parentNode; 3164 newElement = new JQLite(newElement); 3165 3166 for (var i = 0, ii = newElement.length; i < ii; i++) { 3167 var node = newElement[i]; 3168 parent.insertBefore(node, index.nextSibling); 3169 index = node; 3170 } 3171 }, 3172 3173 addClass: jqLiteAddClass, 3174 removeClass: jqLiteRemoveClass, 3175 3176 toggleClass: function(element, selector, condition) { 3177 if (selector) {
vendor: 5,344 bytes, lines 3178-3373
3178 forEach(selector.split(' '), function(className) { 3179 var classCondition = condition; 3180 if (isUndefined(classCondition)) { 3181 classCondition = !jqLiteHasClass(element, className); 3182 } 3183 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 3184 }); 3185 } 3186 }, 3187 3188 parent: function(element) { 3189 var parent = element.parentNode; 3190 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 3191 }, 3192 3193 next: function(element) { 3194 return element.nextElementSibling; 3195 }, 3196 3197 find: function(element, selector) { 3198 if (element.getElementsByTagName) { 3199 return element.getElementsByTagName(selector); 3200 } else { 3201 return []; 3202 } 3203 }, 3204 3205 clone: jqLiteClone, 3206 3207 triggerHandler: function(element, event, extraParameters) { 3208 3209 var dummyEvent, eventFnsCopy, handlerArgs; 3210 var eventName = event.type || event; 3211 var expandoStore = jqLiteExpandoStore(element); 3212 var events = expandoStore && expandoStore.events; 3213 var eventFns = events && events[eventName]; 3214 3215 if (eventFns) { 3216 // Create a dummy event to pass to the handlers 3217 dummyEvent = { 3218 preventDefault: function() { this.defaultPrevented = true; }, 3219 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 3220 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 3221 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 3222 stopPropagation: noop, 3223 type: eventName, 3224 target: element 3225 }; 3226 3227 // If a custom event was provided then extend our dummy event with it 3228 if (event.type) { 3229 dummyEvent = extend(dummyEvent, event); 3230 } 3231 3232 // Copy event handlers in case event handlers array is modified during execution. 3233 eventFnsCopy = shallowCopy(eventFns); 3234 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 3235 3236 forEach(eventFnsCopy, function(fn) { 3237 if (!dummyEvent.isImmediatePropagationStopped()) { 3238 fn.apply(element, handlerArgs); 3239 } 3240 }); 3241 } 3242 } 3243}, function(fn, name) { 3244 /** 3245 * chaining functions 3246 */ 3247 JQLite.prototype[name] = function(arg1, arg2, arg3) { 3248 var value; 3249 3250 for (var i = 0, ii = this.length; i < ii; i++) { 3251 if (isUndefined(value)) { 3252 value = fn(this[i], arg1, arg2, arg3); 3253 if (isDefined(value)) { 3254 // any function which returns a value needs to be wrapped 3255 value = jqLite(value); 3256 } 3257 } else { 3258 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3259 } 3260 } 3261 return isDefined(value) ? value : this; 3262 }; 3263 3264 // bind legacy bind/unbind to on/off 3265 JQLite.prototype.bind = JQLite.prototype.on; 3266 JQLite.prototype.unbind = JQLite.prototype.off; 3267}); 3268 3269 3270// Provider for private $$jqLite service 3271function $$jqLiteProvider() { 3272 this.$get = function $$jqLite() { 3273 return extend(JQLite, { 3274 hasClass: function(node, classes) { 3275 if (node.attr) node = node[0]; 3276 return jqLiteHasClass(node, classes); 3277 }, 3278 addClass: function(node, classes) { 3279 if (node.attr) node = node[0]; 3280 return jqLiteAddClass(node, classes); 3281 }, 3282 removeClass: function(node, classes) { 3283 if (node.attr) node = node[0]; 3284 return jqLiteRemoveClass(node, classes); 3285 } 3286 }); 3287 }; 3288} 3289 3290/** 3291 * Computes a hash of an 'obj'. 3292 * Hash of a: 3293 * string is string 3294 * number is number as string 3295 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3296 * that is also assigned to the $$hashKey property of the object. 3297 * 3298 * @param obj 3299 * @returns {string} hash string such that the same input will have the same hash string. 3300 * The resulting string key is in 'type:hashKey' format. 3301 */ 3302function hashKey(obj, nextUidFn) { 3303 var key = obj && obj.$$hashKey; 3304 3305 if (key) { 3306 if (typeof key === 'function') { 3307 key = obj.$$hashKey(); 3308 } 3309 return key; 3310 } 3311 3312 var objType = typeof obj; 3313 if (objType == 'function' || (objType == 'object' && obj !== null)) { 3314 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 3315 } else { 3316 key = objType + ':' + obj; 3317 } 3318 3319 return key; 3320} 3321 3322/** 3323 * HashMap which can use objects as keys 3324 */ 3325function HashMap(array, isolatedUid) { 3326 if (isolatedUid) { 3327 var uid = 0; 3328 this.nextUid = function() { 3329 return ++uid; 3330 }; 3331 } 3332 forEach(array, this.put, this); 3333} 3334HashMap.prototype = { 3335 /** 3336 * Store key value pair 3337 * @param key key to store can be any type 3338 * @param value value to store can be any type 3339 */ 3340 put: function(key, value) { 3341 this[hashKey(key, this.nextUid)] = value; 3342 }, 3343 3344 /** 3345 * @param key 3346 * @returns {Object} the value for the key 3347 */ 3348 get: function(key) { 3349 return this[hashKey(key, this.nextUid)]; 3350 }, 3351 3352 /** 3353 * Remove the key/value pair 3354 * @param key 3355 */ 3356 remove: function(key) { 3357 var value = this[key = hashKey(key, this.nextUid)]; 3358 delete this[key]; 3359 return value; 3360 } 3361}; 3362 3363/** 3364 * @ngdoc function 3365 * @module ng 3366 * @name angular.injector 3367 * @kind function 3368 * 3369 * @description 3370 * Creates an injector object that can be used for retrieving services as well as for 3371 * dependency injection (see {@link guide/di dependency injection}). 3372 * 3373 * @param {Array.<string|Function>}
3373 modules A list of module functions or their aliases. See 3374 * {@link angular.module}. The `ng` module must be explicitly added. 3375 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 3376 * disallows argument name annotation inference. 3377 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 3378 * 3379 * @example 3380 * Typical usage 3381 * ```js 3382 * // create an injector 3383 * var $injector = angular.injector(['ng']); 3384 * 3385 * // use the injector to kick off your application 3386 * // use the type inference to auto inject arguments, or use implicit injection 3387 * $injector.invoke(function($rootScope, $compile, $document) { 3388 * $compile($document)($rootScope); 3389 * $rootScope.$digest(); 3390 * }); 3391 * ``` 3392 * 3393 * Sometimes you want to get access to the injector of a currently running Angular app 3394 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3395 * application has been bootstrapped. You can do this using the extra `injector()` added 3396 * to JQuery/jqLite elements. See {@link angular.element}. 3397 * 3398 * *This is fairly rare but could be the case if a third party library is injecting the 3399 * markup.* 3400 * 3401 * In the following example a new block of HTML containing a `ng-controller` 3402 * directive is added to the end of the document body by JQuery. We then compile and link 3403 * it into the current AngularJS scope. 3404 * 3405 * ```js 3406 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3407 * $(document.body).append($div); 3408 * 3409 * angular.element(document).injector().invoke(function($compile) { 3410 * var scope = angular.element($div).scope(); 3411 * $compile($div)(scope); 3412 * }); 3413 * ``` 3414 */ 3415 3416 3417/** 3418 * @ngdoc module 3419 * @name auto 3420 * @description 3421 * 3422 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3423 */ 3424 3425var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; 3426var FN_ARG_SPLIT = /,/; 3427var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3428var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3429var $injectorMinErr = minErr('$injector'); 3430 3431function anonFn(fn) { 3432 // For anonymous functions, showing at the very least the function signature can help in 3433 // debugging. 3434 var fnText = fn.toString().replace(STRIP_COMMENTS, ''), 3435 args = fnText.match(FN_ARGS); 3436 if (args) { 3437 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 3438 } 3439 return 'fn'; 3440} 3441 3442function annotate(fn, strictDi, name) { 3443 var $inject, 3444 fnText, 3445 argDecl, 3446 last; 3447 3448 if (typeof fn === 'function') { 3449 if (!($inject = fn.$inject)) { 3450 $inject = []; 3451 if (fn.length) { 3452 if (strictDi) { 3453 if (!isString(name) || !name) { 3454 name = fn.name || anonFn(fn); 3455 } 3456 throw $injectorMinErr('strictdi', 3457 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 3458 } 3459 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3460 argDecl = fnText.match(FN_ARGS); 3461 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 3462 arg.replace(FN_ARG, function(all, underscore, name) { 3463 $inject.push(name); 3464 }); 3465 }); 3466 } 3467 fn.$inject = $inject; 3468 } 3469 } else if (isArray(fn)) { 3470 last = fn.length - 1; 3471 assertArgFn(fn[last], 'fn'); 3472 $inject = fn.slice(0, last); 3473 } else { 3474 assertArgFn(fn, 'fn', true); 3475 } 3476 return $inject; 3477} 3478 3479/////////////////////////////////////// 3480 3481/** 3482 * @ngdoc service 3483 * @name $injector 3484 * 3485 * @description 3486 * 3487 * `$injector` is used to retrieve object instances as defined by 3488 * {@link auto.$provide provider}, instantiate types, invoke methods, 3489 * and load modules. 3490 * 3491 * The following always holds true: 3492 * 3493 * ```js 3494 * var $injector = angular.injector(); 3495 * expect($injector.get('$injector')).toBe($injector); 3496 * expect($injector.invoke(function($injector) { 3497 * return $injector; 3498 * })).toBe($injector); 3499 * ``` 3500 * 3501 * # Injection Function Annotation 3502 * 3503 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3504 * following are all valid ways of annotating function with injection arguments and are equivalent. 3505 * 3506 * ```js 3507 * // inferred (only works if code not minified/obfuscated) 3508 * $injector.invoke(function(serviceA){}); 3509 * 3510 * // annotated 3511 * function explicit(serviceA) {}; 3512 * explicit.$inject = ['serviceA']; 3513 * $injector.invoke(explicit); 3514 * 3515 * // inline 3516 * $injector.invoke(['serviceA', function(serviceA){}]); 3517 * ``` 3518 * 3519 * ## Inference 3520 * 3521 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3522 * can then be parsed and the function arguments can be extracted. This method of discovering 3523 * annotations is disallowed when the injector is in strict mode. 3524 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 3525 * argument names. 3526 * 3527 * ## `$inject` Annotation 3528 * By adding an `$inject` property onto a function the injection parameters can be specified. 3529 * 3530 * ## Inline 3531 * As an array of injection names, where the last item in the array is the function to call. 3532 */ 3533 3534/** 3535 * @ngdoc method 3536 * @name $injector#get 3537 * 3538 * @description 3539 * Return an instance of the service. 3540 * 3541 * @param {string} name The name of the instance to retrieve. 3542 * @param {string} caller An optional string to provide the origin of the function call for error messages. 3543 * @return {*} The instance. 3544 */ 3545 3546/** 3547 * @ngdoc method 3548 * @name $injector#invoke 3549 * 3550 * @description 3551 * Invoke the method and supply the method arguments from the `$injector`. 3552 * 3553 * @param {!Function} fn The function to invoke. Function parameters are injected according to the 3554 * {@link guide/di $inject Annotation} rules. 3555 * @param {Object=} self The `this` for the invoked method. 3556 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3557 * object first, before the `$injector` is consulted. 3558 * @returns {*} the value returned by the invoked `fn` function. 3559 */ 3560 3561/** 3562 * @ngdoc method 3563 * @name $injector#has 3564 * 3565 * @description 3566 * Allows the user to query if the particular service exists. 3567 * 3568 * @param {string} name Name of the service to query. 3569 * @returns {boolean} `true` if injector has given service. 3570 */ 3571 3572/** 3573 * @ngdoc method 3574 * @name $injector#instantiate 3575 * @description 3576 * Create a new instance of JS type. The method takes a constructor function, invokes the new 3577 * operator, and supplies all of the arguments to the constructor function as specified by the 3578 * constructor annotation. 3579 * 3580 * @param {Function} Type Annotated constructor function. 3581 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3582 * object first, before the `$injector` is consulted. 3583 * @returns {Object} new instance of `Type`. 3584 */ 3585 3586/** 3587 * @ngdoc method 3588 * @name $injector#annotate 3589 * 3590 * @description 3591 * Returns an array of service names which the function is requesting for injection. This API is 3592 * used by the injector to determine which services need to be injected into the function when the 3593 * function is invoked. There are three ways in which the function can be annotated with the needed 3594 * dependencies. 3595 * 3596 * # Argument names 3597 * 3598 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3599 * by converting the function into a string using `toString()` method and extracting the argument 3600 * names. 3601 * ```js 3602 * // Given 3603 * function MyController($scope, $route) { 3604 * // ... 3605 * } 3606 * 3607 * // Then 3608 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3609 * ``` 3610 * 3611 * You can disallow this method by using strict injection mode. 3612 * 3613 * This method does not work with code minification / obfuscation. For this reason the following 3614 * annotation strategies are supported. 3615 * 3616 * # The `$inject` property 3617 * 3618 * If a function has an `$inject` property and its value is an array of strings, then the strings 3619 * represent names of services to be injected into the function. 3620 * ```js 3621 * // Given 3622 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 3623 * // ... 3624 * } 3625 * // Define function dependencies 3626 * MyController['$inject'] = ['$scope', '$route']; 3627 * 3628 * // Then 3629 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3630 * ``` 3631 * 3632 * # The array notation 3633 * 3634 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 3635 * is very inconvenient. In these situations using the array notation to specify the dependencies in 3636 * a way that survives minification is a better choice: 3637 * 3638 * ```js 3639 * // We wish to write this (not minification / obfuscation safe) 3640 * injector.invoke(function($compile, $rootScope) { 3641 * // ... 3642 * }); 3643 * 3644 * // We are forced to write break inlining 3645 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 3646 * // ... 3647 * }; 3648 * tmpFn.$inject = ['$compile', '$rootScope']; 3649 * injector.invoke(tmpFn); 3650 * 3651 * // To better support inline function the inline annotation is supported 3652 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 3653 * // ... 3654 * }]); 3655 * 3656 * // Therefore 3657 * expect(injector.annotate( 3658 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 3659 * ).toEqual(['$compile', '$rootScope']); 3660 * ``` 3661 * 3662 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 3663 * be retrieved as described above. 3664 * 3665 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 3666 * 3667 * @returns {Array.<string>} The names of the services which the function requires. 3668 */ 3669 3670 3671 3672 3673/** 3674 * @ngdoc service 3675 * @name $provide 3676 * 3677 * @description 3678 * 3679 * The {@link auto.$provide $provide} service has a number of methods for registering components 3680 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 3681 * {@link angular.Module}. 3682 * 3683 * An Angular **service** is a singleton object created by a **service factory**. These **service 3684 * factories** are functions which, in turn, are created by a **service provider**. 3685 * The **service providers** are constructor functions. When instantiated they must contain a 3686 * property called `$get`, which holds the **service factory** function. 3687 * 3688 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 3689 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 3690 * function to get the instance of the **service**. 3691 * 3692 * Often services have no configuration options and there is no need to add methods to the service 3693 * provider. The provider will be no more than a constructor function with a `$get` property. For 3694 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 3695 * services without specifying a provider. 3696 * 3697 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 3698 * {@link auto.$injector $injector} 3699 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 3700 * providers and services. 3701 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 3702 * services, not providers. 3703 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 3704 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 3705 * given factory function. 3706 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 3707 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 3708 * a new object using the given constructor function. 3709 * 3710 * See the individual methods for more information and examples. 3711 */ 3712 3713/** 3714 * @ngdoc method 3715 * @name $provide#provider 3716 * @description 3717 * 3718 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 3719 * are constructor functions, whose instances are responsible for "providing" a factory for a 3720 * service. 3721 * 3722 * Service provider names start with the name of the service they provide followed by `Provider`. 3723 * For example, the {@link ng.$log $log} service has a provider called 3724 * {@link ng.$logProvider $logProvider}. 3725 * 3726 * Service provider objects can have additional methods which allow configuration of the provider 3727 * and its service. Importantly, you can configure what kind of service is created by the `$get` 3728 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 3729 * method {@link ng.$logProvider#debugEnabled debugEnabled} 3730 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 3731 * console or not. 3732 * 3733 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 3734 'Provider'` key. 3735 * @param {(Object|function())} provider If the provider is: 3736 * 3737 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 3738 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 3739 * - `Constructor`: a new instance of the provider will be created using 3740 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 3741 * 3742 * @returns {Object} registered provider instance 3743 3744 * @example 3745 * 3746 * The following example shows how to create a simple event tracking service and register it using 3747 * {@link auto.$provide#provider $provide.provider()}. 3748 * 3749 * ```js 3750 * // Define the eventTracker provider 3751 * function EventTrackerProvider() { 3752 * var trackingUrl = '/track'; 3753 * 3754 * // A provider method for configuring where the tracked events should been saved 3755 * this.setTrackingUrl = function(url) { 3756 * trackingUrl = url; 3757 * }; 3758 * 3759 * // The service factory function 3760 * this.$get = ['$http', function($http) { 3761 * var trackedEvents = {}; 3762 * return { 3763 * // Call this to track an event 3764 * event: function(event) { 3765 * var count = trackedEvents[event] || 0; 3766 * count += 1; 3767 * trackedEvents[event] = count; 3768 * return count; 3769 * }, 3770 * // Call this to save the tracked events to the trackingUrl 3771 * save: function() { 3772 * $http.post(trackingUrl, trackedEvents); 3773 * } 3774 * }; 3775 * }]; 3776 * } 3777 * 3778 * describe('eventTracker', function() { 3779 * var postSpy; 3780 * 3781 * beforeEach(module(function($provide) { 3782 * // Register the eventTracker provider 3783 * $provide.provider('eventTracker', EventTrackerProvider); 3784 * })); 3785 * 3786 * beforeEach(module(function(eventTrackerProvider) { 3787 * // Configure eventTracker provider 3788 * eventTrackerProvider.setTrackingUrl('/custom-track'); 3789 * })); 3790 * 3791 * it('tracks events', inject(function(eventTracker) { 3792 * expect(eventTracker.event('login')).toEqual(1); 3793 * expect(eventTracker.event('login')).toEqual(2); 3794 * })); 3795 * 3796 * it('saves to the tracking url', inject(function(eventTracker, $http) { 3797 * postSpy = spyOn($http, 'post'); 3798 * eventTracker.event('login'); 3799 * eventTracker.save(); 3800 * expect(postSpy).toHaveBeenCalled(); 3801 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 3802 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 3803 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 3804 * })); 3805 * }); 3806 * ``` 3807 */ 3808 3809/** 3810 * @ngdoc method 3811 * @name $provide#factory 3812 * @description 3813 * 3814 * Register a **service factory**, which will be called to return the service instance. 3815 * This is short for registering a service where its provider consists of only a `$get` property, 3816 * which is the given service factory function. 3817 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 3818 * configure your service in a provider. 3819 * 3820 * @param {string} name The name of the instance. 3821 * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand 3822 * for `$provide.provider(name, {$get: $getFn})`. 3823 * @returns {Object} registered provider instance 3824 * 3825 * @example 3826 * Here is an example of registering a service 3827 * ```js 3828 * $provide.factory('ping', ['$http', function($http) { 3829 * return function ping() { 3830 * return $http.send('/ping'); 3831 * }; 3832 * }]); 3833 * ``` 3834 * You would then inject and use this service like this: 3835 * ```js 3836 * someModule.controller('Ctrl', ['ping', function(ping) { 3837 * ping(); 3838 * }]); 3839 * ``` 3840 */ 3841 3842 3843/** 3844 * @ngdoc method 3845 * @name $provide#service 3846 * @description 3847 * 3848 * Register a **service constructor**, which will be invoked with `new` to create the service 3849 * instance. 3850 * This is short for registering a service where its provider's `$get` property is the service 3851 * constructor function that will be used to instantiate the service instance. 3852 * 3853 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 3854 * as a type/class. 3855 * 3856 * @param {string} name The name of the instance. 3857 * @param {Function} constructor A class (constructor function) that will be instantiated. 3858 * @returns {Object} registered provider instance 3859 * 3860 * @example 3861 * Here is an example of registering a service using 3862 * {@link auto.$provide#service $provide.service(class)}. 3863 * ```js 3864 * var Ping = function($http) { 3865 * this.$http = $http; 3866 * }; 3867 * 3868 * Ping.$inject = ['$http']; 3869 * 3870 * Ping.prototype.send = function() { 3871 * return this.$http.get('/ping'); 3872 * }; 3873 * $provide.service('ping', Ping); 3874 * ``` 3875 * You would then inject and use this service like this: 3876 * ```js 3877 * someModule.controller('Ctrl', ['ping', function(ping) { 3878 * ping.send(); 3879 * }]); 3880 * ``` 3881 */ 3882 3883 3884/** 3885 * @ngdoc method 3886 * @name $provide#value 3887 * @description 3888 * 3889 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 3890 * number, an array, an object or a function. This is short for registering a service where its 3891 * provider's `$get` property is a factory function that takes no arguments and returns the **value 3892 * service**. 3893 * 3894 * Value services are similar to constant services, except that they cannot be injected into a 3895 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 3896 * an Angular 3897 * {@link auto.$provide#decorator decorator}. 3898 * 3899 * @param {string} name The name of the instance. 3900 * @param {*} value The value. 3901 * @returns {Object} registered provider instance 3902 * 3903 * @example 3904 * Here are some examples of creating value services. 3905 * ```js 3906 * $provide.value('ADMIN_USER', 'admin'); 3907 *
3908 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 3909 * 3910 * $provide.value('halfOf', function(value) { 3911 * return value / 2; 3912 * }); 3913 * ``` 3914 */ 3915 3916 3917/** 3918 * @ngdoc method 3919 * @name $provide#constant 3920 * @description 3921 * 3922 * Register a **constant service**, such as a string, a number, an array, an object or a function, 3923 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be 3924 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 3925 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 3926 * 3927 * @param {string} name The name of the constant. 3928 * @param {*} value The constant value. 3929 * @returns {Object} registered instance 3930 * 3931 * @example 3932 * Here a some examples of creating constants: 3933 * ```js 3934 * $provide.constant('SHARD_HEIGHT', 306); 3935 * 3936 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 3937 * 3938 * $provide.constant('double', function(value) { 3939 * return value * 2; 3940 * }); 3941 * ``` 3942 */ 3943 3944 3945/** 3946 * @ngdoc method 3947 * @name $provide#decorator 3948 * @description 3949 * 3950 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 3951 * intercepts the creation of a service, allowing it to override or modify the behaviour of the 3952 * service. The object returned by the decorator may be the original service, or a new service 3953 * object which replaces or wraps and delegates to the original service. 3954 * 3955 * @param {string} name The name of the service to decorate. 3956 * @param {function()} decorator This function will be invoked when the service needs to be 3957 * instantiated and should return the decorated service instance. The function is called using 3958 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 3959 * Local injection arguments: 3960 * 3961 * * `$delegate` - The original service instance, which can be monkey patched, configured, 3962 * decorated or delegated to. 3963 * 3964 * @example 3965 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 3966 * calls to {@link ng.$log#error $log.warn()}. 3967 * ```js 3968 * $provide.decorator('$log', ['$delegate', function($delegate) { 3969 * $delegate.warn = $delegate.error; 3970 * return $delegate; 3971 * }]); 3972 * ``` 3973 */ 3974 3975 3976function createInjector(modulesToLoad, strictDi) { 3977 strictDi = (strictDi === true); 3978 var INSTANTIATING = {}, 3979 providerSuffix = 'Provider', 3980 path = [], 3981 loadedModules = new HashMap([], true), 3982 providerCache = { 3983 $provide: { 3984 provider: supportObject(provider), 3985 factory: supportObject(factory), 3986 service: supportObject(service), 3987 value: supportObject(value), 3988 constant: supportObject(constant), 3989 decorator: decorator 3990 } 3991 }, 3992 providerInjector = (providerCache.$injector = 3993 createInternalInjector(providerCache, function(serviceName, caller) { 3994 if (angular.isString(caller)) { 3995 path.push(caller); 3996 } 3997 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 3998 })), 3999 instanceCache = {}, 4000 instanceInjector = (instanceCache.$injector = 4001 createInternalInjector(instanceCache, function(serviceName, caller) { 4002 var provider = providerInjector.get(serviceName + providerSuffix, caller); 4003 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); 4004 })); 4005 4006 4007 forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); }); 4008 4009 return instanceInjector; 4010 4011 //////////////////////////////////// 4012 // $provider 4013 //////////////////////////////////// 4014 4015 function supportObject(delegate) { 4016 return function(key, value) { 4017 if (isObject(key)) { 4018 forEach(key, reverseParams(delegate)); 4019 } else { 4020 return delegate(key, value); 4021 } 4022 }; 4023 } 4024 4025 function provider(name, provider_) { 4026 assertNotHasOwnProperty(name, 'service'); 4027 if (isFunction(provider_) || isArray(provider_)) { 4028 provider_ = providerInjector.instantiate(provider_); 4029 } 4030 if (!provider_.$get) { 4031 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 4032 } 4033 return providerCache[name + providerSuffix] = provider_; 4034 } 4035 4036 function enforceReturnValue(name, factory) { 4037 return function enforcedReturnValue() { 4038 var result = instanceInjector.invoke(factory, this); 4039 if (isUndefined(result)) { 4040 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); 4041 } 4042 return result; 4043 }; 4044 } 4045 4046 function factory(name, factoryFn, enforce) { 4047 return provider(name, { 4048 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 4049 }); 4050 } 4051 4052 function service(name, constructor) { 4053 return factory(name, ['$injector', function($injector) { 4054 return $injector.instantiate(constructor); 4055 }]); 4056 } 4057 4058 function value(name, val) { return factory(name, valueFn(val), false); } 4059 4060 function constant(name, value) { 4061 assertNotHasOwnProperty(name, 'constant'); 4062 providerCache[name] = value; 4063 instanceCache[name] = value; 4064 } 4065 4066 function decorator(serviceName, decorFn) { 4067 var origProvider = providerInjector.get(serviceName + providerSuffix), 4068 orig$get = origProvider.$get; 4069 4070 origProvider.$get = function() { 4071 var origInstance = instanceInjector.invoke(orig$get, origProvider); 4072 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 4073 }; 4074 } 4075 4076 //////////////////////////////////// 4077 // Module Loading 4078 //////////////////////////////////// 4079 function loadModules(modulesToLoad) { 4080 var runBlocks = [], moduleFn;
vendor: 5,290 bytes, lines 4081-4238
4081 forEach(modulesToLoad, function(module) { 4082 if (loadedModules.get(module)) return; 4083 loadedModules.put(module, true); 4084 4085 function runInvokeQueue(queue) { 4086 var i, ii; 4087 for (i = 0, ii = queue.length; i < ii; i++) { 4088 var invokeArgs = queue[i], 4089 provider = providerInjector.get(invokeArgs[0]); 4090 4091 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 4092 } 4093 } 4094 4095 try { 4096 if (isString(module)) { 4097 moduleFn = angularModule(module); 4098 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 4099 runInvokeQueue(moduleFn._invokeQueue); 4100 runInvokeQueue(moduleFn._configBlocks); 4101 } else if (isFunction(module)) { 4102 runBlocks.push(providerInjector.invoke(module)); 4103 } else if (isArray(module)) { 4104 runBlocks.push(providerInjector.invoke(module)); 4105 } else { 4106 assertArgFn(module, 'module'); 4107 } 4108 } catch (e) { 4109 if (isArray(module)) { 4110 module = module[module.length - 1]; 4111 } 4112 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 4113 // Safari & FF's stack traces don't contain error.message content 4114 // unlike those of Chrome and IE 4115 // So if stack doesn't contain message, we create a new string that contains both. 4116 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 4117 /* jshint -W022 */ 4118 e = e.message + '\n' + e.stack; 4119 } 4120 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 4121 module, e.stack || e.message || e); 4122 } 4123 }); 4124 return runBlocks; 4125 } 4126 4127 //////////////////////////////////// 4128 // internal Injector 4129 //////////////////////////////////// 4130 4131 function createInternalInjector(cache, factory) { 4132 4133 function getService(serviceName, caller) { 4134 if (cache.hasOwnProperty(serviceName)) { 4135 if (cache[serviceName] === INSTANTIATING) { 4136 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 4137 serviceName + ' <- ' + path.join(' <- ')); 4138 } 4139 return cache[serviceName]; 4140 } else { 4141 try { 4142 path.unshift(serviceName); 4143 cache[serviceName] = INSTANTIATING; 4144 return cache[serviceName] = factory(serviceName, caller); 4145 } catch (err) { 4146 if (cache[serviceName] === INSTANTIATING) { 4147 delete cache[serviceName]; 4148 } 4149 throw err; 4150 } finally { 4151 path.shift(); 4152 } 4153 } 4154 } 4155 4156 function invoke(fn, self, locals, serviceName) { 4157 if (typeof locals === 'string') { 4158 serviceName = locals; 4159 locals = null; 4160 } 4161 4162 var args = [], 4163 $inject = createInjector.$$annotate(fn, strictDi, serviceName), 4164 length, i, 4165 key; 4166 4167 for (i = 0, length = $inject.length; i < length; i++) { 4168 key = $inject[i]; 4169 if (typeof key !== 'string') { 4170 throw $injectorMinErr('itkn', 4171 'Incorrect injection token! Expected service name as string, got {0}', key); 4172 } 4173 args.push( 4174 locals && locals.hasOwnProperty(key) 4175 ? locals[key] 4176 : getService(key, serviceName) 4177 ); 4178 } 4179 if (isArray(fn)) { 4180 fn = fn[length]; 4181 } 4182 4183 // http://jsperf.com/angularjs-invoke-apply-vs-switch 4184 // #5388 4185 return fn.apply(self, args); 4186 } 4187 4188 function instantiate(Type, locals, serviceName) { 4189 // Check if Type is annotated and use just the given function at n-1 as parameter 4190 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 4191 // Object creation: http://jsperf.com/create-constructor/2 4192 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null); 4193 var returnedValue = invoke(Type, instance, locals, serviceName); 4194 4195 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 4196 } 4197 4198 return { 4199 invoke: invoke, 4200 instantiate: instantiate, 4201 get: getService, 4202 annotate: createInjector.$$annotate, 4203 has: function(name) { 4204 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 4205 } 4206 }; 4207 } 4208} 4209 4210createInjector.$$annotate = annotate; 4211 4212/** 4213 * @ngdoc provider 4214 * @name $anchorScrollProvider 4215 * 4216 * @description 4217 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 4218 * {@link ng.$location#hash $location.hash()} changes. 4219 */ 4220function $AnchorScrollProvider() { 4221 4222 var autoScrollingEnabled = true; 4223 4224 /** 4225 * @ngdoc method 4226 * @name $anchorScrollProvider#disableAutoScrolling 4227 * 4228 * @description 4229 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 4230 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 4231 * Use this method to disable automatic scrolling. 4232 * 4233 * If automatic scrolling is disabled, one must explicitly call 4234 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 4235 * current hash. 4236 */ 4237 this.disableAutoScrolling = function() { 4238 autoScrollingEnabled = false;
4239 }; 4240 4241 /** 4242 * @ngdoc service 4243 * @name $anchorScroll 4244 * @kind function 4245 * @requires $window 4246 * @requires $location 4247 * @requires $rootScope 4248 * 4249 * @description 4250 * When called, it checks the current value of {@link ng.$location#hash $location.hash()} and 4251 * scrolls to the related element, according to the rules specified in the 4252 * [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). 4253 * 4254 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 4255 * match any anchor whenever it changes. This can be disabled by calling 4256 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 4257 * 4258 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 4259 * vertical scroll-offset (either fixed or dynamic). 4260 * 4261 * @property {(number|function|jqLite)} yOffset 4262 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 4263 * positioned elements at the top of the page, such as navbars, headers etc. 4264 * 4265 * `yOffset` can be specified in various ways: 4266 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 4267 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 4268 * a number representing the offset (in pixels).<br /><br /> 4269 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 4270 * the top of the page to the element's bottom will be used as offset.<br /> 4271 * **Note**: The element will be taken into account only as long as its `position` is set to 4272 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust 4273 * their height and/or positioning according to the viewport's size. 4274 * 4275 * <br /> 4276 * <div class="alert alert-warning"> 4277 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 4278 * not some child element. 4279 * </div> 4280 * 4281 * @example 4282 <example module="anchorScrollExample"> 4283 <file name="index.html"> 4284 <div id="scrollArea" ng-controller="ScrollController"> 4285 <a ng-click="gotoBottom()">Go to bottom</a> 4286 <a id="bottom"></a> You're at the bottom! 4287 </div> 4288 </file> 4289 <file name="script.js"> 4290 angular.module('anchorScrollExample', []) 4291 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 4292 function ($scope, $location, $anchorScroll) { 4293 $scope.gotoBottom = function() { 4294 // set the location.hash to the id of 4295 // the element you wish to scroll to. 4296 $location.hash('bottom'); 4297 4298 // call $anchorScroll() 4299 $anchorScroll(); 4300 }; 4301 }]); 4302 </file> 4303 <file name="style.css"> 4304 #scrollArea { 4305 height: 280px; 4306 overflow: auto; 4307 } 4308 4309 #bottom { 4310 display: block; 4311 margin-top: 2000px; 4312 } 4313 </file> 4314 </example> 4315 * 4316 * <hr /> 4317 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 4318 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 4319 * 4320 * @example 4321 <example module="anchorScrollOffsetExample"> 4322 <file name="index.html"> 4323 <div class="fixed-header" ng-controller="headerCtrl"> 4324 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 4325 Go to anchor {{x}} 4326 </a> 4327 </div> 4328 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 4329 Anchor {{x}} of 5 4330 </div> 4331 </file> 4332 <file name="script.js"> 4333 angular.module('anchorScrollOffsetExample', []) 4334 .run(['$anchorScroll', function($anchorScroll) { 4335 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 4336 }]) 4337 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 4338 function ($anchorScroll, $location, $scope) {
4339 $scope.gotoAnchor = function(x) { 4340 var newHash = 'anchor' + x; 4341 if ($location.hash() !== newHash) { 4342 // set the $location.hash to `newHash` and 4343 // $anchorScroll will automatically scroll to it 4344 $location.hash('anchor' + x); 4345 } else { 4346 // call $anchorScroll() explicitly, 4347 // since $location.hash hasn't changed 4348 $anchorScroll(); 4349 } 4350 }; 4351 } 4352 ]); 4353 </file> 4354 <file name="style.css"> 4355 body { 4356 padding-top: 50px; 4357 } 4358 4359 .anchor { 4360 border: 2px dashed DarkOrchid; 4361 padding: 10px 10px 200px 10px; 4362 } 4363 4364 .fixed-header { 4365 background-color: rgba(0, 0, 0, 0.2); 4366 height: 50px; 4367 position: fixed; 4368 top: 0; left: 0; right: 0; 4369 } 4370 4371 .fixed-header > a { 4372 display: inline-block; 4373 margin: 5px 15px; 4374 } 4375 </file> 4376 </example> 4377 */ 4378 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 4379 var document = $window.document; 4380 4381 // Helper function to get first anchor from a NodeList 4382 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 4383 // and working in all supported browsers.) 4384 function getFirstAnchor(list) { 4385 var result = null; 4386 Array.prototype.some.call(list, function(element) { 4387 if (nodeName_(element) === 'a') { 4388 result = element; 4389 return true; 4390 } 4391 }); 4392 return result; 4393 } 4394 4395 function getYOffset() { 4396 4397 var offset = scroll.yOffset; 4398 4399 if (isFunction(offset)) { 4400 offset = offset(); 4401 } else if (isElement(offset)) { 4402 var elem = offset[0]; 4403 var style = $window.getComputedStyle(elem); 4404 if (style.position !== 'fixed') { 4405 offset = 0; 4406 } else { 4407 offset = elem.getBoundingClientRect().bottom; 4408 } 4409 } else if (!isNumber(offset)) { 4410 offset = 0; 4411 } 4412 4413 return offset; 4414 } 4415 4416 function scrollTo(elem) { 4417 if (elem) { 4418 elem.scrollIntoView(); 4419 4420 var offset = getYOffset(); 4421 4422 if (offset) { 4423 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 4424 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 4425 // top of the viewport. 4426 // 4427 // IF the number of pixels from the top of `elem` to the end of the page's content is less 4428 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 4429 // way down the page. 4430 // 4431 // This is often the case for elements near the bottom of the page. 4432 // 4433 // In such cases we do not need to scroll the whole `offset` up, just the difference between 4434 // the top of the element and the offset, which is enough to align the top of `elem` at the 4435 // desired position. 4436 var elemTop = elem.getBoundingClientRect().top; 4437 $window.scrollBy(0, elemTop - offset); 4438 } 4439 } else { 4440 $window.scrollTo(0, 0); 4441 } 4442 } 4443 4444 function scroll() { 4445 var hash = $location.hash(), elm; 4446 4447 // empty hash, scroll to the top of the page 4448 if (!hash) scrollTo(null); 4449 4450 // element with given id 4451 else if ((elm = document.getElementById(hash))) scrollTo(elm); 4452 4453 // first anchor with given name :-D 4454 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 4455 4456 // no element and hash == 'top', scroll to the top of the page 4457 else if (hash === 'top') scrollTo(null); 4458 } 4459 4460 // does not scroll when user clicks on anchor link that is currently on 4461 // (no url change, no $location.hash() change), browser native does scroll 4462 if (autoScrollingEnabled) { 4463 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 4464 function autoScrollWatchAction(newVal, oldVal) { 4465 // skip the initial scroll if $location.hash is empty 4466 if (newVal === oldVal && newVal === '') return; 4467 4468 jqLiteDocumentLoaded(function() { 4469 $rootScope.$evalAsync(scroll); 4470 }); 4471 }); 4472 } 4473 4474 return scroll; 4475 }]; 4476} 4477 4478var $animateMinErr = minErr('$animate'); 4479 4480/** 4481 * @ngdoc provider 4482 * @name $animateProvider 4483 * 4484 * @description 4485 * Default implementation of $animate that doesn't perform any animations, instead just 4486 * synchronously performs DOM 4487 * updates and calls done() callbacks. 4488 * 4489 * In order to enable animations the ngAnimate module has to be loaded. 4490 * 4491 * To see the functional implementation check out src/ngAnimate/animate.js 4492 */ 4493var $AnimateProvider = ['$provide', function($provide) { 4494 4495 4496 this.$$selectors = {}; 4497 4498 4499 /** 4500 * @ngdoc method 4501 * @name $animateProvider#register 4502 * 4503 * @description 4504 * Registers a new injectable animation factory function. The factory function produces the 4505 * animation object which contains callback functions for each event that is expected to be 4506 * animated. 4507 * 4508 * * `eventFn`: `function(Element, doneFunction)` The element to animate, the `doneFunction` 4509 * must be called once the element animation is complete. If a function is returned then the 4510 * animation service will use this function to cancel the animation whenever a cancel event is 4511 * triggered. 4512 * 4513 * 4514 * ```js 4515 * return { 4516 * eventFn : function(element, done) { 4517 * //code to run the animation 4518 * //once complete, then run done() 4519 * return function cancellationFunction() { 4520 * //code to cancel the animation 4521 * } 4522 * } 4523 * } 4524 * ``` 4525 * 4526 * @param {string} name The name of the animation. 4527 * @param {Function} factory The factory function that will be executed to return the animation 4528 * object. 4529 */ 4530 this.register = function(name, factory) { 4531 var key = name + '-animation'; 4532 if (name && name.charAt(0) != '.') throw $animateMinErr('notcsel', 4533 "Expecting class selector starting with '.' got '{0}'.", name); 4534 this.$$selectors[name.substr(1)] = key; 4535 $provide.factory(key, factory); 4536 }; 4537 4538 /** 4539 * @ngdoc method 4540 * @name $animateProvider#classNameFilter 4541 * 4542 * @description 4543 * Sets and/or returns the CSS class regular expression that is checked when performing 4544 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 4545 * therefore enable $animate to attempt to perform an animation on any element. 4546 * When setting the classNameFilter value, animations will only be performed on elements 4547 * that successfully match the filter expression. This in turn can boost performance 4548 * for low-powered devices as well as applications containing a lot of structural operations. 4549 * @param {RegExp=} expression The className expression which will be checked against all animations 4550 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 4551 */ 4552 this.classNameFilter = function(expression) { 4553 if (arguments.length === 1) { 4554 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 4555 } 4556 return this.$$classNameFilter; 4557 }; 4558 4559 this.$get = ['$$q', '$$asyncCallback', '$rootScope', function($$q, $$asyncCallback, $rootScope) { 4560 4561 var currentDefer; 4562 4563 function runAnimationPostDigest(fn) { 4564 var cancelFn, defer = $$q.defer(); 4565 defer.promise.$$cancelFn = function ngAnimateMaybeCancel() { 4566 cancelFn && cancelFn(); 4567 }; 4568 4569 $rootScope.$$postDigest(function ngAnimatePostDigest() { 4570 cancelFn = fn(function ngAnimateNotifyComplete() { 4571 defer.resolve(); 4572 }); 4573 }); 4574 4575 return defer.promise; 4576 } 4577 4578 function resolveElementClasses(element, classes) { 4579 var toAdd = [], toRemove = []; 4580 4581 var hasClasses = createMap();
4582 forEach((element.attr('class') || '').split(/\s+/), function(className) { 4583 hasClasses[className] = true; 4584 }); 4585 4586 forEach(classes, function(status, className) { 4587 var hasClass = hasClasses[className]; 4588 4589 // If the most recent class manipulation (via $animate) was to remove the class, and the 4590 // element currently has the class, the class is scheduled for removal. Otherwise, if 4591 // the most recent class manipulation (via $animate) was to add the class, and the 4592 // element does not currently have the class, the class is scheduled to be added. 4593 if (status === false && hasClass) { 4594 toRemove.push(className); 4595 } else if (status === true && !hasClass) { 4596 toAdd.push(className); 4597 } 4598 }); 4599 4600 return (toAdd.length + toRemove.length) > 0 && 4601 [toAdd.length ? toAdd : null, toRemove.length ? toRemove : null]; 4602 } 4603 4604 function cachedClassManipulation(cache, classes, op) { 4605 for (var i=0, ii = classes.length; i < ii; ++i) { 4606 var className = classes[i]; 4607 cache[className] = op; 4608 } 4609 } 4610 4611 function asyncPromise() { 4612 // only serve one instance of a promise in order to save CPU cycles 4613 if (!currentDefer) { 4614 currentDefer = $$q.defer(); 4615 $$asyncCallback(function() { 4616 currentDefer.resolve(); 4617 currentDefer = null; 4618 }); 4619 } 4620 return currentDefer.promise; 4621 } 4622 4623 function applyStyles(element, options) { 4624 if (angular.isObject(options)) { 4625 var styles = extend(options.from || {}, options.to || {}); 4626 element.css(styles); 4627 } 4628 } 4629 4630 /** 4631 * 4632 * @ngdoc service 4633 * @name $animate 4634 * @description The $animate service provides rudimentary DOM manipulation functions to 4635 * insert, remove and move elements within the DOM, as well as adding and removing classes. 4636 * This service is the core service used by the ngAnimate $animator service which provides 4637 * high-level animation hooks for CSS and JavaScript. 4638 * 4639 * $animate is available in the AngularJS core, however, the ngAnimate module must be included 4640 * to enable full out animation support. Otherwise, $animate will only perform simple DOM 4641 * manipulation operations. 4642 * 4643 * To learn more about enabling animation support, click here to visit the {@link ngAnimate 4644 * ngAnimate module page} as well as the {@link ngAnimate.$animate ngAnimate $animate service 4645 * page}. 4646 */ 4647 return { 4648 animate: function(element, from, to) { 4649 applyStyles(element, { from: from, to: to }); 4650 return asyncPromise(); 4651 }, 4652 4653 /** 4654 * 4655 * @ngdoc method 4656 * @name $animate#enter 4657 * @kind function 4658 * @description Inserts the element into the DOM either after the `after` element or 4659 * as the first child within the `parent` element. When the function is called a promise 4660 * is returned that will be resolved at a later time. 4661 * @param {DOMElement} element the element which will be inserted into the DOM 4662 * @param {DOMElement} parent the parent element which will append the element as 4663 * a child (if the after element is not present) 4664 * @param {DOMElement} after the sibling element which will append the element 4665 * after itself 4666 * @param {object=} options an optional collection of styles that will be applied to the element. 4667 * @return {Promise} the animation callback promise 4668 */ 4669 enter: function(element, parent, after, options) { 4670 applyStyles(element, options); 4671 after ? after.after(element) 4672 : parent.prepend(element); 4673 return asyncPromise(); 4674 }, 4675 4676 /** 4677 * 4678 * @ngdoc method 4679 * @name $animate#leave 4680 * @kind function 4681 * @description Removes the element from the DOM. When the function is called a promise 4682 * is returned that will be resolved at a later time. 4683 * @param {DOMElement} element the element which will be removed from the DOM 4684 * @param {object=} options an optional collection of options that will be applied to the element. 4685 * @return {Promise} the animation callback promise 4686 */ 4687 leave: function(element, options) { 4688 element.remove(); 4689 return asyncPromise(); 4690 }, 4691 4692 /** 4693 * 4694 * @ngdoc method 4695 * @name $animate#move 4696 * @kind function
4697 * @description Moves the position of the provided element within the DOM to be placed 4698 * either after the `after` element or inside of the `parent` element. When the function 4699 * is called a promise is returned that will be resolved at a later time. 4700 * 4701 * @param {DOMElement} element the element which will be moved around within the 4702 * DOM 4703 * @param {DOMElement} parent the parent element where the element will be 4704 * inserted into (if the after element is not present) 4705 * @param {DOMElement} after the sibling element where the element will be 4706 * positioned next to 4707 * @param {object=} options an optional collection of options that will be applied to the element. 4708 * @return {Promise} the animation callback promise 4709 */ 4710 move: function(element, parent, after, options) { 4711 // Do not remove element before insert. Removing will cause data associated with the 4712 // element to be dropped. Insert will implicitly do the remove. 4713 return this.enter(element, parent, after, options); 4714 }, 4715 4716 /** 4717 * 4718 * @ngdoc method 4719 * @name $animate#addClass 4720 * @kind function 4721 * @description Adds the provided className CSS class value to the provided element. 4722 * When the function is called a promise is returned that will be resolved at a later time. 4723 * @param {DOMElement} element the element which will have the className value 4724 * added to it 4725 * @param {string} className the CSS class which will be added to the element 4726 * @param {object=} options an optional collection of options that will be applied to the element. 4727 * @return {Promise} the animation callback promise 4728 */ 4729 addClass: function(element, className, options) { 4730 return this.setClass(element, className, [], options); 4731 }, 4732 4733 $$addClassImmediately: function(element, className, options) { 4734 element = jqLite(element); 4735 className = !isString(className) 4736 ? (isArray(className) ? className.join(' ') : '') 4737 : className; 4738 forEach(element, function(element) { 4739 jqLiteAddClass(element, className); 4740 }); 4741 applyStyles(element, options); 4742 return asyncPromise(); 4743 }, 4744 4745 /** 4746 * 4747 * @ngdoc method 4748 * @name $animate#removeClass 4749 * @kind function 4750 * @description Removes the provided className CSS class value from the provided element. 4751 * When the function is called a promise is returned that will be resolved at a later time. 4752 * @param {DOMElement} element the element which will have the className value 4753 * removed from it 4754 * @param {string} className the CSS class which will be removed from the element 4755 * @param {object=} options an optional collection of options that will be applied to the element. 4756 * @return {Promise} the animation callback promise 4757 */ 4758 removeClass: function(element, className, options) { 4759 return this.setClass(element, [], className, options); 4760 }, 4761 4762 $$removeClassImmediately: function(element, className, options) { 4763 element = jqLite(element); 4764 className = !isString(className) 4765 ? (isArray(className) ? className.join(' ') : '') 4766 : className; 4767 forEach(element, function(element) { 4768 jqLiteRemoveClass(element, className); 4769 }); 4770 applyStyles(element, options); 4771 return asyncPromise(); 4772 }, 4773 4774 /** 4775 * 4776 * @ngdoc method 4777 * @name $animate#setClass 4778 * @kind function 4779 * @description Adds and/or removes the given CSS classes to and from the element. 4780 * When the function is called a promise is returned that will be resolved at a later time. 4781 * @param {DOMElement} element the element which will have its CSS classes changed 4782 * removed from it 4783 * @param {string} add the CSS classes which will be added to the element 4784 * @param {string} remove the CSS class which will be removed from the element 4785 * @param {object=} options an optional collection of options that will be applied to the element. 4786 * @return {Promise} the animation callback promise 4787 */ 4788 setClass: function(element, add, remove, options) { 4789 var self = this; 4790 var STORAGE_KEY = '$$animateClasses'; 4791 var createdCache = false;
4792 element = jqLite(element); 4793 4794 var cache = element.data(STORAGE_KEY); 4795 if (!cache) { 4796 cache = { 4797 classes: {}, 4798 options: options 4799 }; 4800 createdCache = true; 4801 } else if (options && cache.options) { 4802 cache.options = angular.extend(cache.options || {}, options); 4803 } 4804 4805 var classes = cache.classes; 4806 4807 add = isArray(add) ? add : add.split(' '); 4808 remove = isArray(remove) ? remove : remove.split(' '); 4809 cachedClassManipulation(classes, add, true); 4810 cachedClassManipulation(classes, remove, false); 4811 4812 if (createdCache) { 4813 cache.promise = runAnimationPostDigest(function(done) { 4814 var cache = element.data(STORAGE_KEY); 4815 element.removeData(STORAGE_KEY); 4816 4817 // in the event that the element is removed before postDigest 4818 // is run then the cache will be undefined and there will be 4819 // no need anymore to add or remove and of the element classes 4820 if (cache) { 4821 var classes = resolveElementClasses(element, cache.classes); 4822 if (classes) { 4823 self.$$setClassImmediately(element, classes[0], classes[1], cache.options); 4824 } 4825 } 4826 4827 done(); 4828 }); 4829 element.data(STORAGE_KEY, cache); 4830 } 4831 4832 return cache.promise; 4833 }, 4834 4835 $$setClassImmediately: function(element, add, remove, options) { 4836 add && this.$$addClassImmediately(element, add); 4837 remove && this.$$removeClassImmediately(element, remove); 4838 applyStyles(element, options); 4839 return asyncPromise(); 4840 }, 4841 4842 enabled: noop, 4843 cancel: noop 4844 }; 4845 }]; 4846}]; 4847 4848function $$AsyncCallbackProvider() { 4849 this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { 4850 return $$rAF.supported 4851 ? function(fn) { return $$rAF(fn); } 4852 : function(fn) { 4853 return $timeout(fn, 0, false); 4854 }; 4855 }]; 4856} 4857 4858/* global stripHash: true */ 4859 4860/** 4861 * ! This is a private undocumented service ! 4862 * 4863 * @name $browser 4864 * @requires $log 4865 * @description 4866 * This object has two goals: 4867 * 4868 * - hide all the global state in the browser caused by the window object 4869 * - abstract away all the browser specific features and inconsistencies 4870 * 4871 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser` 4872 * service, which can be used for convenient testing of the application without the interaction with 4873 * the real browser apis. 4874 */ 4875/** 4876 * @param {object} window The global window object. 4877 * @param {object} document jQuery wrapped document. 4878 * @param {object} $log window.console or an object with the same interface. 4879 * @param {object} $sniffer $sniffer service 4880 */ 4881function Browser(window, document, $log, $sniffer) { 4882 var self = this, 4883 rawDocument = document[0], 4884 location = window.location, 4885 history = window.history, 4886 setTimeout = window.setTimeout, 4887 clearTimeout = window.clearTimeout, 4888 pendingDeferIds = {}; 4889 4890 self.isMock = false; 4891 4892 var outstandingRequestCount = 0; 4893 var outstandingRequestCallbacks = []; 4894 4895 // TODO(vojta): remove this temporary api 4896 self.$$completeOutstandingRequest = completeOutstandingRequest; 4897 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 4898 4899 /** 4900 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 4901 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 4902 */ 4903 function completeOutstandingRequest(fn) { 4904 try { 4905 fn.apply(null, sliceArgs(arguments, 1)); 4906 } finally { 4907 outstandingRequestCount--; 4908 if (outstandingRequestCount === 0) { 4909 while (outstandingRequestCallbacks.length) { 4910 try { 4911 outstandingRequestCallbacks.pop()(); 4912 } catch (e) { 4913 $log.error(e); 4914 } 4915 } 4916 } 4917 } 4918 } 4919 4920 function getHash(url) { 4921 var index = url.indexOf('#'); 4922 return index === -1 ? '' : url.substr(index + 1); 4923 } 4924 4925 /** 4926 * @private 4927 * Note: this method is used only by scenario runner 4928 * TODO(vojta): prefix this method with $$ ? 4929 * @param {function()} callback Function that will be called when no outstanding request 4930 */ 4931 self.notifyWhenNoOutstandingRequests = function(callback) { 4932 // force browser to execute all pollFns - this is needed so that cookies and other pollers fire 4933 // at some deterministic time in respect to the test runner's actions. Leaving things up to the 4934 // regular poller would result in flaky tests.
4935 forEach(pollFns, function(pollFn) { pollFn(); }); 4936 4937 if (outstandingRequestCount === 0) { 4938 callback(); 4939 } else { 4940 outstandingRequestCallbacks.push(callback); 4941 } 4942 }; 4943 4944 ////////////////////////////////////////////////////////////// 4945 // Poll Watcher API 4946 ////////////////////////////////////////////////////////////// 4947 var pollFns = [], 4948 pollTimeout; 4949 4950 /** 4951 * @name $browser#addPollFn 4952 * 4953 * @param {function()} fn Poll function to add 4954 * 4955 * @description 4956 * Adds a function to the list of functions that poller periodically executes, 4957 * and starts polling if not started yet. 4958 * 4959 * @returns {function()} the added function 4960 */ 4961 self.addPollFn = function(fn) { 4962 if (isUndefined(pollTimeout)) startPoller(100, setTimeout); 4963 pollFns.push(fn); 4964 return fn; 4965 }; 4966 4967 /** 4968 * @param {number} interval How often should browser call poll functions (ms) 4969 * @param {function()} setTimeout Reference to a real or fake `setTimeout` function. 4970 * 4971 * @description 4972 * Configures the poller to run in the specified intervals, using the specified 4973 * setTimeout fn and kicks it off. 4974 */ 4975 function startPoller(interval, setTimeout) { 4976 (function check() { 4977 forEach(pollFns, function(pollFn) { pollFn(); }); 4978 pollTimeout = setTimeout(check, interval); 4979 })(); 4980 } 4981 4982 ////////////////////////////////////////////////////////////// 4983 // URL API 4984 ////////////////////////////////////////////////////////////// 4985 4986 var cachedState, lastHistoryState, 4987 lastBrowserUrl = location.href, 4988 baseElement = document.find('base'), 4989 reloadLocation = null; 4990 4991 cacheState(); 4992 lastHistoryState = cachedState; 4993 4994 /** 4995 * @name $browser#url 4996 * 4997 * @description 4998 * GETTER: 4999 * Without any argument, this method just returns current value of location.href. 5000 * 5001 * SETTER: 5002 * With at least one argument, this method sets url to new value. 5003 * If html5 history api supported, pushState/replaceState is used, otherwise 5004 * location.href/location.replace is used. 5005 * Returns its own instance to allow chaining 5006 * 5007 * NOTE: this api is intended for use only by the $location service. Please use the 5008 * {@link ng.$location $location service} to change url. 5009 * 5010 * @param {string} url New url (when used as setter) 5011 * @param {boolean=} replace Should new url replace current history record? 5012 * @param {object=} state object to use with pushState/replaceState 5013 */ 5014 self.url = function(url, replace, state) { 5015 // In modern browsers `history.state` is `null` by default; treating it separately 5016 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 5017 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 5018 if (isUndefined(state)) { 5019 state = null; 5020 } 5021 5022 // Android Browser BFCache causes location, history reference to become stale. 5023 if (location !== window.location) location = window.location; 5024 if (history !== window.history) history = window.history; 5025 5026 // setter 5027 if (url) { 5028 var sameState = lastHistoryState === state; 5029 5030 // Don't change anything if previous and current URLs and states match. This also prevents 5031 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 5032 // See https://github.com/angular/angular.js/commit/ffb2701 5033 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 5034 return self; 5035 } 5036 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 5037 lastBrowserUrl = url; 5038 lastHistoryState = state; 5039 // Don't use history API if only the hash changed 5040 // due to a bug in IE10/IE11 which leads 5041 // to not firing a `hashchange` nor `popstate` event 5042 // in some cases (see #9143). 5043 if ($sniffer.history && (!sameBase || !sameState)) { 5044 history[replace ? 'replaceState' : 'pushState'](state, '', url); 5045 cacheState(); 5046 // Do the assignment again so that those two variables are referentially identical. 5047 lastHistoryState = cachedState; 5048 } else { 5049 if (!sameBase) { 5050 reloadLocation = url; 5051 } 5052 if (replace) { 5053 location.replace(url); 5054 } else if (!sameBase) { 5055 location.href = url; 5056 } else { 5057 location.hash = getHash(url); 5058 } 5059 } 5060 return self; 5061 // getter 5062 } else { 5063 // - reloadLocation is needed as browsers don't allow to read out 5064 // the new location.href if a reload happened.
5065 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 5066 return reloadLocation || location.href.replace(/%27/g,"'"); 5067 } 5068 }; 5069 5070 /** 5071 * @name $browser#state 5072 * 5073 * @description 5074 * This method is a getter. 5075 * 5076 * Return history.state or null if history.state is undefined. 5077 * 5078 * @returns {object} state 5079 */ 5080 self.state = function() { 5081 return cachedState; 5082 }; 5083 5084 var urlChangeListeners = [], 5085 urlChangeInit = false; 5086 5087 function cacheStateAndFireUrlChange() { 5088 cacheState(); 5089 fireUrlChange(); 5090 } 5091 5092 // This variable should be used *only* inside the cacheState function. 5093 var lastCachedState = null; 5094 function cacheState() { 5095 // This should be the only place in $browser where `history.state` is read. 5096 cachedState = window.history.state; 5097 cachedState = isUndefined(cachedState) ? null : cachedState; 5098 5099 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 5100 if (equals(cachedState, lastCachedState)) { 5101 cachedState = lastCachedState; 5102 } 5103 lastCachedState = cachedState; 5104 } 5105 5106 function fireUrlChange() { 5107 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { 5108 return; 5109 } 5110 5111 lastBrowserUrl = self.url(); 5112 lastHistoryState = cachedState; 5113 forEach(urlChangeListeners, function(listener) { 5114 listener(self.url(), cachedState); 5115 }); 5116 } 5117 5118 /** 5119 * @name $browser#onUrlChange 5120 * 5121 * @description 5122 * Register callback function that will be called, when url changes. 5123 * 5124 * It's only called when the url is changed from outside of angular: 5125 * - user types different url into address bar 5126 * - user clicks on history (forward/back) button 5127 * - user clicks on a link 5128 * 5129 * It's not called when url is changed by $browser.url() method 5130 * 5131 * The listener gets called with new url as parameter. 5132 * 5133 * NOTE: this api is intended for use only by the $location service. Please use the 5134 * {@link ng.$location $location service} to monitor url changes in angular apps. 5135 * 5136 * @param {function(string)} listener Listener function to be called when url changes. 5137 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 5138 */ 5139 self.onUrlChange = function(callback) { 5140 // TODO(vojta): refactor to use node's syntax for events 5141 if (!urlChangeInit) { 5142 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 5143 // don't fire popstate when user change the address bar and don't fire hashchange when url 5144 // changed by push/replaceState 5145 5146 // html5 history api - popstate event 5147 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 5148 // hashchange event 5149 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 5150 5151 urlChangeInit = true; 5152 } 5153 5154 urlChangeListeners.push(callback); 5155 return callback; 5156 }; 5157 5158 /** 5159 * Checks whether the url has changed outside of Angular. 5160 * Needs to be exported to be able to check for changes that have been done in sync, 5161 * as hashchange/popstate events fire in async. 5162 */ 5163 self.$$checkUrlChange = fireUrlChange; 5164 5165 ////////////////////////////////////////////////////////////// 5166 // Misc API 5167 ////////////////////////////////////////////////////////////// 5168 5169 /** 5170 * @name $browser#baseHref 5171 * 5172 * @description 5173 * Returns current <base href> 5174 * (always relative - without domain) 5175 * 5176 * @returns {string} The current base href 5177 */ 5178 self.baseHref = function() { 5179 var href = baseElement.attr('href'); 5180 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 5181 }; 5182 5183 ////////////////////////////////////////////////////////////// 5184 // Cookies API 5185 ////////////////////////////////////////////////////////////// 5186 var lastCookies = {}; 5187 var lastCookieString = ''; 5188 var cookiePath = self.baseHref(); 5189 5190 function safeDecodeURIComponent(str) { 5191 try { 5192 return decodeURIComponent(str); 5193 } catch (e) { 5194 return str; 5195 } 5196 } 5197 5198 /** 5199 * @name $browser#cookies 5200 * 5201 * @param {string=} name Cookie name 5202 * @param {string=} value Cookie value 5203 * 5204 * @description 5205 * The cookies method provides a 'private' low level access to browser cookies. 5206 * It is not meant to be used directly, use the $cookie service instead. 5207 * 5208 * The return values vary depending on the arguments that the method was called with as follows: 5209 * 5210 * - cookies() -> hash of all cookies, this is NOT a copy of the internal state, so do not modify 5211 * it 5212 * - cookies(name, value) -> set name to value, if value is undefined delete the cookie 5213 * - cookies(name) -> the same as (name, undefined) == DELETES (no one calls it right now that 5214 * way) 5215 * 5216 * @returns {Object} Hash of all cookies (if called without any parameter) 5217 */ 5218 self.cookies = function(name, value) { 5219 var cookieLength, cookieArray, cookie, i, index; 5220 5221 if (name) { 5222 if (value === undefined) { 5223 rawDocument.cookie = encodeURIComponent(name) + "=;
5223path=" + cookiePath + 5224 ";expires=Thu, 01 Jan 1970 00:00:00 GMT"; 5225 } else { 5226 if (isString(value)) { 5227 cookieLength = (rawDocument.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) + 5228 ';path=' + cookiePath).length + 1; 5229 5230 // per http://www.ietf.org/rfc/rfc2109.txt browser must allow at minimum: 5231 // - 300 cookies 5232 // - 20 cookies per unique domain 5233 // - 4096 bytes per cookie 5234 if (cookieLength > 4096) { 5235 $log.warn("Cookie '" + name + 5236 "' possibly not set or overflowed because it was too large (" + 5237 cookieLength + " > 4096 bytes)!"); 5238 } 5239 } 5240 } 5241 } else { 5242 if (rawDocument.cookie !== lastCookieString) { 5243 lastCookieString = rawDocument.cookie; 5244 cookieArray = lastCookieString.split("; "); 5245 lastCookies = {}; 5246 5247 for (i = 0; i < cookieArray.length; i++) { 5248 cookie = cookieArray[i]; 5249 index = cookie.indexOf('='); 5250 if (index > 0) { //ignore nameless cookies 5251 name = safeDecodeURIComponent(cookie.substring(0, index)); 5252 // the first value that is seen for a cookie is the most 5253 // specific one. values for the same cookie name that 5254 // follow are for less specific paths. 5255 if (lastCookies[name] === undefined) { 5256 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 5257 } 5258 } 5259 } 5260 } 5261 return lastCookies; 5262 } 5263 }; 5264 5265 5266 /** 5267 * @name $browser#defer 5268 * @param {function()} fn A function, who's execution should be deferred. 5269 * @param {number=} [delay=0] of milliseconds to defer the function execution. 5270 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 5271 * 5272 * @description 5273 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 5274 * 5275 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 5276 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 5277 * via `$browser.defer.flush()`. 5278 * 5279 */ 5280 self.defer = function(fn, delay) { 5281 var timeoutId; 5282 outstandingRequestCount++; 5283 timeoutId = setTimeout(function() { 5284 delete pendingDeferIds[timeoutId]; 5285 completeOutstandingRequest(fn); 5286 }, delay || 0); 5287 pendingDeferIds[timeoutId] = true; 5288 return timeoutId; 5289 }; 5290 5291 5292 /** 5293 * @name $browser#defer.cancel 5294 * 5295 * @description 5296 * Cancels a deferred task identified with `deferId`. 5297 * 5298 * @param {*} deferId Token returned by the `$browser.defer` function. 5299 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 5300 * canceled. 5301 */ 5302 self.defer.cancel = function(deferId) { 5303 if (pendingDeferIds[deferId]) { 5304 delete pendingDeferIds[deferId]; 5305 clearTimeout(deferId); 5306 completeOutstandingRequest(noop); 5307 return true; 5308 } 5309 return false; 5310 }; 5311 5312} 5313 5314function $BrowserProvider() { 5315 this.$get = ['$window', '$log', '$sniffer', '$document', 5316 function($window, $log, $sniffer, $document) { 5317 return new Browser($window, $document, $log, $sniffer); 5318 }]; 5319} 5320 5321/** 5322 * @ngdoc service 5323 * @name $cacheFactory 5324 * 5325 * @description 5326 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 5327 * them. 5328 * 5329 * ```js 5330 * 5331 * var cache = $cacheFactory('cacheId'); 5332 * expect($cacheFactory.get('cacheId')).toBe(cache); 5333 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 5334 * 5335 * cache.put("key", "value"); 5336 * cache.put("another key", "another value"); 5337 * 5338 * // We've specified no options on creation 5339 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 5340 * 5341 * ``` 5342 * 5343 * 5344 * @param {string} cacheId Name or id of the newly created cache. 5345 * @param {object=} options Options object that specifies the cache behavior. Properties: 5346 * 5347 * - `{number=}` `capacity` â turns the cache into LRU cache. 5348 * 5349 * @returns {object} Newly created cache object with the following set of methods: 5350 * 5351 * - `{object}` `info()` â Returns id, size, and options of cache. 5352 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 5353 * it. 5354 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 5355 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 5356 * - `{void}` `removeAll()` â Removes all cached values. 5357 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 5358 * 5359 * @example 5360 <example module="cacheExampleApp"> 5361 <file name="index.html"> 5362 <div ng-controller="CacheController"> 5363 <input ng-model="newCacheKey" placeholder="Key"> 5364 <input ng-model="newCacheValue" placeholder="Value"> 5365 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 5366 5367 <p ng-if="keys.length">Cached Values</p> 5368 <div ng-repeat="key in keys">
5369 <span ng-bind="key"></span> 5370 <span>: </span> 5371 <b ng-bind="cache.get(key)"></b> 5372 </div> 5373 5374 <p>Cache Info</p> 5375 <div ng-repeat="(key, value) in cache.info()"> 5376 <span ng-bind="key"></span> 5377 <span>: </span> 5378 <b ng-bind="value"></b> 5379 </div> 5380 </div> 5381 </file> 5382 <file name="script.js"> 5383 angular.module('cacheExampleApp', []). 5384 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 5385 $scope.keys = []; 5386 $scope.cache = $cacheFactory('cacheId'); 5387 $scope.put = function(key, value) { 5388 if ($scope.cache.get(key) === undefined) { 5389 $scope.keys.push(key); 5390 } 5391 $scope.cache.put(key, value === undefined ? null : value); 5392 }; 5393 }]); 5394 </file> 5395 <file name="style.css"> 5396 p { 5397 margin: 10px 0 3px; 5398 } 5399 </file> 5400 </example> 5401 */ 5402function $CacheFactoryProvider() { 5403 5404 this.$get = function() { 5405 var caches = {}; 5406 5407 function cacheFactory(cacheId, options) { 5408 if (cacheId in caches) { 5409 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 5410 } 5411 5412 var size = 0, 5413 stats = extend({}, options, {id: cacheId}), 5414 data = {}, 5415 capacity = (options && options.capacity) || Number.MAX_VALUE, 5416 lruHash = {}, 5417 freshEnd = null, 5418 staleEnd = null; 5419 5420 /** 5421 * @ngdoc type 5422 * @name $cacheFactory.Cache 5423 * 5424 * @description 5425 * A cache object used to store and retrieve data, primarily used by 5426 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 5427 * templates and other data. 5428 * 5429 * ```js 5430 * angular.module('superCache') 5431 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 5432 * return $cacheFactory('super-cache'); 5433 * }]); 5434 * ``` 5435 * 5436 * Example test: 5437 * 5438 * ```js 5439 * it('should behave like a cache', inject(function(superCache) { 5440 * superCache.put('key', 'value'); 5441 * superCache.put('another key', 'another value'); 5442 * 5443 * expect(superCache.info()).toEqual({ 5444 * id: 'super-cache', 5445 * size: 2 5446 * }); 5447 * 5448 * superCache.remove('another key'); 5449 * expect(superCache.get('another key')).toBeUndefined(); 5450 * 5451 * superCache.removeAll(); 5452 * expect(superCache.info()).toEqual({ 5453 * id: 'super-cache', 5454 * size: 0 5455 * }); 5456 * })); 5457 * ``` 5458 */ 5459 return caches[cacheId] = { 5460 5461 /** 5462 * @ngdoc method 5463 * @name $cacheFactory.Cache#put 5464 * @kind function 5465 * 5466 * @description 5467 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 5468 * retrieved later, and incrementing the size of the cache if the key was not already 5469 * present in the cache. If behaving like an LRU cache, it will also remove stale 5470 * entries from the set. 5471 * 5472 * It will not insert undefined values into the cache. 5473 * 5474 * @param {string} key the key under which the cached data is stored. 5475 * @param {*} value the value to store alongside the key. If it is undefined, the key 5476 * will not be stored. 5477 * @returns {*} the value stored. 5478 */ 5479 put: function(key, value) { 5480 if (capacity < Number.MAX_VALUE) { 5481 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 5482 5483 refresh(lruEntry); 5484 } 5485 5486 if (isUndefined(value)) return; 5487 if (!(key in data)) size++; 5488 data[key] = value; 5489 5490 if (size > capacity) { 5491 this.remove(staleEnd.key); 5492 } 5493 5494 return value; 5495 }, 5496 5497 /** 5498 * @ngdoc method 5499 * @name $cacheFactory.Cache#get 5500 * @kind function 5501 * 5502 * @description 5503 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 5504 * 5505 * @param {string} key the key of the data to be retrieved 5506 * @returns {*} the value stored. 5507 */ 5508 get: function(key) { 5509 if (capacity < Number.MAX_VALUE) { 5510 var lruEntry = lruHash[key]; 5511 5512 if (!lruEntry) return; 5513 5514 refresh(lruEntry); 5515 } 5516 5517 return data[key]; 5518 }, 5519 5520 5521 /** 5522 * @ngdoc method 5523 * @name $cacheFactory.Cache#remove 5524 * @kind function 5525 *
5526 * @description 5527 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 5528 * 5529 * @param {string} key the key of the entry to be removed 5530 */ 5531 remove: function(key) { 5532 if (capacity < Number.MAX_VALUE) { 5533 var lruEntry = lruHash[key]; 5534 5535 if (!lruEntry) return; 5536 5537 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 5538 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 5539 link(lruEntry.n,lruEntry.p); 5540 5541 delete lruHash[key]; 5542 } 5543 5544 delete data[key]; 5545 size--; 5546 }, 5547 5548 5549 /** 5550 * @ngdoc method 5551 * @name $cacheFactory.Cache#removeAll 5552 * @kind function 5553 * 5554 * @description 5555 * Clears the cache object of any entries. 5556 */ 5557 removeAll: function() { 5558 data = {}; 5559 size = 0; 5560 lruHash = {}; 5561 freshEnd = staleEnd = null; 5562 }, 5563 5564 5565 /** 5566 * @ngdoc method 5567 * @name $cacheFactory.Cache#destroy 5568 * @kind function 5569 * 5570 * @description 5571 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 5572 * removing it from the {@link $cacheFactory $cacheFactory} set. 5573 */ 5574 destroy: function() { 5575 data = null; 5576 stats = null; 5577 lruHash = null; 5578 delete caches[cacheId]; 5579 }, 5580 5581 5582 /** 5583 * @ngdoc method 5584 * @name $cacheFactory.Cache#info 5585 * @kind function 5586 * 5587 * @description 5588 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 5589 * 5590 * @returns {object} an object with the following properties: 5591 * <ul> 5592 * <li>**id**: the id of the cache instance</li> 5593 * <li>**size**: the number of entries kept in the cache instance</li> 5594 * <li>**...**: any additional properties from the options object when creating the 5595 * cache.</li> 5596 * </ul> 5597 */ 5598 info: function() { 5599 return extend({}, stats, {size: size}); 5600 } 5601 }; 5602 5603 5604 /** 5605 * makes the `entry` the freshEnd of the LRU linked list 5606 */ 5607 function refresh(entry) { 5608 if (entry != freshEnd) { 5609 if (!staleEnd) { 5610 staleEnd = entry; 5611 } else if (staleEnd == entry) { 5612 staleEnd = entry.n; 5613 } 5614 5615 link(entry.n, entry.p); 5616 link(entry, freshEnd); 5617 freshEnd = entry; 5618 freshEnd.n = null; 5619 } 5620 } 5621 5622 5623 /** 5624 * bidirectionally links two entries of the LRU linked list 5625 */ 5626 function link(nextEntry, prevEntry) { 5627 if (nextEntry != prevEntry) { 5628 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 5629 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 5630 } 5631 } 5632 } 5633 5634 5635 /** 5636 * @ngdoc method 5637 * @name $cacheFactory#info 5638 * 5639 * @description 5640 * Get information about all the caches that have been created 5641 * 5642 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 5643 */ 5644 cacheFactory.info = function() { 5645 var info = {}; 5646 forEach(caches, function(cache, cacheId) { 5647 info[cacheId] = cache.info(); 5648 }); 5649 return info; 5650 }; 5651 5652 5653 /** 5654 * @ngdoc method 5655 * @name $cacheFactory#get 5656 * 5657 * @description 5658 * Get access to a cache object by the `cacheId` used when it was created. 5659 * 5660 * @param {string} cacheId Name or id of a cache to access. 5661 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 5662 */ 5663 cacheFactory.get = function(cacheId) { 5664 return caches[cacheId]; 5665 }; 5666 5667 5668 return cacheFactory; 5669 }; 5670} 5671 5672/** 5673 * @ngdoc service 5674 * @name $templateCache 5675 * 5676 * @description 5677 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 5678 * can load templates directly into the cache in a `script` tag, or by consuming the 5679 * `$templateCache` service directly. 5680 * 5681 * Adding via the `script` tag: 5682 * 5683 * ```html 5684 * <script type="text/ng-template" id="templateId.html"> 5685 * <p>This is the content of the template</p> 5686 * </script> 5687 * ``` 5688 * 5689 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 5690 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, 5691 * element with ng-app attribute), otherwise the template will be ignored. 5692 * 5693 * Adding via the $templateCache service: 5694 * 5695 * ```js 5696 * var myApp = angular.module('myApp', []); 5697 * myApp.run(function($templateCache) { 5698 * $templateCache.put('templateId.html', 'This is the content of the template'); 5699 * }); 5700 * ``` 5701 * 5702 * To retrieve the template later, simply use it in your HTML: 5703 * ```html 5704 * <div ng-include=" 'templateId.html' "></div> 5705 * ``` 5706 * 5707 * or get it via Javascript: 5708 * ```js 5709 * $templateCache.get('templateId.html') 5710 * ``` 5711 * 5712 * See {@link ng.$cacheFactory $cacheFactory}. 5713 * 5714 */ 5715function $TemplateCacheProvider() { 5716 this.$get = ['$cacheFactory', function($cacheFactory) { 5717 return $cacheFactory('templates'); 5718 }]; 5719} 5720 5721/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 5722 * 5723 * DOM-related variables: 5724 * 5725 * - "node" - DOM Node 5726 * - "element" - DOM Element or Node 5727 * - "$node" or "$element" - jqLite-wrapped node or element 5728 * 5729 * 5730 * Compiler related stuff: 5731 * 5732 * - "linkFn" - linking fn of a single directive 5733 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 5734 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 5735 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 5736 */ 5737 5738 5739/** 5740 * @ngdoc service 5741 * @name $compile 5742 * @kind function 5743 * 5744 * @description 5745 * Compiles an HTML string or DOM into a template and produces a template function, which 5746 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 5747 * 5748 * The compilation is a process of walking the DOM tree and matching DOM elements to 5749 * {@link ng.$compileProvider#directive directives}. 5750 * 5751 * <div class="alert alert-warning"> 5752 * **Note:** This document is an in-depth reference of all directive options. 5753 * For a gentle introduction to directives with examples of common use cases, 5754 * see the {@link guide/directive directive guide}. 5755 * </div> 5756 * 5757 * ## Comprehensive Directive API 5758 * 5759 * There are many different options for a directive. 5760 * 5761 * The difference resides in the return value of the factory function. 5762 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 5763 * or just the `postLink` function (all other properties will have the default values). 5764 * 5765 * <div class="alert alert-success"> 5766 * **Best Practice:** It's recommended to use the "directive definition object" form. 5767 * </div> 5768 * 5769 * Here's an example directive declared with a Directive Definition Object: 5770 * 5771 * ```js 5772 * var myModule = angular.module(...); 5773 * 5774 * myModule.directive('directiveName', function factory(injectables) { 5775 * var directiveDefinitionObject = { 5776 * priority: 0, 5777 * template: '<div></div>
5777', // or // function(tElement, tAttrs) { ... }, 5778 * // or 5779 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 5780 * transclude: false, 5781 * restrict: 'A', 5782 * templateNamespace: 'html', 5783 * scope: false, 5784 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 5785 * controllerAs: 'stringAlias', 5786 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 5787 * compile: function compile(tElement, tAttrs, transclude) { 5788 * return { 5789 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 5790 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 5791 * } 5792 * // or 5793 * // return function postLink( ... ) { ... } 5794 * }, 5795 * // or 5796 * // link: { 5797 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 5798 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 5799 * // } 5800 * // or 5801 * // link: function postLink( ... ) { ... } 5802 * }; 5803 * return directiveDefinitionObject; 5804 * }); 5805 * ``` 5806 * 5807 * <div class="alert alert-warning"> 5808 * **Note:** Any unspecified options will use the default value. You can see the default values below. 5809 * </div> 5810 * 5811 * Therefore the above can be simplified as: 5812 * 5813 * ```js 5814 * var myModule = angular.module(...); 5815 * 5816 * myModule.directive('directiveName', function factory(injectables) { 5817 * var directiveDefinitionObject = { 5818 * link: function postLink(scope, iElement, iAttrs) { ... } 5819 * }; 5820 * return directiveDefinitionObject; 5821 * // or 5822 * // return function postLink(scope, iElement, iAttrs) { ... } 5823 * }); 5824 * ``` 5825 * 5826 * 5827 * 5828 * ### Directive Definition Object 5829 * 5830 * The directive definition object provides instructions to the {@link ng.$compile 5831 * compiler}. The attributes are: 5832 * 5833 * #### `multiElement` 5834 * When this property is set to true, the HTML compiler will collect DOM nodes between 5835 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 5836 * together as the directive elements. It is recommended that this feature be used on directives 5837 * which are not strictly behavioural (such as {@link ngClick}), and which 5838 * do not manipulate or replace child nodes (such as {@link ngInclude}). 5839 * 5840 * #### `priority` 5841 * When there are multiple directives defined on a single DOM element, sometimes it 5842 * is necessary to specify the order in which the directives are applied. The `priority` is used 5843 * to sort the directives before their `compile` functions get called. Priority is defined as a 5844 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 5845 * are also run in priority order, but post-link functions are run in reverse order. The order 5846 * of directives with the same priority is undefined. The default priority is `0`. 5847 * 5848 * #### `terminal` 5849 * If set to true then the current `priority` will be the last set of directives 5850 * which will execute (any directives at the current priority will still execute 5851 * as the order of execution on same `priority` is undefined). Note that expressions 5852 * and other directives used in the directive's template will also be excluded from execution. 5853 * 5854 * #### `scope` 5855 * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the 5856 * same element request a new scope, only one new scope is created. The new scope rule does not 5857 * apply for the root of the template since the root of the template always gets a new scope. 5858 * 5859 * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from 5860 * normal scope in that it does not prototypically inherit from the parent scope. This is useful 5861 * when creating reusable components, which should not accidentally read or modify data in the 5862 * parent scope. 5863 * 5864 * The 'isolate' scope takes an object hash which defines a set of local scope properties 5865 * derived from the parent scope. These local properties are useful for aliasing values for 5866 * templates. Locals definition is a hash of local scope property to its source: 5867 * 5868 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
5869 * always a string since DOM attributes are strings. If no `attr` name is specified then the 5870 * attribute name is assumed to be the same as the local name. 5871 * Given `<widget my-attr="hello {{name}}">` and widget definition 5872 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 5873 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 5874 * `localName` property on the widget scope. The `name` is read from the parent scope (not 5875 * component scope). 5876 * 5877 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 5878 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 5879 * name is specified then the attribute name is assumed to be the same as the local name. 5880 * Given `<widget my-attr="parentModel">` and widget definition of 5881 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 5882 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 5883 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 5884 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 5885 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If 5886 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use 5887 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional). 5888 * 5889 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 5890 * If no `attr` name is specified then the attribute name is assumed to be the same as the 5891 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 5892 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 5893 * a function wrapper for the `count = count + value` expression. Often it's desirable to 5894 * pass data from the isolated scope via an expression to the parent scope, this can be 5895 * done by passing a map of local variable names and values into the expression wrapper fn. 5896 * For example, if the expression is `increment(amount)` then we can specify the amount value 5897 * by calling the `localFn` as `localFn({amount: 22})`. 5898 * 5899 * 5900 * #### `bindToController` 5901 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will 5902 * allow a component to have its properties bound to the controller, rather than to scope. When the controller 5903 * is instantiated, the initial values of the isolate scope bindings are already available. 5904 * 5905 * #### `controller` 5906 * Controller constructor function. The controller is instantiated before the 5907 * pre-linking phase and it is shared with other directives (see 5908 * `require` attribute). This allows the directives to communicate with each other and augment 5909 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 5910 * 5911 * * `$scope` - Current scope associated with the element 5912 * * `$element` - Current element 5913 * * `$attrs` - Current attributes object for the element 5914 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 5915 * `function([scope], cloneLinkingFn, futureParentElement)`. 5916 * * `scope`: optional argument to override the scope. 5917 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content. 5918 * * `futureParentElement`: 5919 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 5920 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 5921 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 5922 * and when the `cloneLinkinFn` is passed, 5923 * as those elements need to created and cloned in a special way when they are defined outside their 5924 * usual containers (e.g. like `<svg>`). 5925 * * See also the `directive.templateNamespace` property. 5926 * 5927 * 5928 * #### `require` 5929 * Require another directive and inject its controller as the fourth argument to the linking function. The
5930 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 5931 * injected argument will be an array in corresponding order. If no such directive can be 5932 * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: 5933 * 5934 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 5935 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 5936 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 5937 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 5938 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 5939 * `null` to the `link` fn if not found. 5940 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 5941 * `null` to the `link` fn if not found. 5942 * 5943 * 5944 * #### `controllerAs` 5945 * Controller alias at the directive scope. An alias for the controller so it 5946 * can be referenced at the directive template. The directive needs to define a scope for this 5947 * configuration to be used. Useful in the case when directive is used as component. 5948 * 5949 * 5950 * #### `restrict` 5951 * String of subset of `EACM` which restricts the directive to a specific directive 5952 * declaration style. If omitted, the defaults (elements and attributes) are used. 5953 * 5954 * * `E` - Element name (default): `<my-directive></my-directive>` 5955 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 5956 * * `C` - Class: `<div class="my-directive: exp;"></div>` 5957 * * `M` - Comment: `<!-- directive: my-directive exp -->` 5958 * 5959 * 5960 * #### `templateNamespace` 5961 * String representing the document type used by the markup in the template. 5962 * AngularJS needs this information as those elements need to be created and cloned 5963 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 5964 * 5965 * * `html` - All root nodes in the template are HTML. Root nodes may also be 5966 * top-level elements such as `<svg>` or `<math>`. 5967 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 5968 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 5969 * 5970 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 5971 * 5972 * #### `template` 5973 * HTML markup that may: 5974 * * Replace the contents of the directive's element (default). 5975 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 5976 * * Wrap the contents of the directive's element (if `transclude` is true). 5977 * 5978 * Value may be: 5979 * 5980 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 5981 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` 5982 * function api below) and returns a string value. 5983 * 5984 * 5985 * #### `templateUrl` 5986 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 5987 * 5988 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 5989 * for later when the template has been resolved. In the meantime it will continue to compile and link 5990 * sibling and parent elements as though this element had not contained any directives. 5991 * 5992 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 5993 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 5994 * case when only one deeply nested directive has `templateUrl`. 5995 * 5996 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} 5997 * 5998 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 5999 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 6000 * a string value representing the url. In either case, the template URL is passed through {@link 6001 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 6002 * 6003 * 6004 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) 6005 * specify what the template should replace. Defaults to `false`. 6006 * 6007 * * `true` - the template will replace the directive's element. 6008 * * `false` - the template will replace the contents of the directive's element. 6009 * 6010 * The replacement process migrates all of the attributes / classes from the old element to the new 6011 * one. See the {@link guide/directive#template-expanding-directive 6012 * Directives Guide} for an example. 6013 * 6014 * There are very few scenarios where element replacement is required for the application function, 6015 * the main one being reusable custom components that are used within SVG contexts 6016 * (because SVG doesn't work with custom elements in the DOM tree). 6017 * 6018 * #### `transclude` 6019 * Extract the contents of the element where the directive appears and make it available to the directive. 6020 * The contents are compiled and provided to the directive as a **transclusion function**. See the 6021 * {@link $compile#transclusion Transclusion} section below. 6022 * 6023 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the 6024 * directive's element or the entire element: 6025 * 6026 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 6027 * * `'element'` - transclude the whole of the directive's element including any directives on this 6028 * element that defined at a lower priority than this directive. When used, the `template` 6029 * property is ignored. 6030 * 6031 * 6032 * #### `compile` 6033 * 6034 * ```js 6035 * function compile(tElement, tAttrs, transclude) { ... } 6036 * ``` 6037 * 6038 * The compile function deals with transforming the template DOM. Since most directives do not do 6039 * template transformation, it is not used often. The compile function takes the following arguments: 6040 * 6041 * * `tElement` - template element - The element where the directive has been declared. It is 6042 * safe to do template transformation on the element and child elements only. 6043 * 6044 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 6045 * between all directive compile functions. 6046 * 6047 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 6048 * 6049 * <div class="alert alert-warning"> 6050 * **Note:** The template instance and the link instance may be different objects if the template has 6051 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 6052 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
6053 * should be done in a linking function rather than in a compile function. 6054 * </div> 6055 6056 * <div class="alert alert-warning"> 6057 * **Note:** The compile function cannot handle directives that recursively use themselves in their 6058 * own templates or compile functions. Compiling these directives results in an infinite loop and a 6059 * stack overflow errors. 6060 * 6061 * This can be avoided by manually using $compile in the postLink function to imperatively compile 6062 * a directive's template instead of relying on automatic template compilation via `template` or 6063 * `templateUrl` declaration or manual compilation inside the compile function. 6064 * </div> 6065 * 6066 * <div class="alert alert-error"> 6067 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 6068 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 6069 * to the link function instead. 6070 * </div> 6071 6072 * A compile function can have a return value which can be either a function or an object. 6073 * 6074 * * returning a (post-link) function - is equivalent to registering the linking function via the 6075 * `link` property of the config object when the compile function is empty. 6076 * 6077 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 6078 * control when a linking function should be called during the linking phase. See info about 6079 * pre-linking and post-linking functions below. 6080 * 6081 * 6082 * #### `link` 6083 * This property is used only if the `compile` property is not defined. 6084 * 6085 * ```js 6086 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 6087 * ``` 6088 * 6089 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 6090 * executed after the template has been cloned. This is where most of the directive logic will be 6091 * put. 6092 * 6093 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 6094 * directive for registering {@link ng.$rootScope.Scope#$watch watches}. 6095 * 6096 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 6097 * manipulate the children of the element only in `postLink` function since the children have 6098 * already been linked. 6099 * 6100 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 6101 * between all directive linking functions. 6102 * 6103 * * `controller` - a controller instance - A controller instance if at least one directive on the 6104 * element defines a controller. The controller is shared among all the directives, which allows 6105 * the directives to use the controllers as a communication channel. 6106 * 6107 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 6108 * This is the same as the `$transclude` 6109 * parameter of directive controllers, see there for details. 6110 * `function([scope], cloneLinkingFn, futureParentElement)`. 6111 * 6112 * #### Pre-linking function 6113 * 6114 * Executed before the child elements are linked. Not safe to do DOM transformation since the 6115 * compiler linking function will fail to locate the correct elements for linking. 6116 * 6117 * #### Post-linking function 6118 * 6119 * Executed after the child elements are linked. 6120 *
6121 * Note that child elements that contain `templateUrl` directives will not have been compiled 6122 * and linked since they are waiting for their template to load asynchronously and their own 6123 * compilation and linking has been suspended until that occurs. 6124 * 6125 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 6126 * for their async templates to be resolved. 6127 * 6128 * 6129 * ### Transclusion 6130 * 6131 * Transclusion is the process of extracting a collection of DOM element from one part of the DOM and 6132 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 6133 * scope from where they were taken. 6134 * 6135 * Transclusion is used (often with {@link ngTransclude}) to insert the 6136 * original contents of a directive's element into a specified place in the template of the directive. 6137 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 6138 * content has access to the properties on the scope from which it was taken, even if the directive 6139 * has isolated scope. 6140 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 6141 * 6142 * This makes it possible for the widget to have private state for its template, while the transcluded 6143 * content has access to its originating scope. 6144 * 6145 * <div class="alert alert-warning"> 6146 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 6147 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 6148 * Testing Transclusion Directives}. 6149 * </div> 6150 * 6151 * #### Transclusion Functions 6152 * 6153 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 6154 * function** to the directive's `link` function and `controller`. This transclusion function is a special 6155 * **linking function** that will return the compiled contents linked to a new transclusion scope. 6156 * 6157 * <div class="alert alert-info"> 6158 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 6159 * ngTransclude will deal with it for us. 6160 * </div> 6161 * 6162 * If you want to manually control the insertion and removal of the transcluded content in your directive 6163 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery 6164 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 6165 * 6166 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 6167 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 6168 * content and the `scope` is the newly created transclusion scope, to which the clone is bound. 6169 * 6170 * <div class="alert alert-info"> 6171 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function 6172 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 6173 * </div> 6174 * 6175 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 6176 * attach function**: 6177 * 6178 * ```js 6179 * var transcludedContent, transclusionScope; 6180 * 6181 * $transclude(function(clone, scope) { 6182 * element.append(clone); 6183 * transcludedContent = clone; 6184 * transclusionScope = scope; 6185 * }); 6186 * ``` 6187 * 6188 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 6189 * associated transclusion scope: 6190 * 6191 * ```js 6192 * transcludedContent.remove(); 6193 * transclusionScope.$destroy(); 6194 * ``` 6195 * 6196 * <div class="alert alert-info"> 6197 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 6198 * (by calling the transclude function to get the DOM and and calling `element.remove()` to remove it), 6199 * then you are also responsible for calling `$destroy` on the transclusion scope. 6200 * </div> 6201 * 6202 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 6203 * automatically destroy their transluded clones as necessary so you do not need to worry about this if 6204 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 6205 * 6206 * 6207 * #### Transclusion Scopes 6208 * 6209 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 6210 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 6211 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 6212 * was taken. 6213 * 6214 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 6215 * like this: 6216 * 6217 * ```html 6218 * <div ng-app> 6219 * <div isolate> 6220 * <div transclusion>
6221 * </div> 6222 * </div> 6223 * </div> 6224 * ``` 6225 * 6226 * The `$parent` scope hierarchy will look like this: 6227 * 6228 * ``` 6229 * - $rootScope 6230 * - isolate 6231 * - transclusion 6232 * ``` 6233 * 6234 * but the scopes will inherit prototypically from different scopes to their `$parent`. 6235 * 6236 * ``` 6237 * - $rootScope 6238 * - transclusion 6239 * - isolate 6240 * ``` 6241 * 6242 * 6243 * ### Attributes 6244 * 6245 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 6246 * `link()` or `compile()` functions. It has a variety of uses. 6247 * 6248 * accessing *Normalized attribute names:* 6249 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 6250 * the attributes object allows for normalized access to 6251 * the attributes. 6252 * 6253 * * *Directive inter-communication:* All directives share the same instance of the attributes 6254 * object which allows the directives to use the attributes object as inter directive 6255 * communication. 6256 * 6257 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 6258 * allowing other directives to read the interpolated value. 6259 * 6260 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 6261 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 6262 * the only way to easily get the actual value because during the linking phase the interpolation 6263 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 6264 * 6265 * ```js 6266 * function linkingFn(scope, elm, attrs, ctrl) { 6267 * // get the attribute value 6268 * console.log(attrs.ngModel); 6269 * 6270 * // change the attribute 6271 * attrs.$set('ngModel', 'new value'); 6272 * 6273 * // observe changes to interpolated attribute 6274 * attrs.$observe('ngModel', function(value) { 6275 * console.log('ngModel has changed value to ' + value); 6276 * }); 6277 * } 6278 * ``` 6279 * 6280 * ## Example 6281 * 6282 * <div class="alert alert-warning"> 6283 * **Note**: Typically directives are registered with `module.directive`. The example below is 6284 * to illustrate how `$compile` works. 6285 * </div> 6286 * 6287 <example module="compileExample"> 6288 <file name="index.html"> 6289 <script> 6290 angular.module('compileExample', [], function($compileProvider) { 6291 // configure new 'compile' directive by passing a directive 6292 // factory function. The factory function injects the '$compile' 6293 $compileProvider.directive('compile', function($compile) { 6294 // directive factory creates a link function 6295 return function(scope, element, attrs) { 6296 scope.$watch( 6297 function(scope) { 6298 // watch the 'compile' expression for changes 6299 return scope.$eval(attrs.compile); 6300 }, 6301 function(value) { 6302 // when the 'compile' expression changes 6303 // assign it into the current DOM 6304 element.html(value); 6305 6306 // compile the new DOM and link it to the current 6307 // scope. 6308 // NOTE: we only compile .childNodes so that 6309 // we don't get into infinite loop compiling ourselves 6310 $compile(element.contents())(scope); 6311 } 6312 ); 6313 }; 6314 }); 6315 }) 6316 .controller('GreeterController', ['$scope', function($scope) { 6317 $scope.name = 'Angular'; 6318 $scope.html = 'Hello {{name}}'; 6319 }]); 6320 </script> 6321 <div ng-controller="GreeterController"> 6322 <input ng-model="name"> <br> 6323 <textarea ng-model="html"></textarea> <br> 6324 <div compile="html"></div> 6325 </div> 6326 </file> 6327 <file name="protractor.js" type="protractor"> 6328 it('should auto compile', function() { 6329 var textarea = $('textarea'); 6330 var output = $('div[compile]'); 6331 // The initial state reads 'Hello Angular'. 6332 expect(output.getText()).toBe('Hello Angular'); 6333 textarea.clear(); 6334 textarea.sendKeys('{{name}}!'); 6335 expect(output.getText()).toBe('Angular!'); 6336 }); 6337 </file> 6338 </example> 6339 6340 * 6341 * 6342 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 6343 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 6344 * 6345 * <div class="alert alert-error"> 6346 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 6347 * e.g. will not use the right outer scope. Please pass the transclude function as a 6348 * `parentBoundTranscludeFn` to the link function instead. 6349 * </div> 6350 * 6351 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
6352 * root element(s), not their children) 6353 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 6354 * (a DOM element/tree) to a scope. Where: 6355 * 6356 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 6357 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 6358 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 6359 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 6360 * called as: <br> `cloneAttachFn(clonedElement, scope)` where: 6361 * 6362 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 6363 * * `scope` - is the current scope with which the linking function is working with. 6364 * 6365 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 6366 * keys may be used to control linking behavior: 6367 * 6368 * * `parentBoundTranscludeFn` - the transclude function made available to 6369 * directives; if given, it will be passed through to the link functions of 6370 * directives found in `element` during compilation. 6371 * * `transcludeControllers` - an object hash with keys that map controller names 6372 * to controller instances; if given, it will make the controllers 6373 * available to directives. 6374 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 6375 * the cloned elements; only needed for transcludes that are allowed to contain non html 6376 * elements (e.g. SVG elements). See also the directive.controller property. 6377 * 6378 * Calling the linking function returns the element of the template. It is either the original 6379 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 6380 * 6381 * After linking the view is not updated until after a call to $digest which typically is done by 6382 * Angular automatically. 6383 * 6384 * If you need access to the bound view, there are two ways to do it: 6385 * 6386 * - If you are not asking the linking function to clone the template, create the DOM element(s) 6387 * before you send them to the compiler and keep this reference around. 6388 * ```js 6389 * var element = $compile('<p>{{total}}</p>')(scope); 6390 * ``` 6391 * 6392 * - if on the other hand, you need the element to be cloned, the view reference from the original 6393 * example would not point to the clone, but rather to the original template that was cloned. In 6394 * this case, you can access the clone via the cloneAttachFn: 6395 * ```js 6396 * var templateElement = angular.element('<p>{{total}}</p>'), 6397 * scope = ....; 6398 * 6399 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 6400 * //attach the clone to DOM document at the right place 6401 * }); 6402 * 6403 * //now we have reference to the cloned DOM via `clonedElement` 6404 * ``` 6405 * 6406 * 6407 * For information on how the compiler works, see the 6408 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 6409 */ 6410 6411var $compileMinErr = minErr('$compile'); 6412 6413/** 6414 * @ngdoc provider 6415 * @name $compileProvider 6416 * 6417 * @description 6418 */ 6419$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 6420function $CompileProvider($provide, $$sanitizeUriProvider) { 6421 var hasDirectives = {}, 6422 Suffix = 'Directive', 6423 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, 6424 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, 6425 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 6426 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 6427 6428 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 6429 // The assumption is that future DOM event attribute names will begin with 6430 // 'on' and be composed of only English letters. 6431 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 6432 6433 function parseIsolateBindings(scope, directiveName) { 6434 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; 6435 6436 var bindings = {}; 6437 6438 forEach(scope, function(definition, scopeName) { 6439 var match = definition.match(LOCAL_REGEXP); 6440 6441 if (!match) { 6442 throw $compileMinErr('iscp', 6443 "Invalid isolate scope definition for directive '{0}'." + 6444 " Definition: {... {1}: '{2}' ...}", 6445 directiveName, scopeName, definition); 6446 } 6447 6448 bindings[scopeName] = { 6449 mode: match[1][0], 6450 collection: match[2] === '*', 6451 optional: match[3] === '?', 6452 attrName: match[4] || scopeName 6453 }; 6454 }); 6455 6456 return bindings; 6457 } 6458 6459 /** 6460 * @ngdoc method 6461 * @name $compileProvider#directive 6462 * @kind function 6463 * 6464 * @description 6465 * Register a new directive with the compiler. 6466 * 6467 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 6468 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 6469 * names and the values are the factories. 6470 * @param {Function|Array} directiveFactory An injectable directive factory function. See 6471 * {@link guide/directive} for more info. 6472 * @returns {ng.$compileProvider} Self for chaining. 6473 */ 6474 this.directive = function registerDirective(name, directiveFactory) { 6475 assertNotHasOwnProperty(name, 'directive'); 6476 if (isString(name)) { 6477 assertArg(directiveFactory, 'directiveFactory'); 6478 if (!hasDirectives.hasOwnProperty(name)) { 6479 hasDirectives[name] = []; 6480 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 6481 function($injector, $exceptionHandler) { 6482 var directives = [];
vendor: 10,281 bytes, lines 6483-6753
6483 forEach(hasDirectives[name], function(directiveFactory, index) { 6484 try { 6485 var directive = $injector.invoke(directiveFactory); 6486 if (isFunction(directive)) { 6487 directive = { compile: valueFn(directive) }; 6488 } else if (!directive.compile && directive.link) { 6489 directive.compile = valueFn(directive.link); 6490 } 6491 directive.priority = directive.priority || 0; 6492 directive.index = index; 6493 directive.name = directive.name || name; 6494 directive.require = directive.require || (directive.controller && directive.name); 6495 directive.restrict = directive.restrict || 'EA'; 6496 if (isObject(directive.scope)) { 6497 directive.$$isolateBindings = parseIsolateBindings(directive.scope, directive.name); 6498 } 6499 directives.push(directive); 6500 } catch (e) { 6501 $exceptionHandler(e); 6502 } 6503 }); 6504 return directives; 6505 }]); 6506 } 6507 hasDirectives[name].push(directiveFactory); 6508 } else { 6509 forEach(name, reverseParams(registerDirective)); 6510 } 6511 return this; 6512 }; 6513 6514 6515 /** 6516 * @ngdoc method 6517 * @name $compileProvider#aHrefSanitizationWhitelist 6518 * @kind function 6519 * 6520 * @description 6521 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 6522 * urls during a[href] sanitization. 6523 * 6524 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 6525 * 6526 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 6527 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 6528 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 6529 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 6530 * 6531 * @param {RegExp=} regexp New regexp to whitelist urls with. 6532 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 6533 * chaining otherwise. 6534 */ 6535 this.aHrefSanitizationWhitelist = function(regexp) { 6536 if (isDefined(regexp)) { 6537 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 6538 return this; 6539 } else { 6540 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 6541 } 6542 }; 6543 6544 6545 /** 6546 * @ngdoc method 6547 * @name $compileProvider#imgSrcSanitizationWhitelist 6548 * @kind function 6549 * 6550 * @description 6551 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 6552 * urls during img[src] sanitization. 6553 * 6554 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 6555 * 6556 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 6557 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 6558 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 6559 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 6560 * 6561 * @param {RegExp=} regexp New regexp to whitelist urls with. 6562 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 6563 * chaining otherwise. 6564 */ 6565 this.imgSrcSanitizationWhitelist = function(regexp) { 6566 if (isDefined(regexp)) { 6567 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 6568 return this; 6569 } else { 6570 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 6571 } 6572 }; 6573 6574 /** 6575 * @ngdoc method 6576 * @name $compileProvider#debugInfoEnabled 6577 * 6578 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 6579 * current debugInfoEnabled state 6580 * @returns {*} current value if used as getter or itself (chaining) if used as setter 6581 * 6582 * @kind function 6583 * 6584 * @description 6585 * Call this method to enable/disable various debug runtime information in the compiler such as adding 6586 * binding information and a reference to the current scope on to DOM elements. 6587 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 6588 * * `ng-binding` CSS class 6589 * * `$binding` data property containing an array of the binding expressions 6590 * 6591 * You may want to disable this in production for a significant performance boost. See 6592 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 6593 * 6594 * The default value is true. 6595 */ 6596 var debugInfoEnabled = true; 6597 this.debugInfoEnabled = function(enabled) { 6598 if (isDefined(enabled)) { 6599 debugInfoEnabled = enabled; 6600 return this; 6601 } 6602 return debugInfoEnabled; 6603 }; 6604 6605 this.$get = [ 6606 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 6607 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 6608 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 6609 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 6610 6611 var Attributes = function(element, attributesToCopy) { 6612 if (attributesToCopy) { 6613 var keys = Object.keys(attributesToCopy); 6614 var i, l, key; 6615 6616 for (i = 0, l = keys.length; i < l; i++) { 6617 key = keys[i]; 6618 this[key] = attributesToCopy[key]; 6619 } 6620 } else { 6621 this.$attr = {}; 6622 } 6623 6624 this.$$element = element; 6625 }; 6626 6627 Attributes.prototype = { 6628 /** 6629 * @ngdoc method 6630 * @name $compile.directive.Attributes#$normalize 6631 * @kind function 6632 * 6633 * @description 6634 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 6635 * `data-`) to its normalized, camelCase form. 6636 * 6637 * Also there is special case for Moz prefix starting with upper case letter. 6638 * 6639 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 6640 * 6641 * @param {string} name Name to normalize 6642 */ 6643 $normalize: directiveNormalize, 6644 6645 6646 /** 6647 * @ngdoc method 6648 * @name $compile.directive.Attributes#$addClass 6649 * @kind function 6650 * 6651 * @description 6652 * Adds the CSS class value specified by the classVal parameter to the element. If animations 6653 * are enabled then an animation will be triggered for the class addition. 6654 * 6655 * @param {string} classVal The className value that will be added to the element 6656 */ 6657 $addClass: function(classVal) { 6658 if (classVal && classVal.length > 0) { 6659 $animate.addClass(this.$$element, classVal); 6660 } 6661 }, 6662 6663 /** 6664 * @ngdoc method 6665 * @name $compile.directive.Attributes#$removeClass 6666 * @kind function 6667 * 6668 * @description 6669 * Removes the CSS class value specified by the classVal parameter from the element. If 6670 * animations are enabled then an animation will be triggered for the class removal. 6671 * 6672 * @param {string} classVal The className value that will be removed from the element 6673 */ 6674 $removeClass: function(classVal) { 6675 if (classVal && classVal.length > 0) { 6676 $animate.removeClass(this.$$element, classVal); 6677 } 6678 }, 6679 6680 /** 6681 * @ngdoc method 6682 * @name $compile.directive.Attributes#$updateClass 6683 * @kind function 6684 * 6685 * @description 6686 * Adds and removes the appropriate CSS class values to the element based on the difference 6687 * between the new and old CSS class values (specified as newClasses and oldClasses). 6688 * 6689 * @param {string} newClasses The current CSS className value 6690 * @param {string} oldClasses The former CSS className value 6691 */ 6692 $updateClass: function(newClasses, oldClasses) { 6693 var toAdd = tokenDifference(newClasses, oldClasses); 6694 if (toAdd && toAdd.length) { 6695 $animate.addClass(this.$$element, toAdd); 6696 } 6697 6698 var toRemove = tokenDifference(oldClasses, newClasses); 6699 if (toRemove && toRemove.length) { 6700 $animate.removeClass(this.$$element, toRemove); 6701 } 6702 }, 6703 6704 /** 6705 * Set a normalized attribute on the element in a way such that all directives 6706 * can share the attribute. This function properly handles boolean attributes. 6707 * @param {string} key Normalized key. (ie ngAttribute) 6708 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 6709 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 6710 * Defaults to true. 6711 * @param {string=} attrName Optional none normalized name. Defaults to key. 6712 */ 6713 $set: function(key, value, writeAttr, attrName) { 6714 // TODO: decide whether or not to throw an error if "class" 6715 //is set through this function since it may cause $updateClass to 6716 //become unstable. 6717 6718 var node = this.$$element[0], 6719 booleanKey = getBooleanAttrName(node, key), 6720 aliasedKey = getAliasedAttrName(node, key), 6721 observer = key, 6722 nodeName; 6723 6724 if (booleanKey) { 6725 this.$$element.prop(key, value); 6726 attrName = booleanKey; 6727 } else if (aliasedKey) { 6728 this[aliasedKey] = value; 6729 observer = aliasedKey; 6730 } 6731 6732 this[key] = value; 6733 6734 // translate normalized key to actual key 6735 if (attrName) { 6736 this.$attr[key] = attrName; 6737 } else { 6738 attrName = this.$attr[key]; 6739 if (!attrName) { 6740 this.$attr[key] = attrName = snake_case(key, '-'); 6741 } 6742 } 6743 6744 nodeName = nodeName_(this.$$element); 6745 6746 if ((nodeName === 'a' && key === 'href') || 6747 (nodeName === 'img' && key === 'src')) { 6748 // sanitize a[href] and img[src] values 6749 this[key] = value = $$sanitizeUri(value, key === 'src'); 6750 } else if (nodeName === 'img' && key === 'srcset') { 6751 // sanitize img[srcset] values 6752 var result = ""; 6753
6754 // first check if there are spaces because it's not the same pattern 6755 var trimmedSrcset = trim(value); 6756 // ( 999x ,| 999w ,| ,|, ) 6757 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 6758 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 6759 6760 // split srcset into tuple of uri and descriptor except for the last item 6761 var rawUris = trimmedSrcset.split(pattern); 6762 6763 // for each tuples 6764 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 6765 for (var i = 0; i < nbrUrisWith2parts; i++) { 6766 var innerIdx = i * 2; 6767 // sanitize the uri 6768 result += $$sanitizeUri(trim(rawUris[innerIdx]), true); 6769 // add the descriptor 6770 result += (" " + trim(rawUris[innerIdx + 1])); 6771 } 6772 6773 // split the last item into uri and descriptor 6774 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 6775 6776 // sanitize the last uri 6777 result += $$sanitizeUri(trim(lastTuple[0]), true); 6778 6779 // and add the last descriptor if any 6780 if (lastTuple.length === 2) { 6781 result += (" " + trim(lastTuple[1])); 6782 } 6783 this[key] = value = result; 6784 } 6785 6786 if (writeAttr !== false) { 6787 if (value === null || value === undefined) { 6788 this.$$element.removeAttr(attrName); 6789 } else { 6790 this.$$element.attr(attrName, value); 6791 } 6792 } 6793 6794 // fire observers 6795 var $$observers = this.$$observers; 6796 $$observers && forEach($$observers[observer], function(fn) { 6797 try { 6798 fn(value); 6799 } catch (e) { 6800 $exceptionHandler(e); 6801 } 6802 }); 6803 }, 6804 6805 6806 /** 6807 * @ngdoc method 6808 * @name $compile.directive.Attributes#$observe 6809 * @kind function 6810 * 6811 * @description 6812 * Observes an interpolated attribute. 6813 * 6814 * The observer function will be invoked once during the next `$digest` following 6815 * compilation. The observer is then invoked whenever the interpolated value 6816 * changes. 6817 * 6818 * @param {string} key Normalized key. (ie ngAttribute) . 6819 * @param {function(interpolatedValue)} fn Function that will be called whenever 6820 the interpolated value of the attribute changes. 6821 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info. 6822 * @returns {function()} Returns a deregistration function for this observer. 6823 */ 6824 $observe: function(key, fn) { 6825 var attrs = this, 6826 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 6827 listeners = ($$observers[key] || ($$observers[key] = [])); 6828 6829 listeners.push(fn); 6830 $rootScope.$evalAsync(function() { 6831 if (!listeners.$$inter && attrs.hasOwnProperty(key)) { 6832 // no one registered attribute interpolation function, so lets call it manually 6833 fn(attrs[key]); 6834 } 6835 }); 6836 6837 return function() { 6838 arrayRemove(listeners, fn); 6839 }; 6840 } 6841 }; 6842 6843 6844 function safeAddClass($element, className) { 6845 try { 6846 $element.addClass(className); 6847 } catch (e) { 6848 // ignore, since it means that we are trying to set class on 6849 // SVG element, where class name is read-only. 6850 } 6851 } 6852 6853 6854 var startSymbol = $interpolate.startSymbol(), 6855 endSymbol = $interpolate.endSymbol(), 6856 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 6857 ? identity 6858 : function denormalizeTemplate(template) { 6859 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 6860 }, 6861 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 6862 6863 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 6864 var bindings = $element.data('$binding') || []; 6865 6866 if (isArray(binding)) { 6867 bindings = bindings.concat(binding); 6868 } else { 6869 bindings.push(binding); 6870 } 6871 6872 $element.data('$binding', bindings); 6873 } : noop; 6874 6875 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 6876 safeAddClass($element, 'ng-binding'); 6877 } : noop; 6878 6879 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 6880 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope')
6880: '$scope'; 6881 $element.data(dataName, scope); 6882 } : noop; 6883 6884 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 6885 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 6886 } : noop; 6887 6888 return compile; 6889 6890 //================================ 6891 6892 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 6893 previousCompileContext) { 6894 if (!($compileNodes instanceof jqLite)) { 6895 // jquery always rewraps, whereas we need to preserve the original selector so that we can 6896 // modify it. 6897 $compileNodes = jqLite($compileNodes); 6898 } 6899 // We can not compile top level text elements since text nodes can be merged and we will 6900 // not be able to attach scope data to them, so we will wrap them in <span> 6901 forEach($compileNodes, function(node, index) { 6902 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) { 6903 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0]; 6904 } 6905 }); 6906 var compositeLinkFn = 6907 compileNodes($compileNodes, transcludeFn, $compileNodes, 6908 maxPriority, ignoreDirective, previousCompileContext); 6909 compile.$$addScopeClass($compileNodes); 6910 var namespace = null; 6911 return function publicLinkFn(scope, cloneConnectFn, options) { 6912 assertArg(scope, 'scope'); 6913 6914 options = options || {}; 6915 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 6916 transcludeControllers = options.transcludeControllers, 6917 futureParentElement = options.futureParentElement; 6918 6919 // When `parentBoundTranscludeFn` is passed, it is a 6920 // `controllersBoundTransclude` function (it was previously passed 6921 // as `transclude` to directive.link) so we must unwrap it to get 6922 // its `boundTranscludeFn` 6923 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 6924 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 6925 } 6926 6927 if (!namespace) { 6928 namespace = detectNamespaceForChildElements(futureParentElement); 6929 } 6930 var $linkNode; 6931 if (namespace !== 'html') { 6932 // When using a directive with replace:true and templateUrl the $compileNodes 6933 // (or a child element inside of them) 6934 // might change, so we need to recreate the namespace adapted compileNodes 6935 // for call to the link function. 6936 // Note: This will already clone the nodes... 6937 $linkNode = jqLite( 6938 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) 6939 ); 6940 } else if (cloneConnectFn) { 6941 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart 6942 // and sometimes changes the structure of the DOM. 6943 $linkNode = JQLitePrototype.clone.call($compileNodes); 6944 } else { 6945 $linkNode = $compileNodes; 6946 } 6947 6948 if (transcludeControllers) { 6949 for (var controllerName in transcludeControllers) { 6950 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 6951 } 6952 } 6953 6954 compile.$$addScopeInfo($linkNode, scope); 6955 6956 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 6957 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 6958 return $linkNode; 6959 }; 6960 } 6961 6962 function detectNamespaceForChildElements(parentElement) { 6963 // TODO: Make this detect MathML as well... 6964 var node = parentElement && parentElement[0]; 6965 if (!node) { 6966 return 'html'; 6967 } else { 6968 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html'; 6969 } 6970 } 6971 6972 /** 6973 * Compile function matches each node in nodeList against the directives. Once all directives 6974 * for a particular node are collected their compile functions are executed. The compile 6975 * functions return values - the linking functions - are combined into a composite linking 6976 * function, which is the a linking function for the node. 6977 * 6978 * @param {NodeList} nodeList an array of nodes or NodeList to compile 6979 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 6980 * scope argument is auto-generated to the new child of the transcluded parent scope. 6981 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 6982 * the rootElement must be set the jqLite collection of the compile root. This is
vendor: 5,956 bytes, lines 6983-7131
6983 * needed so that the jqLite collection items can be replaced with widgets. 6984 * @param {number=} maxPriority Max directive priority. 6985 * @returns {Function} A composite linking function of all of the matched directives or null. 6986 */ 6987 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 6988 previousCompileContext) { 6989 var linkFns = [], 6990 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 6991 6992 for (var i = 0; i < nodeList.length; i++) { 6993 attrs = new Attributes(); 6994 6995 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 6996 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 6997 ignoreDirective); 6998 6999 nodeLinkFn = (directives.length) 7000 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 7001 null, [], [], previousCompileContext) 7002 : null; 7003 7004 if (nodeLinkFn && nodeLinkFn.scope) { 7005 compile.$$addScopeClass(attrs.$$element); 7006 } 7007 7008 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 7009 !(childNodes = nodeList[i].childNodes) || 7010 !childNodes.length) 7011 ? null 7012 : compileNodes(childNodes, 7013 nodeLinkFn ? ( 7014 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 7015 && nodeLinkFn.transclude) : transcludeFn); 7016 7017 if (nodeLinkFn || childLinkFn) { 7018 linkFns.push(i, nodeLinkFn, childLinkFn); 7019 linkFnFound = true; 7020 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 7021 } 7022 7023 //use the previous context only for the first element in the virtual group 7024 previousCompileContext = null; 7025 } 7026 7027 // return a linking function if we have found anything, null otherwise 7028 return linkFnFound ? compositeLinkFn : null; 7029 7030 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 7031 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 7032 var stableNodeList; 7033 7034 7035 if (nodeLinkFnFound) { 7036 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 7037 // offsets don't get screwed up 7038 var nodeListLength = nodeList.length; 7039 stableNodeList = new Array(nodeListLength); 7040 7041 // create a sparse array by only copying the elements which have a linkFn 7042 for (i = 0; i < linkFns.length; i+=3) { 7043 idx = linkFns[i]; 7044 stableNodeList[idx] = nodeList[idx]; 7045 } 7046 } else { 7047 stableNodeList = nodeList; 7048 } 7049 7050 for (i = 0, ii = linkFns.length; i < ii;) { 7051 node = stableNodeList[linkFns[i++]]; 7052 nodeLinkFn = linkFns[i++]; 7053 childLinkFn = linkFns[i++]; 7054 7055 if (nodeLinkFn) { 7056 if (nodeLinkFn.scope) { 7057 childScope = scope.$new(); 7058 compile.$$addScopeInfo(jqLite(node), childScope); 7059 } else { 7060 childScope = scope; 7061 } 7062 7063 if (nodeLinkFn.transcludeOnThisElement) { 7064 childBoundTranscludeFn = createBoundTranscludeFn( 7065 scope, nodeLinkFn.transclude, parentBoundTranscludeFn, 7066 nodeLinkFn.elementTranscludeOnThisElement); 7067 7068 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 7069 childBoundTranscludeFn = parentBoundTranscludeFn; 7070 7071 } else if (!parentBoundTranscludeFn && transcludeFn) { 7072 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 7073 7074 } else { 7075 childBoundTranscludeFn = null; 7076 } 7077 7078 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn); 7079 7080 } else if (childLinkFn) { 7081 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 7082 } 7083 } 7084 } 7085 } 7086 7087 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn, elementTransclusion) { 7088 7089 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 7090 7091 if (!transcludedScope) { 7092 transcludedScope = scope.$new(false, containingScope); 7093 transcludedScope.$$transcluded = true; 7094 } 7095 7096 return transcludeFn(transcludedScope, cloneFn, { 7097 parentBoundTranscludeFn: previousBoundTranscludeFn, 7098 transcludeControllers: controllers, 7099 futureParentElement: futureParentElement 7100 }); 7101 }; 7102 7103 return boundTranscludeFn; 7104 } 7105 7106 /** 7107 * Looks for directives on the given node and adds them to the directive collection which is 7108 * sorted. 7109 * 7110 * @param node Node to search. 7111 * @param directives An array to which the directives are added to. This array is sorted before 7112 * the function returns. 7113 * @param attrs The shared attrs object which is used to populate the normalized attributes. 7114 * @param {number=} maxPriority Max directive priority. 7115 */ 7116 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 7117 var nodeType = node.nodeType, 7118 attrsMap = attrs.$attr, 7119 match, 7120 className; 7121 7122 switch (nodeType) { 7123 case NODE_TYPE_ELEMENT: /* Element */ 7124 // use the node name: <directive> 7125 addDirective(directives, 7126 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); 7127 7128 // iterate over the attributes 7129 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 7130 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 7131 var attrStartName = false;
7132 var attrEndName = false; 7133 7134 attr = nAttrs[j]; 7135 name = attr.name; 7136 value = trim(attr.value); 7137 7138 // support ngAttr attribute binding 7139 ngAttrName = directiveNormalize(name); 7140 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 7141 name = name.replace(PREFIX_REGEXP, '') 7142 .substr(8).replace(/_(.)/g, function(match, letter) { 7143 return letter.toUpperCase(); 7144 }); 7145 } 7146 7147 var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); 7148 if (directiveIsMultiElement(directiveNName)) { 7149 if (ngAttrName === directiveNName + 'Start') { 7150 attrStartName = name; 7151 attrEndName = name.substr(0, name.length - 5) + 'end'; 7152 name = name.substr(0, name.length - 6); 7153 } 7154 } 7155 7156 nName = directiveNormalize(name.toLowerCase()); 7157 attrsMap[nName] = name; 7158 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 7159 attrs[nName] = value; 7160 if (getBooleanAttrName(node, nName)) { 7161 attrs[nName] = true; // presence means true 7162 } 7163 } 7164 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 7165 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 7166 attrEndName); 7167 } 7168 7169 // use class as directive 7170 className = node.className; 7171 if (isObject(className)) { 7172 // Maybe SVGAnimatedString 7173 className = className.animVal; 7174 } 7175 if (isString(className) && className !== '') { 7176 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 7177 nName = directiveNormalize(match[2]); 7178 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 7179 attrs[nName] = trim(match[3]); 7180 } 7181 className = className.substr(match.index + match[0].length); 7182 } 7183 } 7184 break; 7185 case NODE_TYPE_TEXT: /* Text Node */ 7186 addTextInterpolateDirective(directives, node.nodeValue); 7187 break; 7188 case NODE_TYPE_COMMENT: /* Comment */ 7189 try { 7190 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 7191 if (match) { 7192 nName = directiveNormalize(match[1]); 7193 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 7194 attrs[nName] = trim(match[2]); 7195 } 7196 } 7197 } catch (e) { 7198 // turns out that under some circumstances IE9 throws errors when one attempts to read 7199 // comment's node value. 7200 // Just ignore it and continue. (Can't seem to reproduce in test case.) 7201 } 7202 break; 7203 } 7204 7205 directives.sort(byPriority); 7206 return directives; 7207 } 7208 7209 /** 7210 * Given a node with an directive-start it collects all of the siblings until it finds 7211 * directive-end. 7212 * @param node 7213 * @param attrStart 7214 * @param attrEnd 7215 * @returns {*} 7216 */ 7217 function groupScan(node, attrStart, attrEnd) { 7218 var nodes = []; 7219 var depth = 0; 7220 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 7221 do { 7222 if (!node) { 7223 throw $compileMinErr('uterdir', 7224 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 7225 attrStart, attrEnd); 7226 } 7227 if (node.nodeType == NODE_TYPE_ELEMENT) { 7228 if (node.hasAttribute(attrStart)) depth++; 7229 if (node.hasAttribute(attrEnd)) depth--; 7230 } 7231 nodes.push(node); 7232 node = node.nextSibling; 7233 } while (depth > 0); 7234 } else { 7235 nodes.push(node); 7236 } 7237 7238 return jqLite(nodes); 7239 } 7240 7241 /** 7242 * Wrapper for linking function which converts normal linking function into a grouped 7243 * linking function. 7244 * @param linkFn 7245 * @param attrStart 7246 * @param attrEnd 7247 * @returns {Function} 7248 */ 7249 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 7250 return function(scope, element, attrs, controllers, transcludeFn) { 7251 element = groupScan(element[0], attrStart, attrEnd); 7252 return linkFn(scope, element, attrs, controllers, transcludeFn); 7253 }; 7254 } 7255 7256 /** 7257 * Once the directives have been collected, their compile functions are executed. This method 7258 * is responsible for inlining directive templates as well as terminating the application 7259 * of the directives if the terminal directive has been reached. 7260 * 7261 * @param {Array} directives Array of collected directives to execute their compile function. 7262 * this needs to be pre-sorted by priority order. 7263 * @param {Node} compileNode The raw DOM node to apply the compile functions to 7264 * @param {Object} templateAttrs The shared attribute function 7265 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 7266 * scope argument is auto-generated to the new 7267 * child of the transcluded parent scope. 7268 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 7269 * argument has the root jqLite array so that we can replace nodes 7270 * on it. 7271 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 7272 * compiling the transclusion. 7273 * @param {Array.<Function>} preLinkFns 7274 * @param {Array.<Function>} postLinkFns 7275 * @param {Object} previousCompileContext Context used for previous compilation of the current 7276 * node 7277 * @returns {Function} linkFn 7278 */ 7279 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 7280 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 7281 previousCompileContext) { 7282 previousCompileContext = previousCompileContext || {}; 7283 7284 var terminalPriority = -Number.MAX_VALUE, 7285 newScopeDirective, 7286 controllerDirectives = previousCompileContext.controllerDirectives, 7287 controllers, 7288 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 7289 templateDirective = previousCompileContext.templateDirective, 7290 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 7291 hasTranscludeDirective = false, 7292 hasTemplate = false, 7293 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 7294 $compileNode = templateAttrs.$$element = jqLite(compileNode), 7295 directive, 7296 directiveName, 7297 $template, 7298 replaceDirective = originalReplaceDirective, 7299 childTranscludeFn = transcludeFn, 7300 linkFn, 7301 directiveValue; 7302 7303 // executes all directives on the current element 7304 for (var i = 0, ii = directives.length; i < ii; i++) { 7305 directive = directives[i]; 7306 var attrStart = directive.$$start; 7307 var attrEnd = directive.$$end; 7308 7309 // collect multiblock sections 7310 if (attrStart) { 7311 $compileNode = groupScan(compileNode, attrStart, attrEnd); 7312 } 7313 $template = undefined; 7314 7315 if (terminalPriority > directive.priority) { 7316 break; // prevent further processing of directives 7317 } 7318 7319 if (directiveValue = directive.scope) { 7320 7321 // skip the check for directives with async templates, we'll check the derived sync 7322 // directive when the template arrives 7323 if (!directive.templateUrl) { 7324 if (isObject(directiveValue)) { 7325 // This directive is trying to add an isolated scope. 7326 // Check that there is no scope of any kind already 7327 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 7328 directive, $compileNode); 7329 newIsolateScopeDirective = directive; 7330 } else { 7331 // This directive is trying to add a child scope. 7332 // Check that there is no isolated scope already 7333 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 7334 $compileNode); 7335 } 7336 } 7337 7338 newScopeDirective = newScopeDirective || directive; 7339 } 7340 7341 directiveName = directive.name; 7342 7343 if (!directive.templateUrl && directive.controller) { 7344 directiveValue = directive.controller; 7345 controllerDirectives = controllerDirectives || {}; 7346 assertNoDuplicate("'" + directiveName + "' controller", 7347 controllerDirectives[directiveName], directive, $compileNode); 7348 controllerDirectives[directiveName] = directive; 7349 } 7350 7351 if (directiveValue = directive.transclude) { 7352 hasTranscludeDirective = true; 7353 7354 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 7355 // This option should only be used by directives that know how to safely handle element transclusion, 7356 // where the transcluded nodes are added or replaced after linking. 7357 if (!directive.$$tlb) { 7358 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 7359 nonTlbTranscludeDirective = directive; 7360 } 7361 7362 if (directiveValue == 'element') { 7363 hasElementTranscludeDirective = true; 7364 terminalPriority = directive.priority; 7365 $template = $compileNode; 7366 $compileNode = templateAttrs.$$element = 7367 jqLite(document.createComment(' ' + directiveName + ': ' + 7368 templateAttrs[directiveName] + ' ')); 7369 compileNode = $compileNode[0]; 7370 replaceWith(jqCollection, sliceArgs($template), compileNode); 7371 7372 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 7373 replaceDirective && replaceDirective.name, { 7374 // Don't pass in: 7375 // - controllerDirectives - otherwise we'll create duplicates controllers 7376 // - newIsolateScopeDirective or templateDirective - comb
vendor: 4,751 bytes, lines 7376-7484
7376ining templates with 7377 // element transclusion doesn't make sense. 7378 // 7379 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 7380 // on the same element more than once. 7381 nonTlbTranscludeDirective: nonTlbTranscludeDirective 7382 }); 7383 } else { 7384 $template = jqLite(jqLiteClone(compileNode)).contents(); 7385 $compileNode.empty(); // clear contents 7386 childTranscludeFn = compile($template, transcludeFn); 7387 } 7388 } 7389 7390 if (directive.template) { 7391 hasTemplate = true; 7392 assertNoDuplicate('template', templateDirective, directive, $compileNode); 7393 templateDirective = directive; 7394 7395 directiveValue = (isFunction(directive.template)) 7396 ? directive.template($compileNode, templateAttrs) 7397 : directive.template; 7398 7399 directiveValue = denormalizeTemplate(directiveValue); 7400 7401 if (directive.replace) { 7402 replaceDirective = directive; 7403 if (jqLiteIsTextNode(directiveValue)) { 7404 $template = []; 7405 } else { 7406 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 7407 } 7408 compileNode = $template[0]; 7409 7410 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 7411 throw $compileMinErr('tplrt', 7412 "Template for directive '{0}' must have exactly one root element. {1}", 7413 directiveName, ''); 7414 } 7415 7416 replaceWith(jqCollection, $compileNode, compileNode); 7417 7418 var newTemplateAttrs = {$attr: {}}; 7419 7420 // combine directives from the original node and from the template: 7421 // - take the array of directives for this element 7422 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 7423 // - collect directives from the template and sort them by priority 7424 // - combine directives as: processed + template + unprocessed 7425 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 7426 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 7427 7428 if (newIsolateScopeDirective) { 7429 markDirectivesAsIsolate(templateDirectives); 7430 } 7431 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 7432 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 7433 7434 ii = directives.length; 7435 } else { 7436 $compileNode.html(directiveValue); 7437 } 7438 } 7439 7440 if (directive.templateUrl) { 7441 hasTemplate = true; 7442 assertNoDuplicate('template', templateDirective, directive, $compileNode); 7443 templateDirective = directive; 7444 7445 if (directive.replace) { 7446 replaceDirective = directive; 7447 } 7448 7449 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 7450 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 7451 controllerDirectives: controllerDirectives, 7452 newIsolateScopeDirective: newIsolateScopeDirective, 7453 templateDirective: templateDirective, 7454 nonTlbTranscludeDirective: nonTlbTranscludeDirective 7455 }); 7456 ii = directives.length; 7457 } else if (directive.compile) { 7458 try { 7459 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 7460 if (isFunction(linkFn)) { 7461 addLinkFns(null, linkFn, attrStart, attrEnd); 7462 } else if (linkFn) { 7463 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 7464 } 7465 } catch (e) { 7466 $exceptionHandler(e, startingTag($compileNode)); 7467 } 7468 } 7469 7470 if (directive.terminal) { 7471 nodeLinkFn.terminal = true; 7472 terminalPriority = Math.max(terminalPriority, directive.priority); 7473 } 7474 7475 } 7476 7477 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 7478 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 7479 nodeLinkFn.elementTranscludeOnThisElement = hasElementTranscludeDirective; 7480 nodeLinkFn.templateOnThisElement = hasTemplate; 7481 nodeLinkFn.transclude = childTranscludeFn; 7482 7483 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 7484
7485 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 7486 return nodeLinkFn; 7487 7488 //////////////////// 7489 7490 function addLinkFns(pre, post, attrStart, attrEnd) { 7491 if (pre) { 7492 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 7493 pre.require = directive.require; 7494 pre.directiveName = directiveName; 7495 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 7496 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 7497 } 7498 preLinkFns.push(pre); 7499 } 7500 if (post) { 7501 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 7502 post.require = directive.require; 7503 post.directiveName = directiveName; 7504 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 7505 post = cloneAndAnnotateFn(post, {isolateScope: true}); 7506 } 7507 postLinkFns.push(post); 7508 } 7509 } 7510 7511 7512 function getControllers(directiveName, require, $element, elementControllers) { 7513 var value, retrievalMethod = 'data', optional = false;
7514 var $searchElement = $element; 7515 var match; 7516 if (isString(require)) { 7517 match = require.match(REQUIRE_PREFIX_REGEXP); 7518 require = require.substring(match[0].length); 7519 7520 if (match[3]) { 7521 if (match[1]) match[3] = null; 7522 else match[1] = match[3]; 7523 } 7524 if (match[1] === '^') { 7525 retrievalMethod = 'inheritedData'; 7526 } else if (match[1] === '^^') { 7527 retrievalMethod = 'inheritedData'; 7528 $searchElement = $element.parent(); 7529 } 7530 if (match[2] === '?') { 7531 optional = true; 7532 } 7533 7534 value = null; 7535 7536 if (elementControllers && retrievalMethod === 'data') { 7537 if (value = elementControllers[require]) { 7538 value = value.instance; 7539 } 7540 } 7541 value = value || $searchElement[retrievalMethod]('$' + require + 'Controller'); 7542 7543 if (!value && !optional) { 7544 throw $compileMinErr('ctreq', 7545 "Controller '{0}', required by directive '{1}', can't be found!", 7546 require, directiveName); 7547 } 7548 return value || null; 7549 } else if (isArray(require)) { 7550 value = []; 7551 forEach(require, function(require) { 7552 value.push(getControllers(directiveName, require, $element, elementControllers)); 7553 }); 7554 } 7555 return value; 7556 } 7557 7558 7559 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn) { 7560 var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element, 7561 attrs; 7562 7563 if (compileNode === linkNode) { 7564 attrs = templateAttrs; 7565 $element = templateAttrs.$$element; 7566 } else { 7567 $element = jqLite(linkNode); 7568 attrs = new Attributes($element, templateAttrs); 7569 } 7570 7571 if (newIsolateScopeDirective) { 7572 isolateScope = scope.$new(true); 7573 } 7574 7575 if (boundTranscludeFn) { 7576 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 7577 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 7578 transcludeFn = controllersBoundTransclude; 7579 transcludeFn.$$boundTransclude = boundTranscludeFn; 7580 } 7581 7582 if (controllerDirectives) { 7583 // TODO: merge `controllers` and `elementControllers` into single object. 7584 controllers = {}; 7585 elementControllers = {}; 7586 forEach(controllerDirectives, function(directive) { 7587 var locals = { 7588 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 7589 $element: $element, 7590 $attrs: attrs, 7591 $transclude: transcludeFn 7592 }, controllerInstance; 7593 7594 controller = directive.controller; 7595 if (controller == '@') { 7596 controller = attrs[directive.name]; 7597 } 7598 7599 controllerInstance = $controller(controller, locals, true, directive.controllerAs); 7600 7601 // For directives with element transclusion the element is a comment, 7602 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 7603 // clean up (http://bugs.jquery.com/ticket/8335). 7604 // Instead, we save the controllers for the element in a local hash and attach to .data 7605 // later, once we have the actual element. 7606 elementControllers[directive.name] = controllerInstance; 7607 if (!hasElementTranscludeDirective) { 7608 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 7609 } 7610 7611 controllers[directive.name] = controllerInstance; 7612 }); 7613 } 7614 7615 if (newIsolateScopeDirective) { 7616 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 7617 templateDirective === newIsolateScopeDirective.$$originalDirective))); 7618 compile.$$addScopeClass($element, true); 7619 7620 var isolateScopeController = controllers && controllers[newIsolateScopeDirective.name]; 7621 var isolateBindingContext = isolateScope; 7622 if (isolateScopeController && isolateScopeController.identifier && 7623 newIsolateScopeDirective.bindToController === true) { 7624 isolateBindingContext = isolateScopeController.instance; 7625 } 7626
7627 forEach(isolateScope.$$isolateBindings = newIsolateScopeDirective.$$isolateBindings, function(definition, scopeName) { 7628 var attrName = definition.attrName, 7629 optional = definition.optional, 7630 mode = definition.mode, // @, =, or & 7631 lastValue, 7632 parentGet, parentSet, compare; 7633 7634 switch (mode) { 7635 7636 case '@': 7637 attrs.$observe(attrName, function(value) { 7638 isolateBindingContext[scopeName] = value; 7639 }); 7640 attrs.$$observers[attrName].$$scope = scope; 7641 if (attrs[attrName]) { 7642 // If the attribute has been provided then we trigger an interpolation to ensure 7643 // the value is there for use in the link fn 7644 isolateBindingContext[scopeName] = $interpolate(attrs[attrName])(scope); 7645 } 7646 break; 7647 7648 case '=': 7649 if (optional && !attrs[attrName]) { 7650 return; 7651 } 7652 parentGet = $parse(attrs[attrName]); 7653 if (parentGet.literal) { 7654 compare = equals; 7655 } else { 7656 compare = function(a, b) { return a === b || (a !== a && b !== b); }; 7657 } 7658 parentSet = parentGet.assign || function() { 7659 // reset the change, or we will throw this exception on every $digest 7660 lastValue = isolateBindingContext[scopeName] = parentGet(scope); 7661 throw $compileMinErr('nonassign', 7662 "Expression '{0}' used with directive '{1}' is non-assignable!", 7663 attrs[attrName], newIsolateScopeDirective.name); 7664 }; 7665 lastValue = isolateBindingContext[scopeName] = parentGet(scope); 7666 var parentValueWatch = function parentValueWatch(parentValue) { 7667 if (!compare(parentValue, isolateBindingContext[scopeName])) { 7668 // we are out of sync and need to copy 7669 if (!compare(parentValue, lastValue)) { 7670 // parent changed and it has precedence 7671 isolateBindingContext[scopeName] = parentValue; 7672 } else { 7673 // if the parent can be assigned then do so 7674 parentSet(scope, parentValue = isolateBindingContext[scopeName]); 7675 } 7676 } 7677 return lastValue = parentValue; 7678 }; 7679 parentValueWatch.$stateful = true; 7680 var unwatch; 7681 if (definition.collection) { 7682 unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 7683 } else { 7684 unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 7685 } 7686 isolateScope.$on('$destroy', unwatch); 7687 break; 7688 7689 case '&': 7690 parentGet = $parse(attrs[attrName]); 7691 isolateBindingContext[scopeName] = function(locals) { 7692 return parentGet(scope, locals); 7693 }; 7694 break; 7695 } 7696 }); 7697 } 7698 if (controllers) { 7699 forEach(controllers, function(controller) { 7700 controller(); 7701 }); 7702 controllers = null; 7703 } 7704 7705 // PRELINKING 7706 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 7707 linkFn = preLinkFns[i]; 7708 invokeLinkFn(linkFn, 7709 linkFn.isolateScope ? isolateScope : scope, 7710 $element, 7711 attrs, 7712 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 7713 transcludeFn 7714 ); 7715 } 7716 7717 // RECURSION 7718 // We only pass the isolate scope, if the isolate directive has a template, 7719 // otherwise the child elements do not belong to the isolate directive. 7720 var scopeToChild = scope; 7721 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 7722 scopeToChild = isolateScope; 7723 } 7724 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 7725 7726 // POSTLINKING 7727 for (i = postLinkFns.length - 1; i >= 0; i--) { 7728 linkFn = postLinkFns[i]; 7729 invokeLinkFn(linkFn, 7730 linkFn.isolateScope ? isolateScope : scope, 7731 $element, 7732 attrs, 7733 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 7734 transcludeFn 7735 ); 7736 } 7737 7738 // This is the function that is injected as `$transclude`. 7739 // Note: all arguments are optional! 7740 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) { 7741 var transcludeControllers; 7742 7743 // No scope passed in: 7744 if (!isScope(scope)) { 7745 futureParentElement = cloneAttachFn; 7746 cloneAttachFn = scope; 7747 scope = undefined; 7748 } 7749 7750 if (hasElementTranscludeDirective) { 7751 transcludeControllers = elementControllers; 7752 } 7753 if (!futureParentElement) { 7754 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 7755 } 7756 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 7757 } 7758 } 7759 } 7760 7761 function markDirectivesAsIsolate(directives) { 7762 // mark all directives as needing isolate scope. 7763 for (var j = 0, jj = directives.length; j < jj; j++) { 7764 directives[j] = inherit(directives[j], {$$isolateScope: true}); 7765 } 7766 } 7767 7768 /** 7769 * looks up the directive and decorates it with exception handling and proper parameters. We 7770 * call this the boundDirective. 7771 * 7772 * @param {string} name name of the directive to look up. 7773 * @param {string} location The directive must be found in specific format. 7774 * String containing any of theses characters: 7775 * 7776 * * `E`: element name 7777 * * `A': attribute 7778 * * `C`: class 7779 * * `M`: comment 7780 * @returns {boolean} true if directive was added. 7781 */ 7782 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 7783 endAttrName) { 7784 if (name === ignoreDirective) return null; 7785 var match = null; 7786 if (hasDirectives.hasOwnProperty(name)) { 7787 for (var directive, directives = $injector.get(name + Suffix), 7788 i = 0, ii = directives.length; i < ii; i++) { 7789 try { 7790 directive = directives[i]; 7791 if ((maxPriority === undefined || maxPriority > directive.priority) && 7792 directive.restrict.indexOf(location) != -1) { 7793 if (startAttrName) { 7794 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 7795 } 7796 tDirectives.push(directive); 7797 match = directive; 7798 } 7799 } catch (e) { $exceptionHandler(e); } 7800 } 7801 } 7802 return match; 7803 } 7804 7805 7806 /** 7807 * looks up the directive and returns true if it is a multi-element directive, 7808 * and therefore requires DOM nodes between -start and -end markers to be grouped 7809 * together. 7810 * 7811 * @param {string} name name of the directive to look up. 7812 * @returns true if directive was registered as multi-element. 7813 */ 7814 function directiveIsMultiElement(name) { 7815 if (hasDirectives.hasOwnProperty(name)) { 7816 for (var directive, directives = $injector.get(name + Suffix), 7817 i = 0, ii = directives.length; i < ii; i++) { 7818 directive = directives[i]; 7819 if (directive.multiElement) { 7820 return true; 7821 } 7822 } 7823 } 7824 return false;
7825 } 7826 7827 /** 7828 * When the element is replaced with HTML template then the new attributes 7829 * on the template need to be merged with the existing attributes in the DOM. 7830 * The desired effect is to have both of the attributes present. 7831 * 7832 * @param {object} dst destination attributes (original DOM) 7833 * @param {object} src source attributes (from the directive template) 7834 */ 7835 function mergeTemplateAttributes(dst, src) { 7836 var srcAttr = src.$attr, 7837 dstAttr = dst.$attr, 7838 $element = dst.$$element; 7839 7840 // reapply the old attributes to the new element 7841 forEach(dst, function(value, key) { 7842 if (key.charAt(0) != '$') { 7843 if (src[key] && src[key] !== value) { 7844 value += (key === 'style' ? ';' : ' ') + src[key]; 7845 } 7846 dst.$set(key, value, true, srcAttr[key]); 7847 } 7848 }); 7849 7850 // copy the new attributes on the old attrs object 7851 forEach(src, function(value, key) { 7852 if (key == 'class') { 7853 safeAddClass($element, value); 7854 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 7855 } else if (key == 'style') { 7856 $element.attr('style', $element.attr('style') + ';' + value); 7857 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 7858 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 7859 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 7860 // have an attribute like "has-own-property" or "data-has-own-property", etc. 7861 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 7862 dst[key] = value; 7863 dstAttr[key] = srcAttr[key]; 7864 } 7865 }); 7866 } 7867 7868 7869 function compileTemplateUrl(directives, $compileNode, tAttrs, 7870 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 7871 var linkQueue = [], 7872 afterTemplateNodeLinkFn, 7873 afterTemplateChildLinkFn, 7874 beforeTemplateCompileNode = $compileNode[0], 7875 origAsyncDirective = directives.shift(), 7876 derivedSyncDirective = inherit(origAsyncDirective, { 7877 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 7878 }), 7879 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 7880 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 7881 : origAsyncDirective.templateUrl, 7882 templateNamespace = origAsyncDirective.templateNamespace; 7883 7884 $compileNode.empty(); 7885 7886 $templateRequest($sce.getTrustedResourceUrl(templateUrl)) 7887 .then(function(content) { 7888 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 7889 7890 content = denormalizeTemplate(content); 7891 7892 if (origAsyncDirective.replace) { 7893 if (jqLiteIsTextNode(content)) { 7894 $template = []; 7895 } else { 7896 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 7897 } 7898 compileNode = $template[0]; 7899 7900 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 7901 throw $compileMinErr('tplrt', 7902 "Template for directive '{0}' must have exactly one root element. {1}", 7903 origAsyncDirective.name, templateUrl); 7904 } 7905 7906 tempTemplateAttrs = {$attr: {}}; 7907 replaceWith($rootElement, $compileNode, compileNode); 7908 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 7909 7910 if (isObject(origAsyncDirective.scope)) { 7911 markDirectivesAsIsolate(templateDirectives); 7912 } 7913 directives = templateDirectives.concat(directives); 7914 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 7915 } else { 7916 compileNode = beforeTemplateCompileNode; 7917 $compileNode.html(content); 7918 } 7919 7920 directives.unshift(derivedSyncDirective); 7921 7922 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 7923 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 7924 previousCompileContext);
vendor: 17,305 bytes, lines 7925-8396
7925 forEach($rootElement, function(node, i) { 7926 if (node == compileNode) { 7927 $rootElement[i] = $compileNode[0]; 7928 } 7929 }); 7930 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 7931 7932 while (linkQueue.length) { 7933 var scope = linkQueue.shift(), 7934 beforeTemplateLinkNode = linkQueue.shift(), 7935 linkRootElement = linkQueue.shift(), 7936 boundTranscludeFn = linkQueue.shift(), 7937 linkNode = $compileNode[0]; 7938 7939 if (scope.$$destroyed) continue; 7940 7941 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 7942 var oldClasses = beforeTemplateLinkNode.className; 7943 7944 if (!(previousCompileContext.hasElementTranscludeDirective && 7945 origAsyncDirective.replace)) { 7946 // it was cloned therefore we have to clone as well. 7947 linkNode = jqLiteClone(compileNode); 7948 } 7949 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 7950 7951 // Copy in CSS classes from original node 7952 safeAddClass(jqLite(linkNode), oldClasses); 7953 } 7954 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 7955 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 7956 } else { 7957 childBoundTranscludeFn = boundTranscludeFn; 7958 } 7959 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 7960 childBoundTranscludeFn); 7961 } 7962 linkQueue = null; 7963 }); 7964 7965 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 7966 var childBoundTranscludeFn = boundTranscludeFn; 7967 if (scope.$$destroyed) return; 7968 if (linkQueue) { 7969 linkQueue.push(scope, 7970 node, 7971 rootElement, 7972 childBoundTranscludeFn); 7973 } else { 7974 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 7975 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 7976 } 7977 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn); 7978 } 7979 }; 7980 } 7981 7982 7983 /** 7984 * Sorting function for bound directives. 7985 */ 7986 function byPriority(a, b) { 7987 var diff = b.priority - a.priority; 7988 if (diff !== 0) return diff; 7989 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 7990 return a.index - b.index; 7991 } 7992 7993 7994 function assertNoDuplicate(what, previousDirective, directive, element) { 7995 if (previousDirective) { 7996 throw $compileMinErr('multidir', 'Multiple directives [{0}, {1}] asking for {2} on: {3}', 7997 previousDirective.name, directive.name, what, startingTag(element)); 7998 } 7999 } 8000 8001 8002 function addTextInterpolateDirective(directives, text) { 8003 var interpolateFn = $interpolate(text, true); 8004 if (interpolateFn) { 8005 directives.push({ 8006 priority: 0, 8007 compile: function textInterpolateCompileFn(templateNode) { 8008 var templateNodeParent = templateNode.parent(), 8009 hasCompileParent = !!templateNodeParent.length; 8010 8011 // When transcluding a template that has bindings in the root 8012 // we don't have a parent and thus need to add the class during linking fn. 8013 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 8014 8015 return function textInterpolateLinkFn(scope, node) { 8016 var parent = node.parent(); 8017 if (!hasCompileParent) compile.$$addBindingClass(parent); 8018 compile.$$addBindingInfo(parent, interpolateFn.expressions); 8019 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 8020 node[0].nodeValue = value; 8021 }); 8022 }; 8023 } 8024 }); 8025 } 8026 } 8027 8028 8029 function wrapTemplate(type, template) { 8030 type = lowercase(type || 'html'); 8031 switch (type) { 8032 case 'svg': 8033 case 'math': 8034 var wrapper = document.createElement('div'); 8035 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 8036 return wrapper.childNodes[0].childNodes; 8037 default: 8038 return template; 8039 } 8040 } 8041 8042 8043 function getTrustedContext(node, attrNormalizedName) { 8044 if (attrNormalizedName == "srcdoc") { 8045 return $sce.HTML; 8046 } 8047 var tag = nodeName_(node); 8048 // maction[xlink:href] can source SVG. It's not limited to <maction>. 8049 if (attrNormalizedName == "xlinkHref" || 8050 (tag == "form" && attrNormalizedName == "action") || 8051 (tag != "img" && (attrNormalizedName == "src" || 8052 attrNormalizedName == "ngSrc"))) { 8053 return $sce.RESOURCE_URL; 8054 } 8055 } 8056 8057 8058 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { 8059 var trustedContext = getTrustedContext(node, name); 8060 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; 8061 8062 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); 8063 8064 // no interpolation found -> ignore 8065 if (!interpolateFn) return; 8066 8067 8068 if (name === "multiple" && nodeName_(node) === "select") { 8069 throw $compileMinErr("selmulti", 8070 "Binding to the 'multiple' attribute is not supported. Element: {0}", 8071 startingTag(node)); 8072 } 8073 8074 directives.push({ 8075 priority: 100, 8076 compile: function() { 8077 return { 8078 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 8079 var $$observers = (attr.$$observers || (attr.$$observers = {})); 8080 8081 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 8082 throw $compileMinErr('nodomevents', 8083 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 8084 "ng- versions (such as ng-click instead of onclick) instead."); 8085 } 8086 8087 // If the attribute has changed since last $interpolate()ed 8088 var newValue = attr[name]; 8089 if (newValue !== value) { 8090 // we need to interpolate again since the attribute value has been updated 8091 // (e.g. by another directive's compile function) 8092 // ensure unset/empty values make interpolateFn falsy 8093 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 8094 value = newValue; 8095 } 8096 8097 // if attribute was updated so that there is no interpolation going on we don't want to 8098 // register any observers 8099 if (!interpolateFn) return; 8100 8101 // initialize attr object so that it's ready in case we need the value for isolate 8102 // scope initialization, otherwise the value would not be available from isolate 8103 // directive's linking fn during linking phase 8104 attr[name] = interpolateFn(scope); 8105 8106 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 8107 (attr.$$observers && attr.$$observers[name].$$scope || scope). 8108 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 8109 //special case for class attribute addition + removal 8110 //so that class changes can tap into the animation 8111 //hooks provided by the $animate service. Be sure to 8112 //skip animations when the first digest occurs (when 8113 //both the new and the old values are the same) since 8114 //the CSS classes are the non-interpolated values 8115 if (name === 'class' && newValue != oldValue) { 8116 attr.$updateClass(newValue, oldValue); 8117 } else { 8118 attr.$set(name, newValue); 8119 } 8120 }); 8121 } 8122 }; 8123 } 8124 }); 8125 } 8126 8127 8128 /** 8129 * This is a special jqLite.replaceWith, which can replace items which 8130 * have no parents, provided that the containing jqLite collection is provided. 8131 * 8132 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 8133 * in the root of the tree. 8134 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 8135 * the shell, but replace its DOM node reference. 8136 * @param {Node} newNode The new DOM node. 8137 */ 8138 function replaceWith($rootElement, elementsToRemove, newNode) { 8139 var firstElementToRemove = elementsToRemove[0], 8140 removeCount = elementsToRemove.length, 8141 parent = firstElementToRemove.parentNode, 8142 i, ii; 8143 8144 if ($rootElement) { 8145 for (i = 0, ii = $rootElement.length; i < ii; i++) { 8146 if ($rootElement[i] == firstElementToRemove) { 8147 $rootElement[i++] = newNode; 8148 for (var j = i, j2 = j + removeCount - 1, 8149 jj = $rootElement.length; 8150 j < jj; j++, j2++) { 8151 if (j2 < jj) { 8152 $rootElement[j] = $rootElement[j2]; 8153 } else { 8154 delete $rootElement[j]; 8155 } 8156 } 8157 $rootElement.length -= removeCount - 1; 8158 8159 // If the replaced element is also the jQuery .context then replace it 8160 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 8161 // http://api.jquery.com/context/ 8162 if ($rootElement.context === firstElementToRemove) { 8163 $rootElement.context = newNode; 8164 } 8165 break; 8166 } 8167 } 8168 } 8169 8170 if (parent) { 8171 parent.replaceChild(newNode, firstElementToRemove); 8172 } 8173 8174 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it? 8175 var fragment = document.createDocumentFragment(); 8176 fragment.appendChild(firstElementToRemove); 8177 8178 // Copy over user data (that includes Angular's $scope etc.). Don't copy private 8179 // data here because there's no public interface in jQuery to do that and copying over 8180 // event listeners (which is the main use of private data) wouldn't work anyway. 8181 jqLite(newNode).data(jqLite(firstElementToRemove).data()); 8182 8183 // Remove data of the replaced element. We cannot just call .remove() 8184 // on the element it since that would deallocate scope that is needed 8185 // for the new node. Instead, remove the data "manually". 8186 if (!jQuery) { 8187 delete jqLite.cache[firstElementToRemove[jqLite.expando]]; 8188 } else { 8189 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after 8190 // the replaced element. The cleanData version monkey-patched by Angular would cause 8191 // the scope to be trashed and we do need the very same scope to work with the new 8192 // element. However, we cannot just cache the non-patched version and use it here as 8193 // that would break if another library patches the method after Angular does (one 8194 // example is jQuery UI). Instead, set a flag indicating scope destroying should be 8195 // skipped this one time. 8196 skipDestroyOnNextJQueryCleanData = true; 8197 jQuery.cleanData([firstElementToRemove]); 8198 } 8199 8200 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 8201 var element = elementsToRemove[k]; 8202 jqLite(element).remove(); // must do this way to clean up expando 8203 fragment.appendChild(element); 8204 delete elementsToRemove[k]; 8205 } 8206 8207 elementsToRemove[0] = newNode; 8208 elementsToRemove.length = 1; 8209 } 8210 8211 8212 function cloneAndAnnotateFn(fn, annotation) { 8213 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 8214 } 8215 8216 8217 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { 8218 try { 8219 linkFn(scope, $element, attrs, controllers, transcludeFn); 8220 } catch (e) { 8221 $exceptionHandler(e, startingTag($element)); 8222 } 8223 } 8224 }]; 8225} 8226 8227var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; 8228/** 8229 * Converts all accepted directives format into proper directive name. 8230 * @param name Name to normalize 8231 */ 8232function directiveNormalize(name) { 8233 return camelCase(name.replace(PREFIX_REGEXP, '')); 8234} 8235 8236/** 8237 * @ngdoc type 8238 * @name $compile.directive.Attributes 8239 * 8240 * @description 8241 * A shared object between directive compile / linking functions which contains normalized DOM 8242 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 8243 * needed since all of these are treated as equivalent in Angular: 8244 * 8245 * ``` 8246 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 8247 * ``` 8248 */ 8249 8250/** 8251 * @ngdoc property 8252 * @name $compile.directive.Attributes#$attr 8253 * 8254 * @description 8255 * A map of DOM element attribute names to the normalized name. This is 8256 * needed to do reverse lookup from normalized name back to actual name. 8257 */ 8258 8259 8260/** 8261 * @ngdoc method 8262 * @name $compile.directive.Attributes#$set 8263 * @kind function 8264 * 8265 * @description 8266 * Set DOM element attribute value. 8267 * 8268 * 8269 * @param {string} name Normalized element attribute name of the property to modify. The name is 8270 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 8271 * property to the original name. 8272 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 8273 */ 8274 8275 8276 8277/** 8278 * Closure compiler type information 8279 */ 8280 8281function nodesetLinkingFn( 8282 /* angular.Scope */ scope, 8283 /* NodeList */ nodeList, 8284 /* Element */ rootElement, 8285 /* function(Function) */ boundTranscludeFn 8286) {} 8287 8288function directiveLinkingFn( 8289 /* nodesetLinkingFn */ nodesetLinkingFn, 8290 /* angular.Scope */ scope, 8291 /* Node */ node, 8292 /* Element */ rootElement, 8293 /* function(Function) */ boundTranscludeFn 8294) {} 8295 8296function tokenDifference(str1, str2) { 8297 var values = '', 8298 tokens1 = str1.split(/\s+/), 8299 tokens2 = str2.split(/\s+/); 8300 8301 outer: 8302 for (var i = 0; i < tokens1.length; i++) { 8303 var token = tokens1[i]; 8304 for (var j = 0; j < tokens2.length; j++) { 8305 if (token == tokens2[j]) continue outer; 8306 } 8307 values += (values.length > 0 ? ' ' : '') + token; 8308 } 8309 return values; 8310} 8311 8312function removeComments(jqNodes) { 8313 jqNodes = jqLite(jqNodes); 8314 var i = jqNodes.length; 8315 8316 if (i <= 1) { 8317 return jqNodes; 8318 } 8319 8320 while (i--) { 8321 var node = jqNodes[i]; 8322 if (node.nodeType === NODE_TYPE_COMMENT) { 8323 splice.call(jqNodes, i, 1); 8324 } 8325 } 8326 return jqNodes; 8327} 8328 8329var $controllerMinErr = minErr('$controller'); 8330 8331/** 8332 * @ngdoc provider 8333 * @name $controllerProvider 8334 * @description 8335 * The {@link ng.$controller $controller service} is used by Angular to create new 8336 * controllers. 8337 * 8338 * This provider allows controller registration via the 8339 * {@link ng.$controllerProvider#register register} method. 8340 */ 8341function $ControllerProvider() { 8342 var controllers = {}, 8343 globals = false, 8344 CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 8345 8346 8347 /** 8348 * @ngdoc method 8349 * @name $controllerProvider#register 8350 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 8351 * the names and the values are the constructors. 8352 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 8353 * annotations in the array notation). 8354 */ 8355 this.register = function(name, constructor) { 8356 assertNotHasOwnProperty(name, 'controller'); 8357 if (isObject(name)) { 8358 extend(controllers, name); 8359 } else { 8360 controllers[name] = constructor; 8361 } 8362 }; 8363 8364 /** 8365 * @ngdoc method 8366 * @name $controllerProvider#allowGlobals 8367 * @description If called, allows `$controller` to find controller constructors on `window` 8368 */ 8369 this.allowGlobals = function() { 8370 globals = true; 8371 }; 8372 8373 8374 this.$get = ['$injector', '$window', function($injector, $window) { 8375 8376 /** 8377 * @ngdoc service 8378 * @name $controller 8379 * @requires $injector 8380 * 8381 * @param {Function|string} constructor If called with a function then it's considered to be the 8382 * controller constructor function. Otherwise it's considered to be a string which is used 8383 * to retrieve the controller constructor using the following steps: 8384 * 8385 * * check if a controller with given name is registered via `$controllerProvider` 8386 * * check if evaluating the string on the current scope returns a constructor 8387 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global 8388 * `window` object (not recommended) 8389 * 8390 * The string can use the `controller as property` syntax, where the controller instance is published 8391 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 8392 * to work correctly. 8393 * 8394 * @param {Object} locals Injection locals for Controller. 8395 * @return {Object} Instance of given controller. 8396 *
8397 * @description 8398 * `$controller` service is responsible for instantiating controllers. 8399 * 8400 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 8401 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 8402 */ 8403 return function(expression, locals, later, ident) { 8404 // PRIVATE API: 8405 // param `later` --- indicates that the controller's constructor is invoked at a later time. 8406 // If true, $controller will allocate the object with the correct 8407 // prototype chain, but will not invoke the controller until a returned 8408 // callback is invoked. 8409 // param `ident` --- An optional label which overrides the label parsed from the controller 8410 // expression, if any. 8411 var instance, match, constructor, identifier; 8412 later = later === true; 8413 if (ident && isString(ident)) { 8414 identifier = ident; 8415 } 8416 8417 if (isString(expression)) { 8418 match = expression.match(CNTRL_REG); 8419 if (!match) { 8420 throw $controllerMinErr('ctrlfmt', 8421 "Badly formed controller string '{0}'. " + 8422 "Must match `__name__ as __id__` or `__name__`.", expression); 8423 } 8424 constructor = match[1], 8425 identifier = identifier || match[3]; 8426 expression = controllers.hasOwnProperty(constructor) 8427 ? controllers[constructor] 8428 : getter(locals.$scope, constructor, true) || 8429 (globals ? getter($window, constructor, true) : undefined); 8430 8431 assertArgFn(expression, constructor, true); 8432 } 8433 8434 if (later) { 8435 // Instantiate controller later: 8436 // This machinery is used to create an instance of the object before calling the 8437 // controller's constructor itself. 8438 // 8439 // This allows properties to be added to the controller before the constructor is 8440 // invoked. Primarily, this is used for isolate scope bindings in $compile. 8441 // 8442 // This feature is not intended for use by applications, and is thus not documented 8443 // publicly. 8444 // Object creation: http://jsperf.com/create-constructor/2 8445 var controllerPrototype = (isArray(expression) ? 8446 expression[expression.length - 1] : expression).prototype; 8447 instance = Object.create(controllerPrototype || null); 8448 8449 if (identifier) { 8450 addIdentifier(locals, identifier, instance, constructor || expression.name); 8451 } 8452 8453 return extend(function() { 8454 $injector.invoke(expression, instance, locals, constructor); 8455 return instance; 8456 }, { 8457 instance: instance, 8458 identifier: identifier 8459 }); 8460 } 8461 8462 instance = $injector.instantiate(expression, locals, constructor); 8463 8464 if (identifier) { 8465 addIdentifier(locals, identifier, instance, constructor || expression.name); 8466 } 8467 8468 return instance; 8469 }; 8470 8471 function addIdentifier(locals, identifier, instance, name) { 8472 if (!(locals && isObject(locals.$scope))) { 8473 throw minErr('$controller')('noscp', 8474 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 8475 name, identifier); 8476 } 8477 8478 locals.$scope[identifier] = instance; 8479 } 8480 }]; 8481} 8482 8483/** 8484 * @ngdoc service 8485 * @name $document 8486 * @requires $window 8487 * 8488 * @description 8489 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 8490 * 8491 * @example 8492 <example module="documentExample"> 8493 <file name="index.html">
8494 <div ng-controller="ExampleController"> 8495 <p>$document title: <b ng-bind="title"></b></p> 8496 <p>window.document title: <b ng-bind="windowTitle"></b></p> 8497 </div> 8498 </file> 8499 <file name="script.js"> 8500 angular.module('documentExample', []) 8501 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 8502 $scope.title = $document[0].title; 8503 $scope.windowTitle = angular.element(window.document)[0].title; 8504 }]); 8505 </file> 8506 </example> 8507 */ 8508function $DocumentProvider() { 8509 this.$get = ['$window', function(window) { 8510 return jqLite(window.document); 8511 }]; 8512} 8513 8514/** 8515 * @ngdoc service 8516 * @name $exceptionHandler 8517 * @requires ng.$log 8518 * 8519 * @description 8520 * Any uncaught exception in angular expressions is delegated to this service. 8521 * The default implementation simply delegates to `$log.error` which logs it into 8522 * the browser console. 8523 * 8524 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 8525 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 8526 * 8527 * ## Example: 8528 * 8529 * ```js 8530 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { 8531 * return function(exception, cause) { 8532 * exception.message += ' (caused by "' + cause + '")'; 8533 * throw exception; 8534 * }; 8535 * }); 8536 * ``` 8537 * 8538 * This example will override the normal action of `$exceptionHandler`, to make angular 8539 * exceptions fail hard when they happen, instead of just logging to the console. 8540 * 8541 * <hr /> 8542 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 8543 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 8544 * (unless executed during a digest). 8545 * 8546 * If you wish, you can manually delegate exceptions, e.g. 8547 * `try { ... } catch(e) { $exceptionHandler(e); }` 8548 * 8549 * @param {Error} exception Exception associated with the error. 8550 * @param {string=} cause optional information about the context in which 8551 * the error was thrown. 8552 * 8553 */ 8554function $ExceptionHandlerProvider() { 8555 this.$get = ['$log', function($log) { 8556 return function(exception, cause) { 8557 $log.error.apply($log, arguments); 8558 }; 8559 }]; 8560} 8561 8562var APPLICATION_JSON = 'application/json'; 8563var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 8564var JSON_START = /^\[|^\{(?!\{)/; 8565var JSON_ENDS = { 8566 '[': /]$/, 8567 '{': /}$/ 8568}; 8569var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; 8570 8571function defaultHttpResponseTransform(data, headers) { 8572 if (isString(data)) { 8573 // Strip json vulnerability protection prefix and trim whitespace 8574 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 8575 8576 if (tempData) { 8577 var contentType = headers('Content-Type'); 8578 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { 8579 data = fromJson(tempData); 8580 } 8581 } 8582 } 8583 8584 return data; 8585} 8586 8587function isJsonLike(str) { 8588 var jsonStart = str.match(JSON_START); 8589 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 8590} 8591 8592/** 8593 * Parse headers into key value object 8594 * 8595 * @param {string} headers Raw headers as a string 8596 * @returns {Object} Parsed headers as key value object 8597 */ 8598function parseHeaders(headers) { 8599 var parsed = createMap(), key, val, i; 8600 8601 if (!headers) return parsed; 8602 8603 forEach(headers.split('\n'), function(line) { 8604 i = line.indexOf(':');
vendor: 5,893 bytes, lines 8605-8787
8605 key = lowercase(trim(line.substr(0, i))); 8606 val = trim(line.substr(i + 1)); 8607 8608 if (key) { 8609 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 8610 } 8611 }); 8612 8613 return parsed; 8614} 8615 8616 8617/** 8618 * Returns a function that provides access to parsed headers. 8619 * 8620 * Headers are lazy parsed when first requested. 8621 * @see parseHeaders 8622 * 8623 * @param {(string|Object)} headers Headers to provide access to. 8624 * @returns {function(string=)} Returns a getter function which if called with: 8625 * 8626 * - if called with single an argument returns a single header value or null 8627 * - if called with no arguments returns an object containing all headers. 8628 */ 8629function headersGetter(headers) { 8630 var headersObj = isObject(headers) ? headers : undefined; 8631 8632 return function(name) { 8633 if (!headersObj) headersObj = parseHeaders(headers); 8634 8635 if (name) { 8636 var value = headersObj[lowercase(name)]; 8637 if (value === void 0) { 8638 value = null; 8639 } 8640 return value; 8641 } 8642 8643 return headersObj; 8644 }; 8645} 8646 8647 8648/** 8649 * Chain all given functions 8650 * 8651 * This function is used for both request and response transforming 8652 * 8653 * @param {*} data Data to transform. 8654 * @param {function(string=)} headers HTTP headers getter fn. 8655 * @param {number} status HTTP status code of the response. 8656 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 8657 * @returns {*} Transformed data. 8658 */ 8659function transformData(data, headers, status, fns) { 8660 if (isFunction(fns)) 8661 return fns(data, headers, status); 8662 8663 forEach(fns, function(fn) { 8664 data = fn(data, headers, status); 8665 }); 8666 8667 return data; 8668} 8669 8670 8671function isSuccess(status) { 8672 return 200 <= status && status < 300; 8673} 8674 8675 8676/** 8677 * @ngdoc provider 8678 * @name $httpProvider 8679 * @description 8680 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 8681 * */ 8682function $HttpProvider() { 8683 /** 8684 * @ngdoc property 8685 * @name $httpProvider#defaults 8686 * @description 8687 * 8688 * Object containing default values for all {@link ng.$http $http} requests. 8689 * 8690 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} 8691 * that will provide the cache for all requests who set their `cache` property to `true`. 8692 * If you set the `default.cache = false` then only requests that specify their own custom 8693 * cache object will be cached. See {@link $http#caching $http Caching} for more information. 8694 * 8695 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 8696 * Defaults value is `'XSRF-TOKEN'`. 8697 * 8698 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 8699 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 8700 * 8701 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 8702 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 8703 * setting default headers. 8704 * - **`defaults.headers.common`** 8705 * - **`defaults.headers.post`** 8706 * - **`defaults.headers.put`** 8707 * - **`defaults.headers.patch`** 8708 * 8709 **/ 8710 var defaults = this.defaults = { 8711 // transform incoming response data 8712 transformResponse: [defaultHttpResponseTransform], 8713 8714 // transform outgoing request data 8715 transformRequest: [function(d) { 8716 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 8717 }], 8718 8719 // default headers 8720 headers: { 8721 common: { 8722 'Accept': 'application/json, text/plain, */*' 8723 }, 8724 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 8725 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 8726 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 8727 }, 8728 8729 xsrfCookieName: 'XSRF-TOKEN', 8730 xsrfHeaderName: 'X-XSRF-TOKEN' 8731 }; 8732 8733 var useApplyAsync = false; 8734 /** 8735 * @ngdoc method 8736 * @name $httpProvider#useApplyAsync 8737 * @description 8738 * 8739 * Configure $http service to combine processing of multiple http responses received at around 8740 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 8741 * significant performance improvement for bigger applications that make many HTTP requests 8742 * concurrently (common during application bootstrap). 8743 * 8744 * Defaults to false. If no value is specifed, returns the current configured value. 8745 * 8746 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 8747 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 8748 * to load and share the same digest cycle. 8749 * 8750 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 8751 * otherwise, returns the current configured value. 8752 **/ 8753 this.useApplyAsync = function(value) { 8754 if (isDefined(value)) { 8755 useApplyAsync = !!value; 8756 return this; 8757 } 8758 return useApplyAsync; 8759 }; 8760 8761 /** 8762 * @ngdoc property 8763 * @name $httpProvider#interceptors 8764 * @description 8765 * 8766 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 8767 * pre-processing of request or postprocessing of responses. 8768 * 8769 * These service factories are ordered by request, i.e. they are applied in the same order as the 8770 * array, on request, but reverse order, on response. 8771 * 8772 * {@link ng.$http#interceptors Interceptors detailed info} 8773 **/ 8774 var interceptorFactories = this.interceptors = []; 8775 8776 this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', 8777 function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { 8778 8779 var defaultCache = $cacheFactory('$http'); 8780 8781 /** 8782 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 8783 * The reversal is needed so that we can build up the interception chain around the 8784 * server request. 8785 */ 8786 var reversedInterceptors = []; 8787
8788 forEach(interceptorFactories, function(interceptorFactory) { 8789 reversedInterceptors.unshift(isString(interceptorFactory) 8790 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 8791 }); 8792 8793 /** 8794 * @ngdoc service 8795 * @kind function 8796 * @name $http 8797 * @requires ng.$httpBackend 8798 * @requires $cacheFactory 8799 * @requires $rootScope 8800 * @requires $q 8801 * @requires $injector 8802 * 8803 * @description 8804 * The `$http` service is a core Angular service that facilitates communication with the remote 8805 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 8806 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 8807 * 8808 * For unit testing applications that use `$http` service, see 8809 * {@link ngMock.$httpBackend $httpBackend mock}. 8810 * 8811 * For a higher level of abstraction, please check out the {@link ngResource.$resource 8812 * $resource} service. 8813 * 8814 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 8815 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 8816 * it is important to familiarize yourself with these APIs and the guarantees they provide. 8817 * 8818 * 8819 * ## General usage 8820 * The `$http` service is a function which takes a single argument â a configuration object â 8821 * that is used to generate an HTTP request and returns a {@link ng.$q promise} 8822 * with two $http specific methods: `success` and `error`. 8823 * 8824 * ```js 8825 * // Simple GET request example : 8826 * $http.get('/someUrl'). 8827 * success(function(data, status, headers, config) { 8828 * // this callback will be called asynchronously 8829 * // when the response is available 8830 * }). 8831 * error(function(data, status, headers, config) { 8832 * // called asynchronously if an error occurs 8833 * // or server returns response with an error status. 8834 * }); 8835 * ``` 8836 * 8837 * ```js 8838 * // Simple POST request example (passing data) : 8839 * $http.post('/someUrl', {msg:'hello word!'}). 8840 * success(function(data, status, headers, config) { 8841 * // this callback will be called asynchronously 8842 * // when the response is available 8843 * }). 8844 * error(function(data, status, headers, config) { 8845 * // called asynchronously if an error occurs 8846 * // or server returns response with an error status. 8847 * }); 8848 * ``` 8849 * 8850 * 8851 * Since the returned value of calling the $http function is a `promise`, you can also use 8852 * the `then` method to register callbacks, and these callbacks will receive a single argument â 8853 * an object representing the response. See the API signature and type info below for more 8854 * details. 8855 * 8856 * A response status code between 200 and 299 is considered a success status and 8857 * will result in the success callback being called. Note that if the response is a redirect, 8858 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 8859 * called for such responses. 8860 * 8861 * ## Writing Unit Tests that use $http 8862 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 8863 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 8864 * request using trained responses. 8865 * 8866 * ``` 8867 * $httpBackend.expectGET(...); 8868 * $http.get(...); 8869 * $httpBackend.flush(); 8870 * ``` 8871 * 8872 * ## Shortcut methods 8873 * 8874 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 8875 * request data must be passed in for POST/PUT requests. 8876 * 8877 * ```js 8878 * $http.get('/someUrl').success(successCallback); 8879 * $http.post('/someUrl', data).success(successCallback); 8880 * ``` 8881 * 8882 * Complete list of shortcut methods: 8883 * 8884 * - {@link ng.$http#get $http.get} 8885 * - {@link ng.$http#head $http.head} 8886 * - {@link ng.$http#post $http.post} 8887 * - {@link ng.$http#put $http.put} 8888 * - {@link ng.$http#delete $http.delete} 8889 * - {@link ng.$http#jsonp $http.jsonp} 8890 * - {@link ng.$http#patch $http.patch} 8891 * 8892 * 8893 * ## Setting HTTP Headers 8894 * 8895 * The $http service will automatically add certain HTTP headers to all requests. These defaults 8896 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 8897 * object, which currently contains this default configuration: 8898 * 8899 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 8900 * - `Accept: application/json, text/plain, * / *` 8901 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 8902 * - `Content-Type: application/json` 8903 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 8904 * - `Content-Type: application/json` 8905 *
8906 * To add or overwrite these defaults, simply add or remove a property from these configuration 8907 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 8908 * with the lowercased HTTP method name as the key, e.g. 8909 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }. 8910 * 8911 * The defaults can also be set at runtime via the `$http.defaults` object in the same 8912 * fashion. For example: 8913 * 8914 * ``` 8915 * module.run(function($http) { 8916 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' 8917 * }); 8918 * ``` 8919 * 8920 * In addition, you can supply a `headers` property in the config object passed when 8921 * calling `$http(config)`, which overrides the defaults without changing them globally. 8922 * 8923 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 8924 * Use the `headers` property, setting the desired header to `undefined`. For example: 8925 * 8926 * ```js 8927 * var req = { 8928 * method: 'POST', 8929 * url: 'http://example.com', 8930 * headers: { 8931 * 'Content-Type': undefined 8932 * }, 8933 * data: { test: 'test' }, 8934 * } 8935 * 8936 * $http(req).success(function(){...}).error(function(){...}); 8937 * ``` 8938 * 8939 * ## Transforming Requests and Responses 8940 * 8941 * Both requests and responses can be transformed using transformation functions: `transformRequest` 8942 * and `transformResponse`. These properties can be a single function that returns 8943 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 8944 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 8945 * 8946 * ### Default Transformations 8947 * 8948 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 8949 * `defaults.transformResponse` properties. If a request does not provide its own transformations 8950 * then these will be applied. 8951 * 8952 * You can augment or replace the default transformations by modifying these properties by adding to or 8953 * replacing the array. 8954 * 8955 * Angular provides the following default transformations: 8956 * 8957 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): 8958 * 8959 * - If the `data` property of the request configuration object contains an object, serialize it 8960 * into JSON format. 8961 * 8962 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): 8963 * 8964 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 8965 * - If JSON response is detected, deserialize it using a JSON parser. 8966 * 8967 * 8968 * ### Overriding the Default Transformations Per Request 8969 * 8970 * If you wish override the request/response transformations only for a single request then provide 8971 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 8972 * into `$http`. 8973 * 8974 * Note that if you provide these properties on the config object the default transformations will be 8975 * overwritten. If you wish to augment the default transformations then you must include them in your 8976 * local transformation array. 8977 * 8978 * The following code demonstrates adding a new response transformation to be run after the default response 8979 * transformations have been run. 8980 * 8981 * ```js 8982 * function appendTransform(defaults, transform) { 8983 * 8984 * // We can't guarantee that the default transformation is an array 8985 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 8986 * 8987 * // Append the new transformation to the defaults 8988 * return defaults.concat(transform); 8989 * } 8990 * 8991 * $http({ 8992 * url: '...', 8993 * method: 'GET', 8994 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 8995 * return doTransform(value); 8996 * }) 8997 * }); 8998 * ``` 8999 * 9000 * 9001 * ## Caching 9002 *
9003 * To enable caching, set the request configuration `cache` property to `true` (to use default 9004 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 9005 * When the cache is enabled, `$http` stores the response from the server in the specified 9006 * cache. The next time the same request is made, the response is served from the cache without 9007 * sending a request to the server. 9008 * 9009 * Note that even if the response is served from cache, delivery of the data is asynchronous in 9010 * the same way that real requests are. 9011 * 9012 * If there are multiple GET requests for the same URL that should be cached using the same 9013 * cache, but the cache is not populated yet, only one request to the server will be made and 9014 * the remaining requests will be fulfilled using the response from the first request. 9015 * 9016 * You can change the default cache to a new object (built with 9017 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 9018 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set 9019 * their `cache` property to `true` will now use this cache object. 9020 * 9021 * If you set the default cache to `false` then only requests that specify their own custom 9022 * cache object will be cached. 9023 * 9024 * ## Interceptors 9025 * 9026 * Before you start creating interceptors, be sure to understand the 9027 * {@link ng.$q $q and deferred/promise APIs}. 9028 * 9029 * For purposes of global error handling, authentication, or any kind of synchronous or 9030 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 9031 * able to intercept requests before they are handed to the server and 9032 * responses before they are handed over to the application code that 9033 * initiated these requests. The interceptors leverage the {@link ng.$q 9034 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 9035 * 9036 * The interceptors are service factories that are registered with the `$httpProvider` by 9037 * adding them to the `$httpProvider.interceptors` array. The factory is called and 9038 * injected with dependencies (if specified) and returns the interceptor. 9039 * 9040 * There are two kinds of interceptors (and two kinds of rejection interceptors): 9041 * 9042 * * `request`: interceptors get called with a http `config` object. The function is free to 9043 * modify the `config` object or create a new one. The function needs to return the `config` 9044 * object directly, or a promise containing the `config` or a new `config` object. 9045 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 9046 * resolved with a rejection. 9047 * * `response`: interceptors get called with http `response` object. The function is free to 9048 * modify the `response` object or create a new one. The function needs to return the `response` 9049 * object directly, or as a promise containing the `response` or a new `response` object. 9050 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 9051 * resolved with a rejection. 9052 * 9053 * 9054 * ```js 9055 * // register the interceptor as a service 9056 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 9057 * return { 9058 * // optional method 9059 * 'request': function(config) { 9060 * // do something on success 9061 * return config; 9062 * }, 9063 * 9064 * // optional method 9065 * 'requestError': function(rejection) { 9066 * // do something on error 9067 * if (canRecover(rejection)) { 9068 * return responseOrNewPromise 9069 * } 9070 * return $q.reject(rejection); 9071 * }, 9072 * 9073 * 9074 * 9075 * // optional method 9076 * 'response': function(response) { 9077 * // do something on success 9078 * return response; 9079 * }, 9080 * 9081 * // optional method 9082 * 'responseError': function(rejection) {
9083 * // do something on error 9084 * if (canRecover(rejection)) { 9085 * return responseOrNewPromise 9086 * } 9087 * return $q.reject(rejection); 9088 * } 9089 * }; 9090 * }); 9091 * 9092 * $httpProvider.interceptors.push('myHttpInterceptor'); 9093 * 9094 * 9095 * // alternatively, register the interceptor via an anonymous factory 9096 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 9097 * return { 9098 * 'request': function(config) { 9099 * // same as above 9100 * }, 9101 * 9102 * 'response': function(response) { 9103 * // same as above 9104 * } 9105 * }; 9106 * }); 9107 * ``` 9108 * 9109 * ## Security Considerations 9110 * 9111 * When designing web applications, consider security threats from: 9112 * 9113 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 9114 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 9115 * 9116 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 9117 * pre-configured with strategies that address these issues, but for this to work backend server 9118 * cooperation is required. 9119 * 9120 * ### JSON Vulnerability Protection 9121 * 9122 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 9123 * allows third party website to turn your JSON resource URL into 9124 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 9125 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 9126 * Angular will automatically strip the prefix before processing it as JSON. 9127 * 9128 * For example if your server needs to return: 9129 * ```js 9130 * ['one','two'] 9131 * ``` 9132 * 9133 * which is vulnerable to attack, your server can return: 9134 * ```js 9135 * )]}', 9136 * ['one','two'] 9137 * ``` 9138 * 9139 * Angular will strip the prefix, before processing the JSON. 9140 * 9141 * 9142 * ### Cross Site Request Forgery (XSRF) Protection 9143 * 9144 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which 9145 * an unauthorized site can gain your user's private data. Angular provides a mechanism 9146 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 9147 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 9148 * JavaScript that runs on your domain could read the cookie, your server can be assured that 9149 * the XHR came from JavaScript running on your domain. The header will not be set for 9150 * cross-domain requests. 9151 * 9152 * To take advantage of this, your server needs to set a token in a JavaScript readable session 9153 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 9154 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 9155 * that only JavaScript running on your domain could have sent the request. The token must be 9156 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 9157 * making up its own tokens). We recommend that the token is a digest of your site's 9158 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 9159 * for added security. 9160 * 9161 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 9162 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 9163 * or the per-request config object. 9164 * 9165 * 9166 * @param {object} config Object describing the request to be made and how it should be 9167 * processed. The object has following properties: 9168 * 9169 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 9170 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 9171 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be turned 9172 * to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be 9173 * JSONified. 9174 * - **data** â `{string|Object}` â Data to be sent as the request message data. 9175 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 9176 * HTTP headers to send to the server. If the return value of a function is null, the 9177 * header will not be sent. 9178 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 9179 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 9180 * - **transformRequest** â 9181 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>
9181}` â 9182 * transform function or an array of such functions. The transform function takes the http 9183 * request body and headers and returns its transformed (typically serialized) version. 9184 * See {@link ng.$http#overriding-the-default-transformations-per-request 9185 * Overriding the Default Transformations} 9186 * - **transformResponse** â 9187 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` â 9188 * transform function or an array of such functions. The transform function takes the http 9189 * response body, headers and status and returns its transformed (typically deserialized) version. 9190 * See {@link ng.$http#overriding-the-default-transformations-per-request 9191 * Overriding the Default Transformations} 9192 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 9193 * GET request, otherwise if a cache instance built with 9194 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 9195 * caching. 9196 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 9197 * that should abort the request when resolved. 9198 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 9199 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 9200 * for more information. 9201 * - **responseType** - `{string}` - see 9202 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). 9203 * 9204 * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the 9205 * standard `then` method and two http specific methods: `success` and `error`. The `then` 9206 * method takes two arguments a success and an error callback which will be called with a 9207 * response object. The `success` and `error` methods take a single argument - a function that 9208 * will be called when the request succeeds or fails respectively. The arguments passed into 9209 * these functions are destructured representation of the response object passed into the 9210 * `then` method. The response object has these properties: 9211 * 9212 * - **data** â `{string|Object}` â The response body transformed with the transform 9213 * functions. 9214 * - **status** â `{number}` â HTTP status code of the response. 9215 * - **headers** â `{function([headerName])}` â Header getter function. 9216 * - **config** â `{Object}` â The configuration object that was used to generate the request. 9217 * - **statusText** â `{string}` â HTTP status text of the response. 9218 * 9219 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 9220 * requests. This is primarily meant to be used for debugging purposes. 9221 * 9222 * 9223 * @example 9224<example module="httpExample"> 9225<file name="index.html"> 9226 <div ng-controller="FetchController"> 9227 <select ng-model="method"> 9228 <option>GET</option> 9229 <option>JSONP</option> 9230 </select> 9231 <input type="text" ng-model="url" size="80"/> 9232 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 9233 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 9234 <button id="samplejsonpbtn" 9235 ng-click="updateModel('JSONP', 9236 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 9237 Sample JSONP 9238 </button> 9239 <button id="invalidjsonpbtn" 9240 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 9241 Invalid JSONP 9242 </button> 9243 <pre>http status code: {{status}}</pre> 9244 <pre>http response data: {{data}}</pre> 9245 </div> 9246</file> 9247<file name="script.js"> 9248 angular.module('httpExample', []) 9249 .controller('FetchController', ['$scope', '$http', '$templateCache', 9250 function($scope, $http, $templateCache) { 9251 $scope.method = 'GET'; 9252 $scope.url = 'http-hello.html'; 9253 9254 $scope.fetch = function() { 9255 $scope.code = null; 9256 $scope.response = null; 9257 9258 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 9259 success(function(data, status) { 9260 $scope.status = status; 9261 $scope.data = data; 9262 }). 9263 error(function(data, status) { 9264 $scope.data = data || "Request failed"; 9265 $scope.status = status; 9266 }); 9267 }; 9268 9269 $scope.updateModel = function(method, url) { 9270 $scope.method = method; 9271 $scope.url = url; 9272 }; 9273 }]); 9274</file> 9275<file name="http-hello.html"> 9276 Hello, $http! 9277</file> 9278<file name="protractor.js" type="protractor"> 9279 var status = element(by.binding('status')); 9280 var data = element(by.binding('data')); 9281 var fetchBtn = element(by.id('fetchbtn')); 9282 var sampleGetBtn = element(by.id('samplegetbtn')); 9283 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 9284 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 9285 9286 it('should make an xhr GET request', function() { 9287 sampleGetBtn.click(); 9288 fetchBtn.click(); 9289 expect(status.getText()).toMatch('200'); 9290 expect(data.getText()).toMatch(/Hello, \$http!/); 9291 }); 9292 9293// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 9294// it('should make a JSONP request to angularjs.org', function() { 9295// sampleJsonpBtn.click(); 9296// fetchBtn.click(); 9297// expect(status.getText()).toMatch('200'); 9298// expect(data.getText()).toMatch(/Super Hero!/); 9299// }); 9300
9301 it('should make JSONP request to invalid URL and invoke the error handler', 9302 function() { 9303 invalidJsonpBtn.click(); 9304 fetchBtn.click(); 9305 expect(status.getText()).toMatch('0'); 9306 expect(data.getText()).toMatch('Request failed'); 9307 }); 9308</file> 9309</example> 9310 */ 9311 function $http(requestConfig) { 9312 9313 if (!angular.isObject(requestConfig)) { 9314 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 9315 } 9316 9317 var config = extend({ 9318 method: 'get', 9319 transformRequest: defaults.transformRequest, 9320 transformResponse: defaults.transformResponse 9321 }, requestConfig); 9322 9323 config.headers = mergeHeaders(requestConfig); 9324 config.method = uppercase(config.method); 9325 9326 var serverRequest = function(config) { 9327 var headers = config.headers; 9328 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 9329 9330 // strip content-type if data is undefined 9331 if (isUndefined(reqData)) { 9332 forEach(headers, function(value, header) { 9333 if (lowercase(header) === 'content-type') { 9334 delete headers[header]; 9335 } 9336 }); 9337 } 9338 9339 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 9340 config.withCredentials = defaults.withCredentials; 9341 } 9342 9343 // send request 9344 return sendReq(config, reqData).then(transformResponse, transformResponse); 9345 }; 9346 9347 var chain = [serverRequest, undefined]; 9348 var promise = $q.when(config); 9349 9350 // apply interceptors 9351 forEach(reversedInterceptors, function(interceptor) { 9352 if (interceptor.request || interceptor.requestError) { 9353 chain.unshift(interceptor.request, interceptor.requestError); 9354 } 9355 if (interceptor.response || interceptor.responseError) { 9356 chain.push(interceptor.response, interceptor.responseError); 9357 } 9358 }); 9359 9360 while (chain.length) { 9361 var thenFn = chain.shift(); 9362 var rejectFn = chain.shift(); 9363 9364 promise = promise.then(thenFn, rejectFn); 9365 } 9366 9367 promise.success = function(fn) { 9368 promise.then(function(response) { 9369 fn(response.data, response.status, response.headers, config); 9370 }); 9371 return promise; 9372 }; 9373 9374 promise.error = function(fn) { 9375 promise.then(null, function(response) { 9376 fn(response.data, response.status, response.headers, config); 9377 }); 9378 return promise; 9379 }; 9380 9381 return promise; 9382 9383 function transformResponse(response) { 9384 // make a copy since the response must be cacheable 9385 var resp = extend({}, response); 9386 if (!response.data) { 9387 resp.data = response.data; 9388 } else { 9389 resp.data = transformData(response.data, response.headers, response.status, config.transformResponse); 9390 } 9391 return (isSuccess(response.status)) 9392 ? resp 9393 : $q.reject(resp); 9394 } 9395 9396 function executeHeaderFns(headers) { 9397 var headerContent, processedHeaders = {}; 9398 9399 forEach(headers, function(headerFn, header) { 9400 if (isFunction(headerFn)) { 9401 headerContent = headerFn(); 9402 if (headerContent != null) { 9403 processedHeaders[header] = headerContent; 9404 } 9405 } else { 9406 processedHeaders[header] = headerFn; 9407 } 9408 }); 9409 9410 return processedHeaders; 9411 } 9412 9413 function mergeHeaders(config) { 9414 var defHeaders = defaults.headers, 9415 reqHeaders = extend({}, config.headers), 9416 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 9417 9418 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 9419 9420 // using for-in instead of forEach to avoid unecessary iteration after header has been found 9421 defaultHeadersIteration: 9422 for (defHeaderName in defHeaders) { 9423 lowercaseDefHeaderName = lowercase(defHeaderName); 9424 9425 for (reqHeaderName in reqHeaders) { 9426 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 9427 continue defaultHeadersIteration; 9428 } 9429 } 9430 9431 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 9432 } 9433 9434 // execute if header value is a function for merged headers 9435 return executeHeaderFns(reqHeaders); 9436 } 9437 } 9438 9439 $http.pendingRequests = []; 9440 9441 /** 9442 * @ngdoc method 9443 * @name $http#get 9444 *
9445 * @description 9446 * Shortcut method to perform `GET` request. 9447 * 9448 * @param {string} url Relative or absolute URL specifying the destination of the request 9449 * @param {Object=} config Optional configuration object 9450 * @returns {HttpPromise} Future object 9451 */ 9452 9453 /** 9454 * @ngdoc method 9455 * @name $http#delete 9456 * 9457 * @description 9458 * Shortcut method to perform `DELETE` request. 9459 * 9460 * @param {string} url Relative or absolute URL specifying the destination of the request 9461 * @param {Object=} config Optional configuration object 9462 * @returns {HttpPromise} Future object 9463 */ 9464 9465 /** 9466 * @ngdoc method 9467 * @name $http#head 9468 * 9469 * @description 9470 * Shortcut method to perform `HEAD` request. 9471 * 9472 * @param {string} url Relative or absolute URL specifying the destination of the request 9473 * @param {Object=} config Optional configuration object 9474 * @returns {HttpPromise} Future object 9475 */ 9476 9477 /** 9478 * @ngdoc method 9479 * @name $http#jsonp 9480 * 9481 * @description 9482 * Shortcut method to perform `JSONP` request. 9483 * 9484 * @param {string} url Relative or absolute URL specifying the destination of the request. 9485 * The name of the callback should be the string `JSON_CALLBACK`. 9486 * @param {Object=} config Optional configuration object 9487 * @returns {HttpPromise} Future object 9488 */ 9489 createShortMethods('get', 'delete', 'head', 'jsonp'); 9490 9491 /** 9492 * @ngdoc method 9493 * @name $http#post 9494 * 9495 * @description 9496 * Shortcut method to perform `POST` request. 9497 * 9498 * @param {string} url Relative or absolute URL specifying the destination of the request 9499 * @param {*} data Request content 9500 * @param {Object=} config Optional configuration object 9501 * @returns {HttpPromise} Future object 9502 */ 9503 9504 /** 9505 * @ngdoc method 9506 * @name $http#put 9507 * 9508 * @description 9509 * Shortcut method to perform `PUT` request. 9510 * 9511 * @param {string} url Relative or absolute URL specifying the destination of the request 9512 * @param {*} data Request content 9513 * @param {Object=} config Optional configuration object 9514 * @returns {HttpPromise} Future object 9515 */ 9516 9517 /** 9518 * @ngdoc method 9519 * @name $http#patch 9520 * 9521 * @description 9522 * Shortcut method to perform `PATCH` request. 9523 * 9524 * @param {string} url Relative or absolute URL specifying the destination of the request 9525 * @param {*} data Request content 9526 * @param {Object=} config Optional configuration object 9527 * @returns {HttpPromise} Future object 9528 */ 9529 createShortMethodsWithData('post', 'put', 'patch'); 9530 9531 /** 9532 * @ngdoc property 9533 * @name $http#defaults 9534 * 9535 * @description 9536 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 9537 * default headers, withCredentials as well as request and response transformations. 9538 * 9539 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 9540 */ 9541 $http.defaults = defaults; 9542 9543 9544 return $http; 9545 9546 9547 function createShortMethods(names) { 9548 forEach(arguments, function(name) { 9549 $http[name] = function(url, config) { 9550 return $http(extend(config || {}, { 9551 method: name, 9552 url: url 9553 })); 9554 }; 9555 }); 9556 } 9557 9558 9559 function createShortMethodsWithData(name) { 9560 forEach(arguments, function(name) { 9561 $http[name] = function(url, data, config) { 9562 return $http(extend(config || {}, { 9563 method: name, 9564 url: url, 9565 data: data 9566 })); 9567 }; 9568 }); 9569 } 9570 9571 9572 /** 9573 * Makes the request. 9574 * 9575 * !!! ACCESSES CLOSURE VARS: 9576 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 9577 */ 9578 function sendReq(config, reqData) { 9579 var deferred = $q.defer(), 9580 promise = deferred.promise, 9581 cache, 9582 cachedResp, 9583 reqHeaders = config.headers, 9584 url = buildUrl(config.url, config.params); 9585 9586 $http.pendingRequests.push(config); 9587 promise.then(removePendingReq, removePendingReq); 9588 9589 9590 if ((config.cache || defaults.cache) && config.cache !== false && 9591 (config.method === 'GET' || config.method === 'JSONP')) { 9592 cache = isObject(config.cache) ? config.cache 9593 : isObject(defaults.cache) ? defaults.cache 9594 : defaultCache;
9595 } 9596 9597 if (cache) { 9598 cachedResp = cache.get(url); 9599 if (isDefined(cachedResp)) { 9600 if (isPromiseLike(cachedResp)) { 9601 // cached request has already been sent, but there is no response yet 9602 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 9603 } else { 9604 // serving from cache 9605 if (isArray(cachedResp)) { 9606 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 9607 } else { 9608 resolvePromise(cachedResp, 200, {}, 'OK'); 9609 } 9610 } 9611 } else { 9612 // put the promise for the non-transformed response into cache as a placeholder 9613 cache.put(url, promise); 9614 } 9615 } 9616 9617 9618 // if we won't have the response in cache, set the xsrf headers and 9619 // send the request to the backend 9620 if (isUndefined(cachedResp)) { 9621 var xsrfValue = urlIsSameOrigin(config.url) 9622 ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName] 9623 : undefined; 9624 if (xsrfValue) { 9625 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 9626 } 9627 9628 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 9629 config.withCredentials, config.responseType); 9630 } 9631 9632 return promise; 9633 9634 9635 /** 9636 * Callback registered to $httpBackend(): 9637 * - caches the response if desired 9638 * - resolves the raw $http promise 9639 * - calls $apply 9640 */ 9641 function done(status, response, headersString, statusText) { 9642 if (cache) { 9643 if (isSuccess(status)) { 9644 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 9645 } else { 9646 // remove promise from the cache 9647 cache.remove(url); 9648 } 9649 } 9650 9651 function resolveHttpPromise() { 9652 resolvePromise(response, status, headersString, statusText); 9653 } 9654 9655 if (useApplyAsync) { 9656 $rootScope.$applyAsync(resolveHttpPromise); 9657 } else { 9658 resolveHttpPromise(); 9659 if (!$rootScope.$$phase) $rootScope.$apply(); 9660 } 9661 } 9662 9663 9664 /** 9665 * Resolves the raw $http promise. 9666 */ 9667 function resolvePromise(response, status, headers, statusText) { 9668 // normalize internal statuses to 0 9669 status = Math.max(status, 0); 9670 9671 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 9672 data: response, 9673 status: status, 9674 headers: headersGetter(headers), 9675 config: config, 9676 statusText: statusText 9677 }); 9678 } 9679 9680 function resolvePromiseWithResult(result) { 9681 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); 9682 } 9683 9684 function removePendingReq() { 9685 var idx = $http.pendingRequests.indexOf(config); 9686 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 9687 } 9688 } 9689 9690 9691 function buildUrl(url, params) { 9692 if (!params) return url; 9693 var parts = []; 9694 forEachSorted(params, function(value, key) { 9695 if (value === null || isUndefined(value)) return; 9696 if (!isArray(value)) value = [value]; 9697 9698 forEach(value, function(v) { 9699 if (isObject(v)) { 9700 if (isDate(v)) { 9701 v = v.toISOString(); 9702 } else { 9703 v = toJson(v); 9704 } 9705 } 9706 parts.push(encodeUriQuery(key) + '=' + 9707 encodeUriQuery(v)); 9708 }); 9709 }); 9710 if (parts.length > 0) { 9711 url += ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); 9712 } 9713 return url; 9714 } 9715 }]; 9716} 9717 9718function createXhr() { 9719 return new window.XMLHttpRequest(); 9720} 9721 9722/** 9723 * @ngdoc service 9724 * @name $httpBackend 9725 * @requires $window 9726 * @requires $document 9727 * 9728 * @description 9729 * HTTP backend used by the {@link ng.$http service} that delegates to 9730 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 9731 * 9732 * You should never need to use this service directly, instead use the higher-level abstractions: 9733 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 9734 * 9735 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 9736 * $httpBackend} which can be trained with responses. 9737 */ 9738function $HttpBackendProvider() { 9739 this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { 9740 return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); 9741 }]; 9742} 9743 9744function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 9745 // TODO(vojta): fix the signature 9746 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 9747 $browser.$$incOutstandingRequestCount(); 9748 url = url || $browser.url(); 9749 9750 if (lowercase(method) == 'jsonp') { 9751 var callbackId = '_' + (callbacks.counter++).toString(36); 9752 callbacks[callbackId] = function(data) { 9753 callbacks[callbackId].data = data; 9754 callbacks[callbackId].called = true; 9755 }; 9756 9757 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 9758 callbackId, function(status, text) {
9759 completeRequest(callback, status, callbacks[callbackId].data, "", text); 9760 callbacks[callbackId] = noop; 9761 }); 9762 } else { 9763 9764 var xhr = createXhr(); 9765 9766 xhr.open(method, url, true); 9767 forEach(headers, function(value, key) { 9768 if (isDefined(value)) { 9769 xhr.setRequestHeader(key, value); 9770 } 9771 }); 9772 9773 xhr.onload = function requestLoaded() { 9774 var statusText = xhr.statusText || ''; 9775 9776 // responseText is the old-school way of retrieving response (supported by IE8 & 9) 9777 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 9778 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 9779 9780 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 9781 var status = xhr.status === 1223 ? 204 : xhr.status; 9782 9783 // fix status code when it is 0 (0 status is undocumented). 9784 // Occurs when accessing file resources or on Android 4.1 stock browser 9785 // while retrieving files from application cache. 9786 if (status === 0) { 9787 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 9788 } 9789 9790 completeRequest(callback, 9791 status, 9792 response, 9793 xhr.getAllResponseHeaders(), 9794 statusText); 9795 }; 9796 9797 var requestError = function() { 9798 // The response is always empty 9799 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 9800 completeRequest(callback, -1, null, null, ''); 9801 }; 9802 9803 xhr.onerror = requestError; 9804 xhr.onabort = requestError; 9805 9806 if (withCredentials) { 9807 xhr.withCredentials = true; 9808 } 9809 9810 if (responseType) { 9811 try { 9812 xhr.responseType = responseType; 9813 } catch (e) { 9814 // WebKit added support for the json responseType value on 09/03/2013 9815 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 9816 // known to throw when setting the value "json" as the response type. Other older 9817 // browsers implementing the responseType 9818 // 9819 // The json response type can be ignored if not supported, because JSON payloads are 9820 // parsed on the client-side regardless. 9821 if (responseType !== 'json') { 9822 throw e; 9823 } 9824 } 9825 } 9826 9827 xhr.send(post || null); 9828 } 9829 9830 if (timeout > 0) { 9831 var timeoutId = $browserDefer(timeoutRequest, timeout); 9832 } else if (isPromiseLike(timeout)) { 9833 timeout.then(timeoutRequest); 9834 } 9835 9836 9837 function timeoutRequest() { 9838 jsonpDone && jsonpDone(); 9839 xhr && xhr.abort(); 9840 } 9841 9842 function completeRequest(callback, status, response, headersString, statusText) { 9843 // cancel timeout and subsequent timeout promise resolution 9844 if (timeoutId !== undefined) { 9845 $browserDefer.cancel(timeoutId); 9846 } 9847 jsonpDone = xhr = null; 9848 9849 callback(status, response, headersString, statusText); 9850 $browser.$$completeOutstandingRequest(noop); 9851 } 9852 }; 9853 9854 function jsonpReq(url, callbackId, done) { 9855 // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.: 9856 // - fetches local scripts via XHR and evals them 9857 // - adds and immediately removes script elements from the document 9858 var script = rawDocument.createElement('script'), callback = null; 9859 script.type = "text/javascript"; 9860 script.src = url; 9861 script.async = true; 9862 9863 callback = function(event) { 9864 removeEventListenerFn(script, "load", callback); 9865 removeEventListenerFn(script, "error", callback); 9866 rawDocument.body.removeChild(script); 9867 script = null; 9868 var status = -1; 9869 var text = "unknown"; 9870 9871 if (event) { 9872 if (event.type === "load" && !callbacks[callbackId].called) { 9873 event = { type: "error" }; 9874 } 9875 text = event.type; 9876 status = event.type === "error" ? 404 : 200; 9877 } 9878 9879 if (done) { 9880 done(status, text); 9881 } 9882 }; 9883 9884 addEventListenerFn(script, "load", callback); 9885 addEventListenerFn(script, "error", callback); 9886 rawDocument.body.appendChild(script); 9887 return callback; 9888 } 9889} 9890 9891var $interpolateMinErr = minErr('$interpolate'); 9892 9893/** 9894 * @ngdoc provider 9895 * @name $interpolateProvider 9896 * 9897 * @description 9898 *
9899 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 9900 * 9901 * @example 9902<example module="customInterpolationApp"> 9903<file name="index.html"> 9904<script> 9905 var customInterpolationApp = angular.module('customInterpolationApp', []); 9906 9907 customInterpolationApp.config(function($interpolateProvider) { 9908 $interpolateProvider.startSymbol('//'); 9909 $interpolateProvider.endSymbol('//'); 9910 }); 9911 9912 9913 customInterpolationApp.controller('DemoController', function() { 9914 this.label = "This binding is brought you by // interpolation symbols."; 9915 }); 9916</script> 9917<div ng-app="App" ng-controller="DemoController as demo"> 9918 //demo.label// 9919</div> 9920</file> 9921<file name="protractor.js" type="protractor"> 9922 it('should interpolate binding with custom symbols', function() { 9923 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 9924 }); 9925</file> 9926</example> 9927 */ 9928function $InterpolateProvider() { 9929 var startSymbol = '{{'; 9930 var endSymbol = '}}'; 9931 9932 /** 9933 * @ngdoc method 9934 * @name $interpolateProvider#startSymbol 9935 * @description 9936 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 9937 * 9938 * @param {string=} value new value to set the starting symbol to. 9939 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 9940 */ 9941 this.startSymbol = function(value) { 9942 if (value) { 9943 startSymbol = value; 9944 return this; 9945 } else { 9946 return startSymbol; 9947 } 9948 }; 9949 9950 /** 9951 * @ngdoc method 9952 * @name $interpolateProvider#endSymbol 9953 * @description 9954 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 9955 * 9956 * @param {string=} value new value to set the ending symbol to. 9957 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 9958 */ 9959 this.endSymbol = function(value) { 9960 if (value) { 9961 endSymbol = value; 9962 return this; 9963 } else { 9964 return endSymbol; 9965 } 9966 }; 9967 9968 9969 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 9970 var startSymbolLength = startSymbol.length, 9971 endSymbolLength = endSymbol.length, 9972 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 9973 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 9974 9975 function escape(ch) { 9976 return '\\\\\\' + ch; 9977 } 9978 9979 /** 9980 * @ngdoc service 9981 * @name $interpolate 9982 * @kind function 9983 * 9984 * @requires $parse 9985 * @requires $sce 9986 * 9987 * @description 9988 * 9989 * Compiles a string with markup into an interpolation function. This service is used by the 9990 * HTML {@link ng.$compile $compile} service for data binding. See 9991 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 9992 * interpolation markup. 9993 * 9994 * 9995 * ```js 9996 * var $interpolate = ...; // injected 9997 * var exp = $interpolate('Hello {{name | uppercase}}!'); 9998 * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); 9999 * ``` 10000 * 10001 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 10002 * `true`, the interpolation function will return `undefined` unless all embedded expressions 10003 * evaluate to a value other than `undefined`. 10004 * 10005 * ```js 10006 * var $interpolate = ...; // injected 10007 * var context = {greeting: 'Hello', name: undefined }; 10008 * 10009 * // default "forgiving" mode 10010 * var exp = $interpolate('{{greeting}} {{name}}!'); 10011 * expect(exp(context)).toEqual('Hello !'); 10012 * 10013 * // "allOrNothing" mode 10014 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 10015 * expect(exp(context)).toBeUndefined(); 10016 * context.name = 'Angular'; 10017 * expect(exp(context)).toEqual('Hello Angular!'); 10018 * ``` 10019 * 10020 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 10021 * 10022 * ####Escaped Interpolation 10023 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 10024 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 10025 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 10026 * or binding. 10027 * 10028 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 10029 * degree, while also enabling code examples to work without relying on the 10030 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 10031 * 10032 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 10033 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 10034 * interpolation start/end markers with their escaped counterparts.** 10035 * 10036 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 10037 * output when the $interpolate service processes the text. So, for HTML elements interpolated 10038 * by {@link ng.$compile $compile}
10038, or otherwise interpolated with the `mustHaveExpression` parameter 10039 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 10040 * this is typically useful only when user-data is used in rendering a template from the server, or 10041 * when otherwise untrusted data is used by a directive. 10042 * 10043 * <example> 10044 * <file name="index.html"> 10045 * <div ng-init="username='A user'"> 10046 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 10047 * </p> 10048 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 10049 * application, but fails to accomplish their task, because the server has correctly 10050 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 10051 * characters.</p> 10052 * <p>Instead, the result of the attempted script injection is visible, and can be removed 10053 * from the database by an administrator.</p> 10054 * </div> 10055 * </file> 10056 * </example> 10057 * 10058 * @param {string} text The text with markup to interpolate. 10059 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 10060 * embedded expression in order to return an interpolation function. Strings with no 10061 * embedded expression will return null for the interpolation function. 10062 * @param {string=} trustedContext when provided, the returned function passes the interpolated 10063 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 10064 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 10065 * provides Strict Contextual Escaping for details. 10066 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 10067 * unless all embedded expressions evaluate to a value other than `undefined`. 10068 * @returns {function(context)} an interpolation function which is used to compute the 10069 * interpolated string. The function has these parameters: 10070 * 10071 * - `context`: evaluation context for all expressions embedded in the interpolated text 10072 */ 10073 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 10074 allOrNothing = !!allOrNothing; 10075 var startIndex, 10076 endIndex, 10077 index = 0, 10078 expressions = [], 10079 parseFns = [], 10080 textLength = text.length, 10081 exp, 10082 concat = [], 10083 expressionPositions = []; 10084 10085 while (index < textLength) { 10086 if (((startIndex = text.indexOf(startSymbol, index)) != -1) && 10087 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { 10088 if (index !== startIndex) { 10089 concat.push(unescapeText(text.substring(index, startIndex))); 10090 } 10091 exp = text.substring(startIndex + startSymbolLength, endIndex); 10092 expressions.push(exp); 10093 parseFns.push($parse(exp, parseStringifyInterceptor)); 10094 index = endIndex + endSymbolLength; 10095 expressionPositions.push(concat.length); 10096 concat.push(''); 10097 } else { 10098 // we did not find an interpolation, so we have to add the remainder to the separators array 10099 if (index !== textLength) { 10100 concat.push(unescapeText(text.substring(index))); 10101 } 10102 break; 10103 } 10104 } 10105 10106 // Concatenating expressions makes it hard to reason about whether some combination of 10107 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 10108 // single expression be used for iframe[src], object[src], etc., we ensure that the value 10109 // that's used is assigned or constructed by some JS code somewhere that is more testable or 10110 // make it obvious that you bound the value to some user controlled value. This helps reduce 10111 // the load when auditing for XSS issues. 10112 if (trustedContext && concat.length > 1) { 10113 throw $interpolateMinErr('noconcat', 10114 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 10115 "interpolations that concatenate multiple expressions when a trusted value is " + 10116 "required. See http://docs.angularjs.org/api/ng.$sce", text); 10117 } 10118 10119 if (!mustHaveExpression || expressions.length) { 10120 var compute = function(values) { 10121 for (var i = 0, ii = expressions.length; i < ii; i++) { 10122 if (allOrNothing && isUndefined(values[i])) return; 10123 concat[expressionPositions[i]] = values[i]; 10124 } 10125 return concat.join(''); 10126 }; 10127 10128 var getValue = function(value) { 10129 return trustedContext ? 10130 $sce.getTrusted(trustedContext, value) : 10131 $sce.valueOf(value); 10132 }; 10133 10134 var stringify = function(value) { 10135 if (value == null) { // null || undefined 10136 return ''; 10137 } 10138 switch (typeof value) { 10139 case 'string': 10140 break; 10141 case 'number': 10142 value = '' + value; 10143 break; 10144 default: 10145 value = toJson(value); 10146 } 10147 10148 return value; 10149 }; 10150 10151 return extend(function interpolationFn(context) { 10152 var i = 0; 10153 var ii = expressions.length; 10154 var values = new Array(ii); 10155 10156 try { 10157 for (; i < ii; i++) { 10158 values[i] = parseFns[i](context); 10159 } 10160 10161 return compute(values); 10162 } catch (err) { 10163 var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, 10164 err.toString()); 10165 $exceptionHandler(newErr); 10166 } 10167 10168 }, { 10169 // all of these properties are undocumented for now 10170 exp: text, //just for compatibility with regular watchers created via $watch 10171 expressions: expressions, 10172 $$watchDelegate: function(scope, listener, objectEquality) { 10173 var lastValue; 10174 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { 10175 var currValue = compute(values); 10176 if (isFunction(listener)) { 10177 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 10178 }
10179 lastValue = currValue; 10180 }, objectEquality); 10181 } 10182 }); 10183 } 10184 10185 function unescapeText(text) { 10186 return text.replace(escapedStartRegexp, startSymbol). 10187 replace(escapedEndRegexp, endSymbol); 10188 } 10189 10190 function parseStringifyInterceptor(value) { 10191 try { 10192 value = getValue(value); 10193 return allOrNothing && !isDefined(value) ? value : stringify(value); 10194 } catch (err) { 10195 var newErr = $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, 10196 err.toString()); 10197 $exceptionHandler(newErr); 10198 } 10199 } 10200 } 10201 10202 10203 /** 10204 * @ngdoc method 10205 * @name $interpolate#startSymbol 10206 * @description 10207 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 10208 * 10209 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 10210 * the symbol. 10211 * 10212 * @returns {string} start symbol. 10213 */ 10214 $interpolate.startSymbol = function() { 10215 return startSymbol; 10216 }; 10217 10218 10219 /** 10220 * @ngdoc method 10221 * @name $interpolate#endSymbol 10222 * @description 10223 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 10224 * 10225 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 10226 * the symbol. 10227 * 10228 * @returns {string} end symbol. 10229 */ 10230 $interpolate.endSymbol = function() { 10231 return endSymbol; 10232 }; 10233 10234 return $interpolate; 10235 }]; 10236} 10237 10238function $IntervalProvider() { 10239 this.$get = ['$rootScope', '$window', '$q', '$$q', 10240 function($rootScope, $window, $q, $$q) { 10241 var intervals = {}; 10242 10243 10244 /** 10245 * @ngdoc service 10246 * @name $interval 10247 * 10248 * @description 10249 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 10250 * milliseconds. 10251 * 10252 * The return value of registering an interval function is a promise. This promise will be 10253 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 10254 * run indefinitely if `count` is not defined. The value of the notification will be the 10255 * number of iterations that have run. 10256 * To cancel an interval, call `$interval.cancel(promise)`. 10257 * 10258 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 10259 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 10260 * time. 10261 * 10262 * <div class="alert alert-warning"> 10263 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 10264 * with them. In particular they are not automatically destroyed when a controller's scope or a 10265 * directive's element are destroyed. 10266 * You should take this into consideration and make sure to always cancel the interval at the 10267 * appropriate moment. See the example below for more details on how and when to do this. 10268 * </div> 10269 * 10270 * @param {function()} fn A function that should be called repeatedly. 10271 * @param {number} delay Number of milliseconds between each function call. 10272 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 10273 * indefinitely. 10274 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 10275 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 10276 * @returns {promise} A promise which will be notified on each iteration. 10277 * 10278 * @example 10279 * <example module="intervalExample"> 10280 * <file name="index.html"> 10281 * <script> 10282 * angular.module('intervalExample', []) 10283 * .controller('ExampleController', ['$scope', '$interval', 10284 * function($scope, $interval) { 10285 * $scope.format = 'M/d/yy h:mm:ss a'; 10286 * $scope.blood_1 = 100; 10287 * $scope.blood_2 = 120; 10288 * 10289 * var stop; 10290 * $scope.fight = function() { 10291 * // Don't start a new fight if we are already fighting 10292 * if ( angular.isDefined(stop) ) return; 10293 * 10294 * stop = $interval(function() { 10295 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 10296 * $scope.blood_1 = $scope.blood_1 - 3; 10297 * $scope.blood_2 = $scope.blood_2 - 4; 10298 * } else { 10299 * $scope.stopFight(); 10300 * } 10301 * }, 100); 10302 * }; 10303 * 10304 * $scope.stopFight = function() { 10305 * if (angular.isDefined(stop)) { 10306 * $interval.cancel(stop); 10307 * stop = undefined; 10308 * } 10309 * }; 10310 * 10311 * $scope.resetFight = function() { 10312 * $scope.blood_1 = 100; 10313 * $scope.blood_2 = 120; 10314 * }; 10315 * 10316 * $scope.$on('$destroy', function() { 10317 * // Make sure that the interval is destroyed too 10318 * $scope.stopFight(); 10319 * }); 10320 * }]) 10321 * // Register the 'myCurrentTime' directive factory method. 10322 * // We inject $interval and dateFilter service since the factory method is DI. 10323 * .directive('myCurrentTime', ['$interval', 'dateFilter', 10324 * function($interval, dateFilter) { 10325 * // return the directive link function. (compile function not needed) 10326 * return function(scope, element, attrs) { 10327 * var format, // date format 10328 * stopTime; // so that we can cancel the time updates 10329 * 10330 * // used to update the UI 10331 * function updateTime() { 10332 * element.text(dateFilter(new Date(), format)); 10333 * } 10334 * 10335 * // watch the expression, and update the UI on change. 10336 * scope.$watch(attrs.myCurrentTime, function(value) { 10337 * format = value; 10338 * updateTime(); 10339 * }); 10340 * 10341 * stopTime = $interval(updateTime, 1000); 10342 * 10343 * // listen on DOM destroy (removal) event, and cancel the next UI update 10344 * // to prevent updating time after the DOM element was removed. 10345 * element.on('$destroy', function() { 10346 * $interval.cancel(stopTime); 10347 * }); 10348 * } 10349 * }]); 10350 * </script> 10351 * 10352 * <div>
10353 * <div ng-controller="ExampleController"> 10354 * Date format: <input ng-model="format"> <hr/> 10355 * Current time is: <span my-current-time="format"></span> 10356 * <hr/> 10357 * Blood 1 : <font color='red'>{{blood_1}}</font> 10358 * Blood 2 : <font color='red'>{{blood_2}}</font> 10359 * <button type="button" data-ng-click="fight()">Fight</button> 10360 * <button type="button" data-ng-click="stopFight()">StopFight</button> 10361 * <button type="button" data-ng-click="resetFight()">resetFight</button> 10362 * </div> 10363 * </div> 10364 * 10365 * </file> 10366 * </example> 10367 */ 10368 function interval(fn, delay, count, invokeApply) { 10369 var setInterval = $window.setInterval, 10370 clearInterval = $window.clearInterval, 10371 iteration = 0, 10372 skipApply = (isDefined(invokeApply) && !invokeApply), 10373 deferred = (skipApply ? $$q : $q).defer(), 10374 promise = deferred.promise; 10375 10376 count = isDefined(count) ? count : 0; 10377 10378 promise.then(null, null, fn); 10379 10380 promise.$$intervalId = setInterval(function tick() { 10381 deferred.notify(iteration++); 10382 10383 if (count > 0 && iteration >= count) { 10384 deferred.resolve(iteration); 10385 clearInterval(promise.$$intervalId); 10386 delete intervals[promise.$$intervalId]; 10387 } 10388 10389 if (!skipApply) $rootScope.$apply(); 10390 10391 }, delay); 10392 10393 intervals[promise.$$intervalId] = deferred; 10394 10395 return promise; 10396 } 10397 10398 10399 /** 10400 * @ngdoc method 10401 * @name $interval#cancel 10402 * 10403 * @description 10404 * Cancels a task associated with the `promise`. 10405 * 10406 * @param {promise} promise returned by the `$interval` function. 10407 * @returns {boolean} Returns `true` if the task was successfully canceled. 10408 */ 10409 interval.cancel = function(promise) { 10410 if (promise && promise.$$intervalId in intervals) { 10411 intervals[promise.$$intervalId].reject('canceled'); 10412 $window.clearInterval(promise.$$intervalId); 10413 delete intervals[promise.$$intervalId]; 10414 return true; 10415 } 10416 return false; 10417 }; 10418 10419 return interval; 10420 }]; 10421} 10422 10423/** 10424 * @ngdoc service 10425 * @name $locale 10426 * 10427 * @description 10428 * $locale service provides localization rules for various Angular components. As of right now the 10429 * only public api is: 10430 * 10431 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 10432 */ 10433function $LocaleProvider() { 10434 this.$get = function() { 10435 return { 10436 id: 'en-us', 10437 10438 NUMBER_FORMATS: { 10439 DECIMAL_SEP: '.', 10440 GROUP_SEP: ',', 10441 PATTERNS: [ 10442 { // Decimal Pattern 10443 minInt: 1, 10444 minFrac: 0, 10445 maxFrac: 3, 10446 posPre: '', 10447 posSuf: '', 10448 negPre: '-', 10449 negSuf: '', 10450 gSize: 3, 10451 lgSize: 3 10452 },{ //Currency Pattern 10453 minInt: 1, 10454 minFrac: 2, 10455 maxFrac: 2, 10456 posPre: '\u00A4', 10457 posSuf: '', 10458 negPre: '(\u00A4', 10459 negSuf: ')', 10460 gSize: 3, 10461 lgSize: 3 10462 } 10463 ], 10464 CURRENCY_SYM: '$' 10465 }, 10466 10467 DATETIME_FORMATS: { 10468 MONTH: 10469 'January,February,March,April,May,June,July,August,September,October,November,December' 10470 .split(','), 10471 SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), 10472 DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), 10473 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), 10474 AMPMS: ['AM','PM'], 10475 medium: 'MMM d, y h:mm:ss a', 10476 'short': 'M/d/yy h:mm a', 10477 fullDate: 'EEEE, MMMM d, y', 10478 longDate: 'MMMM d, y', 10479 mediumDate: 'MMM d, y', 10480 shortDate: 'M/d/yy', 10481 mediumTime: 'h:mm:ss a', 10482 shortTime: 'h:mm a' 10483 }, 10484 10485 pluralCat: function(num) { 10486 if (num === 1) { 10487 return 'one'; 10488 } 10489 return 'other'; 10490 } 10491 }; 10492 }; 10493} 10494 10495var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 10496 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 10497var $locationMinErr = minErr('$location'); 10498 10499 10500/** 10501 * Encode path using encodeUriSegment, ignoring forward slashes 10502 * 10503 * @param {string} path Path to encode 10504 * @returns {string} 10505 */ 10506function encodePath(path) { 10507 var segments = path.split('/'), 10508 i = segments.length; 10509 10510 while (i--) { 10511 segments[i] = encodeUriSegment(segments[i]); 10512 } 10513 10514 return segments.join('/'); 10515} 10516 10517function parseAbsoluteUrl(absoluteUrl, locationObj) {
10518 var parsedUrl = urlResolve(absoluteUrl); 10519 10520 locationObj.$$protocol = parsedUrl.protocol; 10521 locationObj.$$host = parsedUrl.hostname; 10522 locationObj.$$port = int(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 10523} 10524 10525 10526function parseAppUrl(relativeUrl, locationObj) { 10527 var prefixed = (relativeUrl.charAt(0) !== '/'); 10528 if (prefixed) { 10529 relativeUrl = '/' + relativeUrl; 10530 } 10531 var match = urlResolve(relativeUrl); 10532 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 10533 match.pathname.substring(1) : match.pathname); 10534 locationObj.$$search = parseKeyValue(match.search); 10535 locationObj.$$hash = decodeURIComponent(match.hash); 10536 10537 // make sure path starts with '/'; 10538 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 10539 locationObj.$$path = '/' + locationObj.$$path; 10540 } 10541} 10542 10543 10544/** 10545 * 10546 * @param {string} begin 10547 * @param {string} whole 10548 * @returns {string} returns text from whole after begin or undefined if it does not begin with 10549 * expected string. 10550 */ 10551function beginsWith(begin, whole) { 10552 if (whole.indexOf(begin) === 0) { 10553 return whole.substr(begin.length); 10554 } 10555} 10556 10557 10558function stripHash(url) { 10559 var index = url.indexOf('#'); 10560 return index == -1 ? url : url.substr(0, index); 10561} 10562 10563function trimEmptyHash(url) { 10564 return url.replace(/(#.+)|#$/, '$1'); 10565} 10566 10567 10568function stripFile(url) { 10569 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 10570} 10571 10572/* return the server only (scheme://host:port) */ 10573function serverBase(url) { 10574 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 10575} 10576 10577 10578/** 10579 * LocationHtml5Url represents an url 10580 * This object is exposed as $location service when HTML5 mode is enabled and supported 10581 * 10582 * @constructor 10583 * @param {string} appBase application base URL 10584 * @param {string} basePrefix url path prefix 10585 */ 10586function LocationHtml5Url(appBase, basePrefix) { 10587 this.$$html5 = true; 10588 basePrefix = basePrefix || ''; 10589 var appBaseNoFile = stripFile(appBase); 10590 parseAbsoluteUrl(appBase, this); 10591 10592 10593 /** 10594 * Parse given html5 (regular) url string into properties 10595 * @param {string} url HTML5 url 10596 * @private 10597 */ 10598 this.$$parse = function(url) { 10599 var pathUrl = beginsWith(appBaseNoFile, url); 10600 if (!isString(pathUrl)) { 10601 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 10602 appBaseNoFile); 10603 } 10604 10605 parseAppUrl(pathUrl, this); 10606 10607 if (!this.$$path) { 10608 this.$$path = '/'; 10609 } 10610 10611 this.$$compose(); 10612 }; 10613 10614 /** 10615 * Compose url and update `absUrl` property 10616 * @private 10617 */ 10618 this.$$compose = function() { 10619 var search = toKeyValue(this.$$search), 10620 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 10621 10622 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 10623 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 10624 }; 10625 10626 this.$$parseLinkUrl = function(url, relHref) { 10627 if (relHref && relHref[0] === '#') { 10628 // special case for links to hash fragments: 10629 // keep the old url and only replace the hash fragment 10630 this.hash(relHref.slice(1)); 10631 return true; 10632 } 10633 var appUrl, prevAppUrl; 10634 var rewrittenUrl; 10635 10636 if ((appUrl = beginsWith(appBase, url)) !== undefined) { 10637 prevAppUrl = appUrl; 10638 if ((appUrl = beginsWith(basePrefix, appUrl)) !== undefined) { 10639 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 10640 } else { 10641 rewrittenUrl = appBase + prevAppUrl; 10642 } 10643 } else if ((appUrl = beginsWith(appBaseNoFile, url)) !== undefined) { 10644 rewrittenUrl = appBaseNoFile + appUrl; 10645 } else if (appBaseNoFile == url + '/') { 10646 rewrittenUrl = appBaseNoFile; 10647 } 10648 if (rewrittenUrl) { 10649 this.$$parse(rewrittenUrl); 10650 } 10651 return !!rewrittenUrl; 10652 }; 10653} 10654 10655 10656/** 10657 * LocationHashbangUrl represents url 10658 * This object is exposed as $location service when developer doesn't opt into html5 mode. 10659 * It also serves as the base class for html5 mode fallback on legacy browsers. 10660 * 10661 * @constructor 10662 * @param {string} appBase application base URL 10663 * @param {string} hashPrefix hashbang prefix 10664 */ 10665function LocationHashbangUrl(appBase, hashPrefix) { 10666 var appBaseNoFile = stripFile(appBase); 10667 10668 parseAbsoluteUrl(appBase, this); 10669 10670 10671 /** 10672 * Parse given hashbang url into properties 10673 * @param {string} url Hashbang url 10674 * @private 10675 */ 10676 this.$$parse = function(url) { 10677 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 10678 var withoutHashUrl; 10679 10680 if (withoutBaseUrl.charAt(0) === '#') { 10681 10682 // The rest of the url starts with a hash so we have 10683 // got either a hashbang path or a plain hash fragment 10684 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); 10685 if (isUndefined(withoutHashUrl)) { 10686 // There was no hashbang prefix so we just have a hash fragment 10687 withoutHashUrl = withoutBaseUrl; 10688 } 10689 10690 } else { 10691 // There was no hashbang path nor hash fragment: 10692 // If we are in HTML5 mode we use what is left as the path; 10693 // Otherwise we ignore what is left 10694 withoutHashUrl = this.$$html5 ? withoutBaseUrl : ''; 10695 } 10696 10697 parseAppUrl(withoutHashUrl, this); 10698 10699 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 10700 10701 this.$$compose(); 10702 10703 /* 10704 * In Windows, on an anchor node on documents loaded from 10705 * the filesystem, the browser will return a pathname
10706 * prefixed with the drive name ('/C:/path') when a 10707 * pathname without a drive is set: 10708 * * a.setAttribute('href', '/foo') 10709 * * a.pathname === '/C:/foo' //true 10710 * 10711 * Inside of Angular, we're always using pathnames that 10712 * do not include drive names for routing. 10713 */ 10714 function removeWindowsDriveName(path, url, base) { 10715 /* 10716 Matches paths for file protocol on windows, 10717 such as /C:/foo/bar, and captures only /foo/bar. 10718 */ 10719 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 10720 10721 var firstPathSegmentMatch; 10722 10723 //Get the relative path from the input URL. 10724 if (url.indexOf(base) === 0) { 10725 url = url.replace(base, ''); 10726 } 10727 10728 // The input URL intentionally contains a first path segment that ends with a colon. 10729 if (windowsFilePathExp.exec(url)) { 10730 return path; 10731 } 10732 10733 firstPathSegmentMatch = windowsFilePathExp.exec(path); 10734 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 10735 } 10736 }; 10737 10738 /** 10739 * Compose hashbang url and update `absUrl` property 10740 * @private 10741 */ 10742 this.$$compose = function() { 10743 var search = toKeyValue(this.$$search), 10744 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 10745 10746 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 10747 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 10748 }; 10749 10750 this.$$parseLinkUrl = function(url, relHref) { 10751 if (stripHash(appBase) == stripHash(url)) { 10752 this.$$parse(url); 10753 return true; 10754 } 10755 return false; 10756 }; 10757} 10758 10759 10760/** 10761 * LocationHashbangUrl represents url 10762 * This object is exposed as $location service when html5 history api is enabled but the browser 10763 * does not support it. 10764 * 10765 * @constructor 10766 * @param {string} appBase application base URL 10767 * @param {string} hashPrefix hashbang prefix 10768 */ 10769function LocationHashbangInHtml5Url(appBase, hashPrefix) { 10770 this.$$html5 = true; 10771 LocationHashbangUrl.apply(this, arguments); 10772 10773 var appBaseNoFile = stripFile(appBase); 10774 10775 this.$$parseLinkUrl = function(url, relHref) { 10776 if (relHref && relHref[0] === '#') { 10777 // special case for links to hash fragments: 10778 // keep the old url and only replace the hash fragment 10779 this.hash(relHref.slice(1)); 10780 return true; 10781 } 10782 10783 var rewrittenUrl; 10784 var appUrl; 10785 10786 if (appBase == stripHash(url)) { 10787 rewrittenUrl = url; 10788 } else if ((appUrl = beginsWith(appBaseNoFile, url))) { 10789 rewrittenUrl = appBase + hashPrefix + appUrl; 10790 } else if (appBaseNoFile === url + '/') { 10791 rewrittenUrl = appBaseNoFile; 10792 } 10793 if (rewrittenUrl) { 10794 this.$$parse(rewrittenUrl); 10795 } 10796 return !!rewrittenUrl; 10797 }; 10798 10799 this.$$compose = function() { 10800 var search = toKeyValue(this.$$search), 10801 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 10802 10803 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 10804 // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#' 10805 this.$$absUrl = appBase + hashPrefix + this.$$url; 10806 }; 10807 10808} 10809 10810 10811var locationPrototype = { 10812 10813 /** 10814 * Are we in html5 mode? 10815 * @private 10816 */ 10817 $$html5: false, 10818 10819 /** 10820 * Has any change been replacing? 10821 * @private 10822 */ 10823 $$replace: false, 10824 10825 /** 10826 * @ngdoc method 10827 * @name $location#absUrl 10828 * 10829 * @description 10830 * This method is getter only. 10831 * 10832 * Return full url representation with all segments encoded according to rules specified in 10833 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 10834 * 10835 * 10836 * ```js 10837 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 10838 * var absUrl = $location.absUrl(); 10839 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 10840 * ``` 10841 * 10842 * @return {string} full url 10843 */ 10844 absUrl: locationGetter('$$absUrl'), 10845 10846 /** 10847 * @ngdoc method 10848 * @name $location#url 10849 * 10850 * @description 10851 * This method is getter / setter. 10852 * 10853 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 10854 * 10855 * Change path, search and hash, when called with parameter and return `$location`. 10856 * 10857 * 10858 * ```js
10859 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 10860 * var url = $location.url(); 10861 * // => "/some/path?foo=bar&baz=xoxo" 10862 * ``` 10863 * 10864 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 10865 * @return {string} url 10866 */ 10867 url: function(url) { 10868 if (isUndefined(url)) 10869 return this.$$url; 10870 10871 var match = PATH_MATCH.exec(url); 10872 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 10873 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 10874 this.hash(match[5] || ''); 10875 10876 return this; 10877 }, 10878 10879 /** 10880 * @ngdoc method 10881 * @name $location#protocol 10882 * 10883 * @description 10884 * This method is getter only. 10885 * 10886 * Return protocol of current url. 10887 * 10888 * 10889 * ```js 10890 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 10891 * var protocol = $location.protocol(); 10892 * // => "http" 10893 * ``` 10894 * 10895 * @return {string} protocol of current url 10896 */ 10897 protocol: locationGetter('$$protocol'), 10898 10899 /** 10900 * @ngdoc method 10901 * @name $location#host 10902 * 10903 * @description 10904 * This method is getter only. 10905 * 10906 * Return host of current url. 10907 * 10908 * 10909 * ```js 10910 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 10911 * var host = $location.host(); 10912 * // => "example.com" 10913 * ``` 10914 * 10915 * @return {string} host of current url. 10916 */ 10917 host: locationGetter('$$host'), 10918 10919 /** 10920 * @ngdoc method 10921 * @name $location#port 10922 * 10923 * @description 10924 * This method is getter only. 10925 * 10926 * Return port of current url. 10927 * 10928 * 10929 * ```js 10930 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 10931 * var port = $location.port(); 10932 * // => 80 10933 * ``` 10934 * 10935 * @return {Number} port 10936 */ 10937 port: locationGetter('$$port'), 10938 10939 /** 10940 * @ngdoc method 10941 * @name $location#path 10942 * 10943 * @description 10944 * This method is getter / setter. 10945 * 10946 * Return path of current url when called without any parameter. 10947 * 10948 * Change path when called with parameter and return `$location`. 10949 * 10950 * Note: Path should always begin with forward slash (/), this method will add the forward slash 10951 * if it is missing. 10952 * 10953 * 10954 * ```js 10955 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 10956 * var path = $location.path(); 10957 * // => "/some/path" 10958 * ``` 10959 * 10960 * @param {(string|number)=} path New path 10961 * @return {string} path 10962 */ 10963 path: locationGetterSetter('$$path', function(path) { 10964 path = path !== null ? path.toString() : ''; 10965 return path.charAt(0) == '/' ? path : '/' + path; 10966 }), 10967 10968 /** 10969 * @ngdoc method 10970 * @name $location#search 10971 * 10972 * @description 10973 * This method is getter / setter. 10974 * 10975 * Return search part (as object) of current url when called without any parameter. 10976 * 10977 * Change search part when called with parameter and return `$location`. 10978 * 10979 * 10980 * ```js 10981 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 10982 * var searchObject = $location.search(); 10983 * // => {foo: 'bar', baz: 'xoxo'} 10984 * 10985 * // set foo to 'yipee' 10986 * $location.search('foo', 'yipee'); 10987 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 10988 * ``` 10989 * 10990 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 10991 * hash object. 10992 * 10993 * When called with a single argument the method acts as a setter, setting the `search` component 10994 * of `$location` to the specified value. 10995 * 10996 * If the argument is a hash object containing an array of values, these values will be encoded 10997 * as duplicate search parameters in the url. 10998 * 10999 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 11000 * will override only a single search property. 11001 * 11002 * If `paramValue` is an array, it will override the property of the `search` component of 11003 * `$location` specified via the first argument. 11004 * 11005 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 11006 * 11007 * If `paramValue` is `true`, the property specified via the first argument will be added with no 11008 * value nor trailing equal sign. 11009 * 11010 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 11011 * one or more arguments returns `$location` object itself. 11012 */ 11013 search: function(search, paramValue) { 11014 switch (arguments.length) { 11015 case 0: 11016 return this.$$search; 11017 case 1: 11018 if (isString(search) || isNumber(search)) { 11019 search = search.toString(); 11020 this.$$search = parseKeyValue(search); 11021 } else if (isObject(search)) { 11022 search = copy(search, {}); 11023 // remove object undefined or null properties
vendor: 18,208 bytes, lines 11024-11593
11024 forEach(search, function(value, key) { 11025 if (value == null) delete search[key]; 11026 }); 11027 11028 this.$$search = search; 11029 } else { 11030 throw $locationMinErr('isrcharg', 11031 'The first argument of the `$location#search()` call must be a string or an object.'); 11032 } 11033 break; 11034 default: 11035 if (isUndefined(paramValue) || paramValue === null) { 11036 delete this.$$search[search]; 11037 } else { 11038 this.$$search[search] = paramValue; 11039 } 11040 } 11041 11042 this.$$compose(); 11043 return this; 11044 }, 11045 11046 /** 11047 * @ngdoc method 11048 * @name $location#hash 11049 * 11050 * @description 11051 * This method is getter / setter. 11052 * 11053 * Return hash fragment when called without any parameter. 11054 * 11055 * Change hash fragment when called with parameter and return `$location`. 11056 * 11057 * 11058 * ```js 11059 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 11060 * var hash = $location.hash(); 11061 * // => "hashValue" 11062 * ``` 11063 * 11064 * @param {(string|number)=} hash New hash fragment 11065 * @return {string} hash 11066 */ 11067 hash: locationGetterSetter('$$hash', function(hash) { 11068 return hash !== null ? hash.toString() : ''; 11069 }), 11070 11071 /** 11072 * @ngdoc method 11073 * @name $location#replace 11074 * 11075 * @description 11076 * If called, all changes to $location during current `$digest` will be replacing current history 11077 * record, instead of adding new one. 11078 */ 11079 replace: function() { 11080 this.$$replace = true; 11081 return this; 11082 } 11083}; 11084 11085forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 11086 Location.prototype = Object.create(locationPrototype); 11087 11088 /** 11089 * @ngdoc method 11090 * @name $location#state 11091 * 11092 * @description 11093 * This method is getter / setter. 11094 * 11095 * Return the history state object when called without any parameter. 11096 * 11097 * Change the history state object when called with one parameter and return `$location`. 11098 * The state object is later passed to `pushState` or `replaceState`. 11099 * 11100 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 11101 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 11102 * older browsers (like IE9 or Android < 4.0), don't use this method. 11103 * 11104 * @param {object=} state State object for pushState or replaceState 11105 * @return {object} state 11106 */ 11107 Location.prototype.state = function(state) { 11108 if (!arguments.length) 11109 return this.$$state; 11110 11111 if (Location !== LocationHtml5Url || !this.$$html5) { 11112 throw $locationMinErr('nostate', 'History API state support is available only ' + 11113 'in HTML5 mode and only in browsers supporting HTML5 History API'); 11114 } 11115 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 11116 // but we're changing the $$state reference to $browser.state() during the $digest 11117 // so the modification window is narrow. 11118 this.$$state = isUndefined(state) ? null : state; 11119 11120 return this; 11121 }; 11122}); 11123 11124 11125function locationGetter(property) { 11126 return function() { 11127 return this[property]; 11128 }; 11129} 11130 11131 11132function locationGetterSetter(property, preprocess) { 11133 return function(value) { 11134 if (isUndefined(value)) 11135 return this[property]; 11136 11137 this[property] = preprocess(value); 11138 this.$$compose(); 11139 11140 return this; 11141 }; 11142} 11143 11144 11145/** 11146 * @ngdoc service 11147 * @name $location 11148 * 11149 * @requires $rootElement 11150 * 11151 * @description 11152 * The $location service parses the URL in the browser address bar (based on the 11153 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 11154 * available to your application. Changes to the URL in the address bar are reflected into 11155 * $location service and changes to $location are reflected into the browser address bar. 11156 * 11157 * **The $location service:** 11158 * 11159 * - Exposes the current URL in the browser address bar, so you can 11160 * - Watch and observe the URL. 11161 * - Change the URL. 11162 * - Synchronizes the URL with the browser when the user 11163 * - Changes the address bar. 11164 * - Clicks the back or forward button (or clicks a History link). 11165 * - Clicks on a link. 11166 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 11167 * 11168 * For more information see {@link guide/$location Developer Guide: Using $location} 11169 */ 11170 11171/** 11172 * @ngdoc provider 11173 * @name $locationProvider 11174 * @description 11175 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 11176 */ 11177function $LocationProvider() { 11178 var hashPrefix = '', 11179 html5Mode = { 11180 enabled: false, 11181 requireBase: true, 11182 rewriteLinks: true 11183 }; 11184 11185 /** 11186 * @ngdoc method 11187 * @name $locationProvider#hashPrefix 11188 * @description 11189 * @param {string=} prefix Prefix for hash part (containing path and search) 11190 * @returns {*} current value if used as getter or itself (chaining) if used as setter 11191 */ 11192 this.hashPrefix = function(prefix) { 11193 if (isDefined(prefix)) { 11194 hashPrefix = prefix; 11195 return this; 11196 } else { 11197 return hashPrefix; 11198 } 11199 }; 11200 11201 /** 11202 * @ngdoc method 11203 * @name $locationProvider#html5Mode 11204 * @description 11205 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 11206 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 11207 * properties: 11208 * - **enabled** â `{boolean}` â (default: false) If true, will rely on `history.pushState` to 11209 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 11210 * support `pushState`. 11211 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 11212 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 11213 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 11214 * See the {@link guide/$location $location guide for more information} 11215 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, 11216 * enables/disables url rewriting for relative links. 11217 * 11218 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 11219 */ 11220 this.html5Mode = function(mode) { 11221 if (isBoolean(mode)) { 11222 html5Mode.enabled = mode; 11223 return this; 11224 } else if (isObject(mode)) { 11225 11226 if (isBoolean(mode.enabled)) { 11227 html5Mode.enabled = mode.enabled; 11228 } 11229 11230 if (isBoolean(mode.requireBase)) { 11231 html5Mode.requireBase = mode.requireBase; 11232 } 11233 11234 if (isBoolean(mode.rewriteLinks)) { 11235 html5Mode.rewriteLinks = mode.rewriteLinks; 11236 } 11237 11238 return this; 11239 } else { 11240 return html5Mode; 11241 } 11242 }; 11243 11244 /** 11245 * @ngdoc event 11246 * @name $location#$locationChangeStart 11247 * @eventType broadcast on root scope 11248 * @description 11249 * Broadcasted before a URL will change. 11250 * 11251 * This change can be prevented by calling 11252 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 11253 * details about event object. Upon successful change 11254 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 11255 * 11256 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 11257 * the browser supports the HTML5 History API. 11258 * 11259 * @param {Object} angularEvent Synthetic event object. 11260 * @param {string} newUrl New URL 11261 * @param {string=} oldUrl URL that was before it was changed. 11262 * @param {string=} newState New history state object 11263 * @param {string=} oldState History state object that was before it was changed. 11264 */ 11265 11266 /** 11267 * @ngdoc event 11268 * @name $location#$locationChangeSuccess 11269 * @eventType broadcast on root scope 11270 * @description 11271 * Broadcasted after a URL was changed. 11272 * 11273 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 11274 * the browser supports the HTML5 History API. 11275 * 11276 * @param {Object} angularEvent Synthetic event object. 11277 * @param {string} newUrl New URL 11278 * @param {string=} oldUrl URL that was before it was changed. 11279 * @param {string=} newState New history state object 11280 * @param {string=} oldState History state object that was before it was changed. 11281 */ 11282 11283 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 11284 function($rootScope, $browser, $sniffer, $rootElement, $window) { 11285 var $location, 11286 LocationMode, 11287 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 11288 initialUrl = $browser.url(), 11289 appBase; 11290 11291 if (html5Mode.enabled) { 11292 if (!baseHref && html5Mode.requireBase) { 11293 throw $locationMinErr('nobase', 11294 "$location in HTML5 mode requires a <base> tag to be present!"); 11295 } 11296 appBase = serverBase(initialUrl) + (baseHref || '/'); 11297 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 11298 } else { 11299 appBase = stripHash(initialUrl); 11300 LocationMode = LocationHashbangUrl; 11301 } 11302 $location = new LocationMode(appBase, '#' + hashPrefix); 11303 $location.$$parseLinkUrl(initialUrl, initialUrl); 11304 11305 $location.$$state = $browser.state(); 11306 11307 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 11308 11309 function setBrowserUrlWithFallback(url, replace, state) { 11310 var oldUrl = $location.url(); 11311 var oldState = $location.$$state; 11312 try { 11313 $browser.url(url, replace, state); 11314 11315 // Make sure $location.state() returns referentially identical (not just deeply equal) 11316 // state object; this makes possible quick checking if the state changed in the digest 11317 // loop. Checking deep equality would be too expensive. 11318 $location.$$state = $browser.state(); 11319 } catch (e) { 11320 // Restore old values if pushState fails 11321 $location.url(oldUrl); 11322 $location.$$state = oldState; 11323 11324 throw e; 11325 } 11326 } 11327 11328 $rootElement.on('click', function(event) { 11329 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 11330 // currently we open nice url link and redirect then 11331 11332 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; 11333 11334 var elm = jqLite(event.target); 11335 11336 // traverse the DOM up to find first A tag 11337 while (nodeName_(elm[0]) !== 'a') { 11338 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 11339 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 11340 } 11341 11342 var absHref = elm.prop('href'); 11343 // get the actual href attribute - see 11344 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 11345 var relHref = elm.attr('href') || elm.attr('xlink:href'); 11346 11347 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 11348 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 11349 // an animation. 11350 absHref = urlResolve(absHref.animVal).href; 11351 } 11352 11353 // Ignore when url is started with javascript: or mailto: 11354 if (IGNORE_URI_REGEXP.test(absHref)) return; 11355 11356 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 11357 if ($location.$$parseLinkUrl(absHref, relHref)) { 11358 // We do a preventDefault for all urls that are part of the angular application, 11359 // in html5mode and also without, so that we are able to abort navigation without 11360 // getting double entries in the location history. 11361 event.preventDefault(); 11362 // update location manually 11363 if ($location.absUrl() != $browser.url()) { 11364 $rootScope.$apply(); 11365 // hack to work around FF6 bug 684208 when scenario runner clicks on links 11366 $window.angular['ff-684208-preventDefault'] = true; 11367 } 11368 } 11369 } 11370 }); 11371 11372 11373 // rewrite hashbang url <> html5 url 11374 if ($location.absUrl() != initialUrl) { 11375 $browser.url($location.absUrl(), true); 11376 } 11377 11378 var initializing = true; 11379 11380 // update $location when $browser url changes 11381 $browser.onUrlChange(function(newUrl, newState) { 11382 $rootScope.$evalAsync(function() { 11383 var oldUrl = $location.absUrl(); 11384 var oldState = $location.$$state; 11385 var defaultPrevented; 11386 11387 $location.$$parse(newUrl); 11388 $location.$$state = newState; 11389 11390 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 11391 newState, oldState).defaultPrevented; 11392 11393 // if the location was changed by a `$locationChangeStart` handler then stop 11394 // processing this location change 11395 if ($location.absUrl() !== newUrl) return; 11396 11397 if (defaultPrevented) { 11398 $location.$$parse(oldUrl); 11399 $location.$$state = oldState; 11400 setBrowserUrlWithFallback(oldUrl, false, oldState); 11401 } else { 11402 initializing = false; 11403 afterLocationChange(oldUrl, oldState); 11404 } 11405 }); 11406 if (!$rootScope.$$phase) $rootScope.$digest(); 11407 }); 11408 11409 // update browser 11410 $rootScope.$watch(function $locationWatch() { 11411 var oldUrl = trimEmptyHash($browser.url()); 11412 var newUrl = trimEmptyHash($location.absUrl()); 11413 var oldState = $browser.state(); 11414 var currentReplace = $location.$$replace; 11415 var urlOrStateChanged = oldUrl !== newUrl || 11416 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 11417 11418 if (initializing || urlOrStateChanged) { 11419 initializing = false; 11420 11421 $rootScope.$evalAsync(function() { 11422 var newUrl = $location.absUrl(); 11423 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 11424 $location.$$state, oldState).defaultPrevented; 11425 11426 // if the location was changed by a `$locationChangeStart` handler then stop 11427 // processing this location change 11428 if ($location.absUrl() !== newUrl) return; 11429 11430 if (defaultPrevented) { 11431 $location.$$parse(oldUrl); 11432 $location.$$state = oldState; 11433 } else { 11434 if (urlOrStateChanged) { 11435 setBrowserUrlWithFallback(newUrl, currentReplace, 11436 oldState === $location.$$state ? null : $location.$$state); 11437 } 11438 afterLocationChange(oldUrl, oldState); 11439 } 11440 }); 11441 } 11442 11443 $location.$$replace = false; 11444 11445 // we don't need to return anything because $evalAsync will make the digest loop dirty when 11446 // there is a change 11447 }); 11448 11449 return $location; 11450 11451 function afterLocationChange(oldUrl, oldState) { 11452 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 11453 $location.$$state, oldState); 11454 } 11455}]; 11456} 11457 11458/** 11459 * @ngdoc service 11460 * @name $log 11461 * @requires $window 11462 * 11463 * @description 11464 * Simple service for logging. Default implementation safely writes the message 11465 * into the browser's console (if present). 11466 * 11467 * The main purpose of this service is to simplify debugging and troubleshooting. 11468 * 11469 * The default is to log `debug` messages. You can use 11470 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 11471 * 11472 * @example 11473 <example module="logExample"> 11474 <file name="script.js"> 11475 angular.module('logExample', []) 11476 .controller('LogController', ['$scope', '$log', function($scope, $log) { 11477 $scope.$log = $log; 11478 $scope.message = 'Hello World!'; 11479 }]); 11480 </file> 11481 <file name="index.html"> 11482 <div ng-controller="LogController"> 11483 <p>Reload this page with open console, enter text and hit the log button...</p> 11484 Message: 11485 <input type="text" ng-model="message"/> 11486 <button ng-click="$log.log(message)">log</button> 11487 <button ng-click="$log.warn(message)">warn</button> 11488 <button ng-click="$log.info(message)">info</button> 11489 <button ng-click="$log.error(message)">error</button> 11490 </div> 11491 </file> 11492 </example> 11493 */ 11494 11495/** 11496 * @ngdoc provider 11497 * @name $logProvider 11498 * @description 11499 * Use the `$logProvider` to configure how the application logs messages 11500 */ 11501function $LogProvider() { 11502 var debug = true, 11503 self = this; 11504 11505 /** 11506 * @ngdoc method 11507 * @name $logProvider#debugEnabled 11508 * @description 11509 * @param {boolean=} flag enable or disable debug level messages 11510 * @returns {*} current value if used as getter or itself (chaining) if used as setter 11511 */ 11512 this.debugEnabled = function(flag) { 11513 if (isDefined(flag)) { 11514 debug = flag; 11515 return this; 11516 } else { 11517 return debug; 11518 } 11519 }; 11520 11521 this.$get = ['$window', function($window) { 11522 return { 11523 /** 11524 * @ngdoc method 11525 * @name $log#log 11526 * 11527 * @description 11528 * Write a log message 11529 */ 11530 log: consoleLog('log'), 11531 11532 /** 11533 * @ngdoc method 11534 * @name $log#info 11535 * 11536 * @description 11537 * Write an information message 11538 */ 11539 info: consoleLog('info'), 11540 11541 /** 11542 * @ngdoc method 11543 * @name $log#warn 11544 * 11545 * @description 11546 * Write a warning message 11547 */ 11548 warn: consoleLog('warn'), 11549 11550 /** 11551 * @ngdoc method 11552 * @name $log#error 11553 * 11554 * @description 11555 * Write an error message 11556 */ 11557 error: consoleLog('error'), 11558 11559 /** 11560 * @ngdoc method 11561 * @name $log#debug 11562 * 11563 * @description 11564 * Write a debug message 11565 */ 11566 debug: (function() { 11567 var fn = consoleLog('debug'); 11568 11569 return function() { 11570 if (debug) { 11571 fn.apply(self, arguments); 11572 } 11573 }; 11574 }()) 11575 }; 11576 11577 function formatError(arg) { 11578 if (arg instanceof Error) { 11579 if (arg.stack) { 11580 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 11581 ? 'Error: ' + arg.message + '\n' + arg.stack 11582 : arg.stack; 11583 } else if (arg.sourceURL) { 11584 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 11585 } 11586 } 11587 return arg; 11588 } 11589 11590 function consoleLog(type) { 11591 var console = $window.console || {}, 11592 logFn = console[type] || console.log || noop, 11593 hasApply = false;
11594 11595 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 11596 // The reason behind this is that console.log has type "object" in IE8... 11597 try { 11598 hasApply = !!logFn.apply; 11599 } catch (e) {} 11600 11601 if (hasApply) { 11602 return function() { 11603 var args = []; 11604 forEach(arguments, function(arg) { 11605 args.push(formatError(arg)); 11606 }); 11607 return logFn.apply(console, args); 11608 }; 11609 } 11610 11611 // we are IE which either doesn't have window.console => this is noop and we do nothing, 11612 // or we are IE where console.log doesn't have apply so we log at least first 2 args 11613 return function(arg1, arg2) { 11614 logFn(arg1, arg2 == null ? '' : arg2); 11615 }; 11616 } 11617 }]; 11618} 11619 11620var $parseMinErr = minErr('$parse'); 11621 11622// Sandboxing Angular Expressions 11623// ------------------------------ 11624// Angular expressions are generally considered safe because these expressions only have direct 11625// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by 11626// obtaining a reference to native JS functions such as the Function constructor. 11627// 11628// As an example, consider the following Angular expression: 11629// 11630// {}.toString.constructor('alert("evil JS code")') 11631// 11632// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 11633// against the expression language, but not to prevent exploits that were enabled by exposing 11634// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good 11635// practice and therefore we are not even trying to protect against interaction with an object 11636// explicitly exposed in this way. 11637// 11638// In general, it is not possible to access a Window object from an angular expression unless a 11639// window or some DOM object that has a reference to window is published onto a Scope. 11640// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 11641// native objects. 11642// 11643// See https://docs.angularjs.org/guide/security 11644 11645 11646function ensureSafeMemberName(name, fullExpression) { 11647 if (name === "__defineGetter__" || name === "__defineSetter__" 11648 || name === "__lookupGetter__" || name === "__lookupSetter__" 11649 || name === "__proto__") { 11650 throw $parseMinErr('isecfld', 11651 'Attempting to access a disallowed field in Angular expressions! ' 11652 + 'Expression: {0}', fullExpression); 11653 } 11654 return name; 11655} 11656 11657function ensureSafeObject(obj, fullExpression) { 11658 // nifty check if obj is Function that is fast and works across iframes and other contexts 11659 if (obj) { 11660 if (obj.constructor === obj) { 11661 throw $parseMinErr('isecfn', 11662 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 11663 fullExpression); 11664 } else if (// isWindow(obj) 11665 obj.window === obj) { 11666 throw $parseMinErr('isecwindow', 11667 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 11668 fullExpression); 11669 } else if (// isElement(obj) 11670 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 11671 throw $parseMinErr('isecdom', 11672 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 11673 fullExpression); 11674 } else if (// block Object so that we can't get hold of dangerous Object.* methods 11675 obj === Object) { 11676 throw $parseMinErr('isecobj', 11677 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 11678 fullExpression); 11679 } 11680 } 11681 return obj; 11682} 11683 11684var CALL = Function.prototype.call; 11685var APPLY = Function.prototype.apply; 11686var BIND = Function.prototype.bind; 11687 11688function ensureSafeFunction(obj, fullExpression) { 11689 if (obj) { 11690 if (obj.constructor === obj) { 11691 throw $parseMinErr('isecfn', 11692 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 11693 fullExpression); 11694 } else if (obj === CALL || obj === APPLY || obj === BIND) { 11695 throw $parseMinErr('isecff', 11696 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 11697 fullExpression); 11698 } 11699 } 11700} 11701 11702//Keyword constants 11703var CONSTANTS = createMap(); 11704forEach({ 11705 'null': function() { return null; }, 11706 'true': function() { return true; }, 11707 'false': function() { return false; }, 11708 'undefined': function() {} 11709}, function(constantGetter, name) { 11710 constantGetter.constant = constantGetter.literal = constantGetter.sharedGetter = true; 11711 CONSTANTS[name] = constantGetter; 11712}); 11713 11714//Not quite a constant, but can be lex/parsed the same 11715CONSTANTS['this'] = function(self) { return self; }; 11716CONSTANTS['this'].sharedGetter = true; 11717 11718 11719//Operators - will be wrapped by binaryFn/unaryFn/assignment/filter 11720var OPERATORS = extend(createMap(), { 11721 '+':function(self, locals, a, b) { 11722 a=a(self, locals); b=b(self, locals); 11723 if (isDefined(a)) { 11724 if (isDefined(b)) { 11725 return a + b; 11726 } 11727 return a; 11728 } 11729 return isDefined(b) ? b : undefined;}, 11730 '-':function(self, locals, a, b) { 11731 a=a(self, locals); b=b(self, locals); 11732 return (isDefined(a) ? a : 0) - (isDefined(b) ? b : 0); 11733 }, 11734 '*':function(self, locals, a, b) {return a(self, locals) * b(self, locals);}, 11735 '/':function(self, locals, a, b) {return a(self, locals) / b(self, locals);}, 11736 '%':function(self, locals, a, b) {return a(self, locals) % b(self, locals);}, 11737 '===':function(self, locals, a, b) {return a(self, locals) === b(self, locals);}, 11738 '!==':function(self, locals, a, b) {return a(self, locals) !== b(self, locals);}, 11739 '==':function(self, locals, a, b) {return a(self, locals) == b(self, locals);}, 11740 '!=':function(self, locals, a, b) {return a(self, locals) != b(self, locals);}, 11741 '<':function(self, locals, a, b) {return a(self, locals) < b(self, locals);}, 11742 '>':function(self, locals, a, b) {return a(self, locals) > b(self, locals);}, 11743 '<=':function(self, locals, a, b) {return a(self, locals) <= b(self, locals);}, 11744 '>=':function(self, locals, a, b) {return a(self, locals) >= b(self, locals);}, 11745 '&&':function(self, locals, a, b) {return a(self, locals) && b(self, locals);}, 11746 '||':function(self, locals, a, b) {return a(self, locals) || b(self, locals);}, 11747 '!':function(self, locals, a) {return !a(self, locals);}, 11748 11749 //Tokenized as operators but parsed as assignment/filters 11750 '=':true, 11751 '|':true 11752}); 11753var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 11754 11755
vendor: 3,907 bytes, lines 11756-11897
11756///////////////////////////////////////// 11757 11758 11759/** 11760 * @constructor 11761 */ 11762var Lexer = function(options) { 11763 this.options = options; 11764}; 11765 11766Lexer.prototype = { 11767 constructor: Lexer, 11768 11769 lex: function(text) { 11770 this.text = text; 11771 this.index = 0; 11772 this.tokens = []; 11773 11774 while (this.index < this.text.length) { 11775 var ch = this.text.charAt(this.index); 11776 if (ch === '"' || ch === "'") { 11777 this.readString(ch); 11778 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 11779 this.readNumber(); 11780 } else if (this.isIdent(ch)) { 11781 this.readIdent(); 11782 } else if (this.is(ch, '(){}[].,;:?')) { 11783 this.tokens.push({index: this.index, text: ch}); 11784 this.index++; 11785 } else if (this.isWhitespace(ch)) { 11786 this.index++; 11787 } else { 11788 var ch2 = ch + this.peek(); 11789 var ch3 = ch2 + this.peek(2); 11790 var op1 = OPERATORS[ch]; 11791 var op2 = OPERATORS[ch2]; 11792 var op3 = OPERATORS[ch3]; 11793 if (op1 || op2 || op3) { 11794 var token = op3 ? ch3 : (op2 ? ch2 : ch); 11795 this.tokens.push({index: this.index, text: token, operator: true}); 11796 this.index += token.length; 11797 } else { 11798 this.throwError('Unexpected next character ', this.index, this.index + 1); 11799 } 11800 } 11801 } 11802 return this.tokens; 11803 }, 11804 11805 is: function(ch, chars) { 11806 return chars.indexOf(ch) !== -1; 11807 }, 11808 11809 peek: function(i) { 11810 var num = i || 1; 11811 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 11812 }, 11813 11814 isNumber: function(ch) { 11815 return ('0' <= ch && ch <= '9') && typeof ch === "string"; 11816 }, 11817 11818 isWhitespace: function(ch) { 11819 // IE treats non-breaking space as \u00A0 11820 return (ch === ' ' || ch === '\r' || ch === '\t' || 11821 ch === '\n' || ch === '\v' || ch === '\u00A0'); 11822 }, 11823 11824 isIdent: function(ch) { 11825 return ('a' <= ch && ch <= 'z' || 11826 'A' <= ch && ch <= 'Z' || 11827 '_' === ch || ch === '$'); 11828 }, 11829 11830 isExpOperator: function(ch) { 11831 return (ch === '-' || ch === '+' || this.isNumber(ch)); 11832 }, 11833 11834 throwError: function(error, start, end) { 11835 end = end || this.index; 11836 var colStr = (isDefined(start) 11837 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 11838 : ' ' + end); 11839 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 11840 error, colStr, this.text); 11841 }, 11842 11843 readNumber: function() { 11844 var number = ''; 11845 var start = this.index; 11846 while (this.index < this.text.length) { 11847 var ch = lowercase(this.text.charAt(this.index)); 11848 if (ch == '.' || this.isNumber(ch)) { 11849 number += ch; 11850 } else { 11851 var peekCh = this.peek(); 11852 if (ch == 'e' && this.isExpOperator(peekCh)) { 11853 number += ch; 11854 } else if (this.isExpOperator(ch) && 11855 peekCh && this.isNumber(peekCh) && 11856 number.charAt(number.length - 1) == 'e') { 11857 number += ch; 11858 } else if (this.isExpOperator(ch) && 11859 (!peekCh || !this.isNumber(peekCh)) && 11860 number.charAt(number.length - 1) == 'e') { 11861 this.throwError('Invalid exponent'); 11862 } else { 11863 break; 11864 } 11865 } 11866 this.index++; 11867 } 11868 this.tokens.push({ 11869 index: start, 11870 text: number, 11871 constant: true, 11872 value: Number(number) 11873 }); 11874 }, 11875 11876 readIdent: function() { 11877 var start = this.index; 11878 while (this.index < this.text.length) { 11879 var ch = this.text.charAt(this.index); 11880 if (!(this.isIdent(ch) || this.isNumber(ch))) { 11881 break; 11882 } 11883 this.index++; 11884 } 11885 this.tokens.push({ 11886 index: start, 11887 text: this.text.slice(start, this.index), 11888 identifier: true 11889 }); 11890 }, 11891 11892 readString: function(quote) { 11893 var start = this.index; 11894 this.index++; 11895 var string = ''; 11896 var rawString = quote; 11897 var escape = false;
11898 while (this.index < this.text.length) { 11899 var ch = this.text.charAt(this.index); 11900 rawString += ch; 11901 if (escape) { 11902 if (ch === 'u') { 11903 var hex = this.text.substring(this.index + 1, this.index + 5); 11904 if (!hex.match(/[\da-f]{4}/i)) 11905 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 11906 this.index += 4; 11907 string += String.fromCharCode(parseInt(hex, 16)); 11908 } else { 11909 var rep = ESCAPE[ch]; 11910 string = string + (rep || ch); 11911 } 11912 escape = false; 11913 } else if (ch === '\\') { 11914 escape = true; 11915 } else if (ch === quote) { 11916 this.index++; 11917 this.tokens.push({ 11918 index: start, 11919 text: rawString, 11920 constant: true, 11921 value: string 11922 }); 11923 return; 11924 } else { 11925 string += ch; 11926 } 11927 this.index++; 11928 } 11929 this.throwError('Unterminated quote', start); 11930 } 11931}; 11932 11933 11934function isConstant(exp) { 11935 return exp.constant; 11936} 11937 11938/** 11939 * @constructor 11940 */ 11941var Parser = function(lexer, $filter, options) { 11942 this.lexer = lexer; 11943 this.$filter = $filter; 11944 this.options = options; 11945}; 11946 11947Parser.ZERO = extend(function() { 11948 return 0; 11949}, { 11950 sharedGetter: true, 11951 constant: true 11952}); 11953 11954Parser.prototype = { 11955 constructor: Parser, 11956 11957 parse: function(text) { 11958 this.text = text; 11959 this.tokens = this.lexer.lex(text); 11960 11961 var value = this.statements(); 11962 11963 if (this.tokens.length !== 0) { 11964 this.throwError('is an unexpected token', this.tokens[0]); 11965 } 11966 11967 value.literal = !!value.literal; 11968 value.constant = !!value.constant; 11969 11970 return value; 11971 }, 11972 11973 primary: function() { 11974 var primary; 11975 if (this.expect('(')) { 11976 primary = this.filterChain(); 11977 this.consume(')'); 11978 } else if (this.expect('[')) { 11979 primary = this.arrayDeclaration(); 11980 } else if (this.expect('{')) { 11981 primary = this.object(); 11982 } else if (this.peek().identifier && this.peek().text in CONSTANTS) { 11983 primary = CONSTANTS[this.consume().text]; 11984 } else if (this.peek().identifier) { 11985 primary = this.identifier(); 11986 } else if (this.peek().constant) { 11987 primary = this.constant(); 11988 } else { 11989 this.throwError('not a primary expression', this.peek()); 11990 } 11991 11992 var next, context; 11993 while ((next = this.expect('(', '[', '.'))) { 11994 if (next.text === '(') { 11995 primary = this.functionCall(primary, context); 11996 context = null; 11997 } else if (next.text === '[') { 11998 context = primary; 11999 primary = this.objectIndex(primary); 12000 } else if (next.text === '.') { 12001 context = primary; 12002 primary = this.fieldAccess(primary); 12003 } else { 12004 this.throwError('IMPOSSIBLE'); 12005 } 12006 } 12007 return primary; 12008 }, 12009 12010 throwError: function(msg, token) { 12011 throw $parseMinErr('syntax', 12012 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 12013 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 12014 }, 12015 12016 peekToken: function() { 12017 if (this.tokens.length === 0) 12018 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 12019 return this.tokens[0]; 12020 }, 12021 12022 peek: function(e1, e2, e3, e4) { 12023 return this.peekAhead(0, e1, e2, e3, e4); 12024 }, 12025 peekAhead: function(i, e1, e2, e3, e4) { 12026 if (this.tokens.length > i) { 12027 var token = this.tokens[i]; 12028 var t = token.text; 12029 if (t === e1 || t === e2 || t === e3 || t === e4 || 12030 (!e1 && !e2 && !e3 && !e4)) { 12031 return token; 12032 } 12033 } 12034 return false; 12035 }, 12036 12037 expect: function(e1, e2, e3, e4) { 12038 var token = this.peek(e1, e2, e3, e4); 12039 if (token) { 12040 this.tokens.shift(); 12041 return token; 12042 } 12043 return false; 12044 }, 12045 12046 consume: function(e1) { 12047 if (this.tokens.length === 0) { 12048 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 12049 } 12050 12051 var token = this.expect(e1); 12052 if (!token) { 12053 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 12054 } 12055 return token; 12056 }, 12057 12058 unaryFn: function(op, right) { 12059 var fn = OPERATORS[op]; 12060 return extend(function $parseUnaryFn(self, locals) { 12061 return fn(self, locals, right); 12062 }, {
12063 constant:right.constant, 12064 inputs: [right] 12065 }); 12066 }, 12067 12068 binaryFn: function(left, op, right, isBranching) { 12069 var fn = OPERATORS[op]; 12070 return extend(function $parseBinaryFn(self, locals) { 12071 return fn(self, locals, left, right); 12072 }, { 12073 constant: left.constant && right.constant, 12074 inputs: !isBranching && [left, right] 12075 }); 12076 }, 12077 12078 identifier: function() { 12079 var id = this.consume().text; 12080 12081 //Continue reading each `.identifier` unless it is a method invocation 12082 while (this.peek('.') && this.peekAhead(1).identifier && !this.peekAhead(2, '(')) { 12083 id += this.consume().text + this.consume().text; 12084 } 12085 12086 return getterFn(id, this.options, this.text); 12087 }, 12088 12089 constant: function() { 12090 var value = this.consume().value; 12091 12092 return extend(function $parseConstant() { 12093 return value; 12094 }, { 12095 constant: true, 12096 literal: true 12097 }); 12098 }, 12099 12100 statements: function() { 12101 var statements = []; 12102 while (true) { 12103 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 12104 statements.push(this.filterChain()); 12105 if (!this.expect(';')) { 12106 // optimize for the common case where there is only one statement. 12107 // TODO(size): maybe we should not support multiple statements? 12108 return (statements.length === 1) 12109 ? statements[0] 12110 : function $parseStatements(self, locals) { 12111 var value; 12112 for (var i = 0, ii = statements.length; i < ii; i++) { 12113 value = statements[i](self, locals); 12114 } 12115 return value; 12116 }; 12117 } 12118 } 12119 }, 12120 12121 filterChain: function() { 12122 var left = this.expression(); 12123 var token; 12124 while ((token = this.expect('|'))) { 12125 left = this.filter(left); 12126 } 12127 return left; 12128 }, 12129 12130 filter: function(inputFn) { 12131 var fn = this.$filter(this.consume().text); 12132 var argsFn; 12133 var args; 12134 12135 if (this.peek(':')) { 12136 argsFn = []; 12137 args = []; // we can safely reuse the array 12138 while (this.expect(':')) { 12139 argsFn.push(this.expression()); 12140 } 12141 } 12142 12143 var inputs = [inputFn].concat(argsFn || []); 12144 12145 return extend(function $parseFilter(self, locals) { 12146 var input = inputFn(self, locals); 12147 if (args) { 12148 args[0] = input; 12149 12150 var i = argsFn.length; 12151 while (i--) { 12152 args[i + 1] = argsFn[i](self, locals); 12153 } 12154 12155 return fn.apply(undefined, args); 12156 } 12157 12158 return fn(input); 12159 }, { 12160 constant: !fn.$stateful && inputs.every(isConstant), 12161 inputs: !fn.$stateful && inputs 12162 }); 12163 }, 12164 12165 expression: function() { 12166 return this.assignment(); 12167 }, 12168 12169 assignment: function() { 12170 var left = this.ternary(); 12171 var right; 12172 var token; 12173 if ((token = this.expect('='))) { 12174 if (!left.assign) { 12175 this.throwError('implies assignment but [' + 12176 this.text.substring(0, token.index) + '] can not be assigned to', token); 12177 } 12178 right = this.ternary(); 12179 return extend(function $parseAssignment(scope, locals) { 12180 return left.assign(scope, right(scope, locals), locals); 12181 }, { 12182 inputs: [left, right] 12183 }); 12184 } 12185 return left; 12186 }, 12187 12188 ternary: function() { 12189 var left = this.logicalOR(); 12190 var middle; 12191 var token; 12192 if ((token = this.expect('?'))) { 12193 middle = this.assignment(); 12194 if (this.consume(':')) { 12195 var right = this.assignment(); 12196 12197 return extend(function $parseTernary(self, locals) { 12198 return left(self, locals) ? middle(self, locals) : right(self, locals); 12199 }, { 12200 constant: left.constant && middle.constant && right.constant 12201 }); 12202 } 12203 } 12204 12205 return left; 12206 }, 12207 12208 logicalOR: function() { 12209 var left = this.logicalAND(); 12210 var token; 12211 while ((token = this.expect('||'))) { 12212 left = this.binaryFn(left, token.text, this.logicalAND(), true); 12213 } 12214 return left; 12215 }, 12216 12217 logicalAND: function() { 12218 var left = this.equality(); 12219 var token; 12220 while ((token = this.expect('&&'))) { 12221 left = this.binaryFn(left, token.text, this.equality(), true); 12222 } 12223 return left; 12224 }, 12225 12226 equality: function() { 12227 var left = this.relational(); 12228 var token; 12229 while ((token = this.expect('==','!=','===','!=='))) { 12230 left = this.binaryFn(left, token.text, this.relational()); 12231 } 12232 return left; 12233 }, 12234 12235 relational: function() { 12236 var left = this.additive(); 12237 var token; 12238 while ((token = this.expect('<', '>', '<=', '>='))) { 12239 left = this.binaryFn(left, token.text, this.additive()); 12240 } 12241 return left; 12242 }, 12243 12244 additive: function() { 12245 var left = this.multiplicative(); 12246 var token; 12247 while ((token = this.expect('+','-'))) { 12248 left = this.binaryFn(left, token.text, this.multiplicative()); 12249 } 12250 return left; 12251 }, 12252 12253 multiplicative: function() { 12254 var left = this.unary(); 12255 var token; 12256 while ((token = this.expect('*','/','%'))) { 12257 left = this.binaryFn(left, token.text, this.unary()); 12258 } 12259 return left; 12260 }, 12261 12262 unary: function() { 12263 var token; 12264 if (this.expect('+')) { 12265 return this.primary(); 12266 } else if ((token = this.expect('-'))) { 12267 return this.binaryFn(Parser.ZERO, token.text, this.unary()); 12268 } else if ((token = this.expect('!'))) { 12269 return this.unaryFn(token.text, this.unary()); 12270 } else { 12271 return this.primary(); 12272 } 12273 }, 12274 12275 fieldAccess: function(object) { 12276 var getter = this.identifier(); 12277 12278 return extend(function $parseFieldAccess(scope, locals, self) { 12279 var o = self || object(scope, locals); 12280 return (o == null) ? undefined : getter(o); 12281 }, {
12282 assign: function(scope, value, locals) { 12283 var o = object(scope, locals); 12284 if (!o) object.assign(scope, o = {}, locals); 12285 return getter.assign(o, value); 12286 } 12287 }); 12288 }, 12289 12290 objectIndex: function(obj) { 12291 var expression = this.text; 12292 12293 var indexFn = this.expression(); 12294 this.consume(']'); 12295 12296 return extend(function $parseObjectIndex(self, locals) { 12297 var o = obj(self, locals), 12298 i = indexFn(self, locals), 12299 v; 12300 12301 ensureSafeMemberName(i, expression); 12302 if (!o) return undefined; 12303 v = ensureSafeObject(o[i], expression); 12304 return v; 12305 }, { 12306 assign: function(self, value, locals) { 12307 var key = ensureSafeMemberName(indexFn(self, locals), expression); 12308 // prevent overwriting of Function.constructor which would break ensureSafeObject check 12309 var o = ensureSafeObject(obj(self, locals), expression); 12310 if (!o) obj.assign(self, o = {}, locals); 12311 return o[key] = value; 12312 } 12313 }); 12314 }, 12315 12316 functionCall: function(fnGetter, contextGetter) { 12317 var argsFn = []; 12318 if (this.peekToken().text !== ')') { 12319 do { 12320 argsFn.push(this.expression()); 12321 } while (this.expect(',')); 12322 } 12323 this.consume(')'); 12324 12325 var expressionText = this.text; 12326 // we can safely reuse the array across invocations 12327 var args = argsFn.length ? [] : null; 12328 12329 return function $parseFunctionCall(scope, locals) { 12330 var context = contextGetter ? contextGetter(scope, locals) : isDefined(contextGetter) ? undefined : scope; 12331 var fn = fnGetter(scope, locals, context) || noop; 12332 12333 if (args) { 12334 var i = argsFn.length; 12335 while (i--) { 12336 args[i] = ensureSafeObject(argsFn[i](scope, locals), expressionText); 12337 } 12338 } 12339 12340 ensureSafeObject(context, expressionText); 12341 ensureSafeFunction(fn, expressionText); 12342 12343 // IE doesn't have apply for some native functions 12344 var v = fn.apply 12345 ? fn.apply(context, args) 12346 : fn(args[0], args[1], args[2], args[3], args[4]); 12347 12348 if (args) { 12349 // Free-up the memory (arguments of the last function call). 12350 args.length = 0; 12351 } 12352 12353 return ensureSafeObject(v, expressionText); 12354 }; 12355 }, 12356 12357 // This is used with json array declaration 12358 arrayDeclaration: function() { 12359 var elementFns = []; 12360 if (this.peekToken().text !== ']') { 12361 do { 12362 if (this.peek(']')) { 12363 // Support trailing commas per ES5.1. 12364 break; 12365 } 12366 elementFns.push(this.expression()); 12367 } while (this.expect(',')); 12368 } 12369 this.consume(']'); 12370 12371 return extend(function $parseArrayLiteral(self, locals) { 12372 var array = []; 12373 for (var i = 0, ii = elementFns.length; i < ii; i++) { 12374 array.push(elementFns[i](self, locals)); 12375 } 12376 return array; 12377 }, { 12378 literal: true, 12379 constant: elementFns.every(isConstant), 12380 inputs: elementFns 12381 }); 12382 }, 12383 12384 object: function() { 12385 var keys = [], valueFns = []; 12386 if (this.peekToken().text !== '}') { 12387 do { 12388 if (this.peek('}')) { 12389 // Support trailing commas per ES5.1. 12390 break; 12391 } 12392 var token = this.consume(); 12393 if (token.constant) { 12394 keys.push(token.value); 12395 } else if (token.identifier) { 12396 keys.push(token.text); 12397 } else { 12398 this.throwError("invalid key", token); 12399 } 12400 this.consume(':'); 12401 valueFns.push(this.expression()); 12402 } while (this.expect(',')); 12403 } 12404 this.consume('}'); 12405 12406 return extend(function $parseObjectLiteral(self, locals) { 12407 var object = {}; 12408 for (var i = 0, ii = valueFns.length; i < ii; i++) { 12409 object[keys[i]] = valueFns[i](self, locals); 12410 } 12411 return object; 12412 }, { 12413 literal: true, 12414 constant: valueFns.every(isConstant), 12415 inputs: valueFns 12416 }); 12417 } 12418}; 12419 12420 12421////////////////////////////////////////////////// 12422// Parser helper functions 12423////////////////////////////////////////////////// 12424 12425function setter(obj, locals, path, setValue, fullExp) { 12426 ensureSafeObject(obj, fullExp); 12427 ensureSafeObject(locals, fullExp); 12428 12429 var element = path.split('.'), key; 12430 for (var i = 0; element.length > 1; i++) { 12431 key = ensureSafeMemberName(element.shift(), fullExp); 12432 var propertyObj = (i === 0 && locals && locals[key]) || obj[key]; 12433 if (!propertyObj) { 12434 propertyObj = {}; 12435 obj[key] = propertyObj; 12436 } 12437 obj = ensureSafeObject(propertyObj, fullExp); 12438 } 12439 key = ensureSafeMemberName(element.shift(), fullExp); 12440 ensureSafeObject(obj[key], fullExp); 12441 obj[key] = setValue; 12442 return setValue; 12443} 12444 12445var getterFnCacheDefault = createMap(); 12446var getterFnCacheExpensive = createMap(); 12447 12448function isPossiblyDangerousMemberName(name) { 12449 return name == 'constructor'; 12450} 12451 12452/** 12453 * Implementation of the "Black Hole" variant from: 12454 * - http://jsperf.com/angularjs-parse-getter/4 12455 * - http://jsperf.com/path-evaluation-simplified/7 12456 */ 12457function cspSafeGetterFn(key0, key1, key2, key3, key4, fullExp, expensiveChecks) { 12458 ensureSafeMemberName(key0, fullExp); 12459 ensureSafeMemberName(key1, fullExp); 12460 ensureSafeMemberName(key2, fullExp); 12461 ensureSafeMemberName(key3, fullExp); 12462 ensureSafeMemberName(key4, fullExp); 12463 var eso = function(o) { 12464 return ensureSafeObject(o, fullExp); 12465 }; 12466 var eso0 = (expensiveChecks || isPossiblyDangerousMemberName(key0)) ? eso : identity; 12467 var eso1 = (expensiveChecks || isPossiblyDangerousMemberName(key1)) ? eso : identity; 12468 var eso2 = (expensiveChecks || isPossiblyDangerousMemberName(key2)) ? eso : identity; 12469 var eso3 = (expensiveChecks || isPossiblyDangerousMemberName(key3)) ? eso : identity; 12470 var eso4 = (expensiveChecks || isPossiblyDangerousMemberName(key4)) ? eso : identity; 12471 12472 return function cspSafeGetter(scope, locals) { 12473 var pathVal = (locals && locals.hasOwnProperty(key0)) ? locals : scope; 12474 12475 if (pathVal == null) return pathVal; 12476 pathVal = eso0(pathVal[key0]); 12477 12478 if (!key1) return pathVal; 12479 if (pathVal == null) return undefined; 12480 pathVal = eso1(pathVal[key1]); 12481 12482 if (!key2) return pathVal; 12483 if (pathVal == null) return undefined; 12484 pathVal = eso2(pathVal[key2]); 12485 12486 if (!key3) return pathVal; 12487 if (pathVal == null) return undefined; 12488 pathVal = eso3(pathVal[key3]); 12489 12490 if (!key4) return pathVal; 12491 if (pathVal == null) return undefined; 12492 pathVal = eso4(pathVal[key4]); 12493 12494 return pathVal; 12495 }; 12496} 12497 12498function getterFnWithEnsureSafeObject(fn, fullExpression) { 12499 return function(s, l) { 12500 return fn(s, l, ensureSafeObject, fullExpression); 12501 }; 12502} 12503 12504function getterFn(path, options, fullExp) { 12505 var expensiveChecks = options.expensiveChecks; 12506 var getterFnCache = (expensiveChecks ? getterFnCacheExpensive : getterFnCacheDefault); 12507 var fn = getterFnCache[path]; 12508 if (fn) return fn; 12509 12510 12511 var pathKeys = path.split('.'), 12512 pathKeysLength = pathKeys.length; 12513 12514 // http://jsperf.com/angularjs-parse-getter/6 12515 if (options.csp) { 12516 if (pathKeysLength < 6) { 12517 fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp, expensiveChecks); 12518 } else { 12519 fn = function cspSafeGetter(scope, locals) { 12520 var i = 0, val; 12521 do { 12522 val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], 12523 pathKeys[i++], fullExp, expensiveChecks)(scope, locals); 12524 12525 locals = undefined; // clear after first iteration 12526 scope = val; 12527 } while (i < pathKeysLength); 12528 return val; 12529 }; 12530 } 12531 } else { 12532 var code = ''; 12533 if (expensiveChecks) { 12534 code += 's = eso(s, fe);\nl = eso(l, fe);\n'; 12535 } 12536 var needsEnsureSafeObject = expensiveChecks;
12537 forEach(pathKeys, function(key, index) { 12538 ensureSafeMemberName(key, fullExp); 12539 var lookupJs = (index 12540 // we simply dereference 's' on any .dot notation 12541 ? 's' 12542 // but if we are first then we check locals first, and if so read it first 12543 : '((l&&l.hasOwnProperty("' + key + '"))?l:s)') + '.' + key; 12544 if (expensiveChecks || isPossiblyDangerousMemberName(key)) { 12545 lookupJs = 'eso(' + lookupJs + ', fe)'; 12546 needsEnsureSafeObject = true; 12547 } 12548 code += 'if(s == null) return undefined;\n' + 12549 's=' + lookupJs + ';\n'; 12550 }); 12551 code += 'return s;'; 12552 12553 /* jshint -W054 */ 12554 var evaledFnGetter = new Function('s', 'l', 'eso', 'fe', code); // s=scope, l=locals, eso=ensureSafeObject 12555 /* jshint +W054 */ 12556 evaledFnGetter.toString = valueFn(code); 12557 if (needsEnsureSafeObject) { 12558 evaledFnGetter = getterFnWithEnsureSafeObject(evaledFnGetter, fullExp); 12559 } 12560 fn = evaledFnGetter; 12561 } 12562 12563 fn.sharedGetter = true; 12564 fn.assign = function(self, value, locals) { 12565 return setter(self, locals, path, value, path); 12566 }; 12567 getterFnCache[path] = fn; 12568 return fn; 12569} 12570 12571var objectValueOf = Object.prototype.valueOf; 12572 12573function getValueOf(value) { 12574 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 12575} 12576 12577/////////////////////////////////// 12578 12579/** 12580 * @ngdoc service 12581 * @name $parse 12582 * @kind function 12583 * 12584 * @description 12585 * 12586 * Converts Angular {@link guide/expression expression} into a function. 12587 * 12588 * ```js 12589 * var getter = $parse('user.name'); 12590 * var setter = getter.assign; 12591 * var context = {user:{name:'angular'}}; 12592 * var locals = {user:{name:'local'}}; 12593 * 12594 * expect(getter(context)).toEqual('angular'); 12595 * setter(context, 'newValue'); 12596 * expect(context.user.name).toEqual('newValue'); 12597 * expect(getter(context, locals)).toEqual('local'); 12598 * ``` 12599 * 12600 * 12601 * @param {string} expression String expression to compile. 12602 * @returns {function(context, locals)} a function which represents the compiled expression: 12603 * 12604 * * `context` â `{object}` â an object against which any expressions embedded in the strings 12605 * are evaluated against (typically a scope object). 12606 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 12607 * `context`. 12608 * 12609 * The returned function also has the following properties: 12610 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 12611 * literal. 12612 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 12613 * constant literals. 12614 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 12615 * set to a function to change its value on the given context. 12616 * 12617 */ 12618 12619 12620/** 12621 * @ngdoc provider 12622 * @name $parseProvider 12623 * 12624 * @description 12625 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 12626 * service. 12627 */ 12628function $ParseProvider() { 12629 var cacheDefault = createMap(); 12630 var cacheExpensive = createMap(); 12631 12632 12633 12634 this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { 12635 var $parseOptions = { 12636 csp: $sniffer.csp, 12637 expensiveChecks: false 12638 }, 12639 $parseOptionsExpensive = { 12640 csp: $sniffer.csp, 12641 expensiveChecks: true 12642 }; 12643 12644 function wrapSharedExpression(exp) { 12645 var wrapped = exp; 12646 12647 if (exp.sharedGetter) { 12648 wrapped = function $parseWrapper(self, locals) { 12649 return exp(self, locals); 12650 }; 12651 wrapped.literal = exp.literal; 12652 wrapped.constant = exp.constant; 12653 wrapped.assign = exp.assign; 12654 } 12655 12656 return wrapped; 12657 } 12658 12659 return function $parse(exp, interceptorFn, expensiveChecks) { 12660 var parsedExpression, oneTime, cacheKey; 12661 12662 switch (typeof exp) { 12663 case 'string': 12664 cacheKey = exp = exp.trim(); 12665 12666 var cache = (expensiveChecks ? cacheExpensive : cacheDefault); 12667 parsedExpression = cache[cacheKey]; 12668 12669 if (!parsedExpression) { 12670 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 12671 oneTime = true; 12672 exp = exp.substring(2); 12673 } 12674 12675 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; 12676 var lexer = new Lexer(parseOptions); 12677 var parser = new Parser(lexer, $filter, parseOptions); 12678 parsedExpression = parser.parse(exp); 12679 12680 if (parsedExpression.constant) { 12681 parsedExpression.$$watchDelegate = constantWatchDelegate; 12682 } else if (oneTime) { 12683 //oneTime is not part of the exp passed to the Parser so we may have to 12684 //wrap the parsedExpression before adding a $$watchDelegate 12685 parsedExpression = wrapSharedExpression(parsedExpression); 12686 parsedExpression.$$watchDelegate = parsedExpression.literal ? 12687 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; 12688 } else if (parsedExpression.inputs) { 12689 parsedExpression.$$watchDelegate = inputsWatchDelegate; 12690 } 12691 12692 cache[cacheKey] = parsedExpression; 12693 } 12694 return addInterceptor(parsedExpression, interceptorFn); 12695 12696 case 'function': 12697 return addInterceptor(exp, interceptorFn); 12698 12699 default: 12700 return addInterceptor(noop, interceptorFn); 12701 } 12702 }; 12703 12704 function collectExpressionInputs(inputs, list) {
12705 for (var i = 0, ii = inputs.length; i < ii; i++) { 12706 var input = inputs[i]; 12707 if (!input.constant) { 12708 if (input.inputs) { 12709 collectExpressionInputs(input.inputs, list); 12710 } else if (list.indexOf(input) === -1) { // TODO(perf) can we do better? 12711 list.push(input); 12712 } 12713 } 12714 } 12715 12716 return list; 12717 } 12718 12719 function expressionInputDirtyCheck(newValue, oldValueOfValue) { 12720 12721 if (newValue == null || oldValueOfValue == null) { // null/undefined 12722 return newValue === oldValueOfValue; 12723 } 12724 12725 if (typeof newValue === 'object') { 12726 12727 // attempt to convert the value to a primitive type 12728 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 12729 // be cheaply dirty-checked 12730 newValue = getValueOf(newValue); 12731 12732 if (typeof newValue === 'object') { 12733 // objects/arrays are not supported - deep-watching them would be too expensive 12734 return false; 12735 } 12736 12737 // fall-through to the primitive equality check 12738 } 12739 12740 //Primitive or NaN 12741 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 12742 } 12743 12744 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression) { 12745 var inputExpressions = parsedExpression.$$inputs || 12746 (parsedExpression.$$inputs = collectExpressionInputs(parsedExpression.inputs, [])); 12747 12748 var lastResult; 12749 12750 if (inputExpressions.length === 1) { 12751 var oldInputValue = expressionInputDirtyCheck; // init to something unique so that equals check fails 12752 inputExpressions = inputExpressions[0]; 12753 return scope.$watch(function expressionInputWatch(scope) { 12754 var newInputValue = inputExpressions(scope); 12755 if (!expressionInputDirtyCheck(newInputValue, oldInputValue)) { 12756 lastResult = parsedExpression(scope); 12757 oldInputValue = newInputValue && getValueOf(newInputValue); 12758 } 12759 return lastResult; 12760 }, listener, objectEquality); 12761 } 12762 12763 var oldInputValueOfValues = []; 12764 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 12765 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 12766 } 12767 12768 return scope.$watch(function expressionInputsWatch(scope) { 12769 var changed = false; 12770 12771 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 12772 var newInputValue = inputExpressions[i](scope); 12773 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { 12774 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 12775 } 12776 } 12777 12778 if (changed) { 12779 lastResult = parsedExpression(scope); 12780 } 12781 12782 return lastResult; 12783 }, listener, objectEquality); 12784 } 12785 12786 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { 12787 var unwatch, lastValue; 12788 return unwatch = scope.$watch(function oneTimeWatch(scope) { 12789 return parsedExpression(scope); 12790 }, function oneTimeListener(value, old, scope) { 12791 lastValue = value; 12792 if (isFunction(listener)) { 12793 listener.apply(this, arguments); 12794 } 12795 if (isDefined(value)) { 12796 scope.$$postDigest(function() { 12797 if (isDefined(lastValue)) { 12798 unwatch(); 12799 } 12800 }); 12801 } 12802 }, objectEquality); 12803 } 12804 12805 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { 12806 var unwatch, lastValue; 12807 return unwatch = scope.$watch(function oneTimeWatch(scope) { 12808 return parsedExpression(scope); 12809 }, function oneTimeListener(value, old, scope) { 12810 lastValue = value; 12811 if (isFunction(listener)) { 12812 listener.call(this, value, old, scope); 12813 } 12814 if (isAllDefined(value)) { 12815 scope.$$postDigest(function() { 12816 if (isAllDefined(lastValue)) unwatch(); 12817 }); 12818 } 12819 }, objectEquality); 12820 12821 function isAllDefined(value) { 12822 var allDefined = true;
12823 forEach(value, function(val) { 12824 if (!isDefined(val)) allDefined = false; 12825 }); 12826 return allDefined; 12827 } 12828 } 12829 12830 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 12831 var unwatch; 12832 return unwatch = scope.$watch(function constantWatch(scope) { 12833 return parsedExpression(scope); 12834 }, function constantListener(value, old, scope) { 12835 if (isFunction(listener)) { 12836 listener.apply(this, arguments); 12837 } 12838 unwatch(); 12839 }, objectEquality); 12840 } 12841 12842 function addInterceptor(parsedExpression, interceptorFn) { 12843 if (!interceptorFn) return parsedExpression; 12844 var watchDelegate = parsedExpression.$$watchDelegate; 12845 12846 var regularWatch = 12847 watchDelegate !== oneTimeLiteralWatchDelegate && 12848 watchDelegate !== oneTimeWatchDelegate; 12849 12850 var fn = regularWatch ? function regularInterceptedExpression(scope, locals) { 12851 var value = parsedExpression(scope, locals); 12852 return interceptorFn(value, scope, locals); 12853 } : function oneTimeInterceptedExpression(scope, locals) { 12854 var value = parsedExpression(scope, locals); 12855 var result = interceptorFn(value, scope, locals); 12856 // we only return the interceptor's result if the 12857 // initial value is defined (for bind-once) 12858 return isDefined(value) ? result : value; 12859 }; 12860 12861 // Propagate $$watchDelegates other then inputsWatchDelegate 12862 if (parsedExpression.$$watchDelegate && 12863 parsedExpression.$$watchDelegate !== inputsWatchDelegate) { 12864 fn.$$watchDelegate = parsedExpression.$$watchDelegate; 12865 } else if (!interceptorFn.$stateful) { 12866 // If there is an interceptor, but no watchDelegate then treat the interceptor like 12867 // we treat filters - it is assumed to be a pure function unless flagged with $stateful 12868 fn.$$watchDelegate = inputsWatchDelegate; 12869 fn.inputs = [parsedExpression]; 12870 } 12871 12872 return fn; 12873 } 12874 }]; 12875} 12876 12877/** 12878 * @ngdoc service 12879 * @name $q 12880 * @requires $rootScope 12881 * 12882 * @description 12883 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 12884 * when they are done processing. 12885 * 12886 * This is an implementation of promises/deferred objects inspired by 12887 * [Kris Kowal's Q](https://github.com/kriskowal/q). 12888 * 12889 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 12890 * implementations, and the other which resembles ES6 promises to some degree. 12891 * 12892 * # $q constructor 12893 * 12894 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 12895 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony, 12896 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 12897 * 12898 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are 12899 * available yet. 12900 * 12901 * It can be used like so: 12902 * 12903 * ```js 12904 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 12905 * // are available in the current lexical scope (they could have been injected or passed in). 12906 * 12907 * function asyncGreet(name) { 12908 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 12909 * return $q(function(resolve, reject) { 12910 * setTimeout(function() { 12911 * if (okToGreet(name)) { 12912 * resolve('Hello, ' + name + '!'); 12913 * } else { 12914 * reject('Greeting ' + name + ' is not allowed.'); 12915 * } 12916 * }, 1000); 12917 * }); 12918 * } 12919 * 12920 * var promise = asyncGreet('Robin Hood'); 12921 * promise.then(function(greeting) { 12922 * alert('Success: ' + greeting); 12923 * }, function(reason) { 12924 * alert('Failed: ' + reason); 12925 * }); 12926 * ``` 12927 * 12928 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 12929 * 12930 * However, the more traditional CommonJS-style usage is still available, and documented below. 12931 * 12932 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 12933 * interface for interacting with an object that represents the result of an action that is 12934 * performed asynchronously, and may or may not be finished at any given point in t
12934ime. 12935 * 12936 * From the perspective of dealing with error handling, deferred and promise APIs are to 12937 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 12938 * 12939 * ```js 12940 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 12941 * // are available in the current lexical scope (they could have been injected or passed in). 12942 * 12943 * function asyncGreet(name) { 12944 * var deferred = $q.defer(); 12945 * 12946 * setTimeout(function() { 12947 * deferred.notify('About to greet ' + name + '.'); 12948 * 12949 * if (okToGreet(name)) { 12950 * deferred.resolve('Hello, ' + name + '!'); 12951 * } else { 12952 * deferred.reject('Greeting ' + name + ' is not allowed.'); 12953 * } 12954 * }, 1000); 12955 * 12956 * return deferred.promise; 12957 * } 12958 * 12959 * var promise = asyncGreet('Robin Hood'); 12960 * promise.then(function(greeting) { 12961 * alert('Success: ' + greeting); 12962 * }, function(reason) { 12963 * alert('Failed: ' + reason); 12964 * }, function(update) { 12965 * alert('Got notification: ' + update); 12966 * }); 12967 * ``` 12968 * 12969 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 12970 * comes in the way of guarantees that promise and deferred APIs make, see 12971 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 12972 * 12973 * Additionally the promise api allows for composition that is very hard to do with the 12974 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 12975 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 12976 * section on serial or parallel joining of promises. 12977 * 12978 * # The Deferred API 12979 * 12980 * A new instance of deferred is constructed by calling `$q.defer()`. 12981 * 12982 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 12983 * that can be used for signaling the successful or unsuccessful completion, as well as the status 12984 * of the task. 12985 * 12986 * **Methods** 12987 * 12988 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 12989 * constructed via `$q.reject`, the promise will be rejected instead. 12990 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 12991 * resolving it with a rejection constructed via `$q.reject`. 12992 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 12993 * multiple times before the promise is either resolved or rejected. 12994 * 12995 * **Properties** 12996 * 12997 * - promise â `{Promise}` â promise object associated with this deferred. 12998 * 12999 * 13000 * # The Promise API 13001 * 13002 * A new promise instance is created when a deferred instance is created and can be retrieved by 13003 * calling `deferred.promise`. 13004 * 13005 * The purpose of the promise object is to allow for interested parties to get access to the result 13006 * of the deferred task when it completes. 13007 * 13008 * **Methods** 13009 * 13010 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 13011 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 13012 * as soon as the result is available. The callbacks are called with a single argument: the result 13013 * or rejection reason. Additionally, the notify callback may be called zero or more times to 13014 * provide a progress indication, before the promise is resolved or rejected. 13015 * 13016 * This method *returns a new promise* which is resolved or rejected via the return value of the 13017 * `successCallback`, `errorCallback`. It also notifies via the return value of the 13018 * `notifyCallback` method. The promise cannot be resolved or rejected from the notifyCallback 13019 * method. 13020 * 13021 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 13022 * 13023 * - `finally(callback, notifyCallback)` â allows you to observe either the fulfillment or rejection of a promise, 13024 * but to do so without modifying the final value. This is useful to release resources or do some 13025 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 13026 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 13027 * more information. 13028 * 13029 * # Chaining promises 13030 * 13031 * Because calling the `then` method of a promise returns a new derived promise, it is easily 13032 * possible to create a chain of promises: 13033 * 13034 * ```js 13035 * promiseB = promiseA.then(function(result) { 13036 * return result + 1; 13037 * }); 13038 *
13039 * // promiseB will be resolved immediately after promiseA is resolved and its value 13040 * // will be the result of promiseA incremented by 1 13041 * ``` 13042 * 13043 * It is possible to create chains of any length and since a promise can be resolved with another 13044 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 13045 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 13046 * $http's response interceptors. 13047 * 13048 * 13049 * # Differences between Kris Kowal's Q and $q 13050 * 13051 * There are two main differences: 13052 * 13053 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 13054 * mechanism in angular, which means faster propagation of resolution or rejection into your 13055 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 13056 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains 13057 * all the important functionality needed for common async tasks. 13058 * 13059 * # Testing 13060 * 13061 * ```js 13062 * it('should simulate promise', inject(function($q, $rootScope) { 13063 * var deferred = $q.defer(); 13064 * var promise = deferred.promise; 13065 * var resolvedValue; 13066 * 13067 * promise.then(function(value) { resolvedValue = value; }); 13068 * expect(resolvedValue).toBeUndefined(); 13069 * 13070 * // Simulate resolving of promise 13071 * deferred.resolve(123); 13072 * // Note that the 'then' function does not get called synchronously. 13073 * // This is because we want the promise API to always be async, whether or not 13074 * // it got called synchronously or asynchronously. 13075 * expect(resolvedValue).toBeUndefined(); 13076 * 13077 * // Propagate promise resolution to 'then' functions using $apply(). 13078 * $rootScope.$apply(); 13079 * expect(resolvedValue).toEqual(123); 13080 * })); 13081 * ``` 13082 * 13083 * @param {function(function, function)} resolver Function which is responsible for resolving or 13084 * rejecting the newly created promise. The first parameter is a function which resolves the 13085 * promise, the second parameter is a function which rejects the promise. 13086 * 13087 * @returns {Promise} The newly created promise. 13088 */ 13089function $QProvider() { 13090 13091 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 13092 return qFactory(function(callback) { 13093 $rootScope.$evalAsync(callback); 13094 }, $exceptionHandler); 13095 }]; 13096} 13097 13098function $$QProvider() { 13099 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 13100 return qFactory(function(callback) { 13101 $browser.defer(callback); 13102 }, $exceptionHandler); 13103 }]; 13104} 13105 13106/** 13107 * Constructs a promise manager. 13108 * 13109 * @param {function(function)} nextTick Function for executing functions in the next turn. 13110 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 13111 * debugging purposes. 13112 * @returns {object} Promise manager. 13113 */ 13114function qFactory(nextTick, exceptionHandler) { 13115 var $qMinErr = minErr('$q', TypeError); 13116 function callOnce(self, resolveFn, rejectFn) { 13117 var called = false; 13118 function wrap(fn) { 13119 return function(value) { 13120 if (called) return; 13121 called = true; 13122 fn.call(self, value); 13123 }; 13124 } 13125 13126 return [wrap(resolveFn), wrap(rejectFn)]; 13127 } 13128 13129 /** 13130 * @ngdoc method 13131 * @name ng.$q#defer 13132 * @kind function 13133 * 13134 * @description 13135 * Creates a `Deferred` object which represents a task which will finish in the future. 13136 * 13137 * @returns {Deferred} Returns a new instance of deferred. 13138 */ 13139 var defer = function() { 13140 return new Deferred(); 13141 }; 13142 13143 function Promise() { 13144 this.$$state = { status: 0 }; 13145 } 13146 13147 Promise.prototype = { 13148 then: function(onFulfilled, onRejected, progressBack) { 13149 var result = new Deferred(); 13150 13151 this.$$state.pending = this.$$state.pending || []; 13152 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 13153 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 13154 13155 return result.promise; 13156 }, 13157 13158 "catch": function(callback) { 13159 return this.then(null, callback); 13160 }, 13161 13162 "finally": function(callback, progressBack) { 13163 return this.then(function(value) { 13164 return handleCallback(value, true, callback); 13165 }, function(error) { 13166 return handleCallback(error, false, callback); 13167 }, progressBack); 13168 } 13169 }; 13170 13171 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native 13172 function simpleBind(context, fn) { 13173 return function(value) { 13174 fn.call(context, value); 13175 }; 13176 } 13177 13178 function processQueue(state) { 13179 var fn, promise, pending; 13180 13181 pending = state.pending; 13182 state.processScheduled = false;
13183 state.pending = undefined; 13184 for (var i = 0, ii = pending.length; i < ii; ++i) { 13185 promise = pending[i][0]; 13186 fn = pending[i][state.status]; 13187 try { 13188 if (isFunction(fn)) { 13189 promise.resolve(fn(state.value)); 13190 } else if (state.status === 1) { 13191 promise.resolve(state.value); 13192 } else { 13193 promise.reject(state.value); 13194 } 13195 } catch (e) { 13196 promise.reject(e); 13197 exceptionHandler(e); 13198 } 13199 } 13200 } 13201 13202 function scheduleProcessQueue(state) { 13203 if (state.processScheduled || !state.pending) return; 13204 state.processScheduled = true; 13205 nextTick(function() { processQueue(state); }); 13206 } 13207 13208 function Deferred() { 13209 this.promise = new Promise(); 13210 //Necessary to support unbound execution :/ 13211 this.resolve = simpleBind(this, this.resolve); 13212 this.reject = simpleBind(this, this.reject); 13213 this.notify = simpleBind(this, this.notify); 13214 } 13215 13216 Deferred.prototype = { 13217 resolve: function(val) { 13218 if (this.promise.$$state.status) return; 13219 if (val === this.promise) { 13220 this.$$reject($qMinErr( 13221 'qcycle', 13222 "Expected promise to be resolved with value other than itself '{0}'", 13223 val)); 13224 } else { 13225 this.$$resolve(val); 13226 } 13227 13228 }, 13229 13230 $$resolve: function(val) { 13231 var then, fns; 13232 13233 fns = callOnce(this, this.$$resolve, this.$$reject);
13234 try { 13235 if ((isObject(val) || isFunction(val))) then = val && val.then; 13236 if (isFunction(then)) { 13237 this.promise.$$state.status = -1; 13238 then.call(val, fns[0], fns[1], this.notify); 13239 } else { 13240 this.promise.$$state.value = val; 13241 this.promise.$$state.status = 1; 13242 scheduleProcessQueue(this.promise.$$state); 13243 } 13244 } catch (e) { 13245 fns[1](e); 13246 exceptionHandler(e); 13247 } 13248 }, 13249 13250 reject: function(reason) { 13251 if (this.promise.$$state.status) return; 13252 this.$$reject(reason); 13253 }, 13254 13255 $$reject: function(reason) { 13256 this.promise.$$state.value = reason; 13257 this.promise.$$state.status = 2; 13258 scheduleProcessQueue(this.promise.$$state); 13259 }, 13260 13261 notify: function(progress) { 13262 var callbacks = this.promise.$$state.pending; 13263 13264 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { 13265 nextTick(function() { 13266 var callback, result; 13267 for (var i = 0, ii = callbacks.length; i < ii; i++) { 13268 result = callbacks[i][0]; 13269 callback = callbacks[i][3]; 13270 try { 13271 result.notify(isFunction(callback) ? callback(progress) : progress); 13272 } catch (e) { 13273 exceptionHandler(e); 13274 } 13275 } 13276 }); 13277 } 13278 } 13279 }; 13280 13281 /** 13282 * @ngdoc method 13283 * @name $q#reject 13284 * @kind function 13285 * 13286 * @description 13287 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 13288 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 13289 * a promise chain, you don't need to worry about it. 13290 * 13291 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 13292 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 13293 * a promise error callback and you want to forward the error to the promise derived from the 13294 * current promise, you have to "rethrow" the error by returning a rejection constructed via 13295 * `reject`. 13296 * 13297 * ```js 13298 * promiseB = promiseA.then(function(result) { 13299 * // success: do something and resolve promiseB 13300 * // with the old or a new result 13301 * return result; 13302 * }, function(reason) { 13303 * // error: handle the error if possible and 13304 * // resolve promiseB with newPromiseOrValue, 13305 * // otherwise forward the rejection to promiseB 13306 * if (canHandle(reason)) { 13307 * // handle the error and recover 13308 * return newPromiseOrValue; 13309 * } 13310 * return $q.reject(reason); 13311 * }); 13312 * ``` 13313 * 13314 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 13315 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 13316 */ 13317 var reject = function(reason) { 13318 var result = new Deferred(); 13319 result.reject(reason); 13320 return result.promise; 13321 }; 13322 13323 var makePromise = function makePromise(value, resolved) { 13324 var result = new Deferred(); 13325 if (resolved) { 13326 result.resolve(value); 13327 } else { 13328 result.reject(value); 13329 } 13330 return result.promise; 13331 }; 13332 13333 var handleCallback = function handleCallback(value, isResolved, callback) { 13334 var callbackOutput = null; 13335 try { 13336 if (isFunction(callback)) callbackOutput = callback(); 13337 } catch (e) { 13338 return makePromise(e, false); 13339 } 13340 if (isPromiseLike(callbackOutput)) { 13341 return callbackOutput.then(function() { 13342 return makePromise(value, isResolved); 13343 }, function(error) { 13344 return makePromise(error, false); 13345 }); 13346 } else { 13347 return makePromise(value, isResolved); 13348 } 13349 }; 13350 13351 /** 13352 * @ngdoc method 13353 * @name $q#when 13354 * @kind function 13355 * 13356 * @description 13357 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 13358 * This is useful when you are dealing with an object that might or might not be a promise, or if 13359 * the promise comes from a source that can't be trusted. 13360 * 13361 * @param {*} value Value or a promise 13362 * @returns {Promise} Returns a promise of the passed value or promise 13363 */ 13364 13365 13366 var when = function(value, callback, errback, progressBack) { 13367 var result = new Deferred(); 13368 result.resolve(value); 13369 return result.promise.then(callback, errback, progressBack); 13370 }; 13371 13372 /** 13373 * @ngdoc method 13374 * @name $q#all 13375 * @kind function 13376 * 13377 * @description 13378 * Combines multiple promises into a single promise that is resolved when all of the input 13379 * promises are resolved. 13380 * 13381 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 13382 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 13383 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 13384 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 13385 * with the same rejection value. 13386 */ 13387 13388 function all(promises) { 13389 var deferred = new Deferred(), 13390 counter = 0, 13391 results = isArray(promises) ? [] : {}; 13392
13393 forEach(promises, function(promise, key) { 13394 counter++; 13395 when(promise).then(function(value) { 13396 if (results.hasOwnProperty(key)) return; 13397 results[key] = value; 13398 if (!(--counter)) deferred.resolve(results); 13399 }, function(reason) { 13400 if (results.hasOwnProperty(key)) return; 13401 deferred.reject(reason); 13402 }); 13403 }); 13404 13405 if (counter === 0) { 13406 deferred.resolve(results); 13407 } 13408 13409 return deferred.promise; 13410 } 13411 13412 var $Q = function Q(resolver) { 13413 if (!isFunction(resolver)) { 13414 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); 13415 } 13416 13417 if (!(this instanceof Q)) { 13418 // More useful when $Q is the Promise itself. 13419 return new Q(resolver); 13420 } 13421 13422 var deferred = new Deferred(); 13423 13424 function resolveFn(value) { 13425 deferred.resolve(value); 13426 } 13427 13428 function rejectFn(reason) { 13429 deferred.reject(reason); 13430 } 13431 13432 resolver(resolveFn, rejectFn); 13433 13434 return deferred.promise; 13435 }; 13436 13437 $Q.defer = defer; 13438 $Q.reject = reject; 13439 $Q.when = when; 13440 $Q.all = all; 13441 13442 return $Q; 13443} 13444 13445function $$RAFProvider() { //rAF 13446 this.$get = ['$window', '$timeout', function($window, $timeout) { 13447 var requestAnimationFrame = $window.requestAnimationFrame || 13448 $window.webkitRequestAnimationFrame; 13449 13450 var cancelAnimationFrame = $window.cancelAnimationFrame || 13451 $window.webkitCancelAnimationFrame || 13452 $window.webkitCancelRequestAnimationFrame; 13453 13454 var rafSupported = !!requestAnimationFrame; 13455 var raf = rafSupported 13456 ? function(fn) { 13457 var id = requestAnimationFrame(fn); 13458 return function() { 13459 cancelAnimationFrame(id); 13460 }; 13461 } 13462 : function(fn) { 13463 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 13464 return function() { 13465 $timeout.cancel(timer); 13466 }; 13467 }; 13468 13469 raf.supported = rafSupported; 13470 13471 return raf; 13472 }]; 13473} 13474 13475/** 13476 * DESIGN NOTES 13477 * 13478 * The design decisions behind the scope are heavily favored for speed and memory consumption. 13479 * 13480 * The typical use of scope is to watch the expressions, which most of the time return the same 13481 * value as last time so we optimize the operation. 13482 * 13483 * Closures construction is expensive in terms of speed as well as memory: 13484 * - No closures, instead use prototypical inheritance for API 13485 * - Internal state needs to be stored on scope directly, which means that private state is 13486 * exposed as $$____ properties 13487 * 13488 * Loop operations are optimized by using while(count--) { ... } 13489 * - this means that in order to keep the same order of execution as addition we have to add 13490 * items to the array at the beginning (unshift) instead of at the end (push) 13491 * 13492 * Child scopes are created and removed often 13493 * - Using an array would be slow since inserts in middle are expensive so we use linked list 13494 * 13495 * There are few watches then a lot of observers. This is why you don't want the observer to be 13496 * implemented in the same way as watch. Watch requires return of initialization function which 13497 * are expensive to construct. 13498 */ 13499 13500 13501/** 13502 * @ngdoc provider 13503 * @name $rootScopeProvider 13504 * @description 13505 * 13506 * Provider for the $rootScope service. 13507 */ 13508 13509/** 13510 * @ngdoc method 13511 * @name $rootScopeProvider#digestTtl 13512 * @description 13513 * 13514 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 13515 * assuming that the model is unstable. 13516 * 13517 * The current default is 10 iterations. 13518 * 13519 * In complex applications it's possible that the dependencies between `$watch`s will result in 13520 * several digest iterations. However if an application needs more than the default 10 digest 13521 * iterations for its model to stabilize then you should investigate what is causing the model to 13522 * continuously change during the digest. 13523 * 13524 * Increasing the TTL could have performance implications, so you should not change it without 13525 * proper justification. 13526 * 13527 * @param {number} limit The number of digest iterations. 13528 */ 13529 13530 13531/** 13532 * @ngdoc service 13533 * @name $rootScope 13534 * @description 13535 * 13536 * Every application has a single root {@link ng.$rootScope.Scope scope}. 13537 * All other scopes are descendant scopes of the root scope. Scopes provide separation 13538 * between the model and the view, via a mechanism for watching the model for changes. 13539 * They also provide an event emission/broadcast and subscription facility. See the 13540 * {@link guide/scope developer guide on scopes}. 13541 */ 13542function $RootScopeProvider() { 13543 var TTL = 10; 13544 var $rootScopeMinErr = minErr('$rootScope'); 13545 var lastDirtyWatch = null; 13546 var applyAsyncId = null; 13547 13548 this.digestTtl = function(value) { 13549 if (arguments.length) { 13550 TTL = value; 13551 } 13552 return TTL; 13553 }; 13554 13555 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 13556 function($injector, $exceptionHandler, $parse, $browser) { 13557 13558 /** 13559 * @ngdoc type 13560 * @name $rootScope.Scope 13561 *
13562 * @description 13563 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 13564 * {@link auto.$injector $injector}. Child scopes are created using the 13565 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 13566 * compiled HTML template is executed.) 13567 * 13568 * Here is a simple scope snippet to show how you can interact with the scope. 13569 * ```html 13570 * <file src="./test/ng/rootScopeSpec.js" tag="docs1" /> 13571 * ``` 13572 * 13573 * # Inheritance 13574 * A scope can inherit from a parent scope, as in this example: 13575 * ```js 13576 var parent = $rootScope; 13577 var child = parent.$new(); 13578 13579 parent.salutation = "Hello"; 13580 expect(child.salutation).toEqual('Hello'); 13581 13582 child.salutation = "Welcome"; 13583 expect(child.salutation).toEqual('Welcome'); 13584 expect(parent.salutation).toEqual('Hello'); 13585 * ``` 13586 * 13587 * When interacting with `Scope` in tests, additional helper methods are available on the 13588 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 13589 * details. 13590 * 13591 * 13592 * @param {Object.<string, function()>=} providers Map of service factory which need to be 13593 * provided for the current scope. Defaults to {@link ng}. 13594 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 13595 * append/override services provided by `providers`. This is handy 13596 * when unit-testing and having the need to override a default 13597 * service. 13598 * @returns {Object} Newly created scope. 13599 * 13600 */ 13601 function Scope() { 13602 this.$id = nextUid(); 13603 this.$$phase = this.$parent = this.$$watchers = 13604 this.$$nextSibling = this.$$prevSibling = 13605 this.$$childHead = this.$$childTail = null; 13606 this.$root = this; 13607 this.$$destroyed = false; 13608 this.$$listeners = {}; 13609 this.$$listenerCount = {}; 13610 this.$$isolateBindings = null; 13611 } 13612 13613 /** 13614 * @ngdoc property 13615 * @name $rootScope.Scope#$id 13616 * 13617 * @description 13618 * Unique scope ID (monotonically increasing) useful for debugging. 13619 */ 13620 13621 /** 13622 * @ngdoc property 13623 * @name $rootScope.Scope#$parent 13624 * 13625 * @description 13626 * Reference to the parent scope. 13627 */ 13628 13629 /** 13630 * @ngdoc property 13631 * @name $rootScope.Scope#$root 13632 * 13633 * @description 13634 * Reference to the root scope. 13635 */ 13636 13637 Scope.prototype = { 13638 constructor: Scope, 13639 /** 13640 * @ngdoc method 13641 * @name $rootScope.Scope#$new 13642 * @kind function 13643 * 13644 * @description 13645 * Creates a new child {@link ng.$rootScope.Scope scope}. 13646 * 13647 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 13648 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 13649 * 13650 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 13651 * desired for the scope and its child scopes to be permanently detached from the parent and 13652 * thus stop participating in model change detection and listener notification by invoking. 13653 * 13654 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the 13655 * parent scope. The scope is isolated, as it can not see parent scope properties. 13656 * When creating widgets, it is useful for the widget to not accidentally read parent 13657 * state. 13658 * 13659 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 13660 * of the newly created scope. Defaults to `this` scope if not provided. 13661 * This is used when creating a transclude scope to correctly place it 13662 * in the scope hierarchy while maintaining the correct prototypical 13663 * inheritance. 13664 * 13665 * @returns {Object} The newly created child scope. 13666 * 13667 */ 13668 $new: function(isolate, parent) { 13669 var child; 13670 13671 parent = parent || this; 13672 13673 if (isolate) { 13674 child = new Scope(); 13675 child.$root = this.$root; 13676 } else { 13677 // Only create a child scope class if somebody asks for one, 13678 // but cache it to allow the VM to optimize lookups. 13679 if (!this.$$ChildScope) { 13680 this.$$ChildScope = function ChildScope() {
13681 this.$$watchers = this.$$nextSibling = 13682 this.$$childHead = this.$$childTail = null; 13683 this.$$listeners = {}; 13684 this.$$listenerCount = {}; 13685 this.$id = nextUid(); 13686 this.$$ChildScope = null; 13687 }; 13688 this.$$ChildScope.prototype = this; 13689 } 13690 child = new this.$$ChildScope(); 13691 } 13692 child.$parent = parent; 13693 child.$$prevSibling = parent.$$childTail; 13694 if (parent.$$childHead) { 13695 parent.$$childTail.$$nextSibling = child; 13696 parent.$$childTail = child; 13697 } else { 13698 parent.$$childHead = parent.$$childTail = child; 13699 } 13700 13701 // When the new scope is not isolated or we inherit from `this`, and 13702 // the parent scope is destroyed, the property `$$destroyed` is inherited 13703 // prototypically. In all other cases, this property needs to be set 13704 // when the parent scope is destroyed. 13705 // The listener needs to be added after the parent is set 13706 if (isolate || parent != this) child.$on('$destroy', destroyChild); 13707 13708 return child; 13709 13710 function destroyChild() { 13711 child.$$destroyed = true; 13712 } 13713 }, 13714 13715 /** 13716 * @ngdoc method 13717 * @name $rootScope.Scope#$watch 13718 * @kind function 13719 * 13720 * @description 13721 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 13722 * 13723 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 13724 * $digest()} and should return the value that will be watched. (Since 13725 * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the 13726 * `watchExpression` can execute multiple times per 13727 * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) 13728 * - The `listener` is called only when the value from the current `watchExpression` and the 13729 * previous call to `watchExpression` are not equal (with the exception of the initial run, 13730 * see below). Inequality is determined according to reference inequality, 13731 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 13732 * via the `!==` Javascript operator, unless `objectEquality == true` 13733 * (see next point) 13734 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 13735 * according to the {@link angular.equals} function. To save the value of the object for 13736 * later comparison, the {@link angular.copy} function is used. This therefore means that 13737 * watching complex objects will have adverse memory and performance implications. 13738 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 13739 * This is achieved by rerunning the watchers until no changes are detected. The rerun 13740 * iteration limit is 10 to prevent an infinite loop deadlock. 13741 * 13742 * 13743 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 13744 * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` 13745 * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a 13746 * change is detected, be prepared for multiple calls to your listener.) 13747 * 13748 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 13749 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 13750 * watcher. In rare cases, this is undesirable because the listener is called when the result 13751 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 13752 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 13753 * listener was called due to initialization. 13754 * 13755 * 13756 * 13757 * # Example 13758 * ```js 13759 // let's assume that scope was dependency injected as the $rootScope 13760 var scope = $rootScope; 13761 scope.name = 'misko'; 13762 scope.counter = 0; 13763 13764 expect(scope.counter).toEqual(0); 13765 scope.$watch('name', function(newValue, oldValue) { 13766 scope.counter = scope.counter + 1; 13767 }); 13768 expect(scope.counter).toEqual(0); 13769 13770 scope.$digest(); 13771 // the listener is always called during the first $digest loop after it was registered 13772 expect(scope.counter).toEqual(1); 13773 13774 scope.$digest(); 13775 // but now it will not be called unless the value changes 13776 expect(scope.counter).toEqual(1); 13777 13778 scope.name = 'adam'; 13779 scope.$digest(); 13780 expect(scope.counter).toEqual(2); 13781 13782 13783
13784 // Using a function as a watchExpression 13785 var food; 13786 scope.foodCounter = 0; 13787 expect(scope.foodCounter).toEqual(0); 13788 scope.$watch( 13789 // This function returns the value being watched. It is called for each turn of the $digest loop 13790 function() { return food; }, 13791 // This is the change listener, called when the value returned from the above function changes 13792 function(newValue, oldValue) { 13793 if ( newValue !== oldValue ) { 13794 // Only increment the counter if the value changed 13795 scope.foodCounter = scope.foodCounter + 1; 13796 } 13797 } 13798 ); 13799 // No digest has been run so the counter will be zero 13800 expect(scope.foodCounter).toEqual(0); 13801 13802 // Run the digest but since food has not changed count will still be zero 13803 scope.$digest(); 13804 expect(scope.foodCounter).toEqual(0); 13805 13806 // Update food and run digest. Now the counter will increment 13807 food = 'cheeseburger'; 13808 scope.$digest(); 13809 expect(scope.foodCounter).toEqual(1); 13810 13811 * ``` 13812 * 13813 * 13814 * 13815 * @param {(function()|string)} watchExpression Expression that is evaluated on each 13816 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 13817 * a call to the `listener`. 13818 * 13819 * - `string`: Evaluated as {@link guide/expression expression} 13820 * - `function(scope)`: called with current `scope` as a parameter. 13821 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 13822 * of `watchExpression` changes. 13823 * 13824 * - `newVal` contains the current value of the `watchExpression` 13825 * - `oldVal` contains the previous value of the `watchExpression` 13826 * - `scope` refers to the current scope 13827 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of 13828 * comparing for reference equality. 13829 * @returns {function()} Returns a deregistration function for this listener. 13830 */ 13831 $watch: function(watchExp, listener, objectEquality) { 13832 var get = $parse(watchExp); 13833 13834 if (get.$$watchDelegate) { 13835 return get.$$watchDelegate(this, listener, objectEquality, get); 13836 } 13837 var scope = this, 13838 array = scope.$$watchers, 13839 watcher = { 13840 fn: listener, 13841 last: initWatchVal, 13842 get: get, 13843 exp: watchExp, 13844 eq: !!objectEquality 13845 }; 13846 13847 lastDirtyWatch = null; 13848 13849 if (!isFunction(listener)) { 13850 watcher.fn = noop; 13851 } 13852 13853 if (!array) { 13854 array = scope.$$watchers = []; 13855 } 13856 // we use unshift since we use a while loop in $digest for speed. 13857 // the while loop reads in reverse order. 13858 array.unshift(watcher); 13859 13860 return function deregisterWatch() { 13861 arrayRemove(array, watcher); 13862 lastDirtyWatch = null; 13863 }; 13864 }, 13865 13866 /** 13867 * @ngdoc method 13868 * @name $rootScope.Scope#$watchGroup 13869 * @kind function 13870 * 13871 * @description 13872 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 13873 * If any one expression in the collection changes the `listener` is executed. 13874 * 13875 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every 13876 * call to $digest() to see if any items changes. 13877 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 13878 * 13879 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 13880 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 13881 * 13882 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 13883 * expression in `watchExpressions` changes 13884 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 13885 * those of `watchExpression` 13886 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 13887 * those of `watchExpression` 13888 * The `scope` refers to the current scope. 13889 * @returns {function()} Returns a de-registration function for all listeners. 13890 */ 13891 $watchGroup: function(watchExpressions, listener) { 13892 var oldValues = new Array(watchExpressions.length); 13893 var newValues = new Array(watchExpressions.length); 13894 var deregisterFns = []; 13895 var self = this; 13896 var changeReactionScheduled = false;
vendor: 10,180 bytes, lines 13897-14159
13897 var firstRun = true; 13898 13899 if (!watchExpressions.length) { 13900 // No expressions means we call the listener ASAP 13901 var shouldCall = true; 13902 self.$evalAsync(function() { 13903 if (shouldCall) listener(newValues, newValues, self); 13904 }); 13905 return function deregisterWatchGroup() { 13906 shouldCall = false; 13907 }; 13908 } 13909 13910 if (watchExpressions.length === 1) { 13911 // Special case size of one 13912 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 13913 newValues[0] = value; 13914 oldValues[0] = oldValue; 13915 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 13916 }); 13917 } 13918 13919 forEach(watchExpressions, function(expr, i) { 13920 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { 13921 newValues[i] = value; 13922 oldValues[i] = oldValue; 13923 if (!changeReactionScheduled) { 13924 changeReactionScheduled = true; 13925 self.$evalAsync(watchGroupAction); 13926 } 13927 }); 13928 deregisterFns.push(unwatchFn); 13929 }); 13930 13931 function watchGroupAction() { 13932 changeReactionScheduled = false; 13933 13934 if (firstRun) { 13935 firstRun = false; 13936 listener(newValues, newValues, self); 13937 } else { 13938 listener(newValues, oldValues, self); 13939 } 13940 } 13941 13942 return function deregisterWatchGroup() { 13943 while (deregisterFns.length) { 13944 deregisterFns.shift()(); 13945 } 13946 }; 13947 }, 13948 13949 13950 /** 13951 * @ngdoc method 13952 * @name $rootScope.Scope#$watchCollection 13953 * @kind function 13954 * 13955 * @description 13956 * Shallow watches the properties of an object and fires whenever any of the properties change 13957 * (for arrays, this implies watching the array items; for object maps, this implies watching 13958 * the properties). If a change is detected, the `listener` callback is fired. 13959 * 13960 * - The `obj` collection is observed via standard $watch operation and is examined on every 13961 * call to $digest() to see if any items have been added, removed, or moved. 13962 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 13963 * adding, removing, and moving items belonging to an object or array. 13964 * 13965 * 13966 * # Example 13967 * ```js 13968 $scope.names = ['igor', 'matias', 'misko', 'james']; 13969 $scope.dataCount = 4; 13970 13971 $scope.$watchCollection('names', function(newNames, oldNames) { 13972 $scope.dataCount = newNames.length; 13973 }); 13974 13975 expect($scope.dataCount).toEqual(4); 13976 $scope.$digest(); 13977 13978 //still at 4 ... no changes 13979 expect($scope.dataCount).toEqual(4); 13980 13981 $scope.names.pop(); 13982 $scope.$digest(); 13983 13984 //now there's been a change 13985 expect($scope.dataCount).toEqual(3); 13986 * ``` 13987 * 13988 * 13989 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 13990 * expression value should evaluate to an object or an array which is observed on each 13991 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 13992 * collection will trigger a call to the `listener`. 13993 * 13994 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 13995 * when a change is detected. 13996 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 13997 * - The `oldCollection` object is a copy of the former collection data. 13998 * Due to performance considerations, the`oldCollection` value is computed only if the 13999 * `listener` function declares two or more arguments. 14000 * - The `scope` argument refers to the current scope. 14001 * 14002 * @returns {function()} Returns a de-registration function for this listener. When the 14003 * de-registration function is executed, the internal watch operation is terminated. 14004 */ 14005 $watchCollection: function(obj, listener) { 14006 $watchCollectionInterceptor.$stateful = true; 14007 14008 var self = this; 14009 // the current value, updated on each dirty-check run 14010 var newValue; 14011 // a shallow copy of the newValue from the last dirty-check run, 14012 // updated to match newValue during dirty-check run 14013 var oldValue; 14014 // a shallow copy of the newValue from when the last change happened 14015 var veryOldValue; 14016 // only track veryOldValue if the listener is asking for it 14017 var trackVeryOldValue = (listener.length > 1); 14018 var changeDetected = 0; 14019 var changeDetector = $parse(obj, $watchCollectionInterceptor); 14020 var internalArray = []; 14021 var internalObject = {}; 14022 var initRun = true; 14023 var oldLength = 0; 14024 14025 function $watchCollectionInterceptor(_value) { 14026 newValue = _value; 14027 var newLength, key, bothNaN, newItem, oldItem; 14028 14029 // If the new value is undefined, then return undefined as the watch may be a one-time watch 14030 if (isUndefined(newValue)) return; 14031 14032 if (!isObject(newValue)) { // if primitive 14033 if (oldValue !== newValue) { 14034 oldValue = newValue; 14035 changeDetected++; 14036 } 14037 } else if (isArrayLike(newValue)) { 14038 if (oldValue !== internalArray) { 14039 // we are transitioning from something which was not an array into array. 14040 oldValue = internalArray; 14041 oldLength = oldValue.length = 0; 14042 changeDetected++; 14043 } 14044 14045 newLength = newValue.length; 14046 14047 if (oldLength !== newLength) { 14048 // if lengths do not match we need to trigger change notification 14049 changeDetected++; 14050 oldValue.length = oldLength = newLength; 14051 } 14052 // copy the items to oldValue and look for changes. 14053 for (var i = 0; i < newLength; i++) { 14054 oldItem = oldValue[i]; 14055 newItem = newValue[i]; 14056 14057 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 14058 if (!bothNaN && (oldItem !== newItem)) { 14059 changeDetected++; 14060 oldValue[i] = newItem; 14061 } 14062 } 14063 } else { 14064 if (oldValue !== internalObject) { 14065 // we are transitioning from something which was not an object into object. 14066 oldValue = internalObject = {}; 14067 oldLength = 0; 14068 changeDetected++; 14069 } 14070 // copy the items to oldValue and look for changes. 14071 newLength = 0; 14072 for (key in newValue) { 14073 if (newValue.hasOwnProperty(key)) { 14074 newLength++; 14075 newItem = newValue[key]; 14076 oldItem = oldValue[key]; 14077 14078 if (key in oldValue) { 14079 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 14080 if (!bothNaN && (oldItem !== newItem)) { 14081 changeDetected++; 14082 oldValue[key] = newItem; 14083 } 14084 } else { 14085 oldLength++; 14086 oldValue[key] = newItem; 14087 changeDetected++; 14088 } 14089 } 14090 } 14091 if (oldLength > newLength) { 14092 // we used to have more keys, need to find them and destroy them. 14093 changeDetected++; 14094 for (key in oldValue) { 14095 if (!newValue.hasOwnProperty(key)) { 14096 oldLength--; 14097 delete oldValue[key]; 14098 } 14099 } 14100 } 14101 } 14102 return changeDetected; 14103 } 14104 14105 function $watchCollectionAction() { 14106 if (initRun) { 14107 initRun = false; 14108 listener(newValue, newValue, self); 14109 } else { 14110 listener(newValue, veryOldValue, self); 14111 } 14112 14113 // make a copy for the next time a collection is changed 14114 if (trackVeryOldValue) { 14115 if (!isObject(newValue)) { 14116 //primitive 14117 veryOldValue = newValue; 14118 } else if (isArrayLike(newValue)) { 14119 veryOldValue = new Array(newValue.length); 14120 for (var i = 0; i < newValue.length; i++) { 14121 veryOldValue[i] = newValue[i]; 14122 } 14123 } else { // if object 14124 veryOldValue = {}; 14125 for (var key in newValue) { 14126 if (hasOwnProperty.call(newValue, key)) { 14127 veryOldValue[key] = newValue[key]; 14128 } 14129 } 14130 } 14131 } 14132 } 14133 14134 return this.$watch(changeDetector, $watchCollectionAction); 14135 }, 14136 14137 /** 14138 * @ngdoc method 14139 * @name $rootScope.Scope#$digest 14140 * @kind function 14141 * 14142 * @description 14143 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 14144 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 14145 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 14146 * until no more listeners are firing. This means that it is possible to get into an infinite 14147 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 14148 * iterations exceeds 10. 14149 * 14150 * Usually, you don't call `$digest()` directly in 14151 * {@link ng.directive:ngController controllers} or in 14152 * {@link ng.$compileProvider#directive directives}. 14153 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 14154 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 14155 * 14156 * If you want to be notified whenever `$digest()` is called, 14157 * you can register a `watchExpression` function with 14158 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 14159 *
14160 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 14161 * 14162 * # Example 14163 * ```js 14164 var scope = ...; 14165 scope.name = 'misko'; 14166 scope.counter = 0; 14167 14168 expect(scope.counter).toEqual(0); 14169 scope.$watch('name', function(newValue, oldValue) { 14170 scope.counter = scope.counter + 1; 14171 }); 14172 expect(scope.counter).toEqual(0); 14173 14174 scope.$digest(); 14175 // the listener is always called during the first $digest loop after it was registered 14176 expect(scope.counter).toEqual(1); 14177 14178 scope.$digest(); 14179 // but now it will not be called unless the value changes 14180 expect(scope.counter).toEqual(1); 14181 14182 scope.name = 'adam'; 14183 scope.$digest(); 14184 expect(scope.counter).toEqual(2); 14185 * ``` 14186 * 14187 */ 14188 $digest: function() { 14189 var watch, value, last, 14190 watchers, 14191 length, 14192 dirty, ttl = TTL, 14193 next, current, target = this, 14194 watchLog = [], 14195 logIdx, logMsg, asyncTask; 14196 14197 beginPhase('$digest'); 14198 // Check for changes to browser url that happened in sync before the call to $digest 14199 $browser.$$checkUrlChange(); 14200 14201 if (this === $rootScope && applyAsyncId !== null) { 14202 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 14203 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 14204 $browser.defer.cancel(applyAsyncId); 14205 flushApplyAsync(); 14206 } 14207 14208 lastDirtyWatch = null; 14209 14210 do { // "while dirty" loop 14211 dirty = false; 14212 current = target; 14213 14214 while (asyncQueue.length) { 14215 try { 14216 asyncTask = asyncQueue.shift(); 14217 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); 14218 } catch (e) { 14219 $exceptionHandler(e); 14220 } 14221 lastDirtyWatch = null; 14222 } 14223 14224 traverseScopesLoop: 14225 do { // "traverse the scopes" loop 14226 if ((watchers = current.$$watchers)) { 14227 // process our watches 14228 length = watchers.length; 14229 while (length--) { 14230 try { 14231 watch = watchers[length]; 14232 // Most common watches are on primitives, in which case we can short 14233 // circuit it with === operator, only when === fails do we use .equals 14234 if (watch) { 14235 if ((value = watch.get(current)) !== (last = watch.last) && 14236 !(watch.eq 14237 ? equals(value, last) 14238 : (typeof value === 'number' && typeof last === 'number' 14239 && isNaN(value) && isNaN(last)))) { 14240 dirty = true; 14241 lastDirtyWatch = watch; 14242 watch.last = watch.eq ? copy(value, null) : value; 14243 watch.fn(value, ((last === initWatchVal) ? value : last), current); 14244 if (ttl < 5) { 14245 logIdx = 4 - ttl; 14246 if (!watchLog[logIdx]) watchLog[logIdx] = []; 14247 watchLog[logIdx].push({ 14248 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 14249 newVal: value, 14250 oldVal: last 14251 }); 14252 } 14253 } else if (watch === lastDirtyWatch) { 14254 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 14255 // have already been tested. 14256 dirty = false; 14257 break traverseScopesLoop; 14258 } 14259 } 14260 } catch (e) { 14261 $exceptionHandler(e); 14262 } 14263 } 14264 } 14265 14266 // Insanity Warning: scope depth-first traversal 14267 // yes, this code is a bit crazy, but it works and we have tests to prove it! 14268 // this piece should be kept in sync with the traversal in $broadcast 14269 if (!(next = (current.$$childHead || 14270 (current !== target && current.$$nextSibling)))) { 14271 while (current !== target && !(next = current.$$nextSibling)) { 14272 current = current.$parent; 14273 } 14274 } 14275 } while ((current = next)); 14276 14277 // `break traverseScopesLoop;` takes us to here 14278 14279 if ((dirty || asyncQueue.length) && !(ttl--)) { 14280 clearPhase(); 14281 throw $rootScopeMinErr('infdig', 14282 '{0} $digest() iterations reached. Aborting!\n' + 14283 'Watchers fired in the last 5 iterations: {1}', 14284 TTL, watchLog); 14285 } 14286 14287 }
14287 while (dirty || asyncQueue.length); 14288 14289 clearPhase(); 14290 14291 while (postDigestQueue.length) { 14292 try { 14293 postDigestQueue.shift()(); 14294 } catch (e) { 14295 $exceptionHandler(e); 14296 } 14297 } 14298 }, 14299 14300 14301 /** 14302 * @ngdoc event 14303 * @name $rootScope.Scope#$destroy 14304 * @eventType broadcast on scope being destroyed 14305 * 14306 * @description 14307 * Broadcasted when a scope and its children are being destroyed. 14308 * 14309 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 14310 * clean up DOM bindings before an element is removed from the DOM. 14311 */ 14312 14313 /** 14314 * @ngdoc method 14315 * @name $rootScope.Scope#$destroy 14316 * @kind function 14317 * 14318 * @description 14319 * Removes the current scope (and all of its children) from the parent scope. Removal implies 14320 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 14321 * propagate to the current scope and its children. Removal also implies that the current 14322 * scope is eligible for garbage collection. 14323 * 14324 * The `$destroy()` is usually used by directives such as 14325 * {@link ng.directive:ngRepeat ngRepeat} for managing the 14326 * unrolling of the loop. 14327 * 14328 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 14329 * Application code can register a `$destroy` event handler that will give it a chance to 14330 * perform any necessary cleanup. 14331 * 14332 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 14333 * clean up DOM bindings before an element is removed from the DOM. 14334 */ 14335 $destroy: function() { 14336 // we can't destroy the root scope or a scope that has been already destroyed 14337 if (this.$$destroyed) return; 14338 var parent = this.$parent; 14339 14340 this.$broadcast('$destroy'); 14341 this.$$destroyed = true; 14342 if (this === $rootScope) return; 14343 14344 for (var eventName in this.$$listenerCount) { 14345 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 14346 } 14347 14348 // sever all the references to parent scopes (after this cleanup, the current scope should 14349 // not be retained by any of our references and should be eligible for garbage collection) 14350 if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 14351 if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 14352 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 14353 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 14354 14355 // Disable listeners, watchers and apply/digest methods 14356 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 14357 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 14358 this.$$listeners = {}; 14359 14360 // All of the code below is bogus code that works around V8's memory leak via optimized code 14361 // and inline caches. 14362 // 14363 // see: 14364 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 14365 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 14366 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 14367 14368 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 14369 this.$$childTail = this.$root = this.$$watchers = null; 14370 }, 14371 14372 /** 14373 * @ngdoc method 14374 * @name $rootScope.Scope#$eval 14375 * @kind function 14376 * 14377 * @description 14378 * Executes the `expression` on the current scope and returns the result. Any exceptions in 14379 * the expression are propagated (uncaught). This is useful when evaluating Angular 14380 * expressions. 14381 * 14382 * # Example 14383 * ```js 14384 var scope = ng.$rootScope.Scope(); 14385 scope.a = 1; 14386 scope.b = 2; 14387 14388 expect(scope.$eval('a+b')).toEqual(3); 14389 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 14390 * ``` 14391 * 14392 * @param {(string|function())=} expression An angular expression to be executed. 14393 * 14394 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 14395 * - `function(scope)`: execute the function with the current `scope` parameter. 14396 * 14397 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 14398 * @returns {*} The result of evaluating the expression. 14399 */ 14400 $eval: function(expr, locals) { 14401 return $parse(expr)(this, locals); 14402 }, 14403 14404 /** 14405 * @ngdoc method 14406 * @name $rootScope.Scope#$evalAsync 14407 * @kind function 14408 *
14409 * @description 14410 * Executes the expression on the current scope at a later point in time. 14411 * 14412 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 14413 * that: 14414 * 14415 * - it will execute after the function that scheduled the evaluation (preferably before DOM 14416 * rendering). 14417 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 14418 * `expression` execution. 14419 * 14420 * Any exceptions from the execution of the expression are forwarded to the 14421 * {@link ng.$exceptionHandler $exceptionHandler} service. 14422 * 14423 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 14424 * will be scheduled. However, it is encouraged to always call code that changes the model 14425 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 14426 * 14427 * @param {(string|function())=} expression An angular expression to be executed. 14428 * 14429 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 14430 * - `function(scope)`: execute the function with the current `scope` parameter. 14431 * 14432 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 14433 */ 14434 $evalAsync: function(expr, locals) { 14435 // if we are outside of an $digest loop and this is the first time we are scheduling async 14436 // task also schedule async auto-flush 14437 if (!$rootScope.$$phase && !asyncQueue.length) { 14438 $browser.defer(function() { 14439 if (asyncQueue.length) { 14440 $rootScope.$digest(); 14441 } 14442 }); 14443 } 14444 14445 asyncQueue.push({scope: this, expression: expr, locals: locals}); 14446 }, 14447 14448 $$postDigest: function(fn) { 14449 postDigestQueue.push(fn); 14450 }, 14451 14452 /** 14453 * @ngdoc method 14454 * @name $rootScope.Scope#$apply 14455 * @kind function 14456 * 14457 * @description 14458 * `$apply()` is used to execute an expression in angular from outside of the angular 14459 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 14460 * Because we are calling into the angular framework we need to perform proper scope life 14461 * cycle of {@link ng.$exceptionHandler exception handling}, 14462 * {@link ng.$rootScope.Scope#$digest executing watches}. 14463 * 14464 * ## Life cycle 14465 * 14466 * # Pseudo-Code of `$apply()` 14467 * ```js 14468 function $apply(expr) { 14469 try { 14470 return $eval(expr); 14471 } catch (e) { 14472 $exceptionHandler(e); 14473 } finally { 14474 $root.$digest(); 14475 } 14476 } 14477 * ``` 14478 * 14479 * 14480 * Scope's `$apply()` method transitions through the following stages: 14481 * 14482 * 1. The {@link guide/expression expression} is executed using the 14483 * {@link ng.$rootScope.Scope#$eval $eval()} method. 14484 * 2. Any exceptions from the execution of the expression are forwarded to the 14485 * {@link ng.$exceptionHandler $exceptionHandler} service. 14486 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 14487 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 14488 * 14489 * 14490 * @param {(string|function())=} exp An angular expression to be executed. 14491 * 14492 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 14493 * - `function(scope)`: execute the function with current `scope` parameter. 14494 * 14495 * @returns {*} The result of evaluating the expression. 14496 */ 14497 $apply: function(expr) { 14498 try { 14499 beginPhase('$apply'); 14500 return this.$eval(expr); 14501 } catch (e) { 14502 $exceptionHandler(e); 14503 } finally { 14504 clearPhase(); 14505 try { 14506 $rootScope.$digest(); 14507 } catch (e) { 14508 $exceptionHandler(e); 14509 throw e; 14510 } 14511 } 14512 }, 14513 14514 /** 14515 * @ngdoc method 14516 * @name $rootScope.Scope#$applyAsync 14517 * @kind function 14518 *
14519 * @description 14520 * Schedule the invocation of $apply to occur at a later time. The actual time difference 14521 * varies across browsers, but is typically around ~10 milliseconds. 14522 * 14523 * This can be used to queue up multiple expressions which need to be evaluated in the same 14524 * digest. 14525 * 14526 * @param {(string|function())=} exp An angular expression to be executed. 14527 * 14528 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 14529 * - `function(scope)`: execute the function with current `scope` parameter. 14530 */ 14531 $applyAsync: function(expr) { 14532 var scope = this; 14533 expr && applyAsyncQueue.push($applyAsyncExpression); 14534 scheduleApplyAsync(); 14535 14536 function $applyAsyncExpression() { 14537 scope.$eval(expr); 14538 } 14539 }, 14540 14541 /** 14542 * @ngdoc method 14543 * @name $rootScope.Scope#$on 14544 * @kind function 14545 * 14546 * @description 14547 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 14548 * discussion of event life cycle. 14549 * 14550 * The event listener function format is: `function(event, args...)`. The `event` object 14551 * passed into the listener has the following attributes: 14552 * 14553 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 14554 * `$broadcast`-ed. 14555 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 14556 * event propagates through the scope hierarchy, this property is set to null. 14557 * - `name` - `{string}`: name of the event. 14558 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 14559 * further event propagation (available only for events that were `$emit`-ed). 14560 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 14561 * to true. 14562 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 14563 * 14564 * @param {string} name Event name to listen on. 14565 * @param {function(event, ...args)} listener Function to call when the event is emitted. 14566 * @returns {function()} Returns a deregistration function for this listener. 14567 */ 14568 $on: function(name, listener) { 14569 var namedListeners = this.$$listeners[name]; 14570 if (!namedListeners) { 14571 this.$$listeners[name] = namedListeners = []; 14572 } 14573 namedListeners.push(listener); 14574 14575 var current = this; 14576 do { 14577 if (!current.$$listenerCount[name]) { 14578 current.$$listenerCount[name] = 0; 14579 } 14580 current.$$listenerCount[name]++; 14581 } while ((current = current.$parent)); 14582 14583 var self = this; 14584 return function() { 14585 var indexOfListener = namedListeners.indexOf(listener); 14586 if (indexOfListener !== -1) { 14587 namedListeners[indexOfListener] = null; 14588 decrementListenerCount(self, 1, name); 14589 } 14590 }; 14591 }, 14592 14593 14594 /** 14595 * @ngdoc method 14596 * @name $rootScope.Scope#$emit 14597 * @kind function 14598 * 14599 * @description 14600 * Dispatches an event `name` upwards through the scope hierarchy notifying the 14601 * registered {@link ng.$rootScope.Scope#$on} listeners. 14602 * 14603 * The event life cycle starts at the scope on which `$emit` was called. All 14604 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 14605 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 14606 * registered listeners along the way. The event will stop propagating if one of the listeners 14607 * cancels it. 14608 * 14609 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 14610 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 14611 * 14612 * @param {string} name Event name to emit. 14613 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 14614 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 14615 */ 14616 $emit: function(name, args) { 14617 var empty = [], 14618 namedListeners, 14619 scope = this, 14620 stopPropagation = false, 14621 event = { 14622 name: name, 14623 targetScope: scope, 14624 stopPropagation: function() {stopPropagation = true;}, 14625 preventDefault: function() { 14626 event.defaultPrevented = true; 14627 }, 14628 defaultPrevented: false 14629 }, 14630 listenerArgs = concat([event], arguments, 1), 14631 i, length; 14632 14633 do { 14634 namedListeners = scope.$$listeners[name] || empty; 14635 event.currentScope = scope; 14636 for (i = 0, length = namedListeners.length; i < length; i++) { 14637 14638 // if listeners were deregistered, defragment the array 14639 if (!namedListeners[i]) { 14640 namedListeners.splice(i, 1); 14641 i--; 14642 length--; 14643 continue; 14644 } 14645 try { 14646 //allow all listeners attached to the current scope to run 14647 namedListeners[i].apply(null, listenerArgs); 14648 } catch (e) { 14649 $exceptionHandler(e); 14650 } 14651 } 14652 //if any listener on the current scope stops propagation, prevent bubbling 14653 if (stopPropagation) { 14654 event.currentScope = null; 14655 return event; 14656 } 14657 //traverse upwards 14658 scope = scope.$parent; 14659 } while (scope); 14660 14661 event.currentScope = null; 14662 14663 return event; 14664 }, 14665 14666 14667 /** 14668 * @ngdoc method 14669 * @name $rootScope.Scope#$broadcast 14670 * @kind function 14671 *
14672 * @description 14673 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 14674 * registered {@link ng.$rootScope.Scope#$on} listeners. 14675 * 14676 * The event life cycle starts at the scope on which `$broadcast` was called. All 14677 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 14678 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 14679 * scope and calls all registered listeners along the way. The event cannot be canceled. 14680 * 14681 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 14682 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 14683 * 14684 * @param {string} name Event name to broadcast. 14685 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 14686 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 14687 */ 14688 $broadcast: function(name, args) { 14689 var target = this, 14690 current = target, 14691 next = target, 14692 event = { 14693 name: name, 14694 targetScope: target, 14695 preventDefault: function() { 14696 event.defaultPrevented = true; 14697 }, 14698 defaultPrevented: false 14699 }; 14700 14701 if (!target.$$listenerCount[name]) return event; 14702 14703 var listenerArgs = concat([event], arguments, 1), 14704 listeners, i, length; 14705 14706 //down while you can, then up and next sibling or up and next sibling until back at root 14707 while ((current = next)) { 14708 event.currentScope = current; 14709 listeners = current.$$listeners[name] || []; 14710 for (i = 0, length = listeners.length; i < length; i++) { 14711 // if listeners were deregistered, defragment the array 14712 if (!listeners[i]) { 14713 listeners.splice(i, 1); 14714 i--; 14715 length--; 14716 continue; 14717 } 14718 14719 try { 14720 listeners[i].apply(null, listenerArgs); 14721 } catch (e) { 14722 $exceptionHandler(e); 14723 } 14724 } 14725 14726 // Insanity Warning: scope depth-first traversal 14727 // yes, this code is a bit crazy, but it works and we have tests to prove it! 14728 // this piece should be kept in sync with the traversal in $digest 14729 // (though it differs due to having the extra check for $$listenerCount) 14730 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 14731 (current !== target && current.$$nextSibling)))) { 14732 while (current !== target && !(next = current.$$nextSibling)) { 14733 current = current.$parent; 14734 } 14735 } 14736 } 14737 14738 event.currentScope = null; 14739 return event; 14740 } 14741 }; 14742 14743 var $rootScope = new Scope(); 14744 14745 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 14746 var asyncQueue = $rootScope.$$asyncQueue = []; 14747 var postDigestQueue = $rootScope.$$postDigestQueue = []; 14748 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 14749 14750 return $rootScope; 14751 14752 14753 function beginPhase(phase) { 14754 if ($rootScope.$$phase) { 14755 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 14756 } 14757 14758 $rootScope.$$phase = phase; 14759 } 14760 14761 function clearPhase() { 14762 $rootScope.$$phase = null; 14763 } 14764 14765 14766 function decrementListenerCount(current, count, name) { 14767 do { 14768 current.$$listenerCount[name] -= count; 14769 14770 if (current.$$listenerCount[name] === 0) { 14771 delete current.$$listenerCount[name]; 14772 } 14773 } while ((current = current.$parent)); 14774 } 14775 14776 /** 14777 * function used as an initial value for watchers. 14778 * because it's unique we can easily tell it apart from other values 14779 */ 14780 function initWatchVal() {} 14781 14782 function flushApplyAsync() { 14783 while (applyAsyncQueue.length) { 14784 try { 14785 applyAsyncQueue.shift()(); 14786 } catch (e) { 14787 $exceptionHandler(e); 14788 } 14789 } 14790 applyAsyncId = null; 14791 } 14792 14793 function scheduleApplyAsync() {
vendor: 3,834 bytes, lines 14794-14899
14794 if (applyAsyncId === null) { 14795 applyAsyncId = $browser.defer(function() { 14796 $rootScope.$apply(flushApplyAsync); 14797 }); 14798 } 14799 } 14800 }]; 14801} 14802 14803/** 14804 * @description 14805 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 14806 */ 14807function $$SanitizeUriProvider() { 14808 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 14809 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; 14810 14811 /** 14812 * @description 14813 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 14814 * urls during a[href] sanitization. 14815 * 14816 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 14817 * 14818 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 14819 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 14820 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 14821 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 14822 * 14823 * @param {RegExp=} regexp New regexp to whitelist urls with. 14824 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 14825 * chaining otherwise. 14826 */ 14827 this.aHrefSanitizationWhitelist = function(regexp) { 14828 if (isDefined(regexp)) { 14829 aHrefSanitizationWhitelist = regexp; 14830 return this; 14831 } 14832 return aHrefSanitizationWhitelist; 14833 }; 14834 14835 14836 /** 14837 * @description 14838 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 14839 * urls during img[src] sanitization. 14840 * 14841 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 14842 * 14843 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 14844 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 14845 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 14846 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 14847 * 14848 * @param {RegExp=} regexp New regexp to whitelist urls with. 14849 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 14850 * chaining otherwise. 14851 */ 14852 this.imgSrcSanitizationWhitelist = function(regexp) { 14853 if (isDefined(regexp)) { 14854 imgSrcSanitizationWhitelist = regexp; 14855 return this; 14856 } 14857 return imgSrcSanitizationWhitelist; 14858 }; 14859 14860 this.$get = function() { 14861 return function sanitizeUri(uri, isImage) { 14862 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 14863 var normalizedVal; 14864 normalizedVal = urlResolve(uri).href; 14865 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 14866 return 'unsafe:' + normalizedVal; 14867 } 14868 return uri; 14869 }; 14870 }; 14871} 14872 14873var $sceMinErr = minErr('$sce'); 14874 14875var SCE_CONTEXTS = { 14876 HTML: 'html', 14877 CSS: 'css', 14878 URL: 'url', 14879 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 14880 // url. (e.g. ng-include, script src, templateUrl) 14881 RESOURCE_URL: 'resourceUrl', 14882 JS: 'js' 14883}; 14884 14885// Helper functions follow. 14886 14887function adjustMatcher(matcher) { 14888 if (matcher === 'self') { 14889 return matcher; 14890 } else if (isString(matcher)) { 14891 // Strings match exactly except for 2 wildcards - '*' and '**'. 14892 // '*' matches any character except those from the set ':/.?&'. 14893 // '**' matches any character (like .* in a RegExp). 14894 // More than 2 *'s raises an error as it's ill defined. 14895 if (matcher.indexOf('***') > -1) { 14896 throw $sceMinErr('iwcard', 14897 'Illegal sequence *** in string matcher. String: {0}', matcher); 14898 } 14899 matcher = escapeForRegexp(matcher).
14900 replace('\\*\\*', '.*'). 14901 replace('\\*', '[^:/.?&;]*'); 14902 return new RegExp('^' + matcher + '$'); 14903 } else if (isRegExp(matcher)) { 14904 // The only other type of matcher allowed is a Regexp. 14905 // Match entire URL / disallow partial matches. 14906 // Flags are reset (i.e. no global, ignoreCase or multiline) 14907 return new RegExp('^' + matcher.source + '$'); 14908 } else { 14909 throw $sceMinErr('imatcher', 14910 'Matchers may only be "self", string patterns or RegExp objects'); 14911 } 14912} 14913 14914 14915function adjustMatchers(matchers) { 14916 var adjustedMatchers = []; 14917 if (isDefined(matchers)) { 14918 forEach(matchers, function(matcher) { 14919 adjustedMatchers.push(adjustMatcher(matcher)); 14920 }); 14921 } 14922 return adjustedMatchers; 14923} 14924 14925 14926/** 14927 * @ngdoc service 14928 * @name $sceDelegate 14929 * @kind function 14930 * 14931 * @description 14932 * 14933 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 14934 * Contextual Escaping (SCE)} services to AngularJS. 14935 * 14936 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 14937 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 14938 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 14939 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 14940 * work because `$sce` delegates to `$sceDelegate` for these operations. 14941 * 14942 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 14943 * 14944 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 14945 * can override it completely to change the behavior of `$sce`, the common case would 14946 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 14947 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 14948 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 14949 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 14950 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 14951 */ 14952 14953/** 14954 * @ngdoc provider 14955 * @name $sceDelegateProvider 14956 * @description 14957 * 14958 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 14959 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 14960 * that the URLs used for sourcing Angular templates are safe. Refer {@link 14961 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 14962 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 14963 * 14964 * For the general details about this service in Angular, read the main page for {@link ng.$sce 14965 * Strict Contextual Escaping (SCE)}. 14966 * 14967 * **Example**: Consider the following case. <a name="example"></a> 14968 * 14969 * - your app is hosted at url `http://myapp.example.com/` 14970 * - but some of your templates are hosted on other domains you control such as 14971 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 14972 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 14973 * 14974 * Here is what a secure configuration for this scenario might look like: 14975 * 14976 * ``` 14977 * angular.module('myApp', []).config(function($sceDelegateProvider) { 14978 * $sceDelegateProvider.resourceUrlWhitelist([ 14979 * // Allow same origin resource loads. 14980 * 'self', 14981 * // Allow loading from our assets domain. Notice the difference between * and **. 14982 * 'http://srv*.assets.example.com/**' 14983 * ]); 14984 * 14985 * // The blacklist overrides the whitelist so the open redirect here is blocked. 14986 * $sceDelegateProvider.resourceUrlBlacklist([ 14987 * 'http://myapp.example.com/clickThru**' 14988 * ]); 14989 * }); 14990 * ``` 14991 */ 14992 14993function $SceDelegateProvider() { 14994 this.SCE_CONTEXTS = SCE_CONTEXTS; 14995 14996 // Resource URLs can also be trusted by policy. 14997 var resourceUrlWhitelist = ['self'], 14998 resourceUrlBlacklist = []; 14999 15000 /** 15001 * @ngdoc method 15002 * @name $sceDelegateProvider#resourceUrlWhitelist 15003 * @kind function 15004 * 15005 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 15006 * provided. This must be an array or null. A snapshot of this array is used so further 15007 * changes to the array are ignored. 15008 * 15009 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 15010 * allowed in this array.
vendor: 4,148 bytes, lines 15011-15135
15011 * 15012 * Note: **an empty whitelist array will block all URLs**! 15013 * 15014 * @return {Array} the currently set whitelist array. 15015 * 15016 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 15017 * same origin resource requests. 15018 * 15019 * @description 15020 * Sets/Gets the whitelist of trusted resource URLs. 15021 */ 15022 this.resourceUrlWhitelist = function(value) { 15023 if (arguments.length) { 15024 resourceUrlWhitelist = adjustMatchers(value); 15025 } 15026 return resourceUrlWhitelist; 15027 }; 15028 15029 /** 15030 * @ngdoc method 15031 * @name $sceDelegateProvider#resourceUrlBlacklist 15032 * @kind function 15033 * 15034 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 15035 * provided. This must be an array or null. A snapshot of this array is used so further 15036 * changes to the array are ignored. 15037 * 15038 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 15039 * allowed in this array. 15040 * 15041 * The typical usage for the blacklist is to **block 15042 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 15043 * these would otherwise be trusted but actually return content from the redirected domain. 15044 * 15045 * Finally, **the blacklist overrides the whitelist** and has the final say. 15046 * 15047 * @return {Array} the currently set blacklist array. 15048 * 15049 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 15050 * is no blacklist.) 15051 * 15052 * @description 15053 * Sets/Gets the blacklist of trusted resource URLs. 15054 */ 15055 15056 this.resourceUrlBlacklist = function(value) { 15057 if (arguments.length) { 15058 resourceUrlBlacklist = adjustMatchers(value); 15059 } 15060 return resourceUrlBlacklist; 15061 }; 15062 15063 this.$get = ['$injector', function($injector) { 15064 15065 var htmlSanitizer = function htmlSanitizer(html) { 15066 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 15067 }; 15068 15069 if ($injector.has('$sanitize')) { 15070 htmlSanitizer = $injector.get('$sanitize'); 15071 } 15072 15073 15074 function matchUrl(matcher, parsedUrl) { 15075 if (matcher === 'self') { 15076 return urlIsSameOrigin(parsedUrl); 15077 } else { 15078 // definitely a regex. See adjustMatchers() 15079 return !!matcher.exec(parsedUrl.href); 15080 } 15081 } 15082 15083 function isResourceUrlAllowedByPolicy(url) { 15084 var parsedUrl = urlResolve(url.toString()); 15085 var i, n, allowed = false; 15086 // Ensure that at least one item from the whitelist allows this url. 15087 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 15088 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 15089 allowed = true; 15090 break; 15091 } 15092 } 15093 if (allowed) { 15094 // Ensure that no item from the blacklist blocked this url. 15095 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 15096 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 15097 allowed = false; 15098 break; 15099 } 15100 } 15101 } 15102 return allowed; 15103 } 15104 15105 function generateHolderType(Base) { 15106 var holderType = function TrustedValueHolderType(trustedValue) { 15107 this.$$unwrapTrustedValue = function() { 15108 return trustedValue; 15109 }; 15110 }; 15111 if (Base) { 15112 holderType.prototype = new Base(); 15113 } 15114 holderType.prototype.valueOf = function sceValueOf() { 15115 return this.$$unwrapTrustedValue(); 15116 }; 15117 holderType.prototype.toString = function sceToString() { 15118 return this.$$unwrapTrustedValue().toString(); 15119 }; 15120 return holderType; 15121 } 15122 15123 var trustedValueHolderBase = generateHolderType(), 15124 byType = {}; 15125 15126 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 15127 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 15128 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 15129 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 15130 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 15131 15132 /** 15133 * @ngdoc method 15134 * @name $sceDelegate#trustAs 15135 *
15136 * @description 15137 * Returns an object that is trusted by angular for use in specified strict 15138 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 15139 * attribute interpolation, any dom event binding attribute interpolation 15140 * such as for onclick, etc.) that uses the provided value. 15141 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 15142 * 15143 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 15144 * resourceUrl, html, js and css. 15145 * @param {*} value The value that that should be considered trusted/safe. 15146 * @returns {*} A value that can be used to stand in for the provided `value` in places 15147 * where Angular expects a $sce.trustAs() return value. 15148 */ 15149 function trustAs(type, trustedValue) { 15150 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 15151 if (!Constructor) { 15152 throw $sceMinErr('icontext', 15153 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 15154 type, trustedValue); 15155 } 15156 if (trustedValue === null || trustedValue === undefined || trustedValue === '') { 15157 return trustedValue; 15158 } 15159 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 15160 // mutable objects, we ensure here that the value passed in is actually a string. 15161 if (typeof trustedValue !== 'string') { 15162 throw $sceMinErr('itype', 15163 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 15164 type); 15165 } 15166 return new Constructor(trustedValue); 15167 } 15168 15169 /** 15170 * @ngdoc method 15171 * @name $sceDelegate#valueOf 15172 * 15173 * @description 15174 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 15175 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 15176 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 15177 * 15178 * If the passed parameter is not a value that had been returned by {@link 15179 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 15180 * 15181 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 15182 * call or anything else. 15183 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 15184 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 15185 * `value` unchanged. 15186 */ 15187 function valueOf(maybeTrusted) { 15188 if (maybeTrusted instanceof trustedValueHolderBase) { 15189 return maybeTrusted.$$unwrapTrustedValue(); 15190 } else { 15191 return maybeTrusted; 15192 } 15193 } 15194 15195 /** 15196 * @ngdoc method 15197 * @name $sceDelegate#getTrusted 15198 * 15199 * @description 15200 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 15201 * returns the originally supplied value if the queried context type is a supertype of the 15202 * created type. If this condition isn't satisfied, throws an exception. 15203 * 15204 * @param {string} type The kind of context in which this value is to be used. 15205 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 15206 * `$sceDelegate.trustAs`} call. 15207 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 15208 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 15209 */ 15210 function getTrusted(type, maybeTrusted) { 15211 if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { 15212 return maybeTrusted; 15213 } 15214 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 15215 if (constructor && maybeTrusted instanceof constructor) { 15216 return maybeTrusted.$$unwrapTrustedValue(); 15217 } 15218 // If we get here, then we may only take one of two actions. 15219 // 1. sanitize the value for the requested type, or 15220 // 2. throw an exception. 15221 if (type === SCE_CONTEXTS.RESOURCE_URL) { 15222 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 15223 return maybeTrusted; 15224 } else { 15225 throw $sceMinErr('insecurl', 15226 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 15227 maybeTrusted.toString()); 15228 } 15229 } else if (type === SCE_CONTEXTS.HTML) { 15230 return htmlSanitizer(maybeTrusted); 15231 } 15232 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 15233 } 15234 15235 return { trustAs: trustAs, 15236 getTrusted: getTrusted,
15237 valueOf: valueOf }; 15238 }]; 15239} 15240 15241 15242/** 15243 * @ngdoc provider 15244 * @name $sceProvider 15245 * @description 15246 * 15247 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 15248 * - enable/disable Strict Contextual Escaping (SCE) in a module 15249 * - override the default implementation with a custom delegate 15250 * 15251 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 15252 */ 15253 15254/* jshint maxlen: false*/ 15255 15256/** 15257 * @ngdoc service 15258 * @name $sce 15259 * @kind function 15260 * 15261 * @description 15262 * 15263 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 15264 * 15265 * # Strict Contextual Escaping 15266 * 15267 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 15268 * contexts to result in a value that is marked as safe to use for that context. One example of 15269 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 15270 * to these contexts as privileged or SCE contexts. 15271 * 15272 * As of version 1.2, Angular ships with SCE enabled by default. 15273 * 15274 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow 15275 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 15276 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
15277 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 15278 * to the top of your HTML document. 15279 * 15280 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 15281 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 15282 * 15283 * Here's an example of a binding in a privileged context: 15284 * 15285 * ``` 15286 * <input ng-model="userHtml"> 15287 * <div ng-bind-html="userHtml"></div> 15288 * ``` 15289 * 15290 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 15291 * disabled, this application allows the user to render arbitrary HTML into the DIV. 15292 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 15293 * bindings. (HTML is just one example of a context where rendering user controlled input creates 15294 * security vulnerabilities.) 15295 * 15296 * For the case of HTML, you might use a library, either on the client side, or on the server side, 15297 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 15298 * 15299 * How would you ensure that every place that used these types of bindings was bound to a value that 15300 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 15301 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 15302 * properties/fields and forgot to update the binding to the sanitized value? 15303 * 15304 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 15305 * determine that something explicitly says it's safe to use a value for binding in that
15306 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 15307 * for those values that you can easily tell are safe - because they were received from your server, 15308 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 15309 * allowing only the files in a specific directory to do this. Ensuring that the internal API 15310 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 15311 * 15312 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 15313 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 15314 * obtain values that will be accepted by SCE / privileged contexts. 15315 * 15316 * 15317 * ## How does it work? 15318 * 15319 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 15320 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 15321 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 15322 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 15323 * 15324 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 15325 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 15326 * simplified): 15327 * 15328 * ``` 15329 * var ngBindHtmlDirective = ['$sce', function($sce) { 15330 * return function(scope, element, attr) { 15331 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 15332 * element.html(value || ''); 15333 * }); 15334 * }; 15335 * }]; 15336 * ``` 15337 * 15338 * ## Impact on loading templates 15339 * 15340 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 15341 * `templateUrl`'s specified by {@link guide/directive directives}. 15342 * 15343 * By default, Angular only loads templates from the same domain and protocol as the application 15344 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 15345 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 15346 * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 15347 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 15348 * 15349 * *Please note*: 15350 * The browser's 15351 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 15352 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 15353 * policy apply in addition to this and may further restrict whether the template is successfully 15354 * loaded. This means that without the right CORS policy, loading templates from a different domain 15355 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 15356 * browsers. 15357 * 15358 * ## This feels like too much overhead 15359 * 15360 * It's important to remember that SCE only applies to interpolation expressions. 15361 * 15362 * If your expressions are constant literals, they're automatically trusted and you don't need to 15363 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 15364 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 15365 * 15366 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 15367 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 15368 * 15369 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 15370 * templates in `ng-include` from your application's domain without having to even know about SCE. 15371 * It blocks loading templates from other domains or loading templates over http from an https 15372 * served document. You can change these by setting your own custom {@link 15373 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 15374 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 15375 * 15376 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 15377 * application that's secure and can be audited to verify that with much more ease than bolting 15378 * security onto an application later. 15379 * 15380 * <a name="contexts"></a> 15381 * ## What trusted context types are supported? 15382 * 15383 * | Context | Notes | 15384 * |---------------------|----------------| 15385 * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. | 15386 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 15387 * | `$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. | 15388 * | `$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. | 15389 * | `$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. | 15390 * 15391 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 15392 *
15393 * Each element in these arrays must be one of the following: 15394 * 15395 * - **'self'** 15396 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 15397 * domain** as the application document using the **same protocol**. 15398 * - **String** (except the special value `'self'`) 15399 * - The string is matched against the full *normalized / absolute URL* of the resource 15400 * being tested (substring matches are not good enough.) 15401 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 15402 * match themselves. 15403 * - `*`: matches zero or more occurrences of any character other than one of the following 6 15404 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use 15405 * in a whitelist. 15406 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 15407 * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. 15408 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 15409 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 15410 * http://foo.example.com/templates/**). 15411 * - **RegExp** (*see caveat below*) 15412 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 15413 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 15414 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 15415 * have good test coverage.). For instance, the use of `.` in the regex is correct only in a 15416 * small number of cases. A `.` character in the regex used when matching the scheme or a 15417 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 15418 * is highly recommended to use the string patterns and only fall back to regular expressions 15419 * if they as a last resort. 15420 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 15421 * matched against the **entire** *normalized / absolute URL* of the resource being tested 15422 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 15423 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 15424 * - If you are generating your JavaScript from some other templating engine (not 15425 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 15426 * remember to escape your regular expression (and be aware that you might need more than 15427 * one level of escaping depending on your templating engine and the way you interpolated 15428 * the value.) Do make use of your platform's escaping mechanism as it might be good 15429 * enough before coding your own. e.g. Ruby has 15430 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 15431 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 15432 * Javascript lacks a similar built in function for escaping. Take a look at Google 15433 * Closure library's [goog.string.regExpEscape(s)]( 15434 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 15435 * 15436 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 15437 * 15438 * ## Show me an example using SCE. 15439 * 15440 * <example module="mySceApp" deps="angular-sanitize.js"> 15441 * <file name="index.html"> 15442 * <div ng-controller="AppController as myCtrl"> 15443 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 15444 * <b>User comments</b><br> 15445 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 15446 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 15447 * exploit. 15448 * <div class="well"> 15449 * <div ng-repeat="userComment in myCtrl.userComments"> 15450 * <b>{{userComment.name}}</b>: 15451 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 15452 * <br> 15453 * </div>
15454 * </div> 15455 * </div> 15456 * </file> 15457 * 15458 * <file name="script.js"> 15459 * angular.module('mySceApp', ['ngSanitize']) 15460 * .controller('AppController', ['$http', '$templateCache', '$sce', 15461 * function($http, $templateCache, $sce) { 15462 * var self = this; 15463 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 15464 * self.userComments = userComments; 15465 * }); 15466 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 15467 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 15468 * 'sanitization."">Hover over this text.</span>'); 15469 * }]); 15470 * </file> 15471 * 15472 * <file name="test_data.json"> 15473 * [ 15474 * { "name": "Alice", 15475 * "htmlComment": 15476 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 15477 * }, 15478 * { "name": "Bob", 15479 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 15480 * } 15481 * ] 15482 * </file> 15483 * 15484 * <file name="protractor.js" type="protractor"> 15485 * describe('SCE doc demo', function() { 15486 * it('should sanitize untrusted values', function() { 15487 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) 15488 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 15489 * }); 15490 * 15491 * it('should NOT sanitize explicitly trusted values', function() { 15492 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 15493 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 15494 * 'sanitization."">Hover over this text.</span>'); 15495 * }); 15496 * }); 15497 * </file> 15498 * </example> 15499 * 15500 * 15501 * 15502 * ## Can I disable SCE completely? 15503 * 15504 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 15505 * for little coding overhead. It will be much harder to take an SCE disabled application and 15506 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 15507 * for cases where you have a lot of existing code that was written before SCE was introduced and 15508 * you're migrating them a module at a time. 15509 * 15510 * That said, here's how you can completely disable SCE: 15511 * 15512 * ``` 15513 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 15514 * // Completely disable SCE. For demonstration purposes only! 15515 * // Do not use in new projects. 15516 * $sceProvider.enabled(false); 15517 * }); 15518 * ``` 15519 * 15520 */ 15521/* jshint maxlen: 100 */ 15522 15523function $SceProvider() { 15524 var enabled = true; 15525 15526 /** 15527 * @ngdoc method 15528 * @name $sceProvider#enabled 15529 * @kind function 15530 * 15531 * @param {boolean=} value If provided, then enables/disables SCE. 15532 * @return {boolean} true if SCE is enabled, false otherwise. 15533 * 15534 * @description 15535 * Enables/disables SCE and returns the current value. 15536 */ 15537 this.enabled = function(value) { 15538 if (arguments.length) { 15539 enabled = !!value; 15540 } 15541 return enabled; 15542 }; 15543 15544 15545 /* Design notes on the default implementation for SCE. 15546 * 15547 * The API contract for the SCE delegate 15548 * ------------------------------------- 15549 * The SCE delegate object must provide the following 3 methods: 15550 * 15551 * - trustAs(contextEnum, value) 15552 * This method is used to tell the SCE service that the provided value is OK to use in the 15553 * contexts specified by contextEnum. It must return an object that will be accepted by 15554 * getTrusted() for a compatible contextEnum and return this value. 15555 * 15556 * - valueOf(value) 15557 * For values that were not produced by trustAs(), return them as is. For values that were 15558 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 15559 * trustAs is wrapping the given values into some type, this operation unwraps it when given 15560 * such a value. 15561 * 15562 * - getTrusted(contextEnum, value) 15563 * This function should return the a value that is safe to use in the context specified by 15564 * contextEnum or throw and exception otherwise. 15565 * 15566 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 15567 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 15568 * instance, an implementation could maintain a registry of all trusted objects by context. In 15569 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 15570 * return the same object passed in if it was found in the registry under a compatible context or 15571 * throw an exception otherwise. An implementation might only wrap values some of the time based 15572 * on some criteria. getTrusted() might return a value and not throw an exception for special 15573 * constants or objects even if not wrapped. All such implementations fulfill this contract. 15574 * 15575 * 15576 * A note on the inheritance model for SCE contexts 15577 * ------------------------------------------------ 15578 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 15579 * is purely an implementation details. 15580 * 15581 * The contract is simply this: 15582 * 15583 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 15584 * will also succeed. 15585 * 15586 * Inheritance happens to capture this in a natural way. In some future, we
15587 * may not use inheritance anymore. That is OK because no code outside of 15588 * sce.js and sceSpecs.js would need to be aware of this detail. 15589 */ 15590 15591 this.$get = ['$parse', '$sceDelegate', function( 15592 $parse, $sceDelegate) { 15593 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 15594 // the "expression(javascript expression)" syntax which is insecure. 15595 if (enabled && msie < 8) { 15596 throw $sceMinErr('iequirks', 15597 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 15598 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 15599 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 15600 } 15601 15602 var sce = shallowCopy(SCE_CONTEXTS); 15603 15604 /** 15605 * @ngdoc method 15606 * @name $sce#isEnabled 15607 * @kind function 15608 * 15609 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 15610 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 15611 * 15612 * @description 15613 * Returns a boolean indicating if SCE is enabled. 15614 */ 15615 sce.isEnabled = function() { 15616 return enabled; 15617 }; 15618 sce.trustAs = $sceDelegate.trustAs; 15619 sce.getTrusted = $sceDelegate.getTrusted; 15620 sce.valueOf = $sceDelegate.valueOf; 15621 15622 if (!enabled) { 15623 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 15624 sce.valueOf = identity; 15625 } 15626 15627 /** 15628 * @ngdoc method 15629 * @name $sce#parseAs 15630 * 15631 * @description 15632 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 15633 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 15634 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 15635 * *result*)} 15636 * 15637 * @param {string} type The kind of SCE context in which this result will be used. 15638 * @param {string} expression String expression to compile. 15639 * @returns {function(context, locals)} a function which represents the compiled expression: 15640 * 15641 * * `context` â `{object}` â an object against which any expressions embedded in the strings 15642 * are evaluated against (typically a scope object). 15643 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 15644 * `context`. 15645 */ 15646 sce.parseAs = function sceParseAs(type, expr) { 15647 var parsed = $parse(expr); 15648 if (parsed.literal && parsed.constant) { 15649 return parsed; 15650 } else { 15651 return $parse(expr, function(value) { 15652 return sce.getTrusted(type, value); 15653 }); 15654 } 15655 }; 15656 15657 /** 15658 * @ngdoc method 15659 * @name $sce#trustAs 15660 * 15661 * @description 15662 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 15663 * returns an object that is trusted by angular for use in specified strict contextual 15664 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 15665 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 15666 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 15667 * escaping. 15668 * 15669 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 15670 * resource_url, html, js and css. 15671 * @param {*} value The value that that should be considered trusted/safe. 15672 * @returns {*} A value that can be used to stand in for the provided `value` in places 15673 * where Angular expects a $sce.trustAs() return value. 15674 */ 15675 15676 /** 15677 * @ngdoc method 15678 * @name $sce#trustAsHtml 15679 * 15680 * @description 15681 * Shorthand method. `$sce.trustAsHtml(value)` â 15682 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 15683 * 15684 * @param {*} value The value to trustAs. 15685 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 15686 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 15687 * only accept expressions that are either literal constants or are the 15688 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 15689 */ 15690 15691 /** 15692 * @ngdoc method 15693 * @name $sce#trustAsUrl 15694 *
15695 * @description 15696 * Shorthand method. `$sce.trustAsUrl(value)` â 15697 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 15698 * 15699 * @param {*} value The value to trustAs. 15700 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 15701 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 15702 * only accept expressions that are either literal constants or are the 15703 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 15704 */ 15705 15706 /** 15707 * @ngdoc method 15708 * @name $sce#trustAsResourceUrl 15709 * 15710 * @description 15711 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 15712 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 15713 * 15714 * @param {*} value The value to trustAs. 15715 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 15716 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 15717 * only accept expressions that are either literal constants or are the return 15718 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 15719 */ 15720 15721 /** 15722 * @ngdoc method 15723 * @name $sce#trustAsJs 15724 * 15725 * @description 15726 * Shorthand method. `$sce.trustAsJs(value)` â 15727 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 15728 * 15729 * @param {*} value The value to trustAs. 15730 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 15731 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 15732 * only accept expressions that are either literal constants or are the 15733 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 15734 */ 15735 15736 /** 15737 * @ngdoc method 15738 * @name $sce#getTrusted 15739 * 15740 * @description 15741 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 15742 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 15743 * originally supplied value if the queried context type is a supertype of the created type. 15744 * If this condition isn't satisfied, throws an exception. 15745 * 15746 * @param {string} type The kind of context in which this value is to be used. 15747 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 15748 * call. 15749 * @returns {*} The value the was originally provided to 15750 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 15751 * Otherwise, throws an exception. 15752 */ 15753 15754 /** 15755 * @ngdoc method 15756 * @name $sce#getTrustedHtml 15757 * 15758 * @description 15759 * Shorthand method. `$sce.getTrustedHtml(value)` â 15760 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 15761 * 15762 * @param {*} value The value to pass to `$sce.getTrusted`. 15763 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 15764 */ 15765 15766 /** 15767 * @ngdoc method 15768 * @name $sce#getTrustedCss 15769 * 15770 * @description 15771 * Shorthand method. `$sce.getTrustedCss(value)` â 15772 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 15773 * 15774 * @param {*} value The value to pass to `$sce.getTrusted`. 15775 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 15776 */ 15777 15778 /** 15779 * @ngdoc method 15780 * @name $sce#getTrustedUrl 15781 * 15782 * @description 15783 * Shorthand method. `$sce.getTrustedUrl(value)` â 15784 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 15785 * 15786 * @param {*} value The value to pass to `$sce.getTrusted`. 15787 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 15788 */ 15789 15790 /** 15791 * @ngdoc method 15792 * @name $sce#getTrustedResourceUrl 15793 * 15794 * @description 15795 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 15796 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 15797 * 15798 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 15799 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 15800 */ 15801 15802 /** 15803 * @ngdoc method 15804 * @name $sce#getTrustedJs 15805 *
15806 * @description 15807 * Shorthand method. `$sce.getTrustedJs(value)` â 15808 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 15809 * 15810 * @param {*} value The value to pass to `$sce.getTrusted`. 15811 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 15812 */ 15813 15814 /** 15815 * @ngdoc method 15816 * @name $sce#parseAsHtml 15817 * 15818 * @description 15819 * Shorthand method. `$sce.parseAsHtml(expression string)` â 15820 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 15821 * 15822 * @param {string} expression String expression to compile. 15823 * @returns {function(context, locals)} a function which represents the compiled expression: 15824 * 15825 * * `context` â `{object}` â an object against which any expressions embedded in the strings 15826 * are evaluated against (typically a scope object). 15827 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 15828 * `context`. 15829 */ 15830 15831 /** 15832 * @ngdoc method 15833 * @name $sce#parseAsCss 15834 * 15835 * @description 15836 * Shorthand method. `$sce.parseAsCss(value)` â 15837 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 15838 * 15839 * @param {string} expression String expression to compile. 15840 * @returns {function(context, locals)} a function which represents the compiled expression: 15841 * 15842 * * `context` â `{object}` â an object against which any expressions embedded in the strings 15843 * are evaluated against (typically a scope object). 15844 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 15845 * `context`. 15846 */ 15847 15848 /** 15849 * @ngdoc method 15850 * @name $sce#parseAsUrl 15851 * 15852 * @description 15853 * Shorthand method. `$sce.parseAsUrl(value)` â 15854 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 15855 * 15856 * @param {string} expression String expression to compile. 15857 * @returns {function(context, locals)} a function which represents the compiled expression: 15858 * 15859 * * `context` â `{object}` â an object against which any expressions embedded in the strings 15860 * are evaluated against (typically a scope object). 15861 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 15862 * `context`. 15863 */ 15864 15865 /** 15866 * @ngdoc method 15867 * @name $sce#parseAsResourceUrl 15868 * 15869 * @description 15870 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 15871 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 15872 * 15873 * @param {string} expression String expression to compile. 15874 * @returns {function(context, locals)} a function which represents the compiled expression: 15875 * 15876 * * `context` â `{object}` â an object against which any expressions embedded in the strings 15877 * are evaluated against (typically a scope object). 15878 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 15879 * `context`. 15880 */ 15881 15882 /** 15883 * @ngdoc method 15884 * @name $sce#parseAsJs 15885 * 15886 * @description 15887 * Shorthand method. `$sce.parseAsJs(value)` â 15888 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 15889 * 15890 * @param {string} expression String expression to compile. 15891 * @returns {function(context, locals)} a function which represents the compiled expression: 15892 * 15893 * * `context` â `{object}` â an object against which any expressions embedded in the strings 15894 * are evaluated against (typically a scope object). 15895 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 15896 * `context`. 15897 */ 15898 15899 // Shorthand delegations. 15900 var parse = sce.parseAs, 15901 getTrusted = sce.getTrusted, 15902 trustAs = sce.trustAs; 15903 15904 forEach(SCE_CONTEXTS, function(enumValue, name) { 15905 var lName = lowercase(name); 15906 sce[camelCase("parse_as_" + lName)] = function(expr) { 15907 return parse(enumValue, expr); 15908 }; 15909 sce[camelCase("get_trusted_" + lName)] = function(value) { 15910 return getTrusted(enumValue, value); 15911 }; 15912 sce[camelCase("trust_as_" + lName)] = function(value) { 15913 return trustAs(enumValue, value); 15914 }; 15915 }); 15916 15917 return sce; 15918 }]; 15919} 15920 15921/** 15922 * !!! This is an undocumented "private" service !!! 15923 * 15924 * @name $sniffer 15925 * @requires $window 15926 * @requires $document 15927 * 15928 * @property {boolean} history Does the browser support html5 history api ? 15929 * @property {boolean} transitions Does the browser support CSS transition events ? 15930 * @property {boolean} animations Does the browser support CSS animation events ? 15931 * 15932 * @description 15933 * This is very simple implementation of testing browser's features. 15934 */ 15935function $SnifferProvider() { 15936 this.$get = ['$window', '$document', function($window, $document) { 15937 var eventSupport = {}, 15938 android = 15939 int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 15940 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 15941 document = $document[0] || {}, 15942 vendorPrefix, 15943 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, 15944 bodyStyle = document.body && document.body.style, 15945 transitions = false, 15946 animations = false, 15947 match; 15948 15949 if (bodyStyle) { 15950 for (var prop in bodyStyle) {
15951 if (match = vendorRegex.exec(prop)) { 15952 vendorPrefix = match[0]; 15953 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 15954 break; 15955 } 15956 } 15957 15958 if (!vendorPrefix) { 15959 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 15960 } 15961 15962 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 15963 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 15964 15965 if (android && (!transitions || !animations)) { 15966 transitions = isString(document.body.style.webkitTransition); 15967 animations = isString(document.body.style.webkitAnimation); 15968 } 15969 } 15970 15971 15972 return { 15973 // Android has history.pushState, but it does not update location correctly 15974 // so let's not use the history API at all. 15975 // http://code.google.com/p/android/issues/detail?id=17471 15976 // https://github.com/angular/angular.js/issues/904 15977 15978 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 15979 // so let's not use the history API also 15980 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 15981 // jshint -W018 15982 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 15983 // jshint +W018 15984 hasEvent: function(event) { 15985 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 15986 // it. In particular the event is not fired when backspace or delete key are pressed or 15987 // when cut operation is performed. 15988 // IE10+ implements 'input' event but it erroneously fires under various situations, 15989 // e.g. when placeholder changes, or a form is focused. 15990 if (event === 'input' && msie <= 11) return false; 15991 15992 if (isUndefined(eventSupport[event])) { 15993 var divElm = document.createElement('div'); 15994 eventSupport[event] = 'on' + event in divElm; 15995 } 15996 15997 return eventSupport[event]; 15998 }, 15999 csp: csp(), 16000 vendorPrefix: vendorPrefix, 16001 transitions: transitions, 16002 animations: animations, 16003 android: android 16004 }; 16005 }]; 16006} 16007 16008var $compileMinErr = minErr('$compile'); 16009 16010/** 16011 * @ngdoc service 16012 * @name $templateRequest 16013 * 16014 * @description 16015 * The `$templateRequest` service downloads the provided template using `$http` and, upon success, 16016 * stores the contents inside of `$templateCache`. If the HTTP request fails or the response data 16017 * of the HTTP request is empty, a `$compile` error will be thrown (the exception can be thwarted 16018 * by setting the 2nd parameter of the function to true). 16019 * 16020 * @param {string} tpl The HTTP request template URL 16021 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 16022 * 16023 * @return {Promise} the HTTP Promise for the given. 16024 * 16025 * @property {number} totalPendingRequests total amount of pending template requests being downloaded. 16026 */ 16027function $TemplateRequestProvider() { 16028 this.$get = ['$templateCache', '$http', '$q', function($templateCache, $http, $q) { 16029 function handleRequestFn(tpl, ignoreRequestError) { 16030 handleRequestFn.totalPendingRequests++; 16031 16032 var transformResponse = $http.defaults && $http.defaults.transformResponse; 16033 16034 if (isArray(transformResponse)) { 16035 transformResponse = transformResponse.filter(function(transformer) { 16036 return transformer !== defaultHttpResponseTransform; 16037 }); 16038 } else if (transformResponse === defaultHttpResponseTransform) { 16039 transformResponse = null; 16040 } 16041 16042 var httpOptions = { 16043 cache: $templateCache, 16044 transformResponse: transformResponse 16045 }; 16046 16047 return $http.get(tpl, httpOptions) 16048 .finally(function() { 16049 handleRequestFn.totalPendingRequests--; 16050 }) 16051 .then(function(response) { 16052 return response.data; 16053 }, handleError); 16054 16055 function handleError(resp) { 16056 if (!ignoreRequestError) { 16057 throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl); 16058 } 16059 return $q.reject(resp); 16060 } 16061 } 16062 16063 handleRequestFn.totalPendingRequests = 0; 16064 16065 return handleRequestFn; 16066 }]; 16067} 16068
vendor: 5,832 bytes, lines 16069-16245
16069function $$TestabilityProvider() { 16070 this.$get = ['$rootScope', '$browser', '$location', 16071 function($rootScope, $browser, $location) { 16072 16073 /** 16074 * @name $testability 16075 * 16076 * @description 16077 * The private $$testability service provides a collection of methods for use when debugging 16078 * or by automated test and debugging tools. 16079 */ 16080 var testability = {}; 16081 16082 /** 16083 * @name $$testability#findBindings 16084 * 16085 * @description 16086 * Returns an array of elements that are bound (via ng-bind or {{}}) 16087 * to expressions matching the input. 16088 * 16089 * @param {Element} element The element root to search from. 16090 * @param {string} expression The binding expression to match. 16091 * @param {boolean} opt_exactMatch If true, only returns exact matches 16092 * for the expression. Filters and whitespace are ignored. 16093 */ 16094 testability.findBindings = function(element, expression, opt_exactMatch) { 16095 var bindings = element.getElementsByClassName('ng-binding'); 16096 var matches = []; 16097 forEach(bindings, function(binding) { 16098 var dataBinding = angular.element(binding).data('$binding'); 16099 if (dataBinding) { 16100 forEach(dataBinding, function(bindingName) { 16101 if (opt_exactMatch) { 16102 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 16103 if (matcher.test(bindingName)) { 16104 matches.push(binding); 16105 } 16106 } else { 16107 if (bindingName.indexOf(expression) != -1) { 16108 matches.push(binding); 16109 } 16110 } 16111 }); 16112 } 16113 }); 16114 return matches; 16115 }; 16116 16117 /** 16118 * @name $$testability#findModels 16119 * 16120 * @description 16121 * Returns an array of elements that are two-way found via ng-model to 16122 * expressions matching the input. 16123 * 16124 * @param {Element} element The element root to search from. 16125 * @param {string} expression The model expression to match. 16126 * @param {boolean} opt_exactMatch If true, only returns exact matches 16127 * for the expression. 16128 */ 16129 testability.findModels = function(element, expression, opt_exactMatch) { 16130 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 16131 for (var p = 0; p < prefixes.length; ++p) { 16132 var attributeEquals = opt_exactMatch ? '=' : '*='; 16133 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 16134 var elements = element.querySelectorAll(selector); 16135 if (elements.length) { 16136 return elements; 16137 } 16138 } 16139 }; 16140 16141 /** 16142 * @name $$testability#getLocation 16143 * 16144 * @description 16145 * Shortcut for getting the location in a browser agnostic way. Returns 16146 * the path, search, and hash. (e.g. /path?a=b#hash) 16147 */ 16148 testability.getLocation = function() { 16149 return $location.url(); 16150 }; 16151 16152 /** 16153 * @name $$testability#setLocation 16154 * 16155 * @description 16156 * Shortcut for navigating to a location without doing a full page reload. 16157 * 16158 * @param {string} url The location url (path, search and hash, 16159 * e.g. /path?a=b#hash) to go to. 16160 */ 16161 testability.setLocation = function(url) { 16162 if (url !== $location.url()) { 16163 $location.url(url); 16164 $rootScope.$digest(); 16165 } 16166 }; 16167 16168 /** 16169 * @name $$testability#whenStable 16170 * 16171 * @description 16172 * Calls the callback when $timeout and $http requests are completed. 16173 * 16174 * @param {function} callback 16175 */ 16176 testability.whenStable = function(callback) { 16177 $browser.notifyWhenNoOutstandingRequests(callback); 16178 }; 16179 16180 return testability; 16181 }]; 16182} 16183 16184function $TimeoutProvider() { 16185 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 16186 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 16187 var deferreds = {}; 16188 16189 16190 /** 16191 * @ngdoc service 16192 * @name $timeout 16193 * 16194 * @description 16195 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 16196 * block and delegates any exceptions to 16197 * {@link ng.$exceptionHandler $exceptionHandler} service. 16198 * 16199 * The return value of registering a timeout function is a promise, which will be resolved when 16200 * the timeout is reached and the timeout function is executed. 16201 * 16202 * To cancel a timeout request, call `$timeout.cancel(promise)`. 16203 * 16204 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 16205 * synchronously flush the queue of deferred functions. 16206 * 16207 * @param {function()} fn A function, whose execution should be delayed. 16208 * @param {number=} [delay=0] Delay in milliseconds. 16209 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 16210 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 16211 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 16212 * promise will be resolved with is the return value of the `fn` function. 16213 * 16214 */ 16215 function timeout(fn, delay, invokeApply) { 16216 var skipApply = (isDefined(invokeApply) && !invokeApply), 16217 deferred = (skipApply ? $$q : $q).defer(), 16218 promise = deferred.promise, 16219 timeoutId; 16220 16221 timeoutId = $browser.defer(function() { 16222 try { 16223 deferred.resolve(fn()); 16224 } catch (e) { 16225 deferred.reject(e); 16226 $exceptionHandler(e); 16227 } 16228 finally { 16229 delete deferreds[promise.$$timeoutId]; 16230 } 16231 16232 if (!skipApply) $rootScope.$apply(); 16233 }, delay); 16234 16235 promise.$$timeoutId = timeoutId; 16236 deferreds[timeoutId] = deferred; 16237 16238 return promise; 16239 } 16240 16241 16242 /** 16243 * @ngdoc method 16244 * @name $timeout#cancel 16245 *
16246 * @description 16247 * Cancels a task associated with the `promise`. As a result of this, the promise will be 16248 * resolved with a rejection. 16249 * 16250 * @param {Promise=} promise Promise returned by the `$timeout` function. 16251 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 16252 * canceled. 16253 */ 16254 timeout.cancel = function(promise) { 16255 if (promise && promise.$$timeoutId in deferreds) { 16256 deferreds[promise.$$timeoutId].reject('canceled'); 16257 delete deferreds[promise.$$timeoutId]; 16258 return $browser.defer.cancel(promise.$$timeoutId); 16259 } 16260 return false; 16261 }; 16262 16263 return timeout; 16264 }]; 16265} 16266 16267// NOTE: The usage of window and document instead of $window and $document here is 16268// deliberate. This service depends on the specific behavior of anchor nodes created by the 16269// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 16270// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 16271// doesn't know about mocked locations and resolves URLs to the real document - which is 16272// exactly the behavior needed here. There is little value is mocking these out for this 16273// service. 16274var urlParsingNode = document.createElement("a"); 16275var originUrl = urlResolve(window.location.href); 16276 16277 16278/** 16279 * 16280 * Implementation Notes for non-IE browsers 16281 * ---------------------------------------- 16282 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 16283 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 16284 * URL will be resolved into an absolute URL in the context of the application document. 16285 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 16286 * properties are all populated to reflect the normalized URL. This approach has wide 16287 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 16288 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 16289 * 16290 * Implementation Notes for IE 16291 * --------------------------- 16292 * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other 16293 * browsers. However, the parsed components will not be set if the URL assigned did not specify 16294 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 16295 * work around that by performing the parsing in a 2nd step by taking a previously normalized 16296 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 16297 * properties such as protocol, hostname, port, etc. 16298 * 16299 * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one 16300 * uses the inner HTML approach to assign the URL as part of an HTML snippet - 16301 * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. 16302 * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. 16303 * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that 16304 * method and IE < 8 is unsupported. 16305 * 16306 * References: 16307 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 16308 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 16309 * http://url.spec.whatwg.org/#urlutils 16310 * https://github.com/angular/angular.js/pull/2902 16311 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 16312 * 16313 * @kind function 16314 * @param {string} url The URL to be parsed. 16315 * @description Normalizes and parses a URL. 16316 * @returns {object} Returns the normalized URL as a dictionary. 16317 * 16318 * | member name | Description | 16319 * |---------------|----------------| 16320 * | href | A normalized version of the provided URL if it was not an absolute URL | 16321 * | protocol | The protocol including the trailing colon | 16322 * | host | The host and port (if the port is non-default) of the normalizedUrl | 16323 * | search | The search params, minus the question mark | 16324 * | hash | The hash string, minus the hash symbol 16325 * | hostname | The hostname 16326 * | port | The port, without ":" 16327 * | pathname | The pathname, beginning with "/" 16328 * 16329 */ 16330function urlResolve(url) { 16331 var href = url; 16332 16333 if (msie) { 16334 // Normalize before parse. Refer Implementation Notes on why this is 16335 // done in two steps on IE. 16336 urlParsingNode.setAttribute("href", href); 16337 href = urlParsingNode.href; 16338 } 16339 16340 urlParsingNode.setAttribute('href', href); 16341 16342 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 16343 return { 16344 href: urlParsingNode.href, 16345 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 16346 host: urlParsingNode.host, 16347 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 16348 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 16349 hostname: urlParsingNode.hostname, 16350 port: urlParsingNode.port, 16351 pathname: (urlParsingNode.pathname.charAt(0) === '/') 16352 ? urlParsingNode.pathname 16353 : '/' + urlParsingNode.pathname 16354 }; 16355} 16356 16357/** 16358 * Parse a request URL and determine whether this is a same-origin request as the application document. 16359 * 16360 * @param {string|object} requestUrl The url of the request as a string that will be resolved 16361 * or a parsed URL object. 16362 * @returns {boolean} Whether the request is for the same origin as the application document. 16363 */ 16364function urlIsSameOrigin(requestUrl) { 16365 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 16366 return (parsed.protocol === originUrl.protocol && 16367 parsed.host === originUrl.host); 16368} 16369 16370/** 16371 * @ngdoc service 16372 * @name $window 16373 * 16374 * @description 16375 * A reference to the browser's `window` object. While `window` 16376 * is globally available in JavaScript, it causes testability problems, because 16377 * it is a global variable. In angular we always refer to it through the 16378 * `$window` service, so it may be overridden, removed or mocked for testing. 16379 * 16380 * Expressions, like the one defined for the `ngClick` directive in the example 16381 * below, are evaluated with respect to the current scope. Therefore, there is 16382 * no risk of inadvertently coding in a dependency on a global value in such an 16383 * expression. 16384 * 16385 * @example 16386 <example module="windowExample"> 16387 <file name="index.html"> 16388 <script> 16389 angular.module('windowExample', [])
16390 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 16391 $scope.greeting = 'Hello, World!'; 16392 $scope.doGreeting = function(greeting) { 16393 $window.alert(greeting); 16394 }; 16395 }]); 16396 </script> 16397 <div ng-controller="ExampleController"> 16398 <input type="text" ng-model="greeting" /> 16399 <button ng-click="doGreeting(greeting)">ALERT</button> 16400 </div> 16401 </file> 16402 <file name="protractor.js" type="protractor"> 16403 it('should display the greeting in the input box', function() { 16404 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 16405 // If we click the button it will block the test runner 16406 // element(':button').click(); 16407 }); 16408 </file> 16409 </example> 16410 */ 16411function $WindowProvider() { 16412 this.$get = valueFn(window); 16413} 16414 16415/* global currencyFilter: true, 16416 dateFilter: true, 16417 filterFilter: true, 16418 jsonFilter: true, 16419 limitToFilter: true, 16420 lowercaseFilter: true, 16421 numberFilter: true, 16422 orderByFilter: true, 16423 uppercaseFilter: true, 16424 */ 16425 16426/** 16427 * @ngdoc provider 16428 * @name $filterProvider 16429 * @description 16430 * 16431 * Filters are just functions which transform input to an output. However filters need to be 16432 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 16433 * annotated with dependencies and is responsible for creating a filter function. 16434 * 16435 * ```js 16436 * // Filter registration 16437 * function MyModule($provide, $filterProvider) { 16438 * // create a service to demonstrate injection (not always needed) 16439 * $provide.value('greet', function(name){ 16440 * return 'Hello ' + name + '!'; 16441 * }); 16442 * 16443 * // register a filter factory which uses the 16444 * // greet service to demonstrate DI. 16445 * $filterProvider.register('greet', function(greet){ 16446 * // return the filter function which uses the greet service 16447 * // to generate salutation 16448 * return function(text) { 16449 * // filters need to be forgiving so check input validity 16450 * return text && greet(text) || text; 16451 * }; 16452 * }); 16453 * } 16454 * ``` 16455 * 16456 * The filter function is registered with the `$injector` under the filter name suffix with 16457 * `Filter`. 16458 * 16459 * ```js 16460 * it('should be the same instance', inject( 16461 * function($filterProvider) { 16462 * $filterProvider.register('reverse', function(){ 16463 * return ...; 16464 * }); 16465 * }, 16466 * function($filter, reverseFilter) { 16467 * expect($filter('reverse')).toBe(reverseFilter); 16468 * }); 16469 * ``` 16470 * 16471 * 16472 * For more information about how angular filters work, and how to create your own filters, see 16473 * {@link guide/filter Filters} in the Angular Developer Guide. 16474 */ 16475 16476/** 16477 * @ngdoc service 16478 * @name $filter 16479 * @kind function 16480 * @description 16481 * Filters are used for formatting data displayed to the user. 16482 * 16483 * The general syntax in templates is as follows: 16484 * 16485 * {{ expression [| filter_name[:parameter_value] ... ] }} 16486 * 16487 * @param {String} name Name of the filter function to retrieve 16488 * @return {Function} the filter function 16489 * @example 16490 <example name="$filter" module="filterExample"> 16491 <file name="index.html"> 16492 <div ng-controller="MainCtrl"> 16493 <h3>{{ originalText }}</h3> 16494 <h3>{{ filteredText }}</h3> 16495 </div> 16496 </file> 16497 16498 <file name="script.js"> 16499 angular.module('filterExample', []) 16500 .controller('MainCtrl', function($scope, $filter) { 16501 $scope.originalText = 'hello'; 16502 $scope.filteredText = $filter('uppercase')($scope.originalText); 16503 }); 16504 </file> 16505 </example> 16506 */ 16507$FilterProvider.$inject = ['$provide']; 16508function $FilterProvider($provide) { 16509 var suffix = 'Filter'; 16510 16511 /** 16512 * @ngdoc method 16513 * @name $filterProvider#register 16514 * @param {string|Object} name Name of the filter function, or an object map of filters where 16515 * the keys are the filter names and the values are the filter factories. 16516 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 16517 * of the registered filter instances. 16518 */ 16519 function register(name, factory) { 16520 if (isObject(name)) { 16521 var filters = {};
16522 forEach(name, function(filter, key) { 16523 filters[key] = register(key, filter); 16524 }); 16525 return filters; 16526 } else { 16527 return $provide.factory(name + suffix, factory); 16528 } 16529 } 16530 this.register = register; 16531 16532 this.$get = ['$injector', function($injector) { 16533 return function(name) { 16534 return $injector.get(name + suffix); 16535 }; 16536 }]; 16537 16538 //////////////////////////////////////// 16539 16540 /* global 16541 currencyFilter: false, 16542 dateFilter: false, 16543 filterFilter: false, 16544 jsonFilter: false, 16545 limitToFilter: false, 16546 lowercaseFilter: false, 16547 numberFilter: false, 16548 orderByFilter: false, 16549 uppercaseFilter: false, 16550 */ 16551 16552 register('currency', currencyFilter); 16553 register('date', dateFilter); 16554 register('filter', filterFilter); 16555 register('json', jsonFilter); 16556 register('limitTo', limitToFilter); 16557 register('lowercase', lowercaseFilter); 16558 register('number', numberFilter); 16559 register('orderBy', orderByFilter); 16560 register('uppercase', uppercaseFilter); 16561} 16562 16563/** 16564 * @ngdoc filter 16565 * @name filter 16566 * @kind function 16567 * 16568 * @description 16569 * Selects a subset of items from `array` and returns it as a new array. 16570 * 16571 * @param {Array} array The source array. 16572 * @param {string|Object|function()} expression The predicate to be used for selecting items from 16573 * `array`. 16574 * 16575 * Can be one of: 16576 * 16577 * - `string`: The string is used for matching against the contents of the `array`. All strings or 16578 * objects with string properties in `array` that match this string will be returned. This also 16579 * applies to nested object properties. 16580 * The predicate can be negated by prefixing the string with `!`. 16581 * 16582 * - `Object`: A pattern object can be used to filter specific properties on objects contained 16583 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 16584 * which have property `name` containing "M" and property `phone` containing "1". A special 16585 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 16586 * property of the object or its nested object properties. That's equivalent to the simple 16587 * substring match with a `string` as described above. The predicate can be negated by prefixing 16588 * the string with `!`. 16589 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 16590 * not containing "M". 16591 * 16592 * Note that a named property will match properties on the same level only, while the special 16593 * `$` property will match properties on the same level or deeper. E.g. an array item like 16594 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 16595 * **will** be matched by `{$: 'John'}`. 16596 * 16597 * - `function(value, index)`: A predicate function can be used to write arbitrary filters. The 16598 * function is called for each element of `array`. The final result is an array of those 16599 * elements that the predicate returned true for. 16600 * 16601 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 16602 * determining if the expected value (from the filter expression) and actual value (from 16603 * the object in the array) should be considered a match. 16604 * 16605 * Can be one of: 16606 * 16607 * - `function(actual, expected)`: 16608 * The function will be given the object value and the predicate value to compare and 16609 * should return true if both values should be considered equal. 16610 * 16611 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 16612 * This is essentially strict comparison of expected and actual. 16613 * 16614 * - `false|undefined`: A short hand for a function which will look for a substring match in case 16615 * insensitive way. 16616 * 16617 * @example 16618 <example> 16619 <file name="index.html"> 16620 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 16621 {name:'Mary', phone:'800-BIG-MARY'}, 16622 {name:'Mike', phone:'555-4321'}, 16623 {name:'Adam', phone:'555-5678'}, 16624 {name:'Julie', phone:'555-8765'}, 16625 {name:'Juliette', phone:'555-5678'}]"></div> 16626 16627 Search: <input ng-model="searchText"> 16628 <table id="searchTextResults"> 16629 <tr><th>
16629Name</th><th>Phone</th></tr> 16630 <tr ng-repeat="friend in friends | filter:searchText"> 16631 <td>{{friend.name}}</td> 16632 <td>{{friend.phone}}</td> 16633 </tr> 16634 </table> 16635 <hr> 16636 Any: <input ng-model="search.$"> <br> 16637 Name only <input ng-model="search.name"><br> 16638 Phone only <input ng-model="search.phone"><br> 16639 Equality <input type="checkbox" ng-model="strict"><br> 16640 <table id="searchObjResults"> 16641 <tr><th>Name</th><th>Phone</th></tr> 16642 <tr ng-repeat="friendObj in friends | filter:search:strict"> 16643 <td>{{friendObj.name}}</td> 16644 <td>{{friendObj.phone}}</td> 16645 </tr> 16646 </table> 16647 </file> 16648 <file name="protractor.js" type="protractor"> 16649 var expectFriendNames = function(expectedNames, key) { 16650 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 16651 arr.forEach(function(wd, i) { 16652 expect(wd.getText()).toMatch(expectedNames[i]); 16653 }); 16654 }); 16655 }; 16656 16657 it('should search across all fields when filtering with a string', function() { 16658 var searchText = element(by.model('searchText')); 16659 searchText.clear(); 16660 searchText.sendKeys('m'); 16661 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 16662 16663 searchText.clear(); 16664 searchText.sendKeys('76'); 16665 expectFriendNames(['John', 'Julie'], 'friend'); 16666 }); 16667 16668 it('should search in specific fields when filtering with a predicate object', function() { 16669 var searchAny = element(by.model('search.$')); 16670 searchAny.clear(); 16671 searchAny.sendKeys('i'); 16672 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 16673 }); 16674 it('should use a equal comparison when comparator is true', function() { 16675 var searchName = element(by.model('search.name')); 16676 var strict = element(by.model('strict')); 16677 searchName.clear(); 16678 searchName.sendKeys('Julie'); 16679 strict.click(); 16680 expectFriendNames(['Julie'], 'friendObj'); 16681 }); 16682 </file> 16683 </example> 16684 */ 16685function filterFilter() { 16686 return function(array, expression, comparator) { 16687 if (!isArray(array)) return array; 16688 16689 var predicateFn; 16690 var matchAgainstAnyProp; 16691 16692 switch (typeof expression) { 16693 case 'function': 16694 predicateFn = expression; 16695 break; 16696 case 'boolean': 16697 case 'number': 16698 case 'string': 16699 matchAgainstAnyProp = true; 16700 //jshint -W086 16701 case 'object': 16702 //jshint +W086 16703 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); 16704 break; 16705 default: 16706 return array; 16707 } 16708 16709 return array.filter(predicateFn); 16710 }; 16711} 16712 16713// Helper functions for `filterFilter` 16714function createPredicateFn(expression, comparator, matchAgainstAnyProp) { 16715 var shouldMatchPrimitives = isObject(expression) && ('$' in expression); 16716 var predicateFn; 16717 16718 if (comparator === true) { 16719 comparator = equals; 16720 } else if (!isFunction(comparator)) { 16721 comparator = function(actual, expected) { 16722 if (isObject(actual) || isObject(expected)) { 16723 // Prevent an object to be considered equal to a string like `'[object'` 16724 return false; 16725 } 16726 16727 actual = lowercase('' + actual); 16728 expected = lowercase('' + expected); 16729 return actual.indexOf(expected) !== -1; 16730 }; 16731 } 16732 16733 predicateFn = function(item) { 16734 if (shouldMatchPrimitives && !isObject(item)) { 16735 return deepCompare(item, expression.$, comparator, false); 16736 } 16737 return deepCompare(item, expression, comparator, matchAgainstAnyProp); 16738 }; 16739 16740 return predicateFn; 16741} 16742 16743function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { 16744 var actualType = typeof actual; 16745 var expectedType = typeof expected; 16746 16747 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 16748 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); 16749 } else if (isArray(actual)) { 16750 // In case `actual` is an array, consider it a match 16751 // if ANY of it's items matches `expected` 16752 return actual.some(function(item) { 16753 return deepCompare(item, expected, comparator, matchAgainstAnyProp); 16754 }); 16755 } 16756 16757 switch (actualType) { 16758 case 'object': 16759 var key; 16760 if (matchAgainstAnyProp) { 16761 for (key in actual) { 16762 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) { 16763 return true; 16764 } 16765 } 16766 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); 16767 } else if (expectedType === 'object') { 16768 for (key in expected) { 16769 var expectedVal = expected[key]; 16770 if (isFunction(expectedVal)) { 16771 continue; 16772 } 16773 16774 var matchAnyProperty = key === '$'; 16775 var actualVal = matchAnyProperty ? actual : actual[key]; 16776 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { 16777 return false;
16778 } 16779 } 16780 return true; 16781 } else { 16782 return comparator(actual, expected); 16783 } 16784 break; 16785 case 'function': 16786 return false; 16787 default: 16788 return comparator(actual, expected); 16789 } 16790} 16791 16792/** 16793 * @ngdoc filter 16794 * @name currency 16795 * @kind function 16796 * 16797 * @description 16798 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 16799 * symbol for current locale is used. 16800 * 16801 * @param {number} amount Input to filter. 16802 * @param {string=} symbol Currency symbol or identifier to be displayed. 16803 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 16804 * @returns {string} Formatted number. 16805 * 16806 * 16807 * @example 16808 <example module="currencyExample"> 16809 <file name="index.html"> 16810 <script> 16811 angular.module('currencyExample', []) 16812 .controller('ExampleController', ['$scope', function($scope) { 16813 $scope.amount = 1234.56; 16814 }]); 16815 </script>
16816 <div ng-controller="ExampleController"> 16817 <input type="number" ng-model="amount"> <br> 16818 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 16819 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span> 16820 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 16821 </div> 16822 </file> 16823 <file name="protractor.js" type="protractor"> 16824 it('should init with 1234.56', function() { 16825 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 16826 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 16827 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 16828 }); 16829 it('should update', function() { 16830 if (browser.params.browser == 'safari') { 16831 // Safari does not understand the minus key. See 16832 // https://github.com/angular/protractor/issues/481 16833 return; 16834 } 16835 element(by.model('amount')).clear(); 16836 element(by.model('amount')).sendKeys('-1234'); 16837 expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); 16838 expect(element(by.id('currency-custom')).getText()).toBe('(USD$1,234.00)'); 16839 expect(element(by.id('currency-no-fractions')).getText()).toBe('(USD$1,234)'); 16840 }); 16841 </file> 16842 </example> 16843 */ 16844currencyFilter.$inject = ['$locale']; 16845function currencyFilter($locale) { 16846 var formats = $locale.NUMBER_FORMATS; 16847 return function(amount, currencySymbol, fractionSize) { 16848 if (isUndefined(currencySymbol)) { 16849 currencySymbol = formats.CURRENCY_SYM; 16850 } 16851 16852 if (isUndefined(fractionSize)) { 16853 fractionSize = formats.PATTERNS[1].maxFrac; 16854 } 16855 16856 // if null or undefined pass it through 16857 return (amount == null) 16858 ? amount 16859 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 16860 replace(/\u00A4/g, currencySymbol); 16861 }; 16862} 16863 16864/** 16865 * @ngdoc filter 16866 * @name number 16867 * @kind function 16868 * 16869 * @description 16870 * Formats a number as text. 16871 * 16872 * If the input is not a number an empty string is returned. 16873 * 16874 * @param {number|string} number Number to format. 16875 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 16876 * If this is not provided then the fraction size is computed from the current locale's number 16877 * formatting pattern. In the case of the default locale, it will be 3. 16878 * @returns {string} Number rounded to decimalPlaces and places a â,â after each third digit. 16879 * 16880 * @example 16881 <example module="numberFilterExample"> 16882 <file name="index.html"> 16883 <script> 16884 angular.module('numberFilterExample', []) 16885 .controller('ExampleController', ['$scope', function($scope) { 16886 $scope.val = 1234.56789; 16887 }]); 16888 </script>
16889 <div ng-controller="ExampleController"> 16890 Enter number: <input ng-model='val'><br> 16891 Default formatting: <span id='number-default'>{{val | number}}</span><br> 16892 No fractions: <span>{{val | number:0}}</span><br> 16893 Negative number: <span>{{-val | number:4}}</span> 16894 </div> 16895 </file> 16896 <file name="protractor.js" type="protractor"> 16897 it('should format numbers', function() { 16898 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 16899 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 16900 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 16901 }); 16902 16903 it('should update', function() { 16904 element(by.model('val')).clear(); 16905 element(by.model('val')).sendKeys('3374.333'); 16906 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 16907 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 16908 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 16909 }); 16910 </file> 16911 </example> 16912 */ 16913 16914 16915numberFilter.$inject = ['$locale']; 16916function numberFilter($locale) { 16917 var formats = $locale.NUMBER_FORMATS; 16918 return function(number, fractionSize) { 16919 16920 // if null or undefined pass it through 16921 return (number == null) 16922 ? number 16923 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 16924 fractionSize); 16925 }; 16926} 16927 16928var DECIMAL_SEP = '.'; 16929function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 16930 if (!isFinite(number) || isObject(number)) return ''; 16931 16932 var isNegative = number < 0; 16933 number = Math.abs(number); 16934 var numStr = number + '', 16935 formatedText = '', 16936 parts = []; 16937 16938 var hasExponent = false; 16939 if (numStr.indexOf('e') !== -1) { 16940 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 16941 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 16942 number = 0; 16943 } else { 16944 formatedText = numStr; 16945 hasExponent = true; 16946 } 16947 } 16948 16949 if (!hasExponent) { 16950 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 16951 16952 // determine fractionSize if it is not specified 16953 if (isUndefined(fractionSize)) { 16954 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 16955 } 16956 16957 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics 16958 // inspired by: 16959 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round 16960 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); 16961 16962 var fraction = ('' + number).split(DECIMAL_SEP); 16963 var whole = fraction[0]; 16964 fraction = fraction[1] || ''; 16965 16966 var i, pos = 0, 16967 lgroup = pattern.lgSize, 16968 group = pattern.gSize; 16969 16970 if (whole.length >= (lgroup + group)) { 16971 pos = whole.length - lgroup; 16972 for (i = 0; i < pos; i++) { 16973 if ((pos - i) % group === 0 && i !== 0) { 16974 formatedText += groupSep; 16975 } 16976 formatedText += whole.charAt(i); 16977 } 16978 } 16979 16980 for (i = pos; i < whole.length; i++) { 16981 if ((whole.length - i) % lgroup === 0 && i !== 0) { 16982 formatedText += groupSep; 16983 } 16984 formatedText += whole.charAt(i); 16985 } 16986 16987 // format fraction part. 16988 while (fraction.length < fractionSize) { 16989 fraction += '0'; 16990 } 16991 16992 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 16993 } else { 16994 if (fractionSize > 0 && number < 1) { 16995 formatedText = number.toFixed(fractionSize); 16996 number = parseFloat(formatedText); 16997 } 16998 } 16999 17000 if (number === 0) { 17001 isNegative = false; 17002 } 17003 17004 parts.push(isNegative ? pattern.negPre : pattern.posPre, 17005 formatedText, 17006 isNegative ? pattern.negSuf : pattern.posSuf); 17007 return parts.join(''); 17008} 17009 17010function padNumber(num, digits, trim) { 17011 var neg = ''; 17012 if (num < 0) { 17013 neg = '-'; 17014 num = -num; 17015 } 17016 num = '' + num; 17017 while (num.length < digits) num = '0' + num; 17018 if (trim) 17019 num = num.substr(num.length - digits); 17020 return neg + num; 17021} 17022 17023 17024function dateGetter(name, size, offset, trim) { 17025 offset = offset || 0; 17026 return function(date) { 17027 var value = date['get' + name](); 17028 if (offset > 0 || value > -offset) 17029 value += offset; 17030 if (value === 0 && offset == -12) value = 12; 17031 return padNumber(value, size, trim); 17032 }; 17033} 17034 17035function dateStrGetter(name, shortForm) { 17036 return function(date, formats) { 17037 var value = date['get' + name](); 17038 var get = uppercase(shortForm ? ('SHORT' + name) : name); 17039 17040 return formats[get][value]; 17041 }; 17042} 17043 17044function timeZoneGetter(date) { 17045 var zone = -1 * date.getTimezoneOffset(); 17046 var paddedZone = (zone >= 0) ? "+" : ""; 17047 17048 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 17049 padNumber(Math.abs(zone % 60), 2); 17050 17051 return paddedZone; 17052} 17053 17054function getFirstThursdayOfYear(year) { 17055 // 0 = index of January 17056 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 17057 // 4 = index of Thursday (+1 to account for 1st = 5) 17058 // 11 = index of *next* Thursday (+1 account for 1st = 12) 17059 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 17060} 17061 17062function getThursdayThisWeek(datetime) { 17063 return new Date(datetime.getFullYear(), datetime.getMonth(), 17064 // 4 = index of Thursday 17065 datetime.getDate() + (4 - datetime.getDay())); 17066} 17067 17068function weekGetter(size) { 17069 return function(date) { 17070 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 17071 thisThurs = getThursdayThisWeek(date); 17072 17073 var diff = +thisThurs - +firstThurs, 17074 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 17075 17076 return padNumber(result, size); 17077 }; 17078} 17079 17080function ampmGetter(date, formats) { 17081 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 17082} 17083 17084var DATE_FORMATS = { 17085 yyyy: dateGetter('FullYear', 4), 17086 yy: dateGetter('FullYear', 2, 0, true), 17087 y: dateGetter('FullYear', 1), 17088 MMMM: dateStrGetter('Month'),
17089 MMM: dateStrGetter('Month', true), 17090 MM: dateGetter('Month', 2, 1), 17091 M: dateGetter('Month', 1, 1), 17092 dd: dateGetter('Date', 2), 17093 d: dateGetter('Date', 1), 17094 HH: dateGetter('Hours', 2), 17095 H: dateGetter('Hours', 1), 17096 hh: dateGetter('Hours', 2, -12), 17097 h: dateGetter('Hours', 1, -12), 17098 mm: dateGetter('Minutes', 2), 17099 m: dateGetter('Minutes', 1), 17100 ss: dateGetter('Seconds', 2), 17101 s: dateGetter('Seconds', 1), 17102 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 17103 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 17104 sss: dateGetter('Milliseconds', 3), 17105 EEEE: dateStrGetter('Day'), 17106 EEE: dateStrGetter('Day', true), 17107 a: ampmGetter, 17108 Z: timeZoneGetter, 17109 ww: weekGetter(2), 17110 w: weekGetter(1) 17111}; 17112 17113var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/, 17114 NUMBER_STRING = /^\-?\d+$/; 17115 17116/** 17117 * @ngdoc filter 17118 * @name date 17119 * @kind function 17120 * 17121 * @description 17122 * Formats `date` to a string based on the requested `format`. 17123 * 17124 * `format` string can be composed of the following elements: 17125 * 17126 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 17127 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 17128 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 17129 * * `'MMMM'`: Month in year (January-December) 17130 * * `'MMM'`: Month in year (Jan-Dec) 17131 * * `'MM'`: Month in year, padded (01-12) 17132 * * `'M'`: Month in year (1-12) 17133 * * `'dd'`: Day in month, padded (01-31) 17134 * * `'d'`: Day in month (1-31) 17135 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 17136 * * `'EEE'`: Day in Week, (Sun-Sat) 17137 * * `'HH'`: Hour in day, padded (00-23) 17138 * * `'H'`: Hour in day (0-23) 17139 * * `'hh'`: Hour in AM/PM, padded (01-12) 17140 * * `'h'`: Hour in AM/PM, (1-12) 17141 * * `'mm'`: Minute in hour, padded (00-59) 17142 * * `'m'`: Minute in hour (0-59) 17143 * * `'ss'`: Second in minute, padded (00-59) 17144 * * `'s'`: Second in minute (0-59) 17145 * * `'sss'`: Millisecond in second, padded (000-999) 17146 * * `'a'`: AM/PM marker 17147 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 17148 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 17149 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year 17150 * 17151 * `format` string can also be one of the following predefined 17152 * {@link guide/i18n localizable formats}: 17153 * 17154 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 17155 * (e.g. Sep 3, 2010 12:05:08 PM) 17156 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 17157 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 17158 * (e.g. Friday, September 3, 2010) 17159 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 17160 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 17161 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 17162 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 17163 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 17164 * 17165 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 17166 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 17167 * (e.g. `"h 'o''clock'"`). 17168 * 17169 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 17170 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 17171 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 17172 * specified in the string input, the time is considered to be in the local timezone. 17173 * @param {string=} format Formatting rules (see Description). If not specified, 17174 * `mediumDate` is used. 17175 * @param {string=} timezone Timezone to be used for formatting. Right now, only `'UTC'` is supported. 17176 * If not specified, the timezone of the browser will be used. 17177 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 17178 * 17179 * @example 17180 <example> 17181 <file name="index.html">
17182 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 17183 <span>{{1288323623006 | date:'medium'}}</span><br> 17184 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 17185 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 17186 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 17187 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 17188 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 17189 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 17190 </file> 17191 <file name="protractor.js" type="protractor"> 17192 it('should format date', function() { 17193 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 17194 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 17195 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 17196 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 17197 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 17198 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 17199 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 17200 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 17201 }); 17202 </file> 17203 </example> 17204 */ 17205dateFilter.$inject = ['$locale']; 17206function dateFilter($locale) { 17207 17208 17209 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 17210 // 1 2 3 4 5 6 7 8 9 10 11 17211 function jsonStringToDate(string) { 17212 var match; 17213 if (match = string.match(R_ISO8601_STR)) { 17214 var date = new Date(0), 17215 tzHour = 0, 17216 tzMin = 0, 17217 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 17218 timeSetter = match[8] ? date.setUTCHours : date.setHours; 17219 17220 if (match[9]) { 17221 tzHour = int(match[9] + match[10]); 17222 tzMin = int(match[9] + match[11]); 17223 } 17224 dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3])); 17225 var h = int(match[4] || 0) - tzHour; 17226 var m = int(match[5] || 0) - tzMin; 17227 var s = int(match[6] || 0); 17228 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 17229 timeSetter.call(date, h, m, s, ms); 17230 return date; 17231 } 17232 return string; 17233 } 17234 17235 17236 return function(date, format, timezone) { 17237 var text = '', 17238 parts = [], 17239 fn, match; 17240 17241 format = format || 'mediumDate'; 17242 format = $locale.DATETIME_FORMATS[format] || format; 17243 if (isString(date)) { 17244 date = NUMBER_STRING.test(date) ? int(date) : jsonStringToDate(date); 17245 } 17246 17247 if (isNumber(date)) { 17248 date = new Date(date); 17249 } 17250 17251 if (!isDate(date)) { 17252 return date; 17253 } 17254 17255 while (format) { 17256 match = DATE_FORMATS_SPLIT.exec(format); 17257 if (match) { 17258 parts = concat(parts, match, 1); 17259 format = parts.pop(); 17260 } else { 17261 parts.push(format); 17262 format = null; 17263 } 17264 } 17265 17266 if (timezone && timezone === 'UTC') { 17267 date = new Date(date.getTime()); 17268 date.setMinutes(date.getMinutes() + date.getTimezoneOffset()); 17269 } 17270 forEach(parts, function(value) { 17271 fn = DATE_FORMATS[value]; 17272 text += fn ? fn(date, $locale.DATETIME_FORMATS) 17273 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 17274 }); 17275 17276 return text; 17277 }; 17278} 17279 17280 17281/** 17282 * @ngdoc filter 17283 * @name json 17284 * @kind function 17285 * 17286 * @description 17287 * Allows you to convert a JavaScript object into JSON string. 17288 * 17289 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 17290 * the binding is automatically converted to JSON. 17291 * 17292 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 17293 * @param {number=} spacing The number of spaces to use per indentation, defaults to 2. 17294 * @returns {string} JSON string. 17295 * 17296 * 17297 * @example 17298 <example> 17299 <file name="index.html"> 17300 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 17301 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 17302 </file> 17303 <file name="protractor.js" type="protractor"> 17304 it('should jsonify filtered objects', function() { 17305 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 17306 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 17307 }); 17308 </file> 17309 </example> 17310 * 17311 */ 17312function jsonFilter() { 17313 return function(object, spacing) { 17314 if (isUndefined(spacing)) { 17315 spacing = 2; 17316 } 17317 return toJson(object, spacing); 17318 }; 17319} 17320 17321 17322/** 17323 * @ngdoc filter 17324 * @name lowercase 17325 * @kind function 17326 * @description 17327 * Converts string to lowercase. 17328 * @see angular.lowercase 17329 */ 17330var lowercaseFilter = valueFn(lowercase); 17331 17332
17333/** 17334 * @ngdoc filter 17335 * @name uppercase 17336 * @kind function 17337 * @description 17338 * Converts string to uppercase. 17339 * @see angular.uppercase 17340 */ 17341var uppercaseFilter = valueFn(uppercase); 17342 17343/** 17344 * @ngdoc filter 17345 * @name limitTo 17346 * @kind function 17347 * 17348 * @description 17349 * Creates a new array or string containing only a specified number of elements. The elements 17350 * are taken from either the beginning or the end of the source array, string or number, as specified by 17351 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is 17352 * converted to a string. 17353 * 17354 * @param {Array|string|number} input Source array, string or number to be limited. 17355 * @param {string|number} limit The length of the returned array or string. If the `limit` number 17356 * is positive, `limit` number of items from the beginning of the source array/string are copied. 17357 * If the number is negative, `limit` number of items from the end of the source array/string 17358 * are copied. The `limit` will be trimmed if it exceeds `array.length` 17359 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 17360 * had less than `limit` elements. 17361 * 17362 * @example 17363 <example module="limitToExample"> 17364 <file name="index.html"> 17365 <script> 17366 angular.module('limitToExample', []) 17367 .controller('ExampleController', ['$scope', function($scope) { 17368 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 17369 $scope.letters = "abcdefghi"; 17370 $scope.longNumber = 2345432342; 17371 $scope.numLimit = 3; 17372 $scope.letterLimit = 3; 17373 $scope.longNumberLimit = 3; 17374 }]); 17375 </script>
17376 <div ng-controller="ExampleController"> 17377 Limit {{numbers}} to: <input type="number" step="1" ng-model="numLimit"> 17378 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 17379 Limit {{letters}} to: <input type="number" step="1" ng-model="letterLimit"> 17380 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 17381 Limit {{longNumber}} to: <input type="number" step="1" ng-model="longNumberLimit"> 17382 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 17383 </div> 17384 </file> 17385 <file name="protractor.js" type="protractor"> 17386 var numLimitInput = element(by.model('numLimit')); 17387 var letterLimitInput = element(by.model('letterLimit')); 17388 var longNumberLimitInput = element(by.model('longNumberLimit')); 17389 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 17390 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 17391 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 17392 17393 it('should limit the number array to first three items', function() { 17394 expect(numLimitInput.getAttribute('value')).toBe('3'); 17395 expect(letterLimitInput.getAttribute('value')).toBe('3'); 17396 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 17397 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 17398 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 17399 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 17400 }); 17401 17402 // There is a bug in safari and protractor that doesn't like the minus key 17403 // it('should update the output when -3 is entered', function() { 17404 // numLimitInput.clear(); 17405 // numLimitInput.sendKeys('-3'); 17406 // letterLimitInput.clear(); 17407 // letterLimitInput.sendKeys('-3'); 17408 // longNumberLimitInput.clear(); 17409 // longNumberLimitInput.sendKeys('-3'); 17410 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 17411 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 17412 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 17413 // }); 17414 17415 it('should not exceed the maximum size of input array', function() { 17416 numLimitInput.clear(); 17417 numLimitInput.sendKeys('100'); 17418 letterLimitInput.clear(); 17419 letterLimitInput.sendKeys('100'); 17420 longNumberLimitInput.clear(); 17421 longNumberLimitInput.sendKeys('100'); 17422 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 17423 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 17424 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 17425 }); 17426 </file> 17427 </example> 17428*/ 17429function limitToFilter() { 17430 return function(input, limit) { 17431 if (isNumber(input)) input = input.toString(); 17432 if (!isArray(input) && !isString(input)) return input; 17433 17434 if (Math.abs(Number(limit)) === Infinity) { 17435 limit = Number(limit); 17436 } else { 17437 limit = int(limit); 17438 } 17439 17440 //NaN check on limit 17441 if (limit) { 17442 return limit > 0 ? input.slice(0, limit) : input.slice(limit); 17443 } else { 17444 return isString(input) ? "" : []; 17445 } 17446 }; 17447} 17448 17449/** 17450 * @ngdoc filter 17451 * @name orderBy 17452 * @kind function 17453 * 17454 * @description 17455 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 17456 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 17457 * correctly, make sure they are actually being saved as numbers and not strings. 17458 * 17459 * @param {Array} array The array to sort. 17460 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be 17461 * used by the comparator to determine the order of elements. 17462 * 17463 * Can be one of: 17464 * 17465 * - `function`: Getter function. The result of this function will be sorted using the 17466 * `<`, `=`, `>` operator. 17467 * - `string`: An Angular expression. The result of this expression is used to compare elements 17468 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by 17469 * 3 first characters of a property called `name`). The result of a constant expression 17470 * is interpreted as a property name to be used in comparisons (for example `"special name"` 17471 * to sort object by the value of their `special name` property). An expression can be 17472 * optionally prefixed with `+` or `-` to control ascending or descending sort order 17473 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array 17474 * element itself is used to compare where sorting. 17475 * - `Array`: An array of function or string predicates. The first predicate in the array 17476 * is used for sorting, but when two items are equivalent, the next predicate is used. 17477 * 17478 * If the predicate is missing or empty then it defaults to `'+'`. 17479 * 17480 * @param {boolean=} reverse Reverse the order of the array. 17481 * @returns {Array} Sorted copy of the source array. 17482 * 17483 * @example 17484 <example module="orderByExample"> 17485 <file name="index.html"> 17486 <script> 17487 angular.module('orderByExample', [])
17488 .controller('ExampleController', ['$scope', function($scope) { 17489 $scope.friends = 17490 [{name:'John', phone:'555-1212', age:10}, 17491 {name:'Mary', phone:'555-9876', age:19}, 17492 {name:'Mike', phone:'555-4321', age:21}, 17493 {name:'Adam', phone:'555-5678', age:35}, 17494 {name:'Julie', phone:'555-8765', age:29}]; 17495 $scope.predicate = '-age'; 17496 }]); 17497 </script> 17498 <div ng-controller="ExampleController"> 17499 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 17500 <hr/> 17501 [ <a href="" ng-click="predicate=''">unsorted</a> ] 17502 <table class="friend"> 17503 <tr> 17504 <th><a href="" ng-click="predicate = 'name'; reverse=false">Name</a> 17505 (<a href="" ng-click="predicate = '-name'; reverse=false">^</a>)</th> 17506 <th><a href="" ng-click="predicate = 'phone'; reverse=!reverse">Phone Number</a></th> 17507 <th><a href="" ng-click="predicate = 'age'; reverse=!reverse">Age</a></th> 17508 </tr> 17509 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 17510 <td>{{friend.name}}</td> 17511 <td>{{friend.phone}}</td> 17512 <td>{{friend.age}}</td> 17513 </tr> 17514 </table> 17515 </div> 17516 </file> 17517 </example> 17518 * 17519 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the 17520 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 17521 * desired parameters. 17522 * 17523 * Example: 17524 * 17525 * @example 17526 <example module="orderByExample"> 17527 <file name="index.html">
17528 <div ng-controller="ExampleController"> 17529 <table class="friend"> 17530 <tr> 17531 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a> 17532 (<a href="" ng-click="order('-name',false)">^</a>)</th> 17533 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th> 17534 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th> 17535 </tr> 17536 <tr ng-repeat="friend in friends"> 17537 <td>{{friend.name}}</td> 17538 <td>{{friend.phone}}</td> 17539 <td>{{friend.age}}</td> 17540 </tr> 17541 </table> 17542 </div> 17543 </file> 17544 17545 <file name="script.js"> 17546 angular.module('orderByExample', []) 17547 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { 17548 var orderBy = $filter('orderBy'); 17549 $scope.friends = [ 17550 { name: 'John', phone: '555-1212', age: 10 }, 17551 { name: 'Mary', phone: '555-9876', age: 19 }, 17552 { name: 'Mike', phone: '555-4321', age: 21 }, 17553 { name: 'Adam', phone: '555-5678', age: 35 }, 17554 { name: 'Julie', phone: '555-8765', age: 29 } 17555 ]; 17556 $scope.order = function(predicate, reverse) { 17557 $scope.friends = orderBy($scope.friends, predicate, reverse); 17558 }; 17559 $scope.order('-age',false); 17560 }]); 17561 </file> 17562</example> 17563 */ 17564orderByFilter.$inject = ['$parse']; 17565function orderByFilter($parse) { 17566 return function(array, sortPredicate, reverseOrder) { 17567 if (!(isArrayLike(array))) return array; 17568 sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate]; 17569 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 17570 sortPredicate = sortPredicate.map(function(predicate) { 17571 var descending = false, get = predicate || identity; 17572 if (isString(predicate)) { 17573 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 17574 descending = predicate.charAt(0) == '-'; 17575 predicate = predicate.substring(1); 17576 } 17577 if (predicate === '') { 17578 // Effectively no predicate was passed so we compare identity 17579 return reverseComparator(compare, descending); 17580 } 17581 get = $parse(predicate); 17582 if (get.constant) { 17583 var key = get(); 17584 return reverseComparator(function(a, b) { 17585 return compare(a[key], b[key]); 17586 }, descending); 17587 } 17588 } 17589 return reverseComparator(function(a, b) { 17590 return compare(get(a),get(b)); 17591 }, descending); 17592 }); 17593 return slice.call(array).sort(reverseComparator(comparator, reverseOrder)); 17594 17595 function comparator(o1, o2) { 17596 for (var i = 0; i < sortPredicate.length; i++) { 17597 var comp = sortPredicate[i](o1, o2); 17598 if (comp !== 0) return comp; 17599 } 17600 return 0; 17601 } 17602 function reverseComparator(comp, descending) { 17603 return descending 17604 ? function(a, b) {return comp(b,a);} 17605 : comp; 17606 } 17607 17608 function isPrimitive(value) { 17609 switch (typeof value) { 17610 case 'number': /* falls through */ 17611 case 'boolean': /* falls through */ 17612 case 'string': 17613 return true; 17614 default: 17615 return false; 17616 } 17617 } 17618 17619 function objectToString(value) { 17620 if (value === null) return 'null'; 17621 if (typeof value.valueOf === 'function') { 17622 value = value.valueOf(); 17623 if (isPrimitive(value)) return value; 17624 } 17625 if (typeof value.toString === 'function') { 17626 value = value.toString(); 17627 if (isPrimitive(value)) return value; 17628 } 17629 return ''; 17630 } 17631 17632 function compare(v1, v2) { 17633 var t1 = typeof v1; 17634 var t2 = typeof v2; 17635 if (t1 === t2 && t1 === "object") { 17636 v1 = objectToString(v1); 17637 v2 = objectToString(v2); 17638 } 17639 if (t1 === t2) { 17640 if (t1 === "string") { 17641 v1 = v1.toLowerCase(); 17642 v2 = v2.toLowerCase(); 17643 } 17644 if (v1 === v2) return 0; 17645 return v1 < v2 ? -1 : 1; 17646 } else { 17647 return t1 < t2 ? -1 : 1; 17648 } 17649 } 17650 }; 17651} 17652 17653function ngDirective(directive) { 17654 if (isFunction(directive)) { 17655 directive = { 17656 link: directive 17657 }; 17658 } 17659 directive.restrict = directive.restrict || 'AC'; 17660 return valueFn(directive); 17661} 17662 17663/** 17664 * @ngdoc directive 17665 * @name a 17666 * @restrict E 17667 * 17668 * @description 17669 * Modifies the default behavior of the html A tag so that the default action is prevented when 17670 * the href attribute is empty. 17671 * 17672 * This change permits the easy creation of action links with the `ngClick` directive 17673 * without changing the location or causing page reloads, e.g.: 17674 * `<a href="" ng-click="list.addItem()">Add Item</a>` 17675 */ 17676var htmlAnchorDirective = valueFn({ 17677 restrict: 'E', 17678 compile: function(element, attr) { 17679 if (!attr.href && !attr.xlinkHref && !attr.name) { 17680 return function(scope, element) { 17681 // If the linked element is not an anchor tag anymore, do nothing 17682 if (element[0].nodeName.toLowerCase() !== 'a') return; 17683 17684 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 17685 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 17686 'xlink:href' : 'href'; 17687 element.on('click', function(event) { 17688 // if we have no href url, then don't navigate anywhere. 17689 if (!element.attr(href)) { 17690 event.preventDefault(); 17691 } 17692 }); 17693 }; 17694 } 17695 } 17696}); 17697 17698/** 17699 * @ngdoc directive 17700 * @name ngHref 17701 * @restrict A 17702 * @priority 99 17703 * 17704 * @description 17705 * Using Angular markup like `{{hash}}` in an href attribute will 17706 * make the link go to the wrong URL if the user clicks it before 17707 * Angular has a chance to replace the `{{hash}}
17707` markup with its 17708 * value. Until Angular replaces the markup the link will be broken 17709 * and will most likely return a 404 error. The `ngHref` directive 17710 * solves this problem. 17711 * 17712 * The wrong way to write it: 17713 * ```html 17714 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 17715 * ``` 17716 * 17717 * The correct way to write it: 17718 * ```html 17719 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 17720 * ``` 17721 * 17722 * @element A 17723 * @param {template} ngHref any string which can contain `{{}}` markup. 17724 * 17725 * @example 17726 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 17727 * in links and their different behaviors: 17728 <example> 17729 <file name="index.html"> 17730 <input ng-model="value" /><br /> 17731 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 17732 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 17733 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 17734 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 17735 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 17736 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 17737 </file> 17738 <file name="protractor.js" type="protractor"> 17739 it('should execute ng-click but not reload when href without value', function() { 17740 element(by.id('link-1')).click(); 17741 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 17742 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 17743 }); 17744 17745 it('should execute ng-click but not reload when href empty string', function() { 17746 element(by.id('link-2')).click(); 17747 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 17748 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 17749 }); 17750 17751 it('should execute ng-click and change url when ng-href specified', function() { 17752 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 17753 17754 element(by.id('link-3')).click(); 17755 17756 // At this point, we navigate away from an Angular page, so we need 17757 // to use browser.driver to get the base webdriver. 17758 17759 browser.wait(function() { 17760 return browser.driver.getCurrentUrl().then(function(url) { 17761 return url.match(/\/123$/); 17762 }); 17763 }, 5000, 'page should navigate to /123'); 17764 }); 17765 17766 xit('should execute ng-click but not reload when href empty string and name specified', function() { 17767 element(by.id('link-4')).click(); 17768 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 17769 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 17770 }); 17771 17772 it('should execute ng-click but not reload when no href but name specified', function() { 17773 element(by.id('link-5')).click(); 17774 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 17775 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 17776 }); 17777 17778 it('should only change url when only ng-href', function() { 17779 element(by.model('value')).clear(); 17780 element(by.model('value')).sendKeys('6'); 17781 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 17782 17783 element(by.id('link-6')).click(); 17784 17785 // At this point, we navigate away from an Angular page, so we need 17786 // to use browser.driver to get the base webdriver. 17787 browser.wait(function() { 17788 return browser.driver.getCurrentUrl().then(function(url) { 17789 return url.match(/\/6$/); 17790 }); 17791 }, 5000, 'page should navigate to /6'); 17792 }); 17793 </file> 17794 </example> 17795 */ 17796 17797/** 17798 * @ngdoc directive 17799 * @name ngSrc 17800 * @restrict A 17801 * @priority 99 17802 * 17803 * @description 17804 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 17805 * work right: The browser will fetch from the URL with the literal 17806 * text `{{hash}}` until Angular replaces the expression inside 17807 * `{{hash}}`. The `ngSrc` directive solves this problem. 17808 * 17809 * The buggy way to write it: 17810 * ```html 17811 * <img src="http://www.gravatar.com/avatar/{{hash}}"/> 17812 * ``` 17813 * 17814 * The correct way to write it: 17815 * ```html 17816 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}"/> 17817 * ``` 17818 * 17819 * @element IMG 17820 * @param {template} ngSrc any string which can contain `{{}}` markup. 17821 */ 17822 17823/** 17824 * @ngdoc directive 17825 * @name ngSrcset 17826 * @restrict A 17827 * @priority 99 17828 * 17829 * @description 17830 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 17831 * work right: The browser will fetch from the URL with the literal 17832 * text `{{hash}}` until Angular replaces the expression inside 17833 * `{{hash}}`. The `ngSrcset` directive solves this problem. 17834 * 17835 * The buggy way to write it: 17836 * ```html 17837 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 17838 * ``` 17839 * 17840 * The correct way to write it: 17841 * ```html 17842 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x"/> 17843 * ``` 17844 * 17845 * @element IMG 17846 * @param {template} ngSrcset any string which can contain `{{}}` markup. 17847 */ 17848 17849/** 17850 * @ngdoc directive 17851 * @name ngDisabled 17852 * @restrict A 17853 * @priority 100 17854 * 17855 * @description 17856 * 17857 * We shouldn't do this, because it will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: 17858 * ```html 17859 * <div ng-init="scope = { isDisabled: false }"> 17860 * <button disabled="{{scope.isDisabled}}">Disabled</button> 17861 * </div> 17862 * ``` 17863 * 17864 * The HTML specification does not require browsers to preserve the values of boolean attributes 17865 * such as disabled. (Their presence means true and their absence means false.) 17866 * If we put an Angular interpolation expression into such an attribute then the 17867 * binding information would be lost when the browser removes the attribute. 17868 * The `ngDisabled` directive solves this problem for the `disabled` attribute. 17869 * This complementary directive is not removed by the browser and so provides 17870 * a permanent reliable place to store the binding information. 17871 * 17872 * @example 17873 <example> 17874 <file name="index.html"> 17875 Click me to toggle: <input type="checkbox" ng-model="checked"><br/> 17876 <button ng-model="button" ng-disabled="checked">Button</button> 17877 </file> 17878 <file name="protractor.js" type="protractor"> 17879 it('should toggle button', function() { 17880 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
17881 element(by.model('checked')).click(); 17882 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 17883 }); 17884 </file> 17885 </example> 17886 * 17887 * @element INPUT 17888 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 17889 * then special attribute "disabled" will be set on the element 17890 */ 17891 17892 17893/** 17894 * @ngdoc directive 17895 * @name ngChecked 17896 * @restrict A 17897 * @priority 100 17898 * 17899 * @description 17900 * The HTML specification does not require browsers to preserve the values of boolean attributes 17901 * such as checked. (Their presence means true and their absence means false.) 17902 * If we put an Angular interpolation expression into such an attribute then the 17903 * binding information would be lost when the browser removes the attribute. 17904 * The `ngChecked` directive solves this problem for the `checked` attribute. 17905 * This complementary directive is not removed by the browser and so provides 17906 * a permanent reliable place to store the binding information. 17907 * @example 17908 <example> 17909 <file name="index.html"> 17910 Check me to check both: <input type="checkbox" ng-model="master"><br/> 17911 <input id="checkSlave" type="checkbox" ng-checked="master"> 17912 </file> 17913 <file name="protractor.js" type="protractor"> 17914 it('should check both checkBoxes', function() { 17915 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 17916 element(by.model('master')).click(); 17917 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 17918 }); 17919 </file> 17920 </example> 17921 * 17922 * @element INPUT 17923 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 17924 * then special attribute "checked" will be set on the element 17925 */ 17926 17927 17928/** 17929 * @ngdoc directive 17930 * @name ngReadonly 17931 * @restrict A 17932 * @priority 100 17933 * 17934 * @description 17935 * The HTML specification does not require browsers to preserve the values of boolean attributes 17936 * such as readonly. (Their presence means true and their absence means false.) 17937 * If we put an Angular interpolation expression into such an attribute then the 17938 * binding information would be lost when the browser removes the attribute. 17939 * The `ngReadonly` directive solves this problem for the `readonly` attribute. 17940 * This complementary directive is not removed by the browser and so provides 17941 * a permanent reliable place to store the binding information. 17942 * @example 17943 <example> 17944 <file name="index.html"> 17945 Check me to make text readonly: <input type="checkbox" ng-model="checked"><br/> 17946 <input type="text" ng-readonly="checked" value="I'm Angular"/> 17947 </file> 17948 <file name="protractor.js" type="protractor"> 17949 it('should toggle readonly attr', function() { 17950 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 17951 element(by.model('checked')).click(); 17952 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 17953 }); 17954 </file> 17955 </example> 17956 * 17957 * @element INPUT 17958 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 17959 * then special attribute "readonly" will be set on the element 17960 */ 17961 17962 17963/** 17964 * @ngdoc directive 17965 * @name ngSelected 17966 * @restrict A 17967 * @priority 100 17968 * 17969 * @description 17970 * The HTML specification does not require browsers to preserve the values of boolean attributes 17971 * such as selected. (Their presence means true and their absence means false.) 17972 * If we put an Angular interpolation expression into such an attribute then the 17973 * binding information would be lost when the browser removes the attribute. 17974 * The `ngSelected` directive solves this problem for the `selected` attribute. 17975 * This complementary directive is not removed by the browser and so provides 17976 * a permanent reliable place to store the binding information. 17977 * 17978 * @example 17979 <example> 17980 <file name="index.html"> 17981 Check me to select: <input type="checkbox" ng-model="selected"><br/> 17982 <select> 17983 <option>Hello!</option> 17984 <option id="greet" ng-selected="selected">Greetings!</option> 17985 </select> 17986 </file> 17987 <file name="protractor.js" type="protractor"> 17988 it('should select Greetings!', function() { 17989 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
17990 element(by.model('selected')).click(); 17991 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 17992 }); 17993 </file> 17994 </example> 17995 * 17996 * @element OPTION 17997 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 17998 * then special attribute "selected" will be set on the element 17999 */ 18000 18001/** 18002 * @ngdoc directive 18003 * @name ngOpen 18004 * @restrict A 18005 * @priority 100 18006 * 18007 * @description 18008 * The HTML specification does not require browsers to preserve the values of boolean attributes 18009 * such as open. (Their presence means true and their absence means false.) 18010 * If we put an Angular interpolation expression into such an attribute then the 18011 * binding information would be lost when the browser removes the attribute. 18012 * The `ngOpen` directive solves this problem for the `open` attribute. 18013 * This complementary directive is not removed by the browser and so provides 18014 * a permanent reliable place to store the binding information. 18015 * @example 18016 <example> 18017 <file name="index.html"> 18018 Check me check multiple: <input type="checkbox" ng-model="open"><br/> 18019 <details id="details" ng-open="open"> 18020 <summary>Show/Hide me</summary> 18021 </details> 18022 </file> 18023 <file name="protractor.js" type="protractor"> 18024 it('should toggle open', function() { 18025 expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); 18026 element(by.model('open')).click(); 18027 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 18028 }); 18029 </file> 18030 </example> 18031 * 18032 * @element DETAILS 18033 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 18034 * then special attribute "open" will be set on the element 18035 */ 18036 18037var ngAttributeAliasDirectives = {}; 18038 18039 18040// boolean attrs are evaluated 18041forEach(BOOLEAN_ATTR, function(propName, attrName) { 18042 // binding to multiple is not supported 18043 if (propName == "multiple") return; 18044 18045 var normalized = directiveNormalize('ng-' + attrName); 18046 ngAttributeAliasDirectives[normalized] = function() { 18047 return { 18048 restrict: 'A', 18049 priority: 100, 18050 link: function(scope, element, attr) { 18051 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 18052 attr.$set(attrName, !!value); 18053 }); 18054 } 18055 }; 18056 }; 18057}); 18058 18059// aliased input attrs are evaluated 18060forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 18061 ngAttributeAliasDirectives[ngAttr] = function() { 18062 return { 18063 priority: 100, 18064 link: function(scope, element, attr) { 18065 //special case ngPattern when a literal regular expression value 18066 //is used as the expression (this way we don't have to watch anything). 18067 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { 18068 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 18069 if (match) { 18070 attr.$set("ngPattern", new RegExp(match[1], match[2])); 18071 return; 18072 } 18073 } 18074 18075 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 18076 attr.$set(ngAttr, value); 18077 }); 18078 } 18079 }; 18080 }; 18081}); 18082 18083// ng-src, ng-srcset, ng-href are interpolated 18084forEach(['src', 'srcset', 'href'], function(attrName) { 18085 var normalized = directiveNormalize('ng-' + attrName); 18086 ngAttributeAliasDirectives[normalized] = function() { 18087 return { 18088 priority: 99, // it needs to run after the attributes are interpolated 18089 link: function(scope, element, attr) { 18090 var propName = attrName, 18091 name = attrName; 18092 18093 if (attrName === 'href' && 18094 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 18095 name = 'xlinkHref'; 18096 attr.$attr[name] = 'xlink:href'; 18097 propName = null; 18098 } 18099 18100 attr.$observe(normalized, function(value) { 18101 if (!value) { 18102 if (attrName === 'href') { 18103 attr.$set(name, null); 18104 } 18105 return; 18106 } 18107 18108 attr.$set(name, value); 18109 18110 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 18111 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 18112 // to set the property as well to achieve the desired effect. 18113 // we use attr[attrName] value since $set can sanitize the url. 18114 if (msie && propName) element.prop(propName, attr[name]); 18115 }); 18116 } 18117 }; 18118 }; 18119}); 18120 18121/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true 18122 */ 18123var nullFormCtrl = { 18124 $addControl: noop, 18125 $$renameControl: nullFormRenameControl, 18126 $removeControl: noop, 18127 $setValidity: noop, 18128 $setDirty: noop, 18129 $setPristine: noop, 18130 $setSubmitted: noop 18131}, 18132SUBMITTED_CLASS = 'ng-submitted'; 18133 18134function nullFormRenameControl(control, name) { 18135 control.$name = name; 18136} 18137
18138/** 18139 * @ngdoc type 18140 * @name form.FormController 18141 * 18142 * @property {boolean} $pristine True if user has not interacted with the form yet. 18143 * @property {boolean} $dirty True if user has already interacted with the form. 18144 * @property {boolean} $valid True if all of the containing forms and controls are valid. 18145 * @property {boolean} $invalid True if at least one containing control or form is invalid. 18146 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 18147 * 18148 * @property {Object} $error Is an object hash, containing references to controls or 18149 * forms with failing validators, where: 18150 * 18151 * - keys are validation tokens (error names), 18152 * - values are arrays of controls or forms that have a failing validator for given error name. 18153 * 18154 * Built-in validation tokens: 18155 * 18156 * - `email` 18157 * - `max` 18158 * - `maxlength` 18159 * - `min` 18160 * - `minlength` 18161 * - `number` 18162 * - `pattern` 18163 * - `required` 18164 * - `url` 18165 * - `date` 18166 * - `datetimelocal` 18167 * - `time` 18168 * - `week` 18169 * - `month` 18170 * 18171 * @description 18172 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 18173 * such as being valid/invalid or dirty/pristine. 18174 * 18175 * Each {@link ng.directive:form form} directive creates an instance 18176 * of `FormController`. 18177 * 18178 */ 18179//asks for $scope to fool the BC controller module 18180FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 18181function FormController(element, attrs, $scope, $animate, $interpolate) { 18182 var form = this, 18183 controls = []; 18184 18185 var parentForm = form.$$parentForm = element.parent().controller('form') || nullFormCtrl; 18186 18187 // init state 18188 form.$error = {}; 18189 form.$$success = {}; 18190 form.$pending = undefined; 18191 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); 18192 form.$dirty = false; 18193 form.$pristine = true; 18194 form.$valid = true; 18195 form.$invalid = false; 18196 form.$submitted = false; 18197 18198 parentForm.$addControl(form); 18199 18200 /** 18201 * @ngdoc method 18202 * @name form.FormController#$rollbackViewValue 18203 * 18204 * @description 18205 * Rollback all form controls pending updates to the `$modelValue`. 18206 * 18207 * Updates may be pending by a debounced event or because the input is waiting for a some future 18208 * event defined in `ng-model-options`. This method is typically needed by the reset button of 18209 * a form that uses `ng-model-options` to pend updates. 18210 */ 18211 form.$rollbackViewValue = function() { 18212 forEach(controls, function(control) { 18213 control.$rollbackViewValue(); 18214 }); 18215 }; 18216 18217 /** 18218 * @ngdoc method 18219 * @name form.FormController#$commitViewValue 18220 * 18221 * @description 18222 * Commit all form controls pending updates to the `$modelValue`. 18223 * 18224 * Updates may be pending by a debounced event or because the input is waiting for a some future 18225 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 18226 * usually handles calling this in response to input events. 18227 */ 18228 form.$commitViewValue = function() { 18229 forEach(controls, function(control) { 18230 control.$commitViewValue(); 18231 }); 18232 }; 18233 18234 /** 18235 * @ngdoc method 18236 * @name form.FormController#$addControl 18237 * 18238 * @description 18239 * Register a control with the form. 18240 * 18241 * Input elements using ngModelController do this automatically when they are linked. 18242 */ 18243 form.$addControl = function(control) { 18244 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 18245 // and not added to the scope. Now we throw an error. 18246 assertNotHasOwnProperty(control.$name, 'input'); 18247 controls.push(control); 18248 18249 if (control.$name) { 18250 form[control.$name] = control; 18251 } 18252 }; 18253 18254 // Private API: rename a form control 18255 form.$$renameControl = function(control, newName) { 18256 var oldName = control.$name; 18257 18258 if (form[oldName] === control) { 18259 delete form[oldName]; 18260 } 18261 form[newName] = control; 18262 control.$name = newName; 18263 }; 18264 18265 /** 18266 * @ngdoc method 18267 * @name form.FormController#$removeControl 18268 * 18269 * @description 18270 * Deregister a control from the form. 18271 * 18272 * Input elements using ngModelController do this automatically when they are destroyed. 18273 */ 18274 form.$removeControl = function(control) { 18275 if (control.$name && form[control.$name] === control) { 18276 delete form[control.$name]; 18277 }
18278 forEach(form.$pending, function(value, name) { 18279 form.$setValidity(name, null, control); 18280 }); 18281 forEach(form.$error, function(value, name) { 18282 form.$setValidity(name, null, control); 18283 }); 18284 forEach(form.$$success, function(value, name) { 18285 form.$setValidity(name, null, control); 18286 }); 18287 18288 arrayRemove(controls, control); 18289 }; 18290 18291 18292 /** 18293 * @ngdoc method 18294 * @name form.FormController#$setValidity 18295 * 18296 * @description 18297 * Sets the validity of a form control. 18298 * 18299 * This method will also propagate to parent forms. 18300 */ 18301 addSetValidityMethod({ 18302 ctrl: this, 18303 $element: element, 18304 set: function(object, property, controller) { 18305 var list = object[property]; 18306 if (!list) { 18307 object[property] = [controller]; 18308 } else { 18309 var index = list.indexOf(controller); 18310 if (index === -1) { 18311 list.push(controller); 18312 } 18313 } 18314 }, 18315 unset: function(object, property, controller) { 18316 var list = object[property]; 18317 if (!list) { 18318 return; 18319 } 18320 arrayRemove(list, controller); 18321 if (list.length === 0) { 18322 delete object[property]; 18323 } 18324 }, 18325 parentForm: parentForm, 18326 $animate: $animate 18327 }); 18328 18329 /** 18330 * @ngdoc method 18331 * @name form.FormController#$setDirty 18332 * 18333 * @description 18334 * Sets the form to a dirty state. 18335 * 18336 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 18337 * state (ng-dirty class). This method will also propagate to parent forms. 18338 */ 18339 form.$setDirty = function() { 18340 $animate.removeClass(element, PRISTINE_CLASS); 18341 $animate.addClass(element, DIRTY_CLASS); 18342 form.$dirty = true; 18343 form.$pristine = false; 18344 parentForm.$setDirty(); 18345 }; 18346 18347 /** 18348 * @ngdoc method 18349 * @name form.FormController#$setPristine 18350 * 18351 * @description 18352 * Sets the form to its pristine state. 18353 * 18354 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 18355 * state (ng-pristine class). This method will also propagate to all the controls contained 18356 * in this form. 18357 * 18358 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 18359 * saving or resetting it. 18360 */ 18361 form.$setPristine = function() { 18362 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 18363 form.$dirty = false; 18364 form.$pristine = true; 18365 form.$submitted = false; 18366 forEach(controls, function(control) { 18367 control.$setPristine(); 18368 }); 18369 }; 18370 18371 /** 18372 * @ngdoc method 18373 * @name form.FormController#$setUntouched 18374 * 18375 * @description 18376 * Sets the form to its untouched state. 18377 * 18378 * This method can be called to remove the 'ng-touched' class and set the form controls to their 18379 * untouched state (ng-untouched class). 18380 * 18381 * Setting a form controls back to their untouched state is often useful when setting the form 18382 * back to its pristine state. 18383 */ 18384 form.$setUntouched = function() { 18385 forEach(controls, function(control) { 18386 control.$setUntouched(); 18387 }); 18388 }; 18389 18390 /** 18391 * @ngdoc method 18392 * @name form.FormController#$setSubmitted 18393 * 18394 * @description 18395 * Sets the form to its submitted state. 18396 */ 18397 form.$setSubmitted = function() { 18398 $animate.addClass(element, SUBMITTED_CLASS); 18399 form.$submitted = true; 18400 parentForm.$setSubmitted(); 18401 }; 18402} 18403 18404/** 18405 * @ngdoc directive 18406 * @name ngForm 18407 * @restrict EAC 18408 * 18409 * @description 18410 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 18411 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 18412 * sub-group of controls needs to be determined. 18413 *
18414 * Note: the purpose of `ngForm` is to group controls, 18415 * but not to be a replacement for the `<form>` tag with all of its capabilities 18416 * (e.g. posting to the server, ...). 18417 * 18418 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 18419 * related scope, under this name. 18420 * 18421 */ 18422 18423 /** 18424 * @ngdoc directive 18425 * @name form 18426 * @restrict E 18427 * 18428 * @description 18429 * Directive that instantiates 18430 * {@link form.FormController FormController}. 18431 * 18432 * If the `name` attribute is specified, the form controller is published onto the current scope under 18433 * this name. 18434 * 18435 * # Alias: {@link ng.directive:ngForm `ngForm`} 18436 * 18437 * In Angular forms can be nested. This means that the outer form is valid when all of the child 18438 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 18439 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to 18440 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when 18441 * using Angular validation directives in forms that are dynamically generated using the 18442 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` 18443 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an 18444 * `ngForm` directive and nest these in an outer `form` element. 18445 * 18446 * 18447 * # CSS classes 18448 * - `ng-valid` is set if the form is valid. 18449 * - `ng-invalid` is set if the form is invalid. 18450 * - `ng-pristine` is set if the form is pristine. 18451 * - `ng-dirty` is set if the form is dirty. 18452 * - `ng-submitted` is set if the form was submitted. 18453 * 18454 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 18455 * 18456 * 18457 * # Submitting a form and preventing the default action 18458 * 18459 * Since the role of forms in client-side Angular applications is different than in classical 18460 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 18461 * page reload that sends the data to the server. Instead some javascript logic should be triggered 18462 * to handle the form submission in an application-specific way. 18463 * 18464 * For this reason, Angular prevents the default action (form submission to the server) unless the 18465 * `<form>` element has an `action` attribute specified. 18466 * 18467 * You can use one of the following two ways to specify what javascript method should be called when 18468 * a form is submitted: 18469 * 18470 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 18471 * - {@link ng.directive:ngClick ngClick} directive on the first 18472 * button or input field of type submit (input[type=submit]) 18473 * 18474 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 18475 * or {@link ng.directive:ngClick ngClick} directives. 18476 * This is because of the following form submission rules in the HTML specification: 18477 * 18478 * - If a form has only one input field then hitting enter in this field triggers form submit 18479 * (`ngSubmit`) 18480 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 18481 * doesn't trigger submit 18482 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 18483 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 18484 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 18485 * 18486 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 18487 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 18488 * to have access to the updated model. 18489 * 18490 * ## Animation Hooks 18491 * 18492 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 18493 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 18494 * other validations that are performed within the form. Animations in ngForm are similar to how 18495 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 18496 * as JS animations. 18497 * 18498 * The following example shows a simple way to utilize CSS transitions to style a form element
18499 * that has been rendered as invalid after it has been validated: 18500 * 18501 * <pre> 18502 * //be sure to include ngAnimate as a module to hook into more 18503 * //advanced animations 18504 * .my-form { 18505 * transition:0.5s linear all; 18506 * background: white; 18507 * } 18508 * .my-form.ng-invalid { 18509 * background: red; 18510 * color:white; 18511 * } 18512 * </pre> 18513 * 18514 * @example 18515 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 18516 <file name="index.html"> 18517 <script> 18518 angular.module('formExample', []) 18519 .controller('FormController', ['$scope', function($scope) { 18520 $scope.userType = 'guest'; 18521 }]); 18522 </script> 18523 <style> 18524 .my-form { 18525 -webkit-transition:all linear 0.5s; 18526 transition:all linear 0.5s; 18527 background: transparent; 18528 } 18529 .my-form.ng-invalid { 18530 background: red; 18531 } 18532 </style> 18533 <form name="myForm" ng-controller="FormController" class="my-form"> 18534 userType: <input name="input" ng-model="userType" required> 18535 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 18536 <tt>userType = {{userType}}</tt><br> 18537 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br> 18538 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br> 18539 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 18540 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 18541 </form> 18542 </file> 18543 <file name="protractor.js" type="protractor"> 18544 it('should initialize to model', function() { 18545 var userType = element(by.binding('userType')); 18546 var valid = element(by.binding('myForm.input.$valid')); 18547 18548 expect(userType.getText()).toContain('guest'); 18549 expect(valid.getText()).toContain('true'); 18550 }); 18551 18552 it('should be invalid if empty', function() { 18553 var userType = element(by.binding('userType')); 18554 var valid = element(by.binding('myForm.input.$valid')); 18555 var userInput = element(by.model('userType')); 18556 18557 userInput.clear(); 18558 userInput.sendKeys(''); 18559 18560 expect(userType.getText()).toEqual('userType ='); 18561 expect(valid.getText()).toContain('false'); 18562 }); 18563 </file> 18564 </example> 18565 * 18566 * @param {string=} name Name of the form. If specified, the form controller will be published into 18567 * related scope, under this name. 18568 */ 18569var formDirectiveFactory = function(isNgForm) { 18570 return ['$timeout', function($timeout) { 18571 var formDirective = { 18572 name: 'form', 18573 restrict: isNgForm ? 'EAC' : 'E', 18574 controller: FormController, 18575 compile: function ngFormCompile(formElement) { 18576 // Setup initial state of the control 18577 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 18578 18579 return { 18580 pre: function ngFormPreLink(scope, formElement, attr, controller) { 18581 // if `action` attr is not present on the form, prevent the default action (submission) 18582 if (!('action' in attr)) { 18583 // we can't use jq events because if a form is destroyed during submission the default 18584 // action is not prevented. see #1238 18585 // 18586 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 18587 // page reload if the form was destroyed by submission of the form via a click handler 18588 // on a button in the form. Looks like an IE9 specific bug. 18589 var handleFormSubmission = function(event) { 18590 scope.$apply(function() { 18591 controller.$commitViewValue(); 18592 controller.$setSubmitted(); 18593 }); 18594 18595 event.preventDefault(); 18596 }; 18597 18598 addEventListenerFn(formElement[0], 'submit', handleFormSubmission); 18599 18600 // unregister the preventDefault listener so that we don't not leak memory but in a 18601 // way that will achieve the prevention of the default action. 18602 formElement.on('$destroy', function() { 18603 $timeout(function() { 18604 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); 18605 }, 0, false); 18606 }); 18607 } 18608 18609 var parentFormCtrl = controller.$$parentForm, 18610 alias = controller.$name; 18611 18612 if (alias) { 18613 setter(scope, null, alias, controller, alias); 18614 attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) { 18615 if (alias === newValue) return; 18616 setter(scope, null, alias, undefined, alias); 18617 alias = newValue; 18618 setter(scope, null, alias, controller, alias); 18619 parentFormCtrl.$$renameControl(controller, alias); 18620 }); 18621 } 18622 formElement.on('$destroy', function() { 18623 parentFormCtrl.$removeControl(controller); 18624 if (alias) { 18625 setter(scope, null, alias, undefined, alias); 18626 } 18627 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 18628 }); 18629 } 18630 }; 18631 } 18632 }; 18633 18634 return formDirective; 18635 }]; 18636}; 18637 18638var formDirective = formDirectiveFactory(); 18639var ngFormDirective = formDirectiveFactory(true); 18640 18641/* global VALID_CLASS: false, 18642 INVALID_CLASS: false, 18643 PRISTINE_CLASS: false, 18644 DIRTY_CLASS: false, 18645 UNTOUCHED_CLASS: false, 18646 TOUCHED_CLASS: false, 18647 $ngModelMinErr: false, 18648*/ 18649 18650// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 18651var ISO_DATE_REGEXP = /\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/; 18652var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 18653var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;
18654var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; 18655var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; 18656var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 18657var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; 18658var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; 18659var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 18660 18661var inputType = { 18662 18663 /** 18664 * @ngdoc input 18665 * @name input[text] 18666 * 18667 * @description 18668 * Standard HTML text input with angular data binding, inherited by most of the `input` elements. 18669 * 18670 * 18671 * @param {string} ngModel Assignable angular expression to data-bind to. 18672 * @param {string=} name Property name of the form under which the control is published. 18673 * @param {string=} required Adds `required` validation error key if the value is not entered. 18674 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 18675 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 18676 * `required` when you want to data-bind to the `required` attribute. 18677 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 18678 * minlength. 18679 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 18680 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 18681 * any length. 18682 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 18683 * that contains the regular expression body that will be converted to a regular expression 18684 * as in the ngPattern directive. 18685 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 18686 * a RegExp found by evaluating the Angular expression given in the attribute value. 18687 * If the expression evaluates to a RegExp object then this is used directly. 18688 * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` 18689 * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. 18690 * @param {string=} ngChange Angular expression to be executed when input changes due to user 18691 * interaction with the input element. 18692 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 18693 * This parameter is ignored for input[type=password] controls, which will never trim the 18694 * input. 18695 * 18696 * @example 18697 <example name="text-input-directive" module="textInputExample"> 18698 <file name="index.html"> 18699 <script> 18700 angular.module('textInputExample', []) 18701 .controller('ExampleController', ['$scope', function($scope) { 18702 $scope.example = { 18703 text: 'guest', 18704 word: /^\s*\w*\s*$/ 18705 }; 18706 }]); 18707 </script>
18708 <form name="myForm" ng-controller="ExampleController"> 18709 Single word: <input type="text" name="input" ng-model="example.text" 18710 ng-pattern="example.word" required ng-trim="false"> 18711 <span class="error" ng-show="myForm.input.$error.required"> 18712 Required!</span> 18713 <span class="error" ng-show="myForm.input.$error.pattern"> 18714 Single word only!</span> 18715 18716 <tt>text = {{example.text}}</tt><br/> 18717 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 18718 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 18719 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 18720 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 18721 </form> 18722 </file> 18723 <file name="protractor.js" type="protractor"> 18724 var text = element(by.binding('example.text')); 18725 var valid = element(by.binding('myForm.input.$valid')); 18726 var input = element(by.model('example.text')); 18727 18728 it('should initialize to model', function() { 18729 expect(text.getText()).toContain('guest'); 18730 expect(valid.getText()).toContain('true'); 18731 }); 18732 18733 it('should be invalid if empty', function() { 18734 input.clear(); 18735 input.sendKeys(''); 18736 18737 expect(text.getText()).toEqual('text ='); 18738 expect(valid.getText()).toContain('false'); 18739 }); 18740 18741 it('should be invalid if multi word', function() { 18742 input.clear(); 18743 input.sendKeys('hello world'); 18744 18745 expect(valid.getText()).toContain('false'); 18746 }); 18747 </file> 18748 </example> 18749 */ 18750 'text': textInputType, 18751 18752 /** 18753 * @ngdoc input 18754 * @name input[date] 18755 * 18756 * @description 18757 * Input with date validation and transformation. In browsers that do not yet support 18758 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 18759 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 18760 * modern browsers do not yet support this input type, it is important to provide cues to users on the 18761 * expected input format via a placeholder or label. 18762 * 18763 * The model must always be a Date object, otherwise Angular will throw an error. 18764 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 18765 * 18766 * The timezone to be used to read/write the `Date` instance in the model can be defined using 18767 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 18768 * 18769 * @param {string} ngModel Assignable angular expression to data-bind to. 18770 * @param {string=} name Property name of the form under which the control is published. 18771 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 18772 * valid ISO date string (yyyy-MM-dd). 18773 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 18774 * a valid ISO date string (yyyy-MM-dd). 18775 * @param {string=} required Sets `required` validation error key if the value is not entered. 18776 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 18777 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 18778 * `required` when you want to data-bind to the `required` attribute. 18779 * @param {string=} ngChange Angular expression to be executed when input changes due to user 18780 * interaction with the input element. 18781 * 18782 * @example 18783 <example name="date-input-directive" module="dateInputExample"> 18784 <file name="index.html"> 18785 <script> 18786 angular.module('dateInputExample', []) 18787 .controller('DateController', ['$scope', function($scope) { 18788 $scope.example = { 18789 value: new Date(2013, 9, 22) 18790 }; 18791 }]); 18792 </script> 18793 <form name="myForm" ng-controller="DateController as dateCtrl"> 18794 Pick a date in 2013: 18795 <input type="date" id="exampleInput" name="input" ng-model="example.value" 18796 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
18797 <span class="error" ng-show="myForm.input.$error.required"> 18798 Required!</span> 18799 <span class="error" ng-show="myForm.input.$error.date"> 18800 Not a valid date!</span> 18801 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 18802 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 18803 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 18804 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 18805 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 18806 </form> 18807 </file> 18808 <file name="protractor.js" type="protractor"> 18809 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 18810 var valid = element(by.binding('myForm.input.$valid')); 18811 var input = element(by.model('example.value')); 18812 18813 // currently protractor/webdriver does not support 18814 // sending keys to all known HTML5 input controls 18815 // for various browsers (see https://github.com/angular/protractor/issues/562). 18816 function setInput(val) { 18817 // set the value of the element and force validation. 18818 var scr = "var ipt = document.getElementById('exampleInput'); " + 18819 "ipt.value = '" + val + "';" + 18820 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 18821 browser.executeScript(scr); 18822 } 18823 18824 it('should initialize to model', function() { 18825 expect(value.getText()).toContain('2013-10-22'); 18826 expect(valid.getText()).toContain('myForm.input.$valid = true'); 18827 }); 18828 18829 it('should be invalid if empty', function() { 18830 setInput(''); 18831 expect(value.getText()).toEqual('value ='); 18832 expect(valid.getText()).toContain('myForm.input.$valid = false'); 18833 }); 18834 18835 it('should be invalid if over max', function() { 18836 setInput('2015-01-01'); 18837 expect(value.getText()).toContain(''); 18838 expect(valid.getText()).toContain('myForm.input.$valid = false'); 18839 }); 18840 </file> 18841 </example> 18842 */ 18843 'date': createDateInputType('date', DATE_REGEXP, 18844 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 18845 'yyyy-MM-dd'), 18846 18847 /** 18848 * @ngdoc input 18849 * @name input[datetime-local] 18850 * 18851 * @description 18852 * Input with datetime validation and transformation. In browsers that do not yet support 18853 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 18854 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 18855 * 18856 * The model must always be a Date object, otherwise Angular will throw an error. 18857 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 18858 * 18859 * The timezone to be used to read/write the `Date` instance in the model can be defined using 18860 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 18861 * 18862 * @param {string} ngModel Assignable angular expression to data-bind to. 18863 * @param {string=} name Property name of the form under which the control is published. 18864 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 18865 * valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). 18866 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 18867 * a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). 18868 * @param {string=} required Sets `required` validation error key if the value is not entered. 18869 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 18870 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 18871 * `required` when you want to data-bind to the `required` attribute. 18872 * @param {string=} ngChange Angular expression to be executed when input changes due to user 18873 * interaction with the input element. 18874 * 18875 * @example 18876 <example name="datetimelocal-input-directive" module="dateExample"> 18877 <file name="index.html"> 18878 <script> 18879 angular.module('dateExample', []) 18880 .controller('DateController', ['$scope', function($scope) { 18881 $scope.example = { 18882 value: new Date(2010, 11, 28, 14, 57) 18883 }; 18884 }]); 18885 </script> 18886 <form name="myForm" ng-controller="DateController as dateCtrl"> 18887 Pick a date between in 2013: 18888 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 18889 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
18890 <span class="error" ng-show="myForm.input.$error.required"> 18891 Required!</span> 18892 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 18893 Not a valid date!</span> 18894 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 18895 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 18896 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 18897 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 18898 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 18899 </form> 18900 </file> 18901 <file name="protractor.js" type="protractor"> 18902 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 18903 var valid = element(by.binding('myForm.input.$valid')); 18904 var input = element(by.model('example.value')); 18905 18906 // currently protractor/webdriver does not support 18907 // sending keys to all known HTML5 input controls 18908 // for various browsers (https://github.com/angular/protractor/issues/562). 18909 function setInput(val) { 18910 // set the value of the element and force validation. 18911 var scr = "var ipt = document.getElementById('exampleInput'); " + 18912 "ipt.value = '" + val + "';" + 18913 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 18914 browser.executeScript(scr); 18915 } 18916 18917 it('should initialize to model', function() { 18918 expect(value.getText()).toContain('2010-12-28T14:57:00'); 18919 expect(valid.getText()).toContain('myForm.input.$valid = true'); 18920 }); 18921 18922 it('should be invalid if empty', function() { 18923 setInput(''); 18924 expect(value.getText()).toEqual('value ='); 18925 expect(valid.getText()).toContain('myForm.input.$valid = false'); 18926 }); 18927 18928 it('should be invalid if over max', function() { 18929 setInput('2015-01-01T23:59:00'); 18930 expect(value.getText()).toContain(''); 18931 expect(valid.getText()).toContain('myForm.input.$valid = false'); 18932 }); 18933 </file> 18934 </example> 18935 */ 18936 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 18937 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 18938 'yyyy-MM-ddTHH:mm:ss.sss'), 18939 18940 /** 18941 * @ngdoc input 18942 * @name input[time] 18943 * 18944 * @description 18945 * Input with time validation and transformation. In browsers that do not yet support 18946 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 18947 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 18948 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 18949 * 18950 * The model must always be a Date object, otherwise Angular will throw an error. 18951 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 18952 * 18953 * The timezone to be used to read/write the `Date` instance in the model can be defined using 18954 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 18955 * 18956 * @param {string} ngModel Assignable angular expression to data-bind to. 18957 * @param {string=} name Property name of the form under which the control is published. 18958 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 18959 * valid ISO time format (HH:mm:ss). 18960 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be a 18961 * valid ISO time format (HH:mm:ss). 18962 * @param {string=} required Sets `required` validation error key if the value is not entered. 18963 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 18964 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 18965 * `required` when you want to data-bind to the `required` attribute. 18966 * @param {string=} ngChange Angular expression to be executed when input changes due to user 18967 * interaction with the input element. 18968 * 18969 * @example 18970 <example name="time-input-directive" module="timeExample"> 18971 <file name="index.html"> 18972 <script> 18973 angular.module('timeExample', []) 18974 .controller('DateController', ['$scope', function($scope) { 18975 $scope.example = { 18976 value: new Date(1970, 0, 1, 14, 57, 0) 18977 }; 18978 }]); 18979 </script> 18980 <form name="myForm" ng-controller="DateController as dateCtrl"> 18981 Pick a between 8am and 5pm: 18982 <input type="time" id="exampleInput" name="input" ng-model="example.value" 18983 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
18984 <span class="error" ng-show="myForm.input.$error.required"> 18985 Required!</span> 18986 <span class="error" ng-show="myForm.input.$error.time"> 18987 Not a valid date!</span> 18988 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 18989 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 18990 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 18991 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 18992 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 18993 </form> 18994 </file> 18995 <file name="protractor.js" type="protractor"> 18996 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 18997 var valid = element(by.binding('myForm.input.$valid')); 18998 var input = element(by.model('example.value')); 18999 19000 // currently protractor/webdriver does not support 19001 // sending keys to all known HTML5 input controls 19002 // for various browsers (https://github.com/angular/protractor/issues/562). 19003 function setInput(val) { 19004 // set the value of the element and force validation. 19005 var scr = "var ipt = document.getElementById('exampleInput'); " + 19006 "ipt.value = '" + val + "';" + 19007 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 19008 browser.executeScript(scr); 19009 } 19010 19011 it('should initialize to model', function() { 19012 expect(value.getText()).toContain('14:57:00'); 19013 expect(valid.getText()).toContain('myForm.input.$valid = true'); 19014 }); 19015 19016 it('should be invalid if empty', function() { 19017 setInput(''); 19018 expect(value.getText()).toEqual('value ='); 19019 expect(valid.getText()).toContain('myForm.input.$valid = false'); 19020 }); 19021 19022 it('should be invalid if over max', function() { 19023 setInput('23:59:00'); 19024 expect(value.getText()).toContain(''); 19025 expect(valid.getText()).toContain('myForm.input.$valid = false'); 19026 }); 19027 </file> 19028 </example> 19029 */ 19030 'time': createDateInputType('time', TIME_REGEXP, 19031 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 19032 'HH:mm:ss.sss'), 19033 19034 /** 19035 * @ngdoc input 19036 * @name input[week] 19037 * 19038 * @description 19039 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 19040 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 19041 * week format (yyyy-W##), for example: `2013-W02`. 19042 * 19043 * The model must always be a Date object, otherwise Angular will throw an error. 19044 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 19045 * 19046 * The timezone to be used to read/write the `Date` instance in the model can be defined using 19047 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 19048 * 19049 * @param {string} ngModel Assignable angular expression to data-bind to. 19050 * @param {string=} name Property name of the form under which the control is published. 19051 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 19052 * valid ISO week format (yyyy-W##). 19053 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 19054 * a valid ISO week format (yyyy-W##). 19055 * @param {string=} required Sets `required` validation error key if the value is not entered. 19056 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 19057 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 19058 * `required` when you want to data-bind to the `required` attribute. 19059 * @param {string=} ngChange Angular expression to be executed when input changes due to user 19060 * interaction with the input element. 19061 * 19062 * @example 19063 <example name="week-input-directive" module="weekExample"> 19064 <file name="index.html"> 19065 <script> 19066 angular.module('weekExample', []) 19067 .controller('DateController', ['$scope', function($scope) { 19068 $scope.example = { 19069 value: new Date(2013, 0, 3) 19070 }; 19071 }]); 19072 </script> 19073 <form name="myForm" ng-controller="DateController as dateCtrl"> 19074 Pick a date between in 2013: 19075 <input id="exampleInput" type="week" name="input" ng-model="example.value" 19076 placeholder="YYYY-W##" min="2012-W32" max="2013-W52" required />
19077 <span class="error" ng-show="myForm.input.$error.required"> 19078 Required!</span> 19079 <span class="error" ng-show="myForm.input.$error.week"> 19080 Not a valid date!</span> 19081 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 19082 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 19083 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 19084 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 19085 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 19086 </form> 19087 </file> 19088 <file name="protractor.js" type="protractor"> 19089 var value = element(by.binding('example.value | date: "yyyy-Www"')); 19090 var valid = element(by.binding('myForm.input.$valid')); 19091 var input = element(by.model('example.value')); 19092 19093 // currently protractor/webdriver does not support 19094 // sending keys to all known HTML5 input controls 19095 // for various browsers (https://github.com/angular/protractor/issues/562). 19096 function setInput(val) { 19097 // set the value of the element and force validation. 19098 var scr = "var ipt = document.getElementById('exampleInput'); " + 19099 "ipt.value = '" + val + "';" + 19100 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 19101 browser.executeScript(scr); 19102 } 19103 19104 it('should initialize to model', function() { 19105 expect(value.getText()).toContain('2013-W01'); 19106 expect(valid.getText()).toContain('myForm.input.$valid = true'); 19107 }); 19108 19109 it('should be invalid if empty', function() { 19110 setInput(''); 19111 expect(value.getText()).toEqual('value ='); 19112 expect(valid.getText()).toContain('myForm.input.$valid = false'); 19113 }); 19114 19115 it('should be invalid if over max', function() { 19116 setInput('2015-W01'); 19117 expect(value.getText()).toContain(''); 19118 expect(valid.getText()).toContain('myForm.input.$valid = false'); 19119 }); 19120 </file> 19121 </example> 19122 */ 19123 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 19124 19125 /** 19126 * @ngdoc input 19127 * @name input[month] 19128 * 19129 * @description 19130 * Input with month validation and transformation. In browsers that do not yet support 19131 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 19132 * month format (yyyy-MM), for example: `2009-01`. 19133 * 19134 * The model must always be a Date object, otherwise Angular will throw an error. 19135 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 19136 * If the model is not set to the first of the month, the next view to model update will set it 19137 * to the first of the month. 19138 * 19139 * The timezone to be used to read/write the `Date` instance in the model can be defined using 19140 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 19141 * 19142 * @param {string} ngModel Assignable angular expression to data-bind to. 19143 * @param {string=} name Property name of the form under which the control is published. 19144 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be 19145 * a valid ISO month format (yyyy-MM). 19146 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must 19147 * be a valid ISO month format (yyyy-MM). 19148 * @param {string=} required Sets `required` validation error key if the value is not entered. 19149 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 19150 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 19151 * `required` when you want to data-bind to the `required` attribute. 19152 * @param {string=} ngChange Angular expression to be executed when input changes due to user 19153 * interaction with the input element. 19154 * 19155 * @example 19156 <example name="month-input-directive" module="monthExample"> 19157 <file name="index.html"> 19158 <script> 19159 angular.module('monthExample', []) 19160 .controller('DateController', ['$scope', function($scope) { 19161 $scope.example = { 19162 value: new Date(2013, 9, 1) 19163 }; 19164 }]); 19165 </script> 19166 <form name="myForm" ng-controller="DateController as dateCtrl"> 19167 Pick a month in 2013: 19168 <input id="exampleInput" type="month" name="input" ng-model="example.value" 19169 placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
19170 <span class="error" ng-show="myForm.input.$error.required"> 19171 Required!</span> 19172 <span class="error" ng-show="myForm.input.$error.month"> 19173 Not a valid month!</span> 19174 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 19175 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 19176 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 19177 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 19178 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 19179 </form> 19180 </file> 19181 <file name="protractor.js" type="protractor"> 19182 var value = element(by.binding('example.value | date: "yyyy-MM"')); 19183 var valid = element(by.binding('myForm.input.$valid')); 19184 var input = element(by.model('example.value')); 19185 19186 // currently protractor/webdriver does not support 19187 // sending keys to all known HTML5 input controls 19188 // for various browsers (https://github.com/angular/protractor/issues/562). 19189 function setInput(val) { 19190 // set the value of the element and force validation. 19191 var scr = "var ipt = document.getElementById('exampleInput'); " + 19192 "ipt.value = '" + val + "';" + 19193 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 19194 browser.executeScript(scr); 19195 } 19196 19197 it('should initialize to model', function() { 19198 expect(value.getText()).toContain('2013-10'); 19199 expect(valid.getText()).toContain('myForm.input.$valid = true'); 19200 }); 19201 19202 it('should be invalid if empty', function() { 19203 setInput(''); 19204 expect(value.getText()).toEqual('value ='); 19205 expect(valid.getText()).toContain('myForm.input.$valid = false'); 19206 }); 19207 19208 it('should be invalid if over max', function() { 19209 setInput('2015-01'); 19210 expect(value.getText()).toContain(''); 19211 expect(valid.getText()).toContain('myForm.input.$valid = false'); 19212 }); 19213 </file> 19214 </example> 19215 */ 19216 'month': createDateInputType('month', MONTH_REGEXP, 19217 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 19218 'yyyy-MM'), 19219 19220 /** 19221 * @ngdoc input 19222 * @name input[number] 19223 * 19224 * @description 19225 * Text input with number validation and transformation. Sets the `number` validation 19226 * error if not a valid number. 19227 * 19228 * The model must always be a number, otherwise Angular will throw an error. 19229 * 19230 * @param {string} ngModel Assignable angular expression to data-bind to. 19231 * @param {string=} name Property name of the form under which the control is published. 19232 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 19233 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 19234 * @param {string=} required Sets `required` validation error key if the value is not entered. 19235 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 19236 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 19237 * `required` when you want to data-bind to the `required` attribute. 19238 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 19239 * minlength. 19240 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 19241 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 19242 * any length. 19243 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 19244 * that contains the regular expression body that will be converted to a regular expression 19245 * as in the ngPattern directive. 19246 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 19247 * a RegExp found by evaluating the Angular expression given in the attribute value. 19248 * If the expression evaluates to a RegExp object then this is used directly. 19249 * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` 19250 * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. 19251 * @param {string=} ngChange Angular expression to be executed when input changes due to user 19252 * interaction with the input element. 19253 * 19254 * @example 19255 <example name="number-input-directive" module="numberExample"> 19256 <file name="index.html"> 19257 <script> 19258 angular.module('numberExample', [])
19259 .controller('ExampleController', ['$scope', function($scope) { 19260 $scope.example = { 19261 value: 12 19262 }; 19263 }]); 19264 </script> 19265 <form name="myForm" ng-controller="ExampleController"> 19266 Number: <input type="number" name="input" ng-model="example.value" 19267 min="0" max="99" required> 19268 <span class="error" ng-show="myForm.input.$error.required"> 19269 Required!</span> 19270 <span class="error" ng-show="myForm.input.$error.number"> 19271 Not valid number!</span> 19272 <tt>value = {{example.value}}</tt><br/> 19273 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 19274 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 19275 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 19276 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 19277 </form> 19278 </file> 19279 <file name="protractor.js" type="protractor"> 19280 var value = element(by.binding('example.value')); 19281 var valid = element(by.binding('myForm.input.$valid')); 19282 var input = element(by.model('example.value')); 19283 19284 it('should initialize to model', function() { 19285 expect(value.getText()).toContain('12'); 19286 expect(valid.getText()).toContain('true'); 19287 }); 19288 19289 it('should be invalid if empty', function() { 19290 input.clear(); 19291 input.sendKeys(''); 19292 expect(value.getText()).toEqual('value ='); 19293 expect(valid.getText()).toContain('false'); 19294 }); 19295 19296 it('should be invalid if over max', function() { 19297 input.clear(); 19298 input.sendKeys('123'); 19299 expect(value.getText()).toEqual('value ='); 19300 expect(valid.getText()).toContain('false'); 19301 }); 19302 </file> 19303 </example> 19304 */ 19305 'number': numberInputType, 19306 19307 19308 /** 19309 * @ngdoc input 19310 * @name input[url] 19311 * 19312 * @description 19313 * Text input with URL validation. Sets the `url` validation error key if the content is not a 19314 * valid URL. 19315 * 19316 * <div class="alert alert-warning"> 19317 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 19318 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 19319 * the built-in validators (see the {@link guide/forms Forms guide}) 19320 * </div> 19321 * 19322 * @param {string} ngModel Assignable angular expression to data-bind to. 19323 * @param {string=} name Property name of the form under which the control is published. 19324 * @param {string=} required Sets `required` validation error key if the value is not entered. 19325 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 19326 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 19327 * `required` when you want to data-bind to the `required` attribute. 19328 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 19329 * minlength. 19330 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 19331 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 19332 * any length. 19333 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 19334 * that contains the regular expression body that will be converted to a regular expression 19335 * as in the ngPattern directive. 19336 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 19337 * a RegExp found by evaluating the Angular expression given in the attribute value. 19338 * If the expression evaluates to a RegExp object then this is used directly. 19339 * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` 19340 * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. 19341 * @param {string=} ngChange Angular expression to be executed when input changes due to user 19342 * interaction with the input element. 19343 * 19344 * @example 19345 <example name="url-input-directive" module="urlExample"> 19346 <file name="index.html"> 19347 <script> 19348 angular.module('urlExample', [])
19349 .controller('ExampleController', ['$scope', function($scope) { 19350 $scope.url = { 19351 text: 'http://google.com' 19352 }; 19353 }]); 19354 </script> 19355 <form name="myForm" ng-controller="ExampleController"> 19356 URL: <input type="url" name="input" ng-model="url.text" required> 19357 <span class="error" ng-show="myForm.input.$error.required"> 19358 Required!</span> 19359 <span class="error" ng-show="myForm.input.$error.url"> 19360 Not valid url!</span> 19361 <tt>text = {{url.text}}</tt><br/> 19362 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 19363 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 19364 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 19365 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 19366 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 19367 </form> 19368 </file> 19369 <file name="protractor.js" type="protractor"> 19370 var text = element(by.binding('url.text')); 19371 var valid = element(by.binding('myForm.input.$valid')); 19372 var input = element(by.model('url.text')); 19373 19374 it('should initialize to model', function() { 19375 expect(text.getText()).toContain('http://google.com'); 19376 expect(valid.getText()).toContain('true'); 19377 }); 19378 19379 it('should be invalid if empty', function() { 19380 input.clear(); 19381 input.sendKeys(''); 19382 19383 expect(text.getText()).toEqual('text ='); 19384 expect(valid.getText()).toContain('false'); 19385 }); 19386 19387 it('should be invalid if not url', function() { 19388 input.clear(); 19389 input.sendKeys('box'); 19390 19391 expect(valid.getText()).toContain('false'); 19392 }); 19393 </file> 19394 </example> 19395 */ 19396 'url': urlInputType, 19397 19398 19399 /** 19400 * @ngdoc input 19401 * @name input[email] 19402 * 19403 * @description 19404 * Text input with email validation. Sets the `email` validation error key if not a valid email 19405 * address. 19406 * 19407 * <div class="alert alert-warning"> 19408 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 19409 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can 19410 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) 19411 * </div> 19412 * 19413 * @param {string} ngModel Assignable angular expression to data-bind to. 19414 * @param {string=} name Property name of the form under which the control is published. 19415 * @param {string=} required Sets `required` validation error key if the value is not entered. 19416 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 19417 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 19418 * `required` when you want to data-bind to the `required` attribute. 19419 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 19420 * minlength. 19421 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 19422 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 19423 * any length. 19424 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 19425 * that contains the regular expression body that will be converted to a regular expression 19426 * as in the ngPattern directive. 19427 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 19428 * a RegExp found by evaluating the Angular expression given in the attribute value. 19429 * If the expression evaluates to a RegExp object then this is used directly. 19430 * If the expression is a string then it will be converted to a RegExp after wrapping it in `^` and `$` 19431 * characters. For instance, `"abc"` will be converted to `new RegExp('^abc$')`. 19432 * @param {string=} ngChange Angular expression to be executed when input changes due to user 19433 * interaction with the input element. 19434 * 19435 * @example 19436 <example name="email-input-directive" module="emailExample"> 19437 <file name="index.html"> 19438 <script> 19439 angular.module('emailExample', [])
19440 .controller('ExampleController', ['$scope', function($scope) { 19441 $scope.email = { 19442 text: '[email protected]' 19443 }; 19444 }]); 19445 </script> 19446 <form name="myForm" ng-controller="ExampleController"> 19447 Email: <input type="email" name="input" ng-model="email.text" required> 19448 <span class="error" ng-show="myForm.input.$error.required"> 19449 Required!</span> 19450 <span class="error" ng-show="myForm.input.$error.email"> 19451 Not valid email!</span> 19452 <tt>text = {{email.text}}</tt><br/> 19453 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 19454 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 19455 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 19456 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 19457 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 19458 </form> 19459 </file> 19460 <file name="protractor.js" type="protractor"> 19461 var text = element(by.binding('email.text')); 19462 var valid = element(by.binding('myForm.input.$valid')); 19463 var input = element(by.model('email.text')); 19464 19465 it('should initialize to model', function() { 19466 expect(text.getText()).toContain('[email protected]'); 19467 expect(valid.getText()).toContain('true'); 19468 }); 19469 19470 it('should be invalid if empty', function() { 19471 input.clear(); 19472 input.sendKeys(''); 19473 expect(text.getText()).toEqual('text ='); 19474 expect(valid.getText()).toContain('false'); 19475 }); 19476 19477 it('should be invalid if not email', function() { 19478 input.clear(); 19479 input.sendKeys('xxx'); 19480 19481 expect(valid.getText()).toContain('false'); 19482 }); 19483 </file> 19484 </example> 19485 */ 19486 'email': emailInputType, 19487 19488 19489 /** 19490 * @ngdoc input 19491 * @name input[radio] 19492 * 19493 * @description 19494 * HTML radio button. 19495 * 19496 * @param {string} ngModel Assignable angular expression to data-bind to. 19497 * @param {string} value The value to which the expression should be set when selected. 19498 * @param {string=} name Property name of the form under which the control is published. 19499 * @param {string=} ngChange Angular expression to be executed when input changes due to user 19500 * interaction with the input element. 19501 * @param {string} ngValue Angular expression which sets the value to which the expression should 19502 * be set when selected. 19503 * 19504 * @example 19505 <example name="radio-input-directive" module="radioExample"> 19506 <file name="index.html"> 19507 <script> 19508 angular.module('radioExample', []) 19509 .controller('ExampleController', ['$scope', function($scope) { 19510 $scope.color = { 19511 name: 'blue' 19512 }; 19513 $scope.specialValue = { 19514 "id": "12345", 19515 "value": "green" 19516 }; 19517 }]); 19518 </script>
19519 <form name="myForm" ng-controller="ExampleController"> 19520 <input type="radio" ng-model="color.name" value="red"> Red <br/> 19521 <input type="radio" ng-model="color.name" ng-value="specialValue"> Green <br/> 19522 <input type="radio" ng-model="color.name" value="blue"> Blue <br/> 19523 <tt>color = {{color.name | json}}</tt><br/> 19524 </form> 19525 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 19526 </file> 19527 <file name="protractor.js" type="protractor"> 19528 it('should change state', function() { 19529 var color = element(by.binding('color.name')); 19530 19531 expect(color.getText()).toContain('blue'); 19532 19533 element.all(by.model('color.name')).get(0).click(); 19534 19535 expect(color.getText()).toContain('red'); 19536 }); 19537 </file> 19538 </example> 19539 */ 19540 'radio': radioInputType, 19541 19542 19543 /** 19544 * @ngdoc input 19545 * @name input[checkbox] 19546 * 19547 * @description 19548 * HTML checkbox. 19549 * 19550 * @param {string} ngModel Assignable angular expression to data-bind to. 19551 * @param {string=} name Property name of the form under which the control is published. 19552 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 19553 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 19554 * @param {string=} ngChange Angular expression to be executed when input changes due to user 19555 * interaction with the input element. 19556 * 19557 * @example 19558 <example name="checkbox-input-directive" module="checkboxExample"> 19559 <file name="index.html"> 19560 <script> 19561 angular.module('checkboxExample', []) 19562 .controller('ExampleController', ['$scope', function($scope) {
19563 $scope.checkboxModel = { 19564 value1 : true, 19565 value2 : 'YES' 19566 }; 19567 }]); 19568 </script> 19569 <form name="myForm" ng-controller="ExampleController"> 19570 Value1: <input type="checkbox" ng-model="checkboxModel.value1"> <br/> 19571 Value2: <input type="checkbox" ng-model="checkboxModel.value2" 19572 ng-true-value="'YES'" ng-false-value="'NO'"> <br/> 19573 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 19574 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 19575 </form> 19576 </file> 19577 <file name="protractor.js" type="protractor"> 19578 it('should change state', function() { 19579 var value1 = element(by.binding('checkboxModel.value1')); 19580 var value2 = element(by.binding('checkboxModel.value2')); 19581 19582 expect(value1.getText()).toContain('true'); 19583 expect(value2.getText()).toContain('YES'); 19584 19585 element(by.model('checkboxModel.value1')).click(); 19586 element(by.model('checkboxModel.value2')).click(); 19587 19588 expect(value1.getText()).toContain('false'); 19589 expect(value2.getText()).toContain('NO'); 19590 }); 19591 </file> 19592 </example> 19593 */ 19594 'checkbox': checkboxInputType, 19595 19596 'hidden': noop, 19597 'button': noop, 19598 'submit': noop, 19599 'reset': noop, 19600 'file': noop 19601}; 19602 19603function stringBasedInputType(ctrl) { 19604 ctrl.$formatters.push(function(value) { 19605 return ctrl.$isEmpty(value) ? value : value.toString(); 19606 }); 19607} 19608 19609function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 19610 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 19611 stringBasedInputType(ctrl); 19612} 19613 19614function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 19615 var type = lowercase(element[0].type); 19616 19617 // In composition mode, users are still inputing intermediate text buffer, 19618 // hold the listener until composition is done. 19619 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 19620 if (!$sniffer.android) { 19621 var composing = false; 19622 19623 element.on('compositionstart', function(data) { 19624 composing = true; 19625 }); 19626 19627 element.on('compositionend', function() { 19628 composing = false; 19629 listener(); 19630 }); 19631 } 19632 19633 var listener = function(ev) { 19634 if (timeout) { 19635 $browser.defer.cancel(timeout); 19636 timeout = null; 19637 } 19638 if (composing) return; 19639 var value = element.val(), 19640 event = ev && ev.type; 19641 19642 // By default we will trim the value 19643 // If the attribute ng-trim exists we will avoid trimming 19644 // If input type is 'password', the value is never trimmed 19645 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 19646 value = trim(value); 19647 } 19648 19649 // If a control is suffering from bad input (due to native validators), browsers discard its 19650 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 19651 // control's value is the same empty value twice in a row. 19652 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 19653 ctrl.$setViewValue(value, event); 19654 } 19655 }; 19656 19657 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 19658 // input event on backspace, delete or cut 19659 if ($sniffer.hasEvent('input')) { 19660 element.on('input', listener); 19661 } else { 19662 var timeout; 19663 19664 var deferListener = function(ev, input, origValue) { 19665 if (!timeout) { 19666 timeout = $browser.defer(function() { 19667 timeout = null; 19668 if (!input || input.value !== origValue) { 19669 listener(ev); 19670 } 19671 }); 19672 } 19673 }; 19674 19675 element.on('keydown', function(event) { 19676 var key = event.keyCode; 19677 19678 // ignore 19679 // command modifiers arrows 19680 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 19681 19682 deferListener(event, this, this.value); 19683 }); 19684 19685 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 19686 if ($sniffer.hasEvent('paste')) { 19687 element.on('paste cut', deferListener); 19688 } 19689 } 19690 19691 // if user paste into input using mouse on older browser 19692 // or form autocomplete on newer browser, we need "change" event to catch it 19693 element.on('change', listener); 19694 19695 ctrl.$render = function() { 19696 element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); 19697 }; 19698} 19699 19700function weekParser(isoWeek, existingDate) { 19701 if (isDate(isoWeek)) { 19702 return isoWeek; 19703 } 19704 19705 if (isString(isoWeek)) { 19706 WEEK_REGEXP.lastIndex = 0; 19707 var parts = WEEK_REGEXP.exec(isoWeek); 19708 if (parts) { 19709 var year = +parts[1], 19710 week = +parts[2], 19711 hours = 0, 19712 minutes = 0, 19713 seconds = 0, 19714 milliseconds = 0, 19715 firstThurs = getFirstThursdayOfYear(year), 19716 addDays = (week - 1) * 7; 19717 19718 if (existingDate) { 19719 hours = existingDate.getHours(); 19720 minutes = existingDate.getMinutes(); 19721 seconds = existingDate.getSeconds(); 19722 milliseconds = existingDate.getMilliseconds(); 19723 } 19724 19725 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 19726 } 19727 } 19728 19729 return NaN; 19730} 19731 19732function createDateParser(regexp, mapping) { 19733 return function(iso, date) { 19734 var parts, map; 19735 19736 if (isDate(iso)) { 19737 return iso; 19738 } 19739 19740 if (isString(iso)) { 19741 // When a date is JSON'ified to wraps itself inside of an extra 19742 // set of double quotes. This makes the date parsing code unable 19743 // to match the date string and parse it as a date. 19744 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { 19745 iso = iso.substring(1, iso.length - 1); 19746 }
19747 if (ISO_DATE_REGEXP.test(iso)) { 19748 return new Date(iso); 19749 } 19750 regexp.lastIndex = 0; 19751 parts = regexp.exec(iso); 19752 19753 if (parts) { 19754 parts.shift(); 19755 if (date) { 19756 map = { 19757 yyyy: date.getFullYear(), 19758 MM: date.getMonth() + 1, 19759 dd: date.getDate(), 19760 HH: date.getHours(), 19761 mm: date.getMinutes(), 19762 ss: date.getSeconds(), 19763 sss: date.getMilliseconds() / 1000 19764 }; 19765 } else { 19766 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 19767 } 19768 19769 forEach(parts, function(part, index) { 19770 if (index < mapping.length) { 19771 map[mapping[index]] = +part; 19772 } 19773 }); 19774 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 19775 } 19776 } 19777 19778 return NaN; 19779 }; 19780} 19781 19782function createDateInputType(type, regexp, parseDate, format) { 19783 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { 19784 badInputChecker(scope, element, attr, ctrl); 19785 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 19786 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; 19787 var previousDate; 19788 19789 ctrl.$$parserName = type; 19790 ctrl.$parsers.push(function(value) { 19791 if (ctrl.$isEmpty(value)) return null; 19792 if (regexp.test(value)) { 19793 // Note: We cannot read ctrl.$modelValue, as there might be a different 19794 // parser/formatter in the processing chain so that the model 19795 // contains some different data format! 19796 var parsedDate = parseDate(value, previousDate); 19797 if (timezone === 'UTC') { 19798 parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset()); 19799 } 19800 return parsedDate; 19801 } 19802 return undefined; 19803 }); 19804 19805 ctrl.$formatters.push(function(value) { 19806 if (value && !isDate(value)) { 19807 throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 19808 } 19809 if (isValidDate(value)) { 19810 previousDate = value; 19811 if (previousDate && timezone === 'UTC') { 19812 var timezoneOffset = 60000 * previousDate.getTimezoneOffset(); 19813 previousDate = new Date(previousDate.getTime() + timezoneOffset); 19814 } 19815 return $filter('date')(value, format, timezone); 19816 } else { 19817 previousDate = null; 19818 return ''; 19819 } 19820 }); 19821 19822 if (isDefined(attr.min) || attr.ngMin) { 19823 var minVal; 19824 ctrl.$validators.min = function(value) { 19825 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; 19826 }; 19827 attr.$observe('min', function(val) { 19828 minVal = parseObservedDateValue(val); 19829 ctrl.$validate(); 19830 }); 19831 } 19832 19833 if (isDefined(attr.max) || attr.ngMax) { 19834 var maxVal; 19835 ctrl.$validators.max = function(value) { 19836 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; 19837 }; 19838 attr.$observe('max', function(val) { 19839 maxVal = parseObservedDateValue(val); 19840 ctrl.$validate(); 19841 }); 19842 } 19843 19844 function isValidDate(value) { 19845 // Invalid Date: getTime() returns NaN 19846 return value && !(value.getTime && value.getTime() !== value.getTime()); 19847 } 19848 19849 function parseObservedDateValue(val) { 19850 return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined; 19851 } 19852 }; 19853} 19854 19855function badInputChecker(scope, element, attr, ctrl) { 19856 var node = element[0]; 19857 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 19858 if (nativeValidation) { 19859 ctrl.$parsers.push(function(value) { 19860 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 19861 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430): 19862 // - also sets validity.badInput (should only be validity.typeMismatch). 19863 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email) 19864 // - can ignore this case as we can still read out the erroneous email... 19865 return validity.badInput && !validity.typeMismatch ? undefined : value; 19866 }); 19867 } 19868} 19869 19870function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 19871 badInputChecker(scope, element, attr, ctrl); 19872 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 19873 19874 ctrl.$$parserName = 'number'; 19875 ctrl.$parsers.push(function(value) { 19876 if (ctrl.$isEmpty(value)) return null; 19877 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 19878 return undefined; 19879 }); 19880 19881 ctrl.$formatters.push(function(value) { 19882 if (!ctrl.$isEmpty(value)) { 19883 if (!isNumber(value)) { 19884 throw $ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 19885 } 19886 value = value.toString(); 19887 } 19888 return value; 19889 }); 19890 19891 if (attr.min || attr.ngMin) { 19892 var minVal; 19893 ctrl.$validators.min = function(value) { 19894 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; 19895 }; 19896 19897 attr.$observe('min', function(val) { 19898 if (isDefined(val) && !isNumber(val)) { 19899 val = parseFloat(val, 10); 19900 } 19901 minVal = isNumber(val) && !isNaN(val) ? val : undefined; 19902 // TODO(matsko): implement validateLater to reduce number of validations 19903 ctrl.$validate(); 19904 }); 19905 } 19906 19907 if (attr.max || attr.ngMax) { 19908 var maxVal; 19909 ctrl.$validators.max = function(value) { 19910 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; 19911 }; 19912 19913 attr.$observe('max', function(val) { 19914 if (isDefined(val) && !isNumber(val)) { 19915 val = parseFloat(val, 10); 19916 } 19917 maxVal = isNumber(val) && !isNaN(val) ? val : undefined; 19918 // TODO(matsko): implement validateLater to reduce number of validations 19919 ctrl.$validate(); 19920 }); 19921 } 19922} 19923 19924function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 19925 // Note: no badInputChecker here by purpose as `url` is only a validation 19926 // in browsers, i.e. we can always read out input.value even if it is not val
19926id! 19927 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 19928 stringBasedInputType(ctrl); 19929 19930 ctrl.$$parserName = 'url'; 19931 ctrl.$validators.url = function(modelValue, viewValue) { 19932 var value = modelValue || viewValue; 19933 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 19934 }; 19935} 19936 19937function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 19938 // Note: no badInputChecker here by purpose as `url` is only a validation 19939 // in browsers, i.e. we can always read out input.value even if it is not valid! 19940 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 19941 stringBasedInputType(ctrl); 19942 19943 ctrl.$$parserName = 'email'; 19944 ctrl.$validators.email = function(modelValue, viewValue) { 19945 var value = modelValue || viewValue; 19946 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 19947 }; 19948} 19949 19950function radioInputType(scope, element, attr, ctrl) { 19951 // make the name unique, if not defined 19952 if (isUndefined(attr.name)) { 19953 element.attr('name', nextUid()); 19954 } 19955 19956 var listener = function(ev) { 19957 if (element[0].checked) { 19958 ctrl.$setViewValue(attr.value, ev && ev.type); 19959 } 19960 }; 19961 19962 element.on('click', listener); 19963 19964 ctrl.$render = function() { 19965 var value = attr.value; 19966 element[0].checked = (value == ctrl.$viewValue); 19967 }; 19968 19969 attr.$observe('value', ctrl.$render); 19970} 19971 19972function parseConstantExpr($parse, context, name, expression, fallback) { 19973 var parseFn; 19974 if (isDefined(expression)) { 19975 parseFn = $parse(expression); 19976 if (!parseFn.constant) { 19977 throw minErr('ngModel')('constexpr', 'Expected constant expression for `{0}`, but saw ' + 19978 '`{1}`.', name, expression); 19979 } 19980 return parseFn(context); 19981 } 19982 return fallback; 19983} 19984 19985function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 19986 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 19987 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 19988 19989 var listener = function(ev) { 19990 ctrl.$setViewValue(element[0].checked, ev && ev.type); 19991 }; 19992 19993 element.on('click', listener); 19994 19995 ctrl.$render = function() { 19996 element[0].checked = ctrl.$viewValue; 19997 }; 19998 19999 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 20000 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 20001 // it to a boolean. 20002 ctrl.$isEmpty = function(value) { 20003 return value === false; 20004 }; 20005 20006 ctrl.$formatters.push(function(value) { 20007 return equals(value, trueValue); 20008 }); 20009 20010 ctrl.$parsers.push(function(value) { 20011 return value ? trueValue : falseValue; 20012 }); 20013} 20014 20015 20016/** 20017 * @ngdoc directive 20018 * @name textarea 20019 * @restrict E 20020 * 20021 * @description 20022 * HTML textarea element control with angular data-binding. The data-binding and validation 20023 * properties of this element are exactly the same as those of the 20024 * {@link ng.directive:input input element}. 20025 * 20026 * @param {string} ngModel Assignable angular expression to data-bind to. 20027 * @param {string=} name Property name of the form under which the control is published. 20028 * @param {string=} required Sets `required` validation error key if the value is not entered. 20029 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20030 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20031 * `required` when you want to data-bind to the `required` attribute. 20032 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 20033 * minlength. 20034 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 20035 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 20036 * length. 20037 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 20038 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 20039 * patterns defined as scope expressions. 20040 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20041 * interaction with the input element. 20042 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 20043 */ 20044 20045 20046/** 20047 * @ngdoc directive 20048 * @name input 20049 * @restrict E 20050 * 20051 * @description 20052 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 20053 * input state control, and validation. 20054 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 20055 * 20056 * <div class="alert alert-warning"> 20057 * **Note:** Not every feature offered is available for all input types. 20058 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 20059 * </div> 20060 * 20061 * @param {string} ngModel Assignable angular expression to data-bind to. 20062 * @param {string=} name Property name of the form under which the control is published. 20063 * @param {string=} required Sets `required` validation error key if the value is not entered. 20064 * @param {boolean=} ngRequired Sets `required` attribute if set to true 20065 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 20066 * minlength. 20067 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 20068 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 20069 * length. 20070 * @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the 20071 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for 20072 * patterns defined as scope expressions. 20073 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20074 * interaction with the input element. 20075 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 20076 * This parameter is ignored for input[type=password] controls, which will never trim the 20077 * input. 20078 * 20079 * @example 20080 <example name="input-directive" module="inputExample"> 20081 <file name="index.html"> 20082 <script> 20083 angular.module('inputExample', [])
20084 .controller('ExampleController', ['$scope', function($scope) { 20085 $scope.user = {name: 'guest', last: 'visitor'}; 20086 }]); 20087 </script> 20088 <div ng-controller="ExampleController"> 20089 <form name="myForm"> 20090 User name: <input type="text" name="userName" ng-model="user.name" required> 20091 <span class="error" ng-show="myForm.userName.$error.required"> 20092 Required!</span><br> 20093 Last name: <input type="text" name="lastName" ng-model="user.last" 20094 ng-minlength="3" ng-maxlength="10"> 20095 <span class="error" ng-show="myForm.lastName.$error.minlength"> 20096 Too short!</span> 20097 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 20098 Too long!</span><br> 20099 </form> 20100 <hr> 20101 <tt>user = {{user}}</tt><br/> 20102 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br> 20103 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br> 20104 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br> 20105 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br> 20106 <tt>myForm.$valid = {{myForm.$valid}}</tt><br> 20107 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br> 20108 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br> 20109 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br
vendor: 2,872 bytes, lines 20109-20183
20109> 20110 </div> 20111 </file> 20112 <file name="protractor.js" type="protractor"> 20113 var user = element(by.exactBinding('user')); 20114 var userNameValid = element(by.binding('myForm.userName.$valid')); 20115 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 20116 var lastNameError = element(by.binding('myForm.lastName.$error')); 20117 var formValid = element(by.binding('myForm.$valid')); 20118 var userNameInput = element(by.model('user.name')); 20119 var userLastInput = element(by.model('user.last')); 20120 20121 it('should initialize to model', function() { 20122 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 20123 expect(userNameValid.getText()).toContain('true'); 20124 expect(formValid.getText()).toContain('true'); 20125 }); 20126 20127 it('should be invalid if empty when required', function() { 20128 userNameInput.clear(); 20129 userNameInput.sendKeys(''); 20130 20131 expect(user.getText()).toContain('{"last":"visitor"}'); 20132 expect(userNameValid.getText()).toContain('false'); 20133 expect(formValid.getText()).toContain('false'); 20134 }); 20135 20136 it('should be valid if empty when min length is set', function() { 20137 userLastInput.clear(); 20138 userLastInput.sendKeys(''); 20139 20140 expect(user.getText()).toContain('{"name":"guest","last":""}'); 20141 expect(lastNameValid.getText()).toContain('true'); 20142 expect(formValid.getText()).toContain('true'); 20143 }); 20144 20145 it('should be invalid if less than required min length', function() { 20146 userLastInput.clear(); 20147 userLastInput.sendKeys('xx'); 20148 20149 expect(user.getText()).toContain('{"name":"guest"}'); 20150 expect(lastNameValid.getText()).toContain('false'); 20151 expect(lastNameError.getText()).toContain('minlength'); 20152 expect(formValid.getText()).toContain('false'); 20153 }); 20154 20155 it('should be invalid if longer than max length', function() { 20156 userLastInput.clear(); 20157 userLastInput.sendKeys('some ridiculously long name'); 20158 20159 expect(user.getText()).toContain('{"name":"guest"}'); 20160 expect(lastNameValid.getText()).toContain('false'); 20161 expect(lastNameError.getText()).toContain('maxlength'); 20162 expect(formValid.getText()).toContain('false'); 20163 }); 20164 </file> 20165 </example> 20166 */ 20167var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 20168 function($browser, $sniffer, $filter, $parse) { 20169 return { 20170 restrict: 'E', 20171 require: ['?ngModel'], 20172 link: { 20173 pre: function(scope, element, attr, ctrls) { 20174 if (ctrls[0]) { 20175 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 20176 $browser, $filter, $parse); 20177 } 20178 } 20179 } 20180 }; 20181}]; 20182 20183
20184 20185var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 20186/** 20187 * @ngdoc directive 20188 * @name ngValue 20189 * 20190 * @description 20191 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, 20192 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to 20193 * the bound value. 20194 * 20195 * `ngValue` is useful when dynamically generating lists of radio buttons using 20196 * {@link ngRepeat `ngRepeat`}, as shown below. 20197 * 20198 * Likewise, `ngValue` can be used to generate `<option>` elements for 20199 * the {@link select `select`} element. In that case however, only strings are supported 20200 * for the `value `attribute, so the resulting `ngModel` will always be a string. 20201 * Support for `select` models with non-string values is available via `ngOptions`. 20202 * 20203 * @element input 20204 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 20205 * of the `input` element 20206 * 20207 * @example 20208 <example name="ngValue-directive" module="valueExample"> 20209 <file name="index.html"> 20210 <script> 20211 angular.module('valueExample', []) 20212 .controller('ExampleController', ['$scope', function($scope) { 20213 $scope.names = ['pizza', 'unicorns', 'robots']; 20214 $scope.my = { favorite: 'unicorns' }; 20215 }]); 20216 </script>
20217 <form ng-controller="ExampleController"> 20218 <h2>Which is your favorite?</h2> 20219 <label ng-repeat="name in names" for="{{name}}"> 20220 {{name}} 20221 <input type="radio" 20222 ng-model="my.favorite" 20223 ng-value="name" 20224 id="{{name}}" 20225 name="favorite"> 20226 </label> 20227 <div>You chose {{my.favorite}}</div> 20228 </form> 20229 </file> 20230 <file name="protractor.js" type="protractor"> 20231 var favorite = element(by.binding('my.favorite')); 20232 20233 it('should initialize to model', function() { 20234 expect(favorite.getText()).toContain('unicorns'); 20235 }); 20236 it('should bind the values to the inputs', function() { 20237 element.all(by.model('my.favorite')).get(0).click(); 20238 expect(favorite.getText()).toContain('pizza'); 20239 }); 20240 </file> 20241 </example> 20242 */ 20243var ngValueDirective = function() { 20244 return { 20245 restrict: 'A', 20246 priority: 100, 20247 compile: function(tpl, tplAttr) { 20248 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 20249 return function ngValueConstantLink(scope, elm, attr) { 20250 attr.$set('value', scope.$eval(attr.ngValue)); 20251 }; 20252 } else { 20253 return function ngValueLink(scope, elm, attr) { 20254 scope.$watch(attr.ngValue, function valueWatchAction(value) { 20255 attr.$set('value', value); 20256 }); 20257 }; 20258 } 20259 } 20260 }; 20261}; 20262 20263/** 20264 * @ngdoc directive 20265 * @name ngBind 20266 * @restrict AC 20267 * 20268 * @description 20269 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 20270 * with the value of a given expression, and to update the text content when the value of that 20271 * expression changes. 20272 * 20273 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 20274 * `{{ expression }}` which is similar but less verbose. 20275 * 20276 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 20277 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 20278 * element attribute, it makes the bindings invisible to the user while the page is loading. 20279 * 20280 * An alternative solution to this problem would be using the 20281 * {@link ng.directive:ngCloak ngCloak} directive. 20282 * 20283 * 20284 * @element ANY 20285 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 20286 * 20287 * @example 20288 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 20289 <example module="bindExample"> 20290 <file name="index.html"> 20291 <script> 20292 angular.module('bindExample', []) 20293 .controller('ExampleController', ['$scope', function($scope) { 20294 $scope.name = 'Whirled'; 20295 }]); 20296 </script>
20297 <div ng-controller="ExampleController"> 20298 Enter name: <input type="text" ng-model="name"><br> 20299 Hello <span ng-bind="name"></span>! 20300 </div> 20301 </file> 20302 <file name="protractor.js" type="protractor"> 20303 it('should check ng-bind', function() { 20304 var nameInput = element(by.model('name')); 20305 20306 expect(element(by.binding('name')).getText()).toBe('Whirled'); 20307 nameInput.clear(); 20308 nameInput.sendKeys('world'); 20309 expect(element(by.binding('name')).getText()).toBe('world'); 20310 }); 20311 </file> 20312 </example> 20313 */ 20314var ngBindDirective = ['$compile', function($compile) { 20315 return { 20316 restrict: 'AC', 20317 compile: function ngBindCompile(templateElement) { 20318 $compile.$$addBindingClass(templateElement); 20319 return function ngBindLink(scope, element, attr) { 20320 $compile.$$addBindingInfo(element, attr.ngBind); 20321 element = element[0]; 20322 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 20323 element.textContent = value === undefined ? '' : value; 20324 }); 20325 }; 20326 } 20327 }; 20328}]; 20329 20330 20331/** 20332 * @ngdoc directive 20333 * @name ngBindTemplate 20334 * 20335 * @description 20336 * The `ngBindTemplate` directive specifies that the element 20337 * text content should be replaced with the interpolation of the template 20338 * in the `ngBindTemplate` attribute. 20339 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 20340 * expressions. This directive is needed since some HTML elements 20341 * (such as TITLE and OPTION) cannot contain SPAN elements. 20342 * 20343 * @element ANY 20344 * @param {string} ngBindTemplate template of form 20345 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 20346 * 20347 * @example 20348 * Try it here: enter text in text box and watch the greeting change. 20349 <example module="bindExample"> 20350 <file name="index.html"> 20351 <script> 20352 angular.module('bindExample', []) 20353 .controller('ExampleController', ['$scope', function($scope) { 20354 $scope.salutation = 'Hello'; 20355 $scope.name = 'World'; 20356 }]); 20357 </script>
20358 <div ng-controller="ExampleController"> 20359 Salutation: <input type="text" ng-model="salutation"><br> 20360 Name: <input type="text" ng-model="name"><br> 20361 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 20362 </div> 20363 </file> 20364 <file name="protractor.js" type="protractor"> 20365 it('should check ng-bind', function() { 20366 var salutationElem = element(by.binding('salutation')); 20367 var salutationInput = element(by.model('salutation')); 20368 var nameInput = element(by.model('name')); 20369 20370 expect(salutationElem.getText()).toBe('Hello World!'); 20371 20372 salutationInput.clear(); 20373 salutationInput.sendKeys('Greetings'); 20374 nameInput.clear(); 20375 nameInput.sendKeys('user'); 20376 20377 expect(salutationElem.getText()).toBe('Greetings user!'); 20378 }); 20379 </file> 20380 </example> 20381 */ 20382var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 20383 return { 20384 compile: function ngBindTemplateCompile(templateElement) { 20385 $compile.$$addBindingClass(templateElement); 20386 return function ngBindTemplateLink(scope, element, attr) { 20387 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 20388 $compile.$$addBindingInfo(element, interpolateFn.expressions); 20389 element = element[0]; 20390 attr.$observe('ngBindTemplate', function(value) { 20391 element.textContent = value === undefined ? '' : value; 20392 }); 20393 }; 20394 } 20395 }; 20396}]; 20397 20398 20399/** 20400 * @ngdoc directive 20401 * @name ngBindHtml 20402 * 20403 * @description 20404 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 20405 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 20406 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 20407 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} 20408 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 20409 * 20410 * You may also bypass sanitization for values you know are safe. To do so, bind to 20411 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 20412 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 20413 * 20414 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 20415 * will have an exception (instead of an exploit.) 20416 * 20417 * @element ANY 20418 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 20419 * 20420 * @example 20421 20422 <example module="bindHtmlExample" deps="angular-sanitize.js"> 20423 <file name="index.html">
20424 <div ng-controller="ExampleController"> 20425 <p ng-bind-html="myHTML"></p> 20426 </div> 20427 </file> 20428 20429 <file name="script.js"> 20430 angular.module('bindHtmlExample', ['ngSanitize']) 20431 .controller('ExampleController', ['$scope', function($scope) { 20432 $scope.myHTML = 20433 'I am an <code>HTML</code>string with ' + 20434 '<a href="#">links!</a> and other <em>stuff</em>'; 20435 }]); 20436 </file> 20437 20438 <file name="protractor.js" type="protractor"> 20439 it('should check ng-bind-html', function() { 20440 expect(element(by.binding('myHTML')).getText()).toBe( 20441 'I am an HTMLstring with links! and other stuff'); 20442 }); 20443 </file> 20444 </example> 20445 */ 20446var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 20447 return { 20448 restrict: 'A', 20449 compile: function ngBindHtmlCompile(tElement, tAttrs) { 20450 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 20451 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) { 20452 return (value || '').toString(); 20453 }); 20454 $compile.$$addBindingClass(tElement); 20455 20456 return function ngBindHtmlLink(scope, element, attr) { 20457 $compile.$$addBindingInfo(element, attr.ngBindHtml); 20458 20459 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 20460 // we re-evaluate the expr because we want a TrustedValueHolderType 20461 // for $sce, not a string 20462 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || ''); 20463 }); 20464 }; 20465 } 20466 }; 20467}]; 20468 20469/** 20470 * @ngdoc directive 20471 * @name ngChange 20472 * 20473 * @description 20474 * Evaluate the given expression when the user changes the input. 20475 * The expression is evaluated immediately, unlike the JavaScript onchange event 20476 * which only triggers at the end of a change (usually, when the user leaves the 20477 * form element or presses the return key). 20478 * 20479 * The `ngChange` expression is only evaluated when a change in the input value causes 20480 * a new value to be committed to the model. 20481 * 20482 * It will not be evaluated: 20483 * * if the value returned from the `$parsers` transformation pipeline has not changed 20484 * * if the input has continued to be invalid since the model will stay `null` 20485 * * if the model is changed programmatically and not by a change to the input value 20486 * 20487 * 20488 * Note, this directive requires `ngModel` to be present. 20489 * 20490 * @element input 20491 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 20492 * in input value. 20493 * 20494 * @example 20495 * <example name="ngChange-directive" module="changeExample"> 20496 * <file name="index.html"> 20497 * <script> 20498 * angular.module('changeExample', []) 20499 * .controller('ExampleController', ['$scope', function($scope) { 20500 * $scope.counter = 0; 20501 * $scope.change = function() { 20502 * $scope.counter++; 20503 * }; 20504 * }]); 20505 * </script>
20506 * <div ng-controller="ExampleController"> 20507 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 20508 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 20509 * <label for="ng-change-example2">Confirmed</label><br /> 20510 * <tt>debug = {{confirmed}}</tt><br/> 20511 * <tt>counter = {{counter}}</tt><br/> 20512 * </div> 20513 * </file> 20514 * <file name="protractor.js" type="protractor"> 20515 * var counter = element(by.binding('counter')); 20516 * var debug = element(by.binding('confirmed')); 20517 * 20518 * it('should evaluate the expression if changing from view', function() { 20519 * expect(counter.getText()).toContain('0'); 20520 * 20521 * element(by.id('ng-change-example1')).click(); 20522 * 20523 * expect(counter.getText()).toContain('1'); 20524 * expect(debug.getText()).toContain('true'); 20525 * }); 20526 * 20527 * it('should not evaluate the expression if changing from model', function() { 20528 * element(by.id('ng-change-example2')).click(); 20529 20530 * expect(counter.getText()).toContain('0'); 20531 * expect(debug.getText()).toContain('true'); 20532 * }); 20533 * </file> 20534 * </example> 20535 */ 20536var ngChangeDirective = valueFn({ 20537 restrict: 'A', 20538 require: 'ngModel', 20539 link: function(scope, element, attr, ctrl) { 20540 ctrl.$viewChangeListeners.push(function() { 20541 scope.$eval(attr.ngChange); 20542 }); 20543 } 20544}); 20545 20546function classDirective(name, selector) { 20547 name = 'ngClass' + name; 20548 return ['$animate', function($animate) { 20549 return { 20550 restrict: 'AC', 20551 link: function(scope, element, attr) { 20552 var oldVal; 20553 20554 scope.$watch(attr[name], ngClassWatchAction, true); 20555 20556 attr.$observe('class', function(value) { 20557 ngClassWatchAction(scope.$eval(attr[name])); 20558 }); 20559 20560 20561 if (name !== 'ngClass') { 20562 scope.$watch('$index', function($index, old$index) { 20563 // jshint bitwise: false 20564 var mod = $index & 1; 20565 if (mod !== (old$index & 1)) { 20566 var classes = arrayClasses(scope.$eval(attr[name])); 20567 mod === selector ? 20568 addClasses(classes) : 20569 removeClasses(classes); 20570 } 20571 }); 20572 } 20573 20574 function addClasses(classes) { 20575 var newClasses = digestClassCounts(classes, 1); 20576 attr.$addClass(newClasses); 20577 } 20578 20579 function removeClasses(classes) { 20580 var newClasses = digestClassCounts(classes, -1); 20581 attr.$removeClass(newClasses); 20582 } 20583 20584 function digestClassCounts(classes, count) { 20585 var classCounts = element.data('$classCounts') || {}; 20586 var classesToUpdate = []; 20587 forEach(classes, function(className) { 20588 if (count > 0 || classCounts[className]) { 20589 classCounts[className] = (classCounts[className] || 0) + count; 20590 if (classCounts[className] === +(count > 0)) { 20591 classesToUpdate.push(className); 20592 } 20593 } 20594 }); 20595 element.data('$classCounts', classCounts); 20596 return classesToUpdate.join(' '); 20597 } 20598 20599 function updateClasses(oldClasses, newClasses) { 20600 var toAdd = arrayDifference(newClasses, oldClasses); 20601 var toRemove = arrayDifference(oldClasses, newClasses); 20602 toAdd = digestClassCounts(toAdd, 1); 20603 toRemove = digestClassCounts(toRemove, -1); 20604 if (toAdd && toAdd.length) { 20605 $animate.addClass(element, toAdd); 20606 } 20607 if (toRemove && toRemove.length) { 20608 $animate.removeClass(element, toRemove); 20609 } 20610 } 20611 20612 function ngClassWatchAction(newVal) { 20613 if (selector === true || scope.$index % 2 === selector) { 20614 var newClasses = arrayClasses(newVal || []); 20615 if (!oldVal) { 20616 addClasses(newClasses); 20617 } else if (!equals(newVal,oldVal)) { 20618 var oldClasses = arrayClasses(oldVal); 20619 updateClasses(oldClasses, newClasses); 20620 } 20621 } 20622 oldVal = shallowCopy(newVal); 20623 } 20624 } 20625 }; 20626 20627 function arrayDifference(tokens1, tokens2) { 20628 var values = []; 20629 20630 outer: 20631 for (var i = 0; i < tokens1.length; i++) { 20632 var token = tokens1[i]; 20633 for (var j = 0; j < tokens2.length; j++) { 20634 if (token == tokens2[j]) continue outer; 20635 } 20636 values.push(token); 20637 } 20638 return values; 20639 } 20640
20641 function arrayClasses(classVal) { 20642 if (isArray(classVal)) { 20643 return classVal; 20644 } else if (isString(classVal)) { 20645 return classVal.split(' '); 20646 } else if (isObject(classVal)) { 20647 var classes = []; 20648 forEach(classVal, function(v, k) { 20649 if (v) { 20650 classes = classes.concat(k.split(' ')); 20651 } 20652 }); 20653 return classes; 20654 } 20655 return classVal; 20656 } 20657 }]; 20658} 20659 20660/** 20661 * @ngdoc directive 20662 * @name ngClass 20663 * @restrict AC 20664 * 20665 * @description 20666 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 20667 * an expression that represents all classes to be added. 20668 * 20669 * The directive operates in three different ways, depending on which of three types the expression 20670 * evaluates to: 20671 * 20672 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 20673 * names. 20674 * 20675 * 2. If the expression evaluates to an array, each element of the array should be a string that is 20676 * one or more space-delimited class names. 20677 * 20678 * 3. If the expression evaluates to an object, then for each key-value pair of the 20679 * object with a truthy value the corresponding key is used as a class name. 20680 * 20681 * The directive won't add duplicate classes if a particular class was already set. 20682 * 20683 * When the expression changes, the previously added classes are removed and only then the 20684 * new classes are added. 20685 * 20686 * @animations 20687 * **add** - happens just before the class is applied to the elements 20688 * 20689 * **remove** - happens just before the class is removed from the element 20690 * 20691 * @element ANY 20692 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 20693 * of the evaluation can be a string representing space delimited class 20694 * names, an array, or a map of class names to boolean values. In the case of a map, the 20695 * names of the properties whose values are truthy will be added as css classes to the 20696 * element. 20697 * 20698 * @example Example that demonstrates basic bindings via ngClass directive. 20699 <example> 20700 <file name="index.html"> 20701 <p ng-class="{strike: deleted, bold: important, red: error}">Map Syntax Example</p> 20702 <input type="checkbox" ng-model="deleted"> deleted (apply "strike" class)<br> 20703 <input type="checkbox" ng-model="important"> important (apply "bold" class)<br> 20704 <input type="checkbox" ng-model="error"> error (apply "red" class) 20705 <hr> 20706 <p ng-class="style">Using String Syntax</p> 20707 <input type="text" ng-model="style" placeholder="Type: bold strike red"> 20708 <hr> 20709 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 20710 <input ng-model="style1" placeholder="Type: bold, strike or red"><br> 20711 <input ng-model="style2" placeholder="Type: bold, strike or red"><br> 20712 <input ng-model="style3" placeholder="Type: bold, strike or red"><br> 20713 </file> 20714 <file name="style.css"> 20715 .strike { 20716 text-decoration: line-through; 20717 } 20718 .bold { 20719 font-weight: bold; 20720 } 20721 .red { 20722 color: red; 20723 } 20724 </file> 20725 <file name="protractor.js" type="protractor"> 20726 var ps = element.all(by.css('p')); 20727 20728 it('should let you toggle the class', function() { 20729 20730 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 20731 expect(ps.first().getAttribute('class')).not.toMatch(/red/); 20732 20733 element(by.model('important')).click(); 20734 expect(ps.first().getAttribute('class')).toMatch(/bold/); 20735 20736 element(by.model('error')).click(); 20737 expect(ps.first().getAttribute('class')).toMatch(/red/); 20738 }); 20739 20740 it('should let you toggle string example', function() { 20741 expect(ps.get(1).getAttribute('class')).toBe(''); 20742 element(by.model('style')).clear(); 20743 element(by.model('style')).sendKeys('red'); 20744 expect(ps.get(1).getAttribute('class')).toBe('red'); 20745 }); 20746 20747 it('array example should have 3 classes', function() { 20748 expect(ps.last().getAttribute('class')).toBe(''); 20749 element(by.model('style1')).sendKeys('bold');
20750 element(by.model('style2')).sendKeys('strike'); 20751 element(by.model('style3')).sendKeys('red'); 20752 expect(ps.last().getAttribute('class')).toBe('bold strike red'); 20753 }); 20754 </file> 20755 </example> 20756 20757 ## Animations 20758 20759 The example below demonstrates how to perform animations using ngClass. 20760 20761 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 20762 <file name="index.html"> 20763 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 20764 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 20765 <br> 20766 <span class="base-class" ng-class="myVar">Sample Text</span> 20767 </file> 20768 <file name="style.css"> 20769 .base-class { 20770 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 20771 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 20772 } 20773 20774 .base-class.my-class { 20775 color: red; 20776 font-size:3em; 20777 } 20778 </file> 20779 <file name="protractor.js" type="protractor"> 20780 it('should check ng-class', function() { 20781 expect(element(by.css('.base-class')).getAttribute('class')).not. 20782 toMatch(/my-class/); 20783 20784 element(by.id('setbtn')).click(); 20785 20786 expect(element(by.css('.base-class')).getAttribute('class')). 20787 toMatch(/my-class/); 20788 20789 element(by.id('clearbtn')).click(); 20790 20791 expect(element(by.css('.base-class')).getAttribute('class')).not. 20792 toMatch(/my-class/); 20793 }); 20794 </file> 20795 </example> 20796 20797 20798 ## ngClass and pre-existing CSS3 Transitions/Animations 20799 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 20800 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 20801 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 20802 to view the step by step details of {@link ng.$animate#addClass $animate.addClass} and 20803 {@link ng.$animate#removeClass $animate.removeClass}. 20804 */ 20805var ngClassDirective = classDirective('', true); 20806 20807/** 20808 * @ngdoc directive 20809 * @name ngClassOdd 20810 * @restrict AC 20811 * 20812 * @description 20813 * The `ngClassOdd` and `ngClassEven` directives work exactly as 20814 * {@link ng.directive:ngClass ngClass}, except they work in 20815 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 20816 * 20817 * This directive can be applied only within the scope of an 20818 * {@link ng.directive:ngRepeat ngRepeat}. 20819 * 20820 * @element ANY 20821 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 20822 * of the evaluation can be a string representing space delimited class names or an array. 20823 * 20824 * @example 20825 <example> 20826 <file name="index.html"> 20827 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 20828 <li ng-repeat="name in names"> 20829 <span ng-class-odd="'odd'" ng-class-even="'even'"> 20830 {{name}} 20831 </span> 20832 </li> 20833 </ol> 20834 </file> 20835 <file name="style.css"> 20836 .odd { 20837 color: red; 20838 } 20839 .even { 20840 color: blue; 20841 } 20842 </file> 20843 <file name="protractor.js" type="protractor"> 20844 it('should check ng-class-odd and ng-class-even', function() { 20845 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 20846 toMatch(/odd/); 20847 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 20848 toMatch(/even/); 20849 }); 20850 </file> 20851 </example> 20852 */ 20853var ngClassOddDirective = classDirective('Odd', 0); 20854 20855/** 20856 * @ngdoc directive 20857 * @name ngClassEven 20858 * @restrict AC 20859 * 20860 * @description 20861 * The `ngClassOdd` and `ngClassEven` directives work exactly as 20862 * {@link ng.directive:ngClass ngClass}, except they work in 20863 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 20864 * 20865 * This directive can be applied only within the scope of an 20866 * {@link ng.directive:ngRepeat ngRepeat}. 20867 * 20868 * @element ANY 20869 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 20870 * result of the evaluation can be a string representing space delimited class names or an array.
20871 * 20872 * @example 20873 <example> 20874 <file name="index.html"> 20875 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 20876 <li ng-repeat="name in names"> 20877 <span ng-class-odd="'odd'" ng-class-even="'even'"> 20878 {{name}} 20879 </span> 20880 </li> 20881 </ol> 20882 </file> 20883 <file name="style.css"> 20884 .odd { 20885 color: red; 20886 } 20887 .even { 20888 color: blue; 20889 } 20890 </file> 20891 <file name="protractor.js" type="protractor"> 20892 it('should check ng-class-odd and ng-class-even', function() { 20893 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 20894 toMatch(/odd/); 20895 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 20896 toMatch(/even/); 20897 }); 20898 </file> 20899 </example> 20900 */ 20901var ngClassEvenDirective = classDirective('Even', 1); 20902 20903/** 20904 * @ngdoc directive 20905 * @name ngCloak 20906 * @restrict AC 20907 * 20908 * @description 20909 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 20910 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 20911 * directive to avoid the undesirable flicker effect caused by the html template display. 20912 * 20913 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 20914 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 20915 * of the browser view. 20916 * 20917 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 20918 * `angular.min.js`. 20919 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 20920 * 20921 * ```css 20922 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 20923 * display: none !important; 20924 * } 20925 * ``` 20926 * 20927 * When this css rule is loaded by the browser, all html elements (including their children) that 20928 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 20929 * during the compilation of the template it deletes the `ngCloak` element attribute, making 20930 * the compiled element visible. 20931 * 20932 * For the best result, the `angular.js` script must be loaded in the head section of the html 20933 * document; alternatively, the css rule above must be included in the external stylesheet of the 20934 * application. 20935 * 20936 * Legacy browsers, like IE7, do not provide attribute selector support (a
20936dded in CSS 2.1) so they 20937 * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css 20938 * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below. 20939 * 20940 * @element ANY 20941 * 20942 * @example 20943 <example> 20944 <file name="index.html"> 20945 <div id="template1" ng-cloak>{{ 'hello' }}</div> 20946 <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div> 20947 </file> 20948 <file name="protractor.js" type="protractor"> 20949 it('should remove the template directive and css class', function() { 20950 expect($('#template1').getAttribute('ng-cloak')). 20951 toBeNull(); 20952 expect($('#template2').getAttribute('ng-cloak')). 20953 toBeNull(); 20954 }); 20955 </file> 20956 </example> 20957 * 20958 */ 20959var ngCloakDirective = ngDirective({ 20960 compile: function(element, attr) { 20961 attr.$set('ngCloak', undefined); 20962 element.removeClass('ng-cloak'); 20963 } 20964}); 20965 20966/** 20967 * @ngdoc directive 20968 * @name ngController 20969 * 20970 * @description 20971 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 20972 * supports the principles behind the Model-View-Controller design pattern. 20973 * 20974 * MVC components in angular: 20975 * 20976 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 20977 * are accessed through bindings. 20978 * * View â The template (HTML with data bindings) that is rendered into the View. 20979 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 20980 * logic behind the application to decorate the scope with functions and values 20981 * 20982 * Note that you can also attach controllers to the DOM by declaring it in a route definition 20983 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 20984 * again using `ng-controller` in the template itself. This will cause the controller to be attached 20985 * and executed twice. 20986 * 20987 * @element ANY 20988 * @scope 20989 * @priority 500 20990 * @param {expression} ngController Name of a constructor function registered with the current 20991 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 20992 * that on the current scope evaluates to a constructor function. 20993 * 20994 * The controller instance can be published into a scope property by specifying 20995 * `ng-controller="as propertyName"`. 20996 * 20997 * If the current `$controllerProvider` is configured to use globals (via 20998 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may 20999 * also be the name of a globally accessible constructor function (not recommended). 21000 * 21001 * @example 21002 * Here is a simple form for editing user contact information. Adding, removing, clearing, and 21003 * greeting are methods declared on the controller (see source tab). These methods can 21004 * easily be called from the angular markup. Any changes to the data are automatically reflected 21005 * in the View without the need for a manual update. 21006 * 21007 * Two different declaration styles are included below: 21008 * 21009 * * one binds methods and properties directly onto the controller using `this`: 21010 * `ng-controller="SettingsController1 as settings"` 21011 * * one injects `$scope` into the controller: 21012 * `ng-controller="SettingsController2"` 21013 * 21014 * The second option is more common in the Angular community, and is generally used in boilerplates 21015 * and in this guide. However, there are advantages to binding properties directly to the controller 21016 * and avoiding scope. 21017 * 21018 * * Using `controller as` makes it obvious which controller you are accessing in the template when 21019 * multiple controllers apply to an element. 21020 * * If you are writing your controllers as classes you have easier access to the properties and 21021 * methods, which will appear on the scope, from inside the controller code. 21022 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 21023 * inheritance masking primitives. 21024 * 21025 * This example demonstrates the `controller as` syntax. 21026 * 21027 * <example name="ngControllerAs" module="controllerAsExample"> 21028 * <file name="index.html"> 21029 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 21030 * Name: <input type="text" ng-model="settings.name"/> 21031 * [ <a href="" ng-click="settings.greet()">greet</a> ]<br/> 21032 * Contact: 21033 * <ul> 21034 * <li ng-repeat="contact in settings.contacts"> 21035 * <select ng-model="contact.type"> 21036 * <option>phone</option> 21037 * <option>email</option> 21038 * </select> 21039 * <input type="text" ng-model="contact.value"/> 21040 * [ <a href="" ng-click="settings.clearContact(contact)">clear</a> 21041 * | <a href="" ng-click="settings.removeContact(contact)">X</a> ] 21042 * </li> 21043 * <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li> 21044 * </ul> 21045 * </div> 21046 * </file> 21047 * <file name="app.js">
21048 * angular.module('controllerAsExample', []) 21049 * .controller('SettingsController1', SettingsController1); 21050 * 21051 * function SettingsController1() { 21052 * this.name = "John Smith"; 21053 * this.contacts = [ 21054 * {type: 'phone', value: '408 555 1212'}, 21055 * {type: 'email', value: '[email protected]'} ]; 21056 * } 21057 * 21058 * SettingsController1.prototype.greet = function() { 21059 * alert(this.name); 21060 * }; 21061 * 21062 * SettingsController1.prototype.addContact = function() { 21063 * this.contacts.push({type: 'email', value: '[email protected]'}); 21064 * }; 21065 * 21066 * SettingsController1.prototype.removeContact = function(contactToRemove) { 21067 * var index = this.contacts.indexOf(contactToRemove); 21068 * this.contacts.splice(index, 1); 21069 * }; 21070 * 21071 * SettingsController1.prototype.clearContact = function(contact) { 21072 * contact.type = 'phone'; 21073 * contact.value = ''; 21074 * }; 21075 * </file> 21076 * <file name="protractor.js" type="protractor"> 21077 * it('should check controller as', function() { 21078 * var container = element(by.id('ctrl-as-exmpl')); 21079 * expect(container.element(by.model('settings.name')) 21080 * .getAttribute('value')).toBe('John Smith'); 21081 * 21082 * var firstRepeat = 21083 * container.element(by.repeater('contact in settings.contacts').row(0)); 21084 * var secondRepeat = 21085 * container.element(by.repeater('contact in settings.contacts').row(1)); 21086 * 21087 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 21088 * .toBe('408 555 1212'); 21089 * 21090 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 21091 * .toBe('[email protected]'); 21092 * 21093 * firstRepeat.element(by.linkText('clear')).click(); 21094 * 21095 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 21096 * .toBe(''); 21097 * 21098 * container.element(by.linkText('add')).click(); 21099 * 21100 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 21101 * .element(by.model('contact.value')) 21102 * .getAttribute('value')) 21103 * .toBe('[email protected]'); 21104 * }); 21105 * </file> 21106 * </example> 21107 * 21108 * This example demonstrates the "attach to `$scope`" style of controller. 21109 * 21110 * <example name="ngController" module="controllerExample"> 21111 * <file name="index.html"> 21112 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 21113 * Name: <input type="text" ng-model="name"/> 21114 * [ <a href="" ng-click="greet()">greet</a> ]<br/> 21115 * Contact: 21116 * <ul> 21117 * <li ng-repeat="contact in contacts"> 21118 * <select ng-model="contact.type"> 21119 * <option>phone</option> 21120 * <option>email</option> 21121 * </select> 21122 * <input type="text" ng-model="contact.value"/> 21123 * [ <a href="" ng-click="clearContact(contact)">clear</a> 21124 * | <a href="" ng-click="removeContact(contact)">X</a> ] 21125 * </li> 21126 * <li>[ <a href="" ng-click="addContact()">add</a> ]</li> 21127 * </ul> 21128 * </div> 21129 * </file> 21130 * <file name="app.js"> 21131 * angular.module('controllerExample', []) 21132 * .controller('SettingsController2', ['$scope', SettingsController2]); 21133 * 21134 * function SettingsController2($scope) { 21135 * $scope.name = "John Smith"; 21136 * $scope.contacts = [ 21137 * {type:'phone', value:'408 555 1212'}, 21138 * {type:'email', value:'[email protected]'} ]; 21139 * 21140 * $scope.greet = function() { 21141 * alert($scope.name); 21142 * }; 21143 * 21144 * $scope.addContact = function() { 21145 * $scope.contacts.push({type:'email', value:'[email protected]'}); 21146 * }; 21147 * 21148 * $scope.removeContact = function(contactToRemove) { 21149 * var index = $scope.contacts.indexOf(contactToRemove); 21150 * $scope.contacts.splice(index, 1); 21151 * }; 21152 * 21153 * $scope.clearContact = function(contact) { 21154 * contact.type = 'phone'; 21155 * contact.value = ''; 21156 * }; 21157 * } 21158 * </file> 21159 * <file name="protractor.js" type="protractor"> 21160 * it('should check controller', function() { 21161 * var container = element(by.id('ctrl-exmpl')); 21162 * 21163 * expect(container.element(by.model('name')) 21164 * .getAttribute('value')).toBe('John Smith'); 21165 * 21166 * var firstRepeat = 21167 * container.element(by.repeater('contact in contacts').row(0)); 21168 * var secondRepeat = 21169 * container.element(by.repeater('contact in contacts').row(1)); 21170 * 21171 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 21172 * .toBe('408 555 1212'); 21173 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 21174 * .toBe('[email protected]'); 21175 * 21176 * firstRepeat.element(by.linkText('clear')).click(); 21177 * 21178 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 21179 * .toBe(''); 21180 * 21181 * container.element(by.linkText('add')).click(); 21182 * 21183 * expect(container.element(by.repeater('contact in contacts').row(2)) 21184 * .element(by.model('contact.value')) 21185 * .getAttribute('value')) 21186 * .toBe('[email protected]'); 21187 * }); 21188 * </file> 21189 *</example> 21190 21191 */ 21192var ngControllerDirective = [function() { 21193 return { 21194 restrict: 'A', 21195 scope: true, 21196 controller: '@', 21197 priority: 500 21198 }; 21199}]; 21200 21201/** 21202 * @ngdoc directive 21203 * @name ngCsp 21204 * 21205 * @element html 21206 * @description 21207 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. 21208 * 21209 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 21210 * 21211 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). 21212 * For Angular to be CSP compatible there are only two things that we need to do differently: 21213 * 21214 * - don't use `Function` constructor to generate optimized value getters 21215 * - don't inject custom stylesheet into the document 21216 * 21217 * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` 21218 * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will
21219 * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will 21220 * be raised. 21221 * 21222 * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically 21223 * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). 21224 * To make those directives work in CSP mode, include the `angular-csp.css` manually. 21225 * 21226 * Angular tries to autodetect if CSP is active and automatically turn on the CSP-safe mode. This 21227 * autodetection however triggers a CSP error to be logged in the console: 21228 * 21229 * ``` 21230 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 21231 * script in the following Content Security Policy directive: "default-src 'self'". Note that 21232 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 21233 * ``` 21234 * 21235 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 21236 * directive on the root element of the application or on the `angular.js` script tag, whichever 21237 * appears first in the html document. 21238 * 21239 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 21240 * 21241 * @example 21242 * This example shows how to apply the `ngCsp` directive to the `html` tag. 21243 ```html 21244 <!doctype html> 21245 <html ng-app ng-csp> 21246 ... 21247 ... 21248 </html> 21249 ``` 21250 * @example 21251 // Note: the suffix `.csp` in the example name triggers 21252 // csp mode in our http server! 21253 <example name="example.csp" module="cspExample" ng-csp="true"> 21254 <file name="index.html"> 21255 <div ng-controller="MainController as ctrl"> 21256 <div> 21257 <button ng-click="ctrl.inc()" id="inc">Increment</button> 21258 <span id="counter"> 21259 {{ctrl.counter}} 21260 </span> 21261 </div> 21262 21263 <div> 21264 <button ng-click="ctrl.evil()" id="evil">Evil</button> 21265 <span id="evilError"> 21266 {{ctrl.evilError}} 21267 </span> 21268 </div> 21269 </div> 21270 </file> 21271 <file name="script.js"> 21272 angular.module('cspExample', []) 21273 .controller('MainController', function() { 21274 this.counter = 0; 21275 this.inc = function() { 21276 this.counter++; 21277 }; 21278 this.evil = function() { 21279 // jshint evil:true 21280 try { 21281 eval('1+2'); 21282 } catch (e) { 21283 this.evilError = e.message; 21284 } 21285 }; 21286 }); 21287 </file> 21288 <file name="protractor.js" type="protractor"> 21289 var util, webdriver; 21290 21291 var incBtn = element(by.id('inc')); 21292 var counter = element(by.id('counter')); 21293 var evilBtn = element(by.id('evil')); 21294 var evilError = element(by.id('evilError')); 21295 21296 function getAndClearSevereErrors() { 21297 return browser.manage().logs().get('browser').then(function(browserLog) { 21298 return browserLog.filter(function(logEntry) { 21299 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 21300 }); 21301 }); 21302 } 21303 21304 function clearErrors() { 21305 getAndClearSevereErrors(); 21306 } 21307 21308 function expectNoErrors() { 21309 getAndClearSevereErrors().then(function(filteredLog) { 21310 expect(filteredLog.length).toEqual(0); 21311 if (filteredLog.length) { 21312 console.log('browser console errors: ' + util.inspect(filteredLog)); 21313 } 21314 }); 21315 } 21316 21317 function expectError(regex) { 21318 getAndClearSevereErrors().then(function(filteredLog) { 21319 var found = false; 21320 filteredLog.forEach(function(log) { 21321 if (log.message.match(regex)) { 21322 found = true; 21323 } 21324 }); 21325 if (!found) { 21326 throw new Error('expected an error that matches ' + regex); 21327 } 21328 }); 21329 } 21330 21331 beforeEach(function() { 21332 util = require('util'); 21333 webdriver = require('protractor/node_modules/selenium-webdriver'); 21334 }); 21335 21336 // For now, we only test on Chrome, 21337 // as Safari does not load the page with Protractor's injected scripts, 21338 // and Firefox webdriver always disables content security policy (#6358) 21339 if (browser.params.browser !== 'chrome') { 21340 return; 21341 } 21342 21343 it('should not report errors when the page is loaded', function() { 21344 // clear errors so we are not dependent on previous tests 21345 clearErrors(); 21346 // Need to reload the page as the page is already loaded when 21347 // we come here 21348 browser.driver.getCurrentUrl().then(function(url) { 21349 browser.get(url); 21350 }); 21351 expectNoErrors(); 21352 }); 21353 21354 it('should evaluate expressions', function() { 21355 expect(counter.getText()).toEqual('0'); 21356 incBtn.click(); 21357 expect(counter.getText()).toEqual('1'); 21358 expectNoErrors(); 21359 }); 21360 21361 it('should throw and report an error when using "eval"', function() { 21362 evilBtn.click(); 21363 expect(evilError.getText()).toMatch(/Content Security Policy/); 21364 expectError(/Content Security Policy/); 21365 }); 21366 </file> 21367 </example> 21368 */ 21369 21370// ngCsp is not implemented as a proper directive any more, because we need it be processed while we
21371// bootstrap the system (before $parse is instantiated), for this reason we just have 21372// the csp.isActive() fn that looks for ng-csp attribute anywhere in the current doc 21373 21374/** 21375 * @ngdoc directive 21376 * @name ngClick 21377 * 21378 * @description 21379 * The ngClick directive allows you to specify custom behavior when 21380 * an element is clicked. 21381 * 21382 * @element ANY 21383 * @priority 0 21384 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 21385 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 21386 * 21387 * @example 21388 <example> 21389 <file name="index.html"> 21390 <button ng-click="count = count + 1" ng-init="count=0"> 21391 Increment 21392 </button> 21393 <span> 21394 count: {{count}} 21395 </span> 21396 </file> 21397 <file name="protractor.js" type="protractor"> 21398 it('should check ng-click', function() { 21399 expect(element(by.binding('count')).getText()).toMatch('0'); 21400 element(by.css('button')).click(); 21401 expect(element(by.binding('count')).getText()).toMatch('1'); 21402 }); 21403 </file> 21404 </example> 21405 */ 21406/* 21407 * A collection of directives that allows creation of custom event handlers that are defined as 21408 * angular expressions and are compiled and executed within the current scope. 21409 */ 21410var ngEventDirectives = {}; 21411 21412// For events that might fire synchronously during DOM manipulation 21413// we need to execute their event handlers asynchronously using $evalAsync, 21414// so that they are not executed in an inconsistent state. 21415var forceAsyncEvents = { 21416 'blur': true, 21417 'focus': true 21418}; 21419forEach( 21420 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 21421 function(eventName) { 21422 var directiveName = directiveNormalize('ng-' + eventName); 21423 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 21424 return { 21425 restrict: 'A', 21426 compile: function($element, attr) { 21427 // We expose the powerful $event object on the scope that provides access to the Window, 21428 // etc. that isn't protected by the fast paths in $parse. We explicitly request better 21429 // checks at the cost of speed since event handler expressions are not executed as 21430 // frequently as regular change detection. 21431 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); 21432 return function ngEventHandler(scope, element) { 21433 element.on(eventName, function(event) { 21434 var callback = function() { 21435 fn(scope, {$event:event}); 21436 }; 21437 if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 21438 scope.$evalAsync(callback); 21439 } else { 21440 scope.$apply(callback); 21441 } 21442 }); 21443 }; 21444 } 21445 }; 21446 }]; 21447 } 21448); 21449 21450/** 21451 * @ngdoc directive 21452 * @name ngDblclick 21453 * 21454 * @description 21455 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 21456 * 21457 * @element ANY 21458 * @priority 0 21459 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 21460 * a dblclick. (The Event object is available as `$event`) 21461 * 21462 * @example 21463 <example> 21464 <file name="index.html"> 21465 <button ng-dblclick="count = count + 1" ng-init="count=0"> 21466 Increment (on double click) 21467 </button> 21468 count: {{count}} 21469 </file> 21470 </example> 21471 */ 21472 21473 21474/** 21475 * @ngdoc directive 21476 * @name ngMousedown 21477 * 21478 * @description 21479 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 21480 * 21481 * @element ANY 21482 * @priority 0 21483 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 21484 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 21485 * 21486 * @example 21487 <example> 21488 <file name="index.html"> 21489 <button ng-mousedown="count = count + 1" ng-init="count=0"> 21490 Increment (on mouse down) 21491 </button> 21492 count: {{count}} 21493 </file> 21494 </example> 21495 */ 21496 21497 21498/** 21499 * @ngdoc directive 21500 * @name ngMouseup 21501 * 21502 * @description 21503 * Specify custom behavior on mouseup event. 21504 * 21505 * @element ANY 21506 * @priority 0 21507 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 21508 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 21509 * 21510 * @example 21511 <example> 21512 <file name="index.html"> 21513 <button ng-mouseup="count = count + 1" ng-init="count=0"> 21514 Increment (on mouse up) 21515 </button> 21516 count: {{count}} 21517 </file> 21518 </example> 21519 */ 21520 21521/** 21522 * @ngdoc directive 21523 * @name ngMouseover 21524 * 21525 * @description 21526 * Specify custom behavior on mouseover event. 21527 * 21528 * @element ANY 21529 * @priority 0 21530 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 21531 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 21532 * 21533 * @example 21534 <example> 21535 <file name="index.html"> 21536 <button ng-mouseover="count = count + 1" ng-init="count=0"> 21537 Increment (when mouse is over) 21538 </button> 21539 count: {{count}} 21540 </file> 21541 </example> 21542 */ 21543 21544 21545/** 21546 * @ngdoc directive 21547 * @name ngMouseenter 21548 * 21549 * @description 21550 * Specify custom behavior on mouseenter event. 21551 * 21552 * @element ANY 21553 * @priority 0 21554 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 21555 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 21556 * 21557 * @example 21558 <example> 21559 <file name="index.html"> 21560 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 21561 Increment (when mouse enters) 21562 </button> 21563 count: {{count}} 21564 </file> 21565 </example> 21566 */ 21567 21568 21569/** 21570 * @ngdoc directive 21571 * @name ngMouseleave 21572 * 21573 * @description 21574 * Specify custom behavior on mouseleave event. 21575 * 21576 * @element ANY 21577 * @priority 0 21578 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 21579 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 21580 * 21581 * @example 21582 <example> 21583 <file name="index.html"> 21584 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 21585 Increment (when mouse leaves) 21586 </button> 21587 count: {{count}} 21588 </file> 21589 </example> 21590 */ 21591 21592 21593/** 21594 * @ngdoc directive 21595 * @name ngMousemove 21596 * 21597 * @description 21598 * Specify custom behavior on mousemove event. 21599 * 21600 * @element ANY 21601 * @priority 0 21602 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 21603 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 21604 * 21605 * @example 21606 <example> 21607 <file name="index.html"> 21608 <button ng-mousemove="count = count + 1" ng-init="count=0"> 21609 Increment (when mouse moves) 21610 </button> 21611 count: {{count}} 21612 </file> 21613 </example> 21614 */ 21615 21616 21617/** 21618 * @ngdoc directive 21619 * @name ngKeydown 21620 * 21621 * @description 21622 * Specify custom behavior on keydown event. 21623 * 21624 * @element ANY 21625 * @priority 0 21626 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 21627 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 21628 * 21629 * @example 21630 <example> 21631 <file name="index.html"> 21632 <input ng-keydown="count = count + 1" ng-init="count=0"> 21633 key down count: {{count}} 21634 </file> 21635 </example> 21636 */ 21637 21638 21639/** 21640 * @ngdoc directive 21641 * @name ngKeyup 21642 * 21643 * @description 21644 * Specify custom behavior on keyup event. 21645 * 21646 * @element ANY 21647 * @priority 0 21648 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 21649 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 21650 * 21651 * @example 21652 <example> 21653 <file name="index.html"> 21654 <p>Typing in the input box below updates the key count</p> 21655 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 21656 21657 <p>Typing in the input box below updates the keycode</p> 21658 <input ng-keyup="event=$event"> 21659 <p>event keyCode: {{ event.keyCode }}</p> 21660 <p>event altKey: {{ event.altKey }}</p> 21661 </file> 21662 </example> 21663 */ 21664 21665 21666/** 21667 * @ngdoc directive 21668 * @name ngKeypress 21669 * 21670 * @description 21671 * Specify custom behavior on keypress event. 21672 * 21673 * @element ANY 21674 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 21675 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 21676 * and can be interrogated for keyCode, altKey, etc.) 21677 * 21678 * @example 21679 <example> 21680 <file name="index.html"> 21681 <input ng-keypress="count = count + 1" ng-init="count=0"> 21682 key press count: {{count}} 21683 </file> 21684 </example> 21685 */ 21686 21687 21688/** 21689 * @ngdoc directive 21690 * @name ngSubmit 21691 * 21692 * @description 21693 * Enables binding angular expressions to onsubmit events. 21694 * 21695 * Additionally it prevents the default action (which for form means
21695sending the request to the 21696 * server and reloading the current page), but only if the form does not contain `action`, 21697 * `data-action`, or `x-action` attributes. 21698 * 21699 * <div class="alert alert-warning"> 21700 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 21701 * `ngSubmit` handlers together. See the 21702 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 21703 * for a detailed discussion of when `ngSubmit` may be triggered. 21704 * </div> 21705 * 21706 * @element form 21707 * @priority 0 21708 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 21709 * ({@link guide/expression#-event- Event object is available as `$event`}) 21710 * 21711 * @example 21712 <example module="submitExample"> 21713 <file name="index.html"> 21714 <script> 21715 angular.module('submitExample', []) 21716 .controller('ExampleController', ['$scope', function($scope) { 21717 $scope.list = []; 21718 $scope.text = 'hello'; 21719 $scope.submit = function() { 21720 if ($scope.text) { 21721 $scope.list.push(this.text); 21722 $scope.text = ''; 21723 } 21724 }; 21725 }]); 21726 </script>
21727 <form ng-submit="submit()" ng-controller="ExampleController"> 21728 Enter text and hit enter: 21729 <input type="text" ng-model="text" name="text" /> 21730 <input type="submit" id="submit" value="Submit" /> 21731 <pre>list={{list}}</pre> 21732 </form> 21733 </file> 21734 <file name="protractor.js" type="protractor"> 21735 it('should check ng-submit', function() { 21736 expect(element(by.binding('list')).getText()).toBe('list=[]'); 21737 element(by.css('#submit')).click(); 21738 expect(element(by.binding('list')).getText()).toContain('hello'); 21739 expect(element(by.model('text')).getAttribute('value')).toBe(''); 21740 }); 21741 it('should ignore empty strings', function() { 21742 expect(element(by.binding('list')).getText()).toBe('list=[]'); 21743 element(by.css('#submit')).click(); 21744 element(by.css('#submit')).click(); 21745 expect(element(by.binding('list')).getText()).toContain('hello'); 21746 }); 21747 </file> 21748 </example> 21749 */ 21750 21751/** 21752 * @ngdoc directive 21753 * @name ngFocus 21754 * 21755 * @description 21756 * Specify custom behavior on focus event. 21757 * 21758 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 21759 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 21760 * during an `$apply` to ensure a consistent state. 21761 * 21762 * @element window, input, select, textarea, a 21763 * @priority 0 21764 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 21765 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 21766 * 21767 * @example 21768 * See {@link ng.directive:ngClick ngClick} 21769 */ 21770 21771/** 21772 * @ngdoc directive 21773 * @name ngBlur 21774 * 21775 * @description 21776 * Specify custom behavior on blur event. 21777 * 21778 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 21779 * an element has lost focus. 21780 * 21781 * Note: As the `blur` event is executed synchronously also during DOM manipulations 21782 * (e.g. removing a focussed input), 21783 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 21784 * during an `$apply` to ensure a consistent state. 21785 * 21786 * @element window, input, select, textarea, a 21787 * @priority 0 21788 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 21789 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 21790 * 21791 * @example 21792 * See {@link ng.directive:ngClick ngClick} 21793 */ 21794 21795/** 21796 * @ngdoc directive 21797 * @name ngCopy 21798 * 21799 * @description 21800 * Specify custom behavior on copy event. 21801 * 21802 * @element window, input, select, textarea, a 21803 * @priority 0 21804 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 21805 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 21806 * 21807 * @example 21808 <example> 21809 <file name="index.html"> 21810 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 21811 copied: {{copied}} 21812 </file> 21813 </example> 21814 */ 21815 21816/** 21817 * @ngdoc directive 21818 * @name ngCut 21819 * 21820 * @description 21821 * Specify custom behavior on cut event. 21822 * 21823 * @element window, input, select, textarea, a 21824 * @priority 0 21825 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 21826 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 21827 * 21828 * @example 21829 <example> 21830 <file name="index.html"> 21831 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 21832 cut: {{cut}} 21833 </file> 21834 </example> 21835 */ 21836 21837/** 21838 * @ngdoc directive 21839 * @name ngPaste 21840 * 21841 * @description 21842 * Specify custom behavior on paste event. 21843 * 21844 * @element window, input, select, textarea, a 21845 * @priority 0 21846 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 21847 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 21848 * 21849 * @example 21850 <example> 21851 <file name="index.html"> 21852 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 21853 pasted: {{paste}} 21854 </file> 21855 </example> 21856 */ 21857 21858/** 21859 * @ngdoc directive 21860 * @name ngIf 21861 * @restrict A 21862 * 21863 * @description 21864 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 21865 * {expression}. If the expression assigned to `ngIf` evaluates to a false 21866 * value then the element is removed from the DOM, otherwise a clone of the 21867 * element is reinserted into the DOM. 21868 * 21869 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 21870 * element in the DOM rather than changing its visibility via the `display` css property. A common 21871 * case when this difference is significant is when using css selectors that rely on an element's 21872 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
21872s. 21873 * 21874 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 21875 * is created when the element is restored. The scope created within `ngIf` inherits from 21876 * its parent scope using 21877 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 21878 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 21879 * a javascript primitive defined in the parent scope. In this case any modifications made to the 21880 * variable within the child scope will override (hide) the value in the parent scope. 21881 * 21882 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 21883 * is if an element's class attribute is directly modified after it's compiled, using something like 21884 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 21885 * the added class will be lost because the original compiled state is used to regenerate the element. 21886 * 21887 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 21888 * and `leave` effects. 21889 * 21890 * @animations 21891 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container 21892 * leave - happens just before the `ngIf` contents are removed from the DOM 21893 * 21894 * @element ANY 21895 * @scope 21896 * @priority 600 21897 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 21898 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 21899 * element is added to the DOM tree. 21900 * 21901 * @example 21902 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 21903 <file name="index.html"> 21904 Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /><br/> 21905 Show when checked: 21906 <span ng-if="checked" class="animate-if"> 21907 This is removed when the checkbox is unchecked. 21908 </span> 21909 </file> 21910 <file name="animations.css"> 21911 .animate-if { 21912 background:white; 21913 border:1px solid black; 21914 padding:10px; 21915 } 21916 21917 .animate-if.ng-enter, .animate-if.ng-leave { 21918 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 21919 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 21920 } 21921 21922 .animate-if.ng-enter, 21923 .animate-if.ng-leave.ng-leave-active { 21924 opacity:0; 21925 } 21926 21927 .animate-if.ng-leave, 21928 .animate-if.ng-enter.ng-enter-active { 21929 opacity:1; 21930 } 21931 </file> 21932 </example> 21933 */ 21934var ngIfDirective = ['$animate', function($animate) { 21935 return { 21936 multiElement: true, 21937 transclude: 'element', 21938 priority: 600, 21939 terminal: true, 21940 restrict: 'A', 21941 $$tlb: true, 21942 link: function($scope, $element, $attr, ctrl, $transclude) { 21943 var block, childScope, previousElements; 21944 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 21945 21946 if (value) { 21947 if (!childScope) { 21948 $transclude(function(clone, newScope) { 21949 childScope = newScope; 21950 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 21951 // Note: We only need the first/last node of the cloned nodes. 21952 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 21953 // by a directive with templateUrl when its template arrives. 21954 block = { 21955 clone: clone 21956 }; 21957 $animate.enter(clone, $element.parent(), $element); 21958 }); 21959 } 21960 } else { 21961 if (previousElements) { 21962 previousElements.remove(); 21963 previousElements = null; 21964 } 21965 if (childScope) { 21966 childScope.$destroy(); 21967 childScope = null; 21968 } 21969 if (block) { 21970 previousElements = getBlockNodes(block.clone); 21971 $animate.leave(previousElements).then(function() { 21972 previousElements = null; 21973 }); 21974 block = null; 21975 } 21976 } 21977 }); 21978 } 21979 }; 21980}]; 21981 21982/** 21983 * @ngdoc directive 21984 * @name ngInclude 21985 * @restrict ECA 21986 * 21987 * @description 21988 * Fetches, compiles and includes an external HTML fragment. 21989 * 21990 * By default, the template URL is restricted to the same domain and protocol as the 21991 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 21992 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 21993 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 21994 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 21995 * ng.$sce Strict Contextual Escaping}. 21996 * 21997 * In addition, the browser's 21998 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 21999 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 22000 * policy may further restrict whether the template is successfully loaded. 22001 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 22002 * access on some browsers. 22003 * 22004 * @animations 22005 * enter - animation is used to bring new content into the browser. 22006 * leave - animation is used to animate existing content away. 22007 * 22008 * The enter and leave animation occur concurrently. 22009 * 22010 * @scope 22011 * @priority 400 22012 * 22013 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 22014 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 22015 * @param {string=} onload Expression to evaluate when a new partial is loaded. 22016 * 22017 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 22018 * $anchorScroll} to scroll the viewport after the content is loaded. 22019 * 22020 * - If the attribute is not set, disable scrolling. 22021 * - If the attribute is set without value, enable scrolling. 22022 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 22023 * 22024 * @example 22025 <example module="includeExample" deps="angular-animate.js" animations="true"> 22026 <file name="index.html">
22027 <div ng-controller="ExampleController"> 22028 <select ng-model="template" ng-options="t.name for t in templates"> 22029 <option value="">(blank)</option> 22030 </select> 22031 url of the template: <code>{{template.url}}</code> 22032 <hr/> 22033 <div class="slide-animate-container"> 22034 <div class="slide-animate" ng-include="template.url"></div> 22035 </div> 22036 </div> 22037 </file> 22038 <file name="script.js"> 22039 angular.module('includeExample', ['ngAnimate']) 22040 .controller('ExampleController', ['$scope', function($scope) { 22041 $scope.templates = 22042 [ { name: 'template1.html', url: 'template1.html'}, 22043 { name: 'template2.html', url: 'template2.html'} ]; 22044 $scope.template = $scope.templates[0]; 22045 }]); 22046 </file> 22047 <file name="template1.html"> 22048 Content of template1.html 22049 </file> 22050 <file name="template2.html"> 22051 Content of template2.html 22052 </file> 22053 <file name="animations.css"> 22054 .slide-animate-container { 22055 position:relative; 22056 background:white; 22057 border:1px solid black; 22058 height:40px; 22059 overflow:hidden; 22060 } 22061 22062 .slide-animate { 22063 padding:10px; 22064 } 22065 22066 .slide-animate.ng-enter, .slide-animate.ng-leave { 22067 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 22068 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 22069 22070 position:absolute; 22071 top:0; 22072 left:0; 22073 right:0; 22074 bottom:0; 22075 display:block; 22076 padding:10px; 22077 } 22078 22079 .slide-animate.ng-enter { 22080 top:-50px; 22081 } 22082 .slide-animate.ng-enter.ng-enter-active { 22083 top:0; 22084 } 22085 22086 .slide-animate.ng-leave { 22087 top:0; 22088 } 22089 .slide-animate.ng-leave.ng-leave-active { 22090 top:50px; 22091 } 22092 </file> 22093 <file name="protractor.js" type="protractor"> 22094 var templateSelect = element(by.model('template')); 22095 var includeElem = element(by.css('[ng-include]')); 22096 22097 it('should load template1.html', function() { 22098 expect(includeElem.getText()).toMatch(/Content of template1.html/); 22099 }); 22100 22101 it('should load template2.html', function() { 22102 if (browser.params.browser == 'firefox') { 22103 // Firefox can't handle using selects 22104 // See https://github.com/angular/protractor/issues/480 22105 return; 22106 } 22107 templateSelect.click(); 22108 templateSelect.all(by.css('option')).get(2).click(); 22109 expect(includeElem.getText()).toMatch(/Content of template2.html/); 22110 }); 22111 22112 it('should change to blank', function() { 22113 if (browser.params.browser == 'firefox') { 22114 // Firefox can't handle using selects 22115 return; 22116 } 22117 templateSelect.click(); 22118 templateSelect.all(by.css('option')).get(0).click(); 22119 expect(includeElem.isPresent()).toBe(false); 22120 }); 22121 </file> 22122 </example> 22123 */ 22124 22125 22126/** 22127 * @ngdoc event 22128 * @name ngInclude#$includeContentRequested 22129 * @eventType emit on the scope ngInclude was declared in 22130 * @description 22131 * Emitted every time the ngInclude content is requested. 22132 * 22133 * @param {Object} angularEvent Synthetic event object. 22134 * @param {String} src URL of content to load. 22135 */ 22136 22137 22138/** 22139 * @ngdoc event 22140 * @name ngInclude#$includeContentLoaded 22141 * @eventType emit on the current ngInclude scope 22142 * @description 22143 * Emitted every time the ngInclude content is reloaded. 22144 * 22145 * @param {Object} angularEvent Synthetic event object. 22146 * @param {String} src URL of content to load. 22147 */ 22148 22149 22150/** 22151 * @ngdoc event 22152 * @name ngInclude#$includeContentError 22153 * @eventType emit on the scope ngInclude was declared in 22154 * @description 22155 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 22156 * 22157 * @param {Object} angularEvent Synthetic event object. 22158 * @param {String} src URL of content to load. 22159 */ 22160var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', '$sce', 22161 function($templateRequest, $anchorScroll, $animate, $sce) { 22162 return { 22163 restrict: 'ECA', 22164 priority: 400, 22165 terminal: true, 22166 transclude: 'element', 22167 controller: angular.noop, 22168 compile: function(element, attr) { 22169 var srcExp = attr.ngInclude || attr.src, 22170 onloadExp = attr.onload || '', 22171 autoScrollExp = attr.autoscroll; 22172 22173 return function(scope, $element, $attr, ctrl, $transclude) { 22174 var changeCounter = 0, 22175 currentScope, 22176 previousElement, 22177 currentElement; 22178 22179 var cleanupLastIncludeContent = function() { 22180 if (previousElement) { 22181 previousElement.remove(); 22182 previousElement = null; 22183 } 22184 if (currentScope) { 22185 currentScope.$destroy(); 22186 currentScope = null; 22187 } 22188 if (currentElement) { 22189 $animate.leave(currentElement).then(function() { 22190 previousElement = null; 22191 }); 22192 previousElement = currentElement; 22193 currentElement = null; 22194 } 22195 }; 22196 22197 scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { 22198 var afterAnimation = function() { 22199 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 22200 $anchorScroll(); 22201 } 22202 }; 22203 var thisChangeId = ++changeCounter; 22204 22205 if (src) { 22206 //set the 2nd param to true to ignore the template request error so that the inner 22207 //contents and scope can be cleaned up. 22208 $templateRequest(src, true).then(function(response) { 22209 if (thisChangeId !== changeCounter) return; 22210 var newScope = scope.$new(); 22211 ctrl.template = response; 22212 22213 // Note: This will also link all children of ng-include that were contained in the original 22214 // html. If that content contains controllers, ... they could pollute/change the scope. 22215 // However, using ng-include on an element with additional content does not make sense... 22216 // Note: We can't remove them in the cloneAttchFn of $transclude as that 22217 // function is called before linking the content, which would apply child 22218 // directives to non existing elements. 22219 var clone = $transclude(newScope, function(clone) { 22220 cleanupLastIncludeContent(); 22221 $animate.enter(clone, null, $element).then(afterAnimation); 22222 }); 22223 22224 currentScope = newScope; 22225 currentElement = clone; 22226 22227 currentScope.$emit('$includeContentLoaded', src); 22228 scope.$eval(onloadExp); 22229 }, function() { 22230 if (thisChangeId === changeCounter) { 22231 cleanupLastIncludeContent();
vendor: 2,650 bytes, lines 22232-22312
22232 scope.$emit('$includeContentError', src); 22233 } 22234 }); 22235 scope.$emit('$includeContentRequested', src); 22236 } else { 22237 cleanupLastIncludeContent(); 22238 ctrl.template = null; 22239 } 22240 }); 22241 }; 22242 } 22243 }; 22244}]; 22245 22246// This directive is called during the $transclude call of the first `ngInclude` directive. 22247// It will replace and compile the content of the element with the loaded template. 22248// We need this directive so that the element content is already filled when 22249// the link function of another directive on the same element as ngInclude 22250// is called. 22251var ngIncludeFillContentDirective = ['$compile', 22252 function($compile) { 22253 return { 22254 restrict: 'ECA', 22255 priority: -400, 22256 require: 'ngInclude', 22257 link: function(scope, $element, $attr, ctrl) { 22258 if (/SVG/.test($element[0].toString())) { 22259 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 22260 // support innerHTML, so detect this here and try to generate the contents 22261 // specially. 22262 $element.empty(); 22263 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope, 22264 function namespaceAdaptedClone(clone) { 22265 $element.append(clone); 22266 }, {futureParentElement: $element}); 22267 return; 22268 } 22269 22270 $element.html(ctrl.template); 22271 $compile($element.contents())(scope); 22272 } 22273 }; 22274 }]; 22275 22276/** 22277 * @ngdoc directive 22278 * @name ngInit 22279 * @restrict AC 22280 * 22281 * @description 22282 * The `ngInit` directive allows you to evaluate an expression in the 22283 * current scope. 22284 * 22285 * <div class="alert alert-error"> 22286 * The only appropriate use of `ngInit` is for aliasing special properties of 22287 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you 22288 * should use {@link guide/controller controllers} rather than `ngInit` 22289 * to initialize values on a scope. 22290 * </div> 22291 * <div class="alert alert-warning"> 22292 * **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make 22293 * sure you have parenthesis for correct precedence: 22294 * <pre class="prettyprint"> 22295 * `<div ng-init="test1 = (data | orderBy:'name')"></div>` 22296 * </pre> 22297 * </div> 22298 * 22299 * @priority 450 22300 * 22301 * @element ANY 22302 * @param {expression} ngInit {@link guide/expression Expression} to eval. 22303 * 22304 * @example 22305 <example module="initExample"> 22306 <file name="index.html"> 22307 <script> 22308 angular.module('initExample', []) 22309 .controller('ExampleController', ['$scope', function($scope) { 22310 $scope.list = [['a', 'b'], ['c', 'd']]; 22311 }]); 22312 </script>
22313 <div ng-controller="ExampleController"> 22314 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 22315 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 22316 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 22317 </div> 22318 </div> 22319 </div> 22320 </file> 22321 <file name="protractor.js" type="protractor"> 22322 it('should alias index positions', function() { 22323 var elements = element.all(by.css('.example-init')); 22324 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 22325 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 22326 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 22327 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 22328 }); 22329 </file> 22330 </example> 22331 */ 22332var ngInitDirective = ngDirective({ 22333 priority: 450, 22334 compile: function() { 22335 return { 22336 pre: function(scope, element, attrs) { 22337 scope.$eval(attrs.ngInit); 22338 } 22339 }; 22340 } 22341}); 22342 22343/** 22344 * @ngdoc directive 22345 * @name ngList 22346 * 22347 * @description 22348 * Text input that converts between a delimited string and an array of strings. The default 22349 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 22350 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 22351 * 22352 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 22353 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 22354 * list item is respected. This implies that the user of the directive is responsible for 22355 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 22356 * tab or newline character. 22357 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 22358 * when joining the list items back together) and whitespace around each list item is stripped 22359 * before it is added to the model. 22360 * 22361 * ### Example with Validation 22362 * 22363 * <example name="ngList-directive" module="listExample"> 22364 * <file name="app.js"> 22365 * angular.module('listExample', []) 22366 * .controller('ExampleController', ['$scope', function($scope) { 22367 * $scope.names = ['morpheus', 'neo', 'trinity']; 22368 * }]); 22369 * </file> 22370 * <file name="index.html">
22371 * <form name="myForm" ng-controller="ExampleController"> 22372 * List: <input name="namesInput" ng-model="names" ng-list required> 22373 * <span class="error" ng-show="myForm.namesInput.$error.required"> 22374 * Required!</span> 22375 * <br> 22376 * <tt>names = {{names}}</tt><br/> 22377 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 22378 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 22379 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 22380 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 22381 * </form> 22382 * </file> 22383 * <file name="protractor.js" type="protractor"> 22384 * var listInput = element(by.model('names')); 22385 * var names = element(by.exactBinding('names')); 22386 * var valid = element(by.binding('myForm.namesInput.$valid')); 22387 * var error = element(by.css('span.error')); 22388 * 22389 * it('should initialize to model', function() { 22390 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 22391 * expect(valid.getText()).toContain('true'); 22392 * expect(error.getCssValue('display')).toBe('none'); 22393 * }); 22394 * 22395 * it('should be invalid if empty', function() { 22396 * listInput.clear(); 22397 * listInput.sendKeys(''); 22398 * 22399 * expect(names.getText()).toContain(''); 22400 * expect(valid.getText()).toContain('false'); 22401 * expect(error.getCssValue('display')).not.toBe('none'); 22402 * }); 22403 * </file> 22404 * </example> 22405 * 22406 * ### Example - splitting on whitespace 22407 * <example name="ngList-directive-newlines"> 22408 * <file name="index.html"> 22409 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 22410 * <pre>{{ list | json }}</pre> 22411 * </file> 22412 * <file name="protractor.js" type="protractor"> 22413 * it("should split the text by newlines", function() { 22414 * var listInput = element(by.model('list')); 22415 * var output = element(by.binding('list | json')); 22416 * listInput.sendKeys('abc\ndef\nghi'); 22417 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 22418 * }); 22419 * </file> 22420 * </example> 22421 * 22422 * @element input 22423 * @param {string=} ngList optional delimiter that should be used to split the value. 22424 */ 22425var ngListDirective = function() { 22426 return { 22427 restrict: 'A', 22428 priority: 100, 22429 require: 'ngModel', 22430 link: function(scope, element, attr, ctrl) { 22431 // We want to control whitespace trimming so we use this convoluted approach 22432 // to access the ngList attribute, which doesn't pre-trim the attribute 22433 var ngList = element.attr(attr.$attr.ngList) || ', '; 22434 var trimValues = attr.ngTrim !== 'false'; 22435 var separator = trimValues ? trim(ngList) : ngList; 22436 22437 var parse = function(viewValue) { 22438 // If the viewValue is invalid (say required but empty) it will be `undefined` 22439 if (isUndefined(viewValue)) return; 22440 22441 var list = []; 22442 22443 if (viewValue) { 22444 forEach(viewValue.split(separator), function(value) { 22445 if (value) list.push(trimValues ? trim(value) : value); 22446 }); 22447 } 22448 22449 return list; 22450 }; 22451 22452 ctrl.$parsers.push(parse); 22453 ctrl.$formatters.push(function(value) { 22454 if (isArray(value)) { 22455 return value.join(ngList); 22456 } 22457 22458 return undefined; 22459 }); 22460 22461 // Override the standard $isEmpty because an empty array means the input is empty. 22462 ctrl.$isEmpty = function(value) { 22463 return !value || !value.length; 22464 }; 22465 } 22466 }; 22467}; 22468 22469/* global VALID_CLASS: true, 22470 INVALID_CLASS: true, 22471 PRISTINE_CLASS: true, 22472 DIRTY_CLASS: true, 22473 UNTOUCHED_CLASS: true, 22474 TOUCHED_CLASS: true, 22475*/ 22476 22477var VALID_CLASS = 'ng-valid', 22478 INVALID_CLASS = 'ng-invalid', 22479 PRISTINE_CLASS = 'ng-pristine', 22480 DIRTY_CLASS = 'ng-dirty', 22481 UNTOUCHED_CLASS = 'ng-untouched', 22482 TOUCHED_CLASS = 'ng-touched', 22483 PENDING_CLASS = 'ng-pending'; 22484 22485 22486var $ngModelMinErr = new minErr('ngModel'); 22487 22488/** 22489 * @ngdoc type 22490 * @name ngModel.NgModelController 22491 * 22492 * @property {string} $viewValue Actual string value in the view. 22493 * @property {*} $modelValue The value in the model that the control is bound to. 22494 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 22495 the control reads value from the DOM. The functions are called in array order, each passing 22496 its return value through to the next. The last return value is forwarded to the 22497 {@link ngModel.NgModelController#$validators `$validators`} collection. 22498 22499Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 22500`$viewValue`}. 22501 22502Returning `undefined` from a parser means a parse error occurred. In that case, 22503no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 22504will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 22505is set to `true`. The parse error is stored in `ngModel.$error.parse`. 22506 22507 * 22508 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 22509 the model value changes. The functions are called in reverse array order, each passing the value through to the 22510 next. The last return value is used as the actual DOM value. 22511 Used to format / convert values for display in the control. 22512 * ```js 22513 * function formatter(value) { 22514 * if (value) { 22515 * return value.toUpperCase(); 22516 * } 22517 * } 22518 * ngModel.$formatters.push(formatter); 22519 * ``` 22520 * 22521 * @property {Object.<string, function>} $validators A collection of validators that are applied 22522 * whenever the model value changes. The key value within the object refers to the name of the 22523 * validator while the function refers to the validation operation. The validation operation is 22524 * provided with the model value as an argument and must return a true or false value depending 22525 * on the response of that validation. 22526 * 22527 * ```js 22528 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 22529 * var value = modelValue || viewValue; 22530 * return /[0-9]+/.test(value) && 22531 * /[a-z]+/.test(value) && 22532 * /[A-Z]+/.test(value) && 22533 * /\W+/.test(value); 22534 * }; 22535 * ``` 22536 * 22537 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 22538 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 22539 * is expected to return a promise when it is run during the model validation process. Once the promise 22540 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 22541 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 22542 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 22543 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 22544 * will only run once all synchronous validators have passed. 22545 * 22546 * Please note that if $http is used then it is important that the server returns a success HTTP response code 22547 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 22548 * 22549 * ```js 22550 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 22551 * var value = modelValue || viewValue; 22552 * 22553 * // Lookup user by username 22554 * return $http.get('/api/users/' + value). 22555 * then(function resolved() { 22556 * //username exists, this means validation fails 22557 * return $q.reject('exists'); 22558 * }, function rejected() { 22559 * //username does not exist, therefore this validation passes 22560 * return true; 22561 * }); 22562 * }; 22563 * ``` 22564 * 22565 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 22566 * view value has changed. It is called with no arguments, and its return value is ignored. 22567 * This can be used in place of additional $watches against the model value. 22568 * 22569 * @property {Object} $error An object hash with all failing validator ids as keys. 22570 * @property {Object} $pending An object hash with all pending validator ids as keys. 22571 * 22572 * @property {boolean} $untouched True if control has not lost focus yet. 22573 * @property {boolean} $touched True if control has lost focus. 22574 * @property {boolean} $pristine True if user has not interacted with the control yet. 22575 * @property {boolean} $dirty True if user has already interacted with the control. 22576 * @property {boolean} $valid True if there is no error. 22577 * @property {boolean}
22577 $invalid True if at least one error on the control. 22578 * @property {string} $name The name attribute of the control. 22579 * 22580 * @description 22581 * 22582 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 22583 * The controller contains services for data-binding, validation, CSS updates, and value formatting 22584 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 22585 * listening to DOM events. 22586 * Such DOM related logic should be provided by other directives which make use of 22587 * `NgModelController` for data-binding to control elements. 22588 * Angular provides this DOM logic for most {@link input `input`} elements. 22589 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 22590 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 22591 * 22592 * @example 22593 * ### Custom Control Example 22594 * This example shows how to use `NgModelController` with a custom control to achieve 22595 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 22596 * collaborate together to achieve the desired result. 22597 * 22598 * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element 22599 * contents be edited in place by the user. This will not work on older browsers. 22600 * 22601 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 22602 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 22603 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 22604 * that content using the `$sce` service. 22605 * 22606 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 22607 <file name="style.css"> 22608 [contenteditable] { 22609 border: 1px solid black; 22610 background-color: white; 22611 min-height: 20px; 22612 } 22613 22614 .ng-invalid { 22615 border: 1px solid red; 22616 } 22617 22618 </file> 22619 <file name="script.js"> 22620 angular.module('customControl', ['ngSanitize']). 22621 directive('contenteditable', ['$sce', function($sce) { 22622 return { 22623 restrict: 'A', // only activate on element attribute 22624 require: '?ngModel', // get a hold of NgModelController 22625 link: function(scope, element, attrs, ngModel) { 22626 if (!ngModel) return; // do nothing if no ng-model 22627 22628 // Specify how UI should be updated 22629 ngModel.$render = function() { 22630 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 22631 }; 22632 22633 // Listen for change events to enable binding 22634 element.on('blur keyup change', function() { 22635 scope.$evalAsync(read); 22636 }); 22637 read(); // initialize 22638 22639 // Write data to the model 22640 function read() { 22641 var html = element.html(); 22642 // When we clear the content editable the browser leaves a <br> behind 22643 // If strip-br attribute is provided then we strip this out 22644 if ( attrs.stripBr && html == '<br>' ) { 22645 html = ''; 22646 } 22647 ngModel.$setViewValue(html); 22648 } 22649 } 22650 }; 22651 }]); 22652 </file> 22653 <file name="index.html"> 22654 <form name="myForm"> 22655 <div contenteditable 22656 name="myWidget" ng-model="userContent" 22657 strip-br="true" 22658 required>Change me!</div> 22659 <span ng-show="myForm.myWidget.$error.required">Required!</span> 22660 <hr> 22661 <textarea ng-model="userContent"></textarea> 22662 </form> 22663 </file> 22664 <file name="protractor.js" type="protractor"> 22665 it('should data-bind and become invalid', function() { 22666 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 22667 // SafariDriver can't handle contenteditable 22668 // and Firefox driver can't clear contenteditables very well 22669 return; 22670 } 22671 var contentEditable = element(by.css('[contenteditable]')); 22672 var content = 'Change me!'; 22673 22674 expect(contentEditable.getText()).toEqual(content); 22675 22676 contentEditable.clear(); 22677 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 22678 expect(contentEditable.getText()).toEqual(''); 22679 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 22680 }); 22681 </file> 22682 * </example> 22683 * 22684 * 22685 */ 22686var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', 22687 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { 22688 this.$viewValue = Number.NaN; 22689 this.$modelValue = Number.NaN; 22690 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 22691 this.$validators = {}; 22692 this.$asyncValidators = {}; 22693 this.$parsers = []; 22694 this.$formatters = []; 22695 this.$viewChangeListeners = []; 22696 this.$untouched = true; 22697 this.$touched = false;
22698 this.$pristine = true; 22699 this.$dirty = false; 22700 this.$valid = true; 22701 this.$invalid = false; 22702 this.$error = {}; // keep invalid keys here 22703 this.$$success = {}; // keep valid keys here 22704 this.$pending = undefined; // keep pending keys here 22705 this.$name = $interpolate($attr.name || '', false)($scope); 22706 22707 22708 var parsedNgModel = $parse($attr.ngModel), 22709 parsedNgModelAssign = parsedNgModel.assign, 22710 ngModelGet = parsedNgModel, 22711 ngModelSet = parsedNgModelAssign, 22712 pendingDebounce = null, 22713 ctrl = this; 22714 22715 this.$$setOptions = function(options) { 22716 ctrl.$options = options; 22717 if (options && options.getterSetter) { 22718 var invokeModelGetter = $parse($attr.ngModel + '()'), 22719 invokeModelSetter = $parse($attr.ngModel + '($$$p)'); 22720 22721 ngModelGet = function($scope) { 22722 var modelValue = parsedNgModel($scope); 22723 if (isFunction(modelValue)) { 22724 modelValue = invokeModelGetter($scope); 22725 } 22726 return modelValue; 22727 }; 22728 ngModelSet = function($scope, newValue) { 22729 if (isFunction(parsedNgModel($scope))) { 22730 invokeModelSetter($scope, {$$$p: ctrl.$modelValue}); 22731 } else { 22732 parsedNgModelAssign($scope, ctrl.$modelValue); 22733 } 22734 }; 22735 } else if (!parsedNgModel.assign) { 22736 throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 22737 $attr.ngModel, startingTag($element)); 22738 } 22739 }; 22740 22741 /** 22742 * @ngdoc method 22743 * @name ngModel.NgModelController#$render 22744 * 22745 * @description 22746 * Called when the view needs to be updated. It is expected that the user of the ng-model 22747 * directive will implement this method. 22748 * 22749 * The `$render()` method is invoked in the following situations: 22750 * 22751 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 22752 * committed value then `$render()` is called to update the input control. 22753 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 22754 * the `$viewValue` are different to last time. 22755 * 22756 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 22757 * `$modelValue` and `$viewValue` are actually different to their previous value. If `$modelValue` 22758 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 22759 * invoked if you only change a property on the objects. 22760 */ 22761 this.$render = noop; 22762 22763 /** 22764 * @ngdoc method 22765 * @name ngModel.NgModelController#$isEmpty 22766 * 22767 * @description 22768 * This is called when we need to determine if the value of an input is empty. 22769 * 22770 * For instance, the required directive does this to work out if the input has data or not. 22771 * 22772 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 22773 * 22774 * You can override this for input directives whose concept of being empty is different to the 22775 * default. The `checkboxInputType` directive does this because in its case a value of `false` 22776 * implies empty. 22777 * 22778 * @param {*} value The value of the input to check for emptiness. 22779 * @returns {boolean} True if `value` is "empty". 22780 */ 22781 this.$isEmpty = function(value) { 22782 return isUndefined(value) || value === '' || value === null || value !== value; 22783 }; 22784 22785 var parentForm = $element.inheritedData('$formController') || nullFormCtrl, 22786 currentValidationRunId = 0; 22787 22788 /** 22789 * @ngdoc method 22790 * @name ngModel.NgModelController#$setValidity 22791 * 22792 * @description 22793 * Change the validity state, and notify the form. 22794 * 22795 * This method can be called within $parsers/$formatters or a custom validation implementation. 22796 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 22797 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 22798 * 22799 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 22800 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 22801 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
22802 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 22803 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 22804 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 22805 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 22806 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 22807 * Skipped is used by Angular when validators do not run because of parse errors and 22808 * when `$asyncValidators` do not run because any of the `$validators` failed. 22809 */ 22810 addSetValidityMethod({ 22811 ctrl: this, 22812 $element: $element, 22813 set: function(object, property) { 22814 object[property] = true; 22815 }, 22816 unset: function(object, property) { 22817 delete object[property]; 22818 }, 22819 parentForm: parentForm, 22820 $animate: $animate 22821 }); 22822 22823 /** 22824 * @ngdoc method 22825 * @name ngModel.NgModelController#$setPristine 22826 * 22827 * @description 22828 * Sets the control to its pristine state. 22829 * 22830 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 22831 * state (`ng-pristine` class). A model is considered to be pristine when the control 22832 * has not been changed from when first compiled. 22833 */ 22834 this.$setPristine = function() { 22835 ctrl.$dirty = false; 22836 ctrl.$pristine = true; 22837 $animate.removeClass($element, DIRTY_CLASS); 22838 $animate.addClass($element, PRISTINE_CLASS); 22839 }; 22840 22841 /** 22842 * @ngdoc method 22843 * @name ngModel.NgModelController#$setDirty 22844 * 22845 * @description 22846 * Sets the control to its dirty state. 22847 * 22848 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 22849 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 22850 * from when first compiled. 22851 */ 22852 this.$setDirty = function() { 22853 ctrl.$dirty = true; 22854 ctrl.$pristine = false; 22855 $animate.removeClass($element, PRISTINE_CLASS); 22856 $animate.addClass($element, DIRTY_CLASS); 22857 parentForm.$setDirty(); 22858 }; 22859 22860 /** 22861 * @ngdoc method 22862 * @name ngModel.NgModelController#$setUntouched 22863 * 22864 * @description 22865 * Sets the control to its untouched state. 22866 * 22867 * This method can be called to remove the `ng-touched` class and set the control to its 22868 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 22869 * by default, however this function can be used to restore that state if the model has 22870 * already been touched by the user. 22871 */ 22872 this.$setUntouched = function() { 22873 ctrl.$touched = false; 22874 ctrl.$untouched = true; 22875 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); 22876 }; 22877 22878 /** 22879 * @ngdoc method 22880 * @name ngModel.NgModelController#$setTouched 22881 * 22882 * @description 22883 * Sets the control to its touched state. 22884 * 22885 * This method can be called to remove the `ng-untouched` class and set the control to its 22886 * touched state (`ng-touched` class). A model is considered to be touched when the user has 22887 * first focused the control element and then shifted focus away from the control (blur event). 22888 */ 22889 this.$setTouched = function() { 22890 ctrl.$touched = true; 22891 ctrl.$untouched = false; 22892 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); 22893 }; 22894 22895 /** 22896 * @ngdoc method 22897 * @name ngModel.NgModelController#$rollbackViewValue 22898 * 22899 * @description 22900 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 22901 * which may be caused by a pending debounced event or because the input is waiting for a some 22902 * future event. 22903 * 22904 * If you have an input that uses `ng-model-options` to set up debounced events or events such 22905 * as blur you can have a situation where there is a period when the `$viewValue` 22906 * is out of synch with the ngModel's `$modelValue`. 22907 * 22908 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue` 22909 * programmatically before these debounced/future events have resolved/occurred, because Angular's 22910 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 22911 * 22912 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 22913 * input which may have such events pending. This is important in order to make sure that the 22914 * input field will be updated with the new model value and any pending operations are cancelled. 22915 * 22916 * <example name="ng-model-cancel-update" module="cancel-update-example"> 22917 * <file name="app.js">
22918 * angular.module('cancel-update-example', []) 22919 * 22920 * .controller('CancelUpdateController', ['$scope', function($scope) { 22921 * $scope.resetWithCancel = function(e) { 22922 * if (e.keyCode == 27) { 22923 * $scope.myForm.myInput1.$rollbackViewValue(); 22924 * $scope.myValue = ''; 22925 * } 22926 * }; 22927 * $scope.resetWithoutCancel = function(e) { 22928 * if (e.keyCode == 27) { 22929 * $scope.myValue = ''; 22930 * } 22931 * }; 22932 * }]); 22933 * </file> 22934 * <file name="index.html"> 22935 * <div ng-controller="CancelUpdateController"> 22936 * <p>Try typing something in each input. See that the model only updates when you 22937 * blur off the input. 22938 * </p> 22939 * <p>Now see what happens if you start typing then press the Escape key</p> 22940 * 22941 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 22942 * <p>With $rollbackViewValue()</p> 22943 * <input name="myInput1" ng-model="myValue" ng-keydown="resetWithCancel($event)"><br/> 22944 * myValue: "{{ myValue }}" 22945 * 22946 * <p>Without $rollbackViewValue()</p> 22947 * <input name="myInput2" ng-model="myValue" ng-keydown="resetWithoutCancel($event)"><br/> 22948 * myValue: "{{ myValue }}" 22949 * </form> 22950 * </div> 22951 * </file> 22952 * </example> 22953 */ 22954 this.$rollbackViewValue = function() { 22955 $timeout.cancel(pendingDebounce); 22956 ctrl.$viewValue = ctrl.$$lastCommittedViewValue; 22957 ctrl.$render(); 22958 }; 22959 22960 /** 22961 * @ngdoc method 22962 * @name ngModel.NgModelController#$validate 22963 * 22964 * @description 22965 * Runs each of the registered validators (first synchronous validators and then 22966 * asynchronous validators). 22967 * If the validity changes to invalid, the model will be set to `undefined`, 22968 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 22969 * If the validity changes to valid, it will set the model to the last available valid 22970 * modelValue, i.e. either the last parsed value or the last value set from the scope. 22971 */ 22972 this.$validate = function() { 22973 // ignore $validate before model is initialized 22974 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 22975 return; 22976 } 22977 22978 var viewValue = ctrl.$$lastCommittedViewValue; 22979 // Note: we use the $$rawModelValue as $modelValue might have been 22980 // set to undefined during a view -> model update that found validation 22981 // errors. We can't parse the view here, since that could change 22982 // the model although neither viewValue nor the model on the scope changed 22983 var modelValue = ctrl.$$rawModelValue; 22984 22985 // Check if the there's a parse error, so we don't unset it accidentially 22986 var parserName = ctrl.$$parserName || 'parse'; 22987 var parserValid = ctrl.$error[parserName] ? false : undefined; 22988 22989 var prevValid = ctrl.$valid; 22990 var prevModelValue = ctrl.$modelValue; 22991 22992 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 22993 22994 ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) { 22995 // If there was no change in validity, don't update the model 22996 // This prevents changing an invalid modelValue to undefined 22997 if (!allowInvalid && prevValid !== allValid) { 22998 // Note: Don't check ctrl.$valid here, as we could have 22999 // external validators (e.g. calculated on the server), 23000 // that just call $setValidity and need the model value 23001 // to calculate their validity. 23002 ctrl.$modelValue = allValid ? modelValue : undefined; 23003 23004 if (ctrl.$modelValue !== prevModelValue) { 23005 ctrl.$$writeModelToScope(); 23006 } 23007 } 23008 }); 23009 23010 }; 23011 23012 this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) { 23013 currentValidationRunId++; 23014 var localValidationRunId = currentValidationRunId; 23015 23016 // check parser error 23017 if (!processParseErrors(parseValid)) { 23018 validationDone(false); 23019 return; 23020 } 23021 if (!processSyncValidators()) { 23022 validationDone(false); 23023 return; 23024 } 23025 processAsyncValidators(); 23026 23027 function processParseErrors(parseValid) { 23028 var errorKey = ctrl.$$parserName || 'parse'; 23029 if (parseValid === undefined) {
23030 setValidity(errorKey, null); 23031 } else { 23032 setValidity(errorKey, parseValid); 23033 if (!parseValid) { 23034 forEach(ctrl.$validators, function(v, name) { 23035 setValidity(name, null); 23036 }); 23037 forEach(ctrl.$asyncValidators, function(v, name) { 23038 setValidity(name, null); 23039 }); 23040 return false; 23041 } 23042 } 23043 return true; 23044 } 23045 23046 function processSyncValidators() { 23047 var syncValidatorsValid = true; 23048 forEach(ctrl.$validators, function(validator, name) { 23049 var result = validator(modelValue, viewValue); 23050 syncValidatorsValid = syncValidatorsValid && result; 23051 setValidity(name, result); 23052 }); 23053 if (!syncValidatorsValid) { 23054 forEach(ctrl.$asyncValidators, function(v, name) { 23055 setValidity(name, null); 23056 }); 23057 return false; 23058 } 23059 return true; 23060 } 23061 23062 function processAsyncValidators() { 23063 var validatorPromises = []; 23064 var allValid = true; 23065 forEach(ctrl.$asyncValidators, function(validator, name) { 23066 var promise = validator(modelValue, viewValue); 23067 if (!isPromiseLike(promise)) { 23068 throw $ngModelMinErr("$asyncValidators", 23069 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise); 23070 } 23071 setValidity(name, undefined); 23072 validatorPromises.push(promise.then(function() { 23073 setValidity(name, true); 23074 }, function(error) { 23075 allValid = false; 23076 setValidity(name, false); 23077 })); 23078 }); 23079 if (!validatorPromises.length) { 23080 validationDone(true); 23081 } else { 23082 $q.all(validatorPromises).then(function() { 23083 validationDone(allValid); 23084 }, noop); 23085 } 23086 } 23087 23088 function setValidity(name, isValid) { 23089 if (localValidationRunId === currentValidationRunId) { 23090 ctrl.$setValidity(name, isValid); 23091 } 23092 } 23093 23094 function validationDone(allValid) { 23095 if (localValidationRunId === currentValidationRunId) { 23096 23097 doneCallback(allValid); 23098 } 23099 } 23100 }; 23101 23102 /** 23103 * @ngdoc method 23104 * @name ngModel.NgModelController#$commitViewValue 23105 * 23106 * @description 23107 * Commit a pending update to the `$modelValue`. 23108 * 23109 * Updates may be pending by a debounced event or because the input is waiting for a some future 23110 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 23111 * usually handles calling this in response to input events. 23112 */ 23113 this.$commitViewValue = function() { 23114 var viewValue = ctrl.$viewValue; 23115 23116 $timeout.cancel(pendingDebounce); 23117 23118 // If the view value has not changed then we should just exit, except in the case where there is 23119 // a native validator on the element. In this case the validation state may have changed even though 23120 // the viewValue has stayed empty. 23121 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { 23122 return; 23123 } 23124 ctrl.$$lastCommittedViewValue = viewValue; 23125 23126 // change to dirty 23127 if (ctrl.$pristine) { 23128 this.$setDirty(); 23129 } 23130 this.$$parseAndValidate(); 23131 }; 23132 23133 this.$$parseAndValidate = function() { 23134 var viewValue = ctrl.$$lastCommittedViewValue; 23135 var modelValue = viewValue; 23136 var parserValid = isUndefined(modelValue) ? undefined : true; 23137 23138 if (parserValid) { 23139 for (var i = 0; i < ctrl.$parsers.length; i++) { 23140 modelValue = ctrl.$parsers[i](modelValue); 23141 if (isUndefined(modelValue)) { 23142 parserValid = false; 23143 break; 23144 } 23145 } 23146 } 23147 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 23148 // ctrl.$modelValue has not been touched yet... 23149 ctrl.$modelValue = ngModelGet($scope); 23150 } 23151 var prevModelValue = ctrl.$modelValue; 23152 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 23153 ctrl.$$rawModelValue = modelValue; 23154 23155 if (allowInvalid) { 23156 ctrl.$modelValue = modelValue; 23157 writeToModelIfNeeded(); 23158 } 23159 23160 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 23161 // This can happen if e.g. $setViewValue is called from inside a parser 23162 ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { 23163 if (!allowInvalid) { 23164 // Note: Don't check ctrl.$valid here, as we could have 23165 // external validators (e.g. calculated on the server), 23166 // that just call $setValidity and need the model value 23167 // to calculate their validity. 23168 ctrl.$modelValue = allValid ? modelValue : undefined; 23169 writeToModelIfNeeded(); 23170 } 23171 }); 23172 23173 function writeToModelIfNeeded() { 23174 if (ctrl.$modelValue !== prevModelValue) { 23175 ctrl.$$writeModelToScope(); 23176 } 23177 } 23178 }; 23179 23180 this.$$writeModelToScope = function() { 23181 ngModelSet($scope, ctrl.$modelValue);
23182 forEach(ctrl.$viewChangeListeners, function(listener) { 23183 try { 23184 listener(); 23185 } catch (e) { 23186 $exceptionHandler(e); 23187 } 23188 }); 23189 }; 23190 23191 /** 23192 * @ngdoc method 23193 * @name ngModel.NgModelController#$setViewValue 23194 * 23195 * @description 23196 * Update the view value. 23197 * 23198 * This method should be called when an input directive want to change the view value; typically, 23199 * this is done from within a DOM event handler. 23200 * 23201 * For example {@link ng.directive:input input} calls it when the value of the input changes and 23202 * {@link ng.directive:select select} calls it when an option is selected. 23203 * 23204 * If the new `value` is an object (rather than a string or a number), we should make a copy of the 23205 * object before passing it to `$setViewValue`. This is because `ngModel` does not perform a deep 23206 * watch of objects, it only looks for a change of identity. If you only change the property of 23207 * the object then ngModel will not realise that the object has changed and will not invoke the 23208 * `$parsers` and `$validators` pipelines. 23209 * 23210 * For this reason, you should not change properties of the copy once it has been passed to 23211 * `$setViewValue`. Otherwise you may cause the model value on the scope to change incorrectly. 23212 * 23213 * When this method is called, the new `value` will be staged for committing through the `$parsers` 23214 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 23215 * value sent directly for processing, finally to be applied to `$modelValue` and then the 23216 * **expression** specified in the `ng-model` attribute. 23217 * 23218 * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. 23219 * 23220 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 23221 * and the `default` trigger is not listed, all those actions will remain pending until one of the 23222 * `updateOn` events is triggered on the DOM element. 23223 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 23224 * directive is used with a custom debounce for this particular event. 23225 * 23226 * Note that calling this function does not trigger a `$digest`. 23227 * 23228 * @param {string} value Value from the view. 23229 * @param {string} trigger Event that triggered the update. 23230 */ 23231 this.$setViewValue = function(value, trigger) { 23232 ctrl.$viewValue = value; 23233 if (!ctrl.$options || ctrl.$options.updateOnDefault) { 23234 ctrl.$$debounceViewValueCommit(trigger); 23235 } 23236 }; 23237 23238 this.$$debounceViewValueCommit = function(trigger) { 23239 var debounceDelay = 0, 23240 options = ctrl.$options, 23241 debounce; 23242 23243 if (options && isDefined(options.debounce)) { 23244 debounce = options.debounce; 23245 if (isNumber(debounce)) { 23246 debounceDelay = debounce; 23247 } else if (isNumber(debounce[trigger])) { 23248 debounceDelay = debounce[trigger]; 23249 } else if (isNumber(debounce['default'])) { 23250 debounceDelay = debounce['default']; 23251 } 23252 } 23253 23254 $timeout.cancel(pendingDebounce); 23255 if (debounceDelay) { 23256 pendingDebounce = $timeout(function() { 23257 ctrl.$commitViewValue(); 23258 }, debounceDelay); 23259 } else if ($rootScope.$$phase) { 23260 ctrl.$commitViewValue(); 23261 } else { 23262 $scope.$apply(function() { 23263 ctrl.$commitViewValue(); 23264 }); 23265 } 23266 }; 23267 23268 // model -> value 23269 // Note: we cannot use a normal scope.$watch as we want to detect the following: 23270 // 1. scope value is 'a' 23271 // 2. user enters 'b' 23272 // 3. ng-change kicks in and reverts scope value to 'a' 23273 // -> scope value did not change since the last digest as 23274 // ng-change executes in apply phase 23275 // 4. view should be changed back to 'a' 23276 $scope.$watch(function ngModelWatch() { 23277 var modelValue = ngModelGet($scope); 23278 23279 // if scope model value and ngModel value are out of sync 23280 // TODO(perf): why not move this to the action fn? 23281 if (modelValue !== ctrl.$modelValue) { 23282 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; 23283 23284 var formatters = ctrl.$formatters, 23285 idx = formatters.length; 23286 23287 var viewValue = modelValue; 23288 while (idx--) { 23289 viewValue = formatters[idx](viewValue); 23290 } 23291 if (ctrl.$viewValue !== viewValue) { 23292 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; 23293 ctrl.$render(); 23294 23295 ctrl.$$runValidators(undefined, modelValue, viewValue, noop); 23296 } 23297 } 23298 23299 return modelValue; 23300 }); 23301}]; 23302 23303 23304/** 23305 * @ngdoc directive 23306 * @name ngModel 23307 * 23308 * @element input 23309 * @priority 1 23310 * 23311 * @description 23312 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 23313 * property on the scope using {@link ngModel.NgModelController NgModelController}, 23314 * which is created and exposed by this directive. 23315 * 23316 * `ngModel` is responsible for: 23317 * 23318 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 23319 * require. 23320 * - Providing validation behavior (i.e. required, number, email, url). 23321 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 23322 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations. 23323 * - Registering the control with its parent {@link ng.directive:form form}. 23324 *
23325 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 23326 * current scope. If the property doesn't already exist on this scope, it will be created 23327 * implicitly and added to the scope. 23328 * 23329 * For best practices on using `ngModel`, see: 23330 * 23331 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 23332 * 23333 * For basic examples, how to use `ngModel`, see: 23334 * 23335 * - {@link ng.directive:input input} 23336 * - {@link input[text] text} 23337 * - {@link input[checkbox] checkbox} 23338 * - {@link input[radio] radio} 23339 * - {@link input[number] number} 23340 * - {@link input[email] email} 23341 * - {@link input[url] url} 23342 * - {@link input[date] date} 23343 * - {@link input[datetime-local] datetime-local} 23344 * - {@link input[time] time} 23345 * - {@link input[month] month} 23346 * - {@link input[week] week} 23347 * - {@link ng.directive:select select} 23348 * - {@link ng.directive:textarea textarea} 23349 * 23350 * # CSS classes 23351 * The following CSS classes are added and removed on the associated input/select/textarea element 23352 * depending on the validity of the model. 23353 * 23354 * - `ng-valid`: the model is valid 23355 * - `ng-invalid`: the model is invalid 23356 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 23357 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 23358 * - `ng-pristine`: the control hasn't been interacted with yet 23359 * - `ng-dirty`: the control has been interacted with 23360 * - `ng-touched`: the control has been blurred 23361 * - `ng-untouched`: the control hasn't been blurred 23362 * - `ng-pending`: any `$asyncValidators` are unfulfilled 23363 * 23364 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 23365 * 23366 * ## Animation Hooks 23367 * 23368 * Animations within models are triggered when any of the associated CSS classes are added and removed 23369 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, 23370 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 23371 * The animations that are triggered within ngModel are similar to how they work in ngClass and 23372 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 23373 * 23374 * The following example shows a simple way to utilize CSS transitions to style an input element 23375 * that has been rendered as invalid after it has been validated: 23376 * 23377 * <pre> 23378 * //be sure to include ngAnimate as a module to hook into more 23379 * //advanced animations 23380 * .my-input { 23381 * transition:0.5s linear all; 23382 * background: white; 23383 * } 23384 * .my-input.ng-invalid { 23385 * background: red; 23386 * color:white; 23387 * } 23388 * </pre> 23389 * 23390 * @example 23391 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> 23392 <file name="index.html"> 23393 <script> 23394 angular.module('inputExample', []) 23395 .controller('ExampleController', ['$scope', function($scope) { 23396 $scope.val = '1'; 23397 }]); 23398 </script> 23399 <style> 23400 .my-input { 23401 -webkit-transition:all linear 0.5s; 23402 transition:all linear 0.5s; 23403 background: transparent; 23404 } 23405 .my-input.ng-invalid { 23406 color:white; 23407 background: red; 23408 } 23409 </style> 23410 Update input to see transitions when valid/invalid. 23411 Integer is a valid value.
23412 <form name="testForm" ng-controller="ExampleController"> 23413 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" /> 23414 </form> 23415 </file> 23416 * </example> 23417 * 23418 * ## Binding to a getter/setter 23419 * 23420 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 23421 * function that returns a representation of the model when called with zero arguments, and sets 23422 * the internal state of a model when called with an argument. It's sometimes useful to use this 23423 * for models that have an internal representation that's different than what the model exposes 23424 * to the view. 23425 * 23426 * <div class="alert alert-success"> 23427 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more 23428 * frequently than other parts of your code. 23429 * </div> 23430 * 23431 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 23432 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 23433 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 23434 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 23435 * 23436 * The following example shows how to use `ngModel` with a getter/setter: 23437 * 23438 * @example 23439 * <example name="ngModel-getter-setter" module="getterSetterExample"> 23440 <file name="index.html">
23441 <div ng-controller="ExampleController"> 23442 <form name="userForm"> 23443 Name: 23444 <input type="text" name="userName" 23445 ng-model="user.name" 23446 ng-model-options="{ getterSetter: true }" /> 23447 </form> 23448 <pre>user.name = <span ng-bind="user.name()"></span></pre> 23449 </div> 23450 </file> 23451 <file name="app.js"> 23452 angular.module('getterSetterExample', []) 23453 .controller('ExampleController', ['$scope', function($scope) { 23454 var _name = 'Brian'; 23455 $scope.user = { 23456 name: function(newName) { 23457 if (angular.isDefined(newName)) { 23458 _name = newName; 23459 } 23460 return _name; 23461 } 23462 }; 23463 }]); 23464 </file> 23465 * </example> 23466 */ 23467var ngModelDirective = ['$rootScope', function($rootScope) { 23468 return { 23469 restrict: 'A', 23470 require: ['ngModel', '^?form', '^?ngModelOptions'], 23471 controller: NgModelController, 23472 // Prelink needs to run before any input directive 23473 // so that we can set the NgModelOptions in NgModelController 23474 // before anyone else uses it. 23475 priority: 1, 23476 compile: function ngModelCompile(element) { 23477 // Setup initial state of the control 23478 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 23479 23480 return { 23481 pre: function ngModelPreLink(scope, element, attr, ctrls) { 23482 var modelCtrl = ctrls[0], 23483 formCtrl = ctrls[1] || nullFormCtrl; 23484 23485 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); 23486 23487 // notify others, especially parent forms 23488 formCtrl.$addControl(modelCtrl); 23489 23490 attr.$observe('name', function(newValue) { 23491 if (modelCtrl.$name !== newValue) { 23492 formCtrl.$$renameControl(modelCtrl, newValue); 23493 } 23494 }); 23495 23496 scope.$on('$destroy', function() { 23497 formCtrl.$removeControl(modelCtrl); 23498 }); 23499 }, 23500 post: function ngModelPostLink(scope, element, attr, ctrls) { 23501 var modelCtrl = ctrls[0]; 23502 if (modelCtrl.$options && modelCtrl.$options.updateOn) { 23503 element.on(modelCtrl.$options.updateOn, function(ev) { 23504 modelCtrl.$$debounceViewValueCommit(ev && ev.type); 23505 }); 23506 } 23507 23508 element.on('blur', function(ev) { 23509 if (modelCtrl.$touched) return; 23510 23511 if ($rootScope.$$phase) { 23512 scope.$evalAsync(modelCtrl.$setTouched); 23513 } else { 23514 scope.$apply(modelCtrl.$setTouched); 23515 } 23516 }); 23517 } 23518 }; 23519 } 23520 }; 23521}]; 23522 23523var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 23524 23525/** 23526 * @ngdoc directive 23527 * @name ngModelOptions 23528 * 23529 * @description 23530 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of 23531 * events that will trigger a model update and/or a debouncing delay so that the actual update only 23532 * takes place when a timer expires; this timer will be reset after another change takes place. 23533 * 23534 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 23535 * be different than the value in the actual model. This means that if you update the model you 23536 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in 23537 * order to make sure it is synchronized with the model and that any debounced action is canceled. 23538 * 23539 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} 23540 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 23541 * important because `form` controllers are published to the related scope under the name in their 23542 * `name` attribute. 23543 * 23544 * Any pending changes will take place immediately when an enclosing form is submitted via the 23545 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 23546 * to have access to the updated model. 23547 * 23548 * `ngModelOptions` has an effect on the element it's declared on and its descendants. 23549 * 23550 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: 23551 * - `updateOn`: string specifying which event should the input be bound to. You can set several 23552 * events using an space delimited list. There is a special event called `default` that 23553 * matches the default events belonging of the control. 23554 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 23555 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 23556 * custom value for each event. For example: 23557 * `ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"` 23558 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 23559 * not validate correctly instead of the default behavior of setting the model to undefined. 23560 * - `getterSetter`: boolean value which determines whether or not to treat functions boun
23560d to 23561 `ngModel` as getters/setters. 23562 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 23563 * `<input type="date">`, `<input type="time">`, ... . Right now, the only supported value is `'UTC'`, 23564 * otherwise the default timezone of the browser will be used. 23565 * 23566 * @example 23567 23568 The following example shows how to override immediate updates. Changes on the inputs within the 23569 form will update the model only when the control loses focus (blur event). If `escape` key is 23570 pressed while the input field is focused, the value is reset to the value in the current model. 23571 23572 <example name="ngModelOptions-directive-blur" module="optionsExample"> 23573 <file name="index.html"> 23574 <div ng-controller="ExampleController"> 23575 <form name="userForm"> 23576 Name: 23577 <input type="text" name="userName" 23578 ng-model="user.name" 23579 ng-model-options="{ updateOn: 'blur' }" 23580 ng-keyup="cancel($event)" /><br /> 23581 23582 Other data: 23583 <input type="text" ng-model="user.data" /><br /> 23584 </form> 23585 <pre>user.name = <span ng-bind="user.name"></span></pre> 23586 </div> 23587 </file> 23588 <file name="app.js"> 23589 angular.module('optionsExample', []) 23590 .controller('ExampleController', ['$scope', function($scope) { 23591 $scope.user = { name: 'say', data: '' }; 23592 23593 $scope.cancel = function(e) { 23594 if (e.keyCode == 27) { 23595 $scope.userForm.userName.$rollbackViewValue(); 23596 } 23597 }; 23598 }]); 23599 </file> 23600 <file name="protractor.js" type="protractor"> 23601 var model = element(by.binding('user.name')); 23602 var input = element(by.model('user.name')); 23603 var other = element(by.model('user.data')); 23604 23605 it('should allow custom events', function() { 23606 input.sendKeys(' hello'); 23607 input.click(); 23608 expect(model.getText()).toEqual('say'); 23609 other.click(); 23610 expect(model.getText()).toEqual('say hello'); 23611 }); 23612 23613 it('should $rollbackViewValue when model changes', function() { 23614 input.sendKeys(' hello'); 23615 expect(input.getAttribute('value')).toEqual('say hello'); 23616 input.sendKeys(protractor.Key.ESCAPE); 23617 expect(input.getAttribute('value')).toEqual('say'); 23618 other.click(); 23619 expect(model.getText()).toEqual('say'); 23620 }); 23621 </file> 23622 </example> 23623 23624 This one shows how to debounce model changes. Model will be updated only 1 sec after last change. 23625 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 23626 23627 <example name="ngModelOptions-directive-debounce" module="optionsExample"> 23628 <file name="index.html">
23629 <div ng-controller="ExampleController"> 23630 <form name="userForm"> 23631 Name: 23632 <input type="text" name="userName" 23633 ng-model="user.name" 23634 ng-model-options="{ debounce: 1000 }" /> 23635 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br /> 23636 </form> 23637 <pre>user.name = <span ng-bind="user.name"></span></pre> 23638 </div> 23639 </file> 23640 <file name="app.js"> 23641 angular.module('optionsExample', []) 23642 .controller('ExampleController', ['$scope', function($scope) { 23643 $scope.user = { name: 'say' }; 23644 }]); 23645 </file> 23646 </example> 23647 23648 This one shows how to bind to getter/setters: 23649 23650 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 23651 <file name="index.html"> 23652 <div ng-controller="ExampleController"> 23653 <form name="userForm"> 23654 Name: 23655 <input type="text" name="userName" 23656 ng-model="user.name" 23657 ng-model-options="{ getterSetter: true }" /> 23658 </form> 23659 <pre>user.name = <span ng-bind="user.name()"></span></pre> 23660 </div> 23661 </file> 23662 <file name="app.js"> 23663 angular.module('getterSetterExample', []) 23664 .controller('ExampleController', ['$scope', function($scope) { 23665 var _name = 'Brian'; 23666 $scope.user = { 23667 name: function(newName) { 23668 return angular.isDefined(newName) ? (_name = newName) : _name; 23669 } 23670 }; 23671 }]); 23672 </file> 23673 </example> 23674 */ 23675var ngModelOptionsDirective = function() { 23676 return { 23677 restrict: 'A', 23678 controller: ['$scope', '$attrs', function($scope, $attrs) { 23679 var that = this; 23680 this.$options = $scope.$eval($attrs.ngModelOptions); 23681 // Allow adding/overriding bound events 23682 if (this.$options.updateOn !== undefined) { 23683 this.$options.updateOnDefault = false;
23684 // extract "default" pseudo-event from list of events that can trigger a model update 23685 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { 23686 that.$options.updateOnDefault = true; 23687 return ' '; 23688 })); 23689 } else { 23690 this.$options.updateOnDefault = true; 23691 } 23692 }] 23693 }; 23694}; 23695 23696 23697 23698// helper methods 23699function addSetValidityMethod(context) { 23700 var ctrl = context.ctrl, 23701 $element = context.$element, 23702 classCache = {}, 23703 set = context.set, 23704 unset = context.unset, 23705 parentForm = context.parentForm, 23706 $animate = context.$animate; 23707 23708 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); 23709 23710 ctrl.$setValidity = setValidity; 23711 23712 function setValidity(validationErrorKey, state, controller) { 23713 if (state === undefined) { 23714 createAndSet('$pending', validationErrorKey, controller); 23715 } else { 23716 unsetAndCleanup('$pending', validationErrorKey, controller); 23717 } 23718 if (!isBoolean(state)) { 23719 unset(ctrl.$error, validationErrorKey, controller); 23720 unset(ctrl.$$success, validationErrorKey, controller); 23721 } else { 23722 if (state) { 23723 unset(ctrl.$error, validationErrorKey, controller); 23724 set(ctrl.$$success, validationErrorKey, controller); 23725 } else { 23726 set(ctrl.$error, validationErrorKey, controller); 23727 unset(ctrl.$$success, validationErrorKey, controller); 23728 } 23729 } 23730 if (ctrl.$pending) { 23731 cachedToggleClass(PENDING_CLASS, true); 23732 ctrl.$valid = ctrl.$invalid = undefined; 23733 toggleValidationCss('', null); 23734 } else { 23735 cachedToggleClass(PENDING_CLASS, false); 23736 ctrl.$valid = isObjectEmpty(ctrl.$error); 23737 ctrl.$invalid = !ctrl.$valid; 23738 toggleValidationCss('', ctrl.$valid); 23739 } 23740 23741 // re-read the state as the set/unset methods could have 23742 // combined state in ctrl.$error[validationError] (used for forms), 23743 // where setting/unsetting only increments/decrements the value, 23744 // and does not replace it. 23745 var combinedState; 23746 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { 23747 combinedState = undefined; 23748 } else if (ctrl.$error[validationErrorKey]) { 23749 combinedState = false; 23750 } else if (ctrl.$$success[validationErrorKey]) { 23751 combinedState = true; 23752 } else { 23753 combinedState = null; 23754 } 23755
23756 toggleValidationCss(validationErrorKey, combinedState); 23757 parentForm.$setValidity(validationErrorKey, combinedState, ctrl); 23758 } 23759 23760 function createAndSet(name, value, controller) { 23761 if (!ctrl[name]) { 23762 ctrl[name] = {}; 23763 } 23764 set(ctrl[name], value, controller); 23765 } 23766 23767 function unsetAndCleanup(name, value, controller) { 23768 if (ctrl[name]) { 23769 unset(ctrl[name], value, controller); 23770 } 23771 if (isObjectEmpty(ctrl[name])) { 23772 ctrl[name] = undefined; 23773 } 23774 } 23775 23776 function cachedToggleClass(className, switchValue) { 23777 if (switchValue && !classCache[className]) { 23778 $animate.addClass($element, className); 23779 classCache[className] = true; 23780 } else if (!switchValue && classCache[className]) { 23781 $animate.removeClass($element, className); 23782 classCache[className] = false; 23783 } 23784 } 23785 23786 function toggleValidationCss(validationErrorKey, isValid) { 23787 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 23788 23789 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); 23790 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); 23791 } 23792} 23793 23794function isObjectEmpty(obj) { 23795 if (obj) { 23796 for (var prop in obj) { 23797 return false; 23798 } 23799 } 23800 return true; 23801} 23802 23803/** 23804 * @ngdoc directive 23805 * @name ngNonBindable 23806 * @restrict AC 23807 * @priority 1000 23808 * 23809 * @description 23810 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 23811 * DOM element. This is useful if the element contains what appears to be Angular directives and 23812 * bindings but which should be ignored by Angular. This could be the case if you have a site that 23813 * displays snippets of code, for instance. 23814 * 23815 * @element ANY 23816 * 23817 * @example 23818 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 23819 * but the one wrapped in `ngNonBindable` is left alone. 23820 * 23821 * @example 23822 <example> 23823 <file name="index.html"> 23824 <div>Normal: {{1 + 2}}</div> 23825 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 23826 </file> 23827 <file name="protractor.js" type="protractor"> 23828 it('should check ng-non-bindable', function() { 23829 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 23830 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 23831 }); 23832 </file> 23833 </example> 23834 */ 23835var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 23836 23837/** 23838 * @ngdoc directive 23839 * @name ngPluralize 23840 * @restrict EA 23841 * 23842 * @description 23843 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 23844 * These rules are bundled with angular.js, but can be overridden 23845 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 23846 * by specifying the mappings between 23847 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 23848 * and the strings to be displayed. 23849 * 23850 * # Plural categories and explicit number rules 23851 * There are two 23852 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 23853 * in Angular's default en-US locale: "one" and "other". 23854 * 23855 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 23856 * any number that is not 1), an explicit number rule can only match one number. For example, the 23857 * explicit number rule for "3" matches the number 3. There are examples of plural categories 23858 * and explicit number rules throughout the rest of this documentation. 23859 * 23860 * # Configuring ngPluralize 23861 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 23862 * You can also provide an optional attribute, `offset`. 23863 * 23864 * The value of the `count` attribute can be either a string or an {@link guide/expression 23865 * Angular expression}; these are evaluated on the current scope for its bound value. 23866 * 23867 * The `when` attribute specifies the mappings between plural categories and the actual 23868 * string to be displayed. The value of the attribute should be a JSON object. 23869 * 23870 * The following example shows how to configure ngPluralize: 23871 * 23872 * ```html 23873 * <ng-pluralize count="personCount"
23874 when="{'0': 'Nobody is viewing.', 23875 * 'one': '1 person is viewing.', 23876 * 'other': '{} people are viewing.'}"> 23877 * </ng-pluralize> 23878 *``` 23879 * 23880 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 23881 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 23882 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 23883 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 23884 * show "a dozen people are viewing". 23885 * 23886 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 23887 * into pluralized strings. In the previous example, Angular will replace `{}` with 23888 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 23889 * for <span ng-non-bindable>{{numberExpression}}</span>. 23890 * 23891 * # Configuring ngPluralize with offset 23892 * The `offset` attribute allows further customization of pluralized text, which can result in 23893 * a better user experience. For example, instead of the message "4 people are viewing this document", 23894 * you might display "John, Kate and 2 others are viewing this document". 23895 * The offset attribute allows you to offset a number by any desired value. 23896 * Let's take a look at an example: 23897 * 23898 * ```html 23899 * <ng-pluralize count="personCount" offset=2 23900 * when="{'0': 'Nobody is viewing.', 23901 * '1': '{{person1}} is viewing.', 23902 * '2': '{{person1}} and {{person2}} are viewing.', 23903 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 23904 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 23905 * </ng-pluralize> 23906 * ``` 23907 * 23908 * Notice that we are still using two plural categories(one, other), but we added 23909 * three explicit number rules 0, 1 and 2. 23910 * When one person, perhaps John, views the document, "John is viewing" will be shown. 23911 * When three people view the document, no explicit number rule is found, so 23912 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 23913 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 23914 * is shown. 23915 * 23916 * Note that when you specify offsets, you must provide explicit number rules for 23917 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 23918 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 23919 * plural categories "one" and "other". 23920 * 23921 * @param {string|expression} count The variable to be bound to. 23922 * @param {string} when The mapping between plural category to its corresponding strings. 23923 * @param {number=} offset Offset to deduct from the total number. 23924 * 23925 * @example 23926 <example module="pluralizeExample"> 23927 <file name="index.html"> 23928 <script> 23929 angular.module('pluralizeExample', []) 23930 .controller('ExampleController', ['$scope', function($scope) { 23931 $scope.person1 = 'Igor'; 23932 $scope.person2 = 'Misko'; 23933 $scope.personCount = 1; 23934 }]); 23935 </script>
23936 <div ng-controller="ExampleController"> 23937 Person 1:<input type="text" ng-model="person1" value="Igor" /><br/> 23938 Person 2:<input type="text" ng-model="person2" value="Misko" /><br/> 23939 Number of People:<input type="text" ng-model="personCount" value="1" /><br/> 23940 23941 <!--- Example with simple pluralization rules for en locale ---> 23942 Without Offset: 23943 <ng-pluralize count="personCount" 23944 when="{'0': 'Nobody is viewing.', 23945 'one': '1 person is viewing.', 23946 'other': '{} people are viewing.'}"> 23947 </ng-pluralize><br> 23948 23949 <!--- Example with offset ---> 23950 With Offset(2): 23951 <ng-pluralize count="personCount" offset=2 23952 when="{'0': 'Nobody is viewing.', 23953 '1': '{{person1}} is viewing.', 23954 '2': '{{person1}} and {{person2}} are viewing.', 23955 'one': '{{person1}}, {{person2}} and one other person are viewing.', 23956 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 23957 </ng-pluralize> 23958 </div> 23959 </file> 23960 <file name="protractor.js" type="protractor"> 23961 it('should show correct pluralized string', function() { 23962 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 23963 var withOffset = element.all(by.css('ng-pluralize')).get(1); 23964 var countInput = element(by.model('personCount')); 23965 23966 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 23967 expect(withOffset.getText()).toEqual('Igor is viewing.'); 23968 23969 countInput.clear(); 23970 countInput.sendKeys('0'); 23971 23972 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 23973 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 23974 23975 countInput.clear(); 23976 countInput.sendKeys('2'); 23977 23978 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 23979 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 23980 23981 countInput.clear(); 23982 countInput.sendKeys('3'); 23983 23984 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 23985 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 23986 23987 countInput.clear(); 23988 countInput.sendKeys('4'); 23989 23990 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 23991 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 23992 }); 23993 it('should show data-bound names', function() { 23994 var withOffset = element.all(by.css('ng-pluralize')).get(1); 23995 var personCount = element(by.model('personCount')); 23996 var person1 = element(by.model('person1')); 23997 var person2 = element(by.model('person2')); 23998 personCount.clear(); 23999 personCount.sendKeys('4'); 24000 person1.clear(); 24001 person1.sendKeys('Di'); 24002 person2.clear(); 24003 person2.sendKeys('Vojta'); 24004 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 24005 }); 24006 </file> 24007 </example> 24008 */ 24009var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { 24010 var BRACE = /{}/g, 24011 IS_WHEN = /^when(Minus)?(.+)$/; 24012 24013 return { 24014 restrict: 'EA', 24015 link: function(scope, element, attr) { 24016 var numberExp = attr.count, 24017 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 24018 offset = attr.offset || 0, 24019 whens = scope.$eval(whenExp) || {}, 24020 whensExpFns = {}, 24021 startSymbol = $interpolate.startSymbol(), 24022 endSymbol = $interpolate.endSymbol(), 24023 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 24024 watchRemover = angular.noop, 24025 lastCount; 24026 24027 forEach(attr, function(expression, attributeName) { 24028 var tmpMatch = IS_WHEN.exec(attributeName); 24029 if (tmpMatch) { 24030 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 24031 whens[whenKey] = element.attr(attr.$attr[attributeName]); 24032 } 24033 });
vendor: 10,186 bytes, lines 24034-24265
24034 forEach(whens, function(expression, key) { 24035 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 24036 24037 }); 24038 24039 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 24040 var count = parseFloat(newVal); 24041 var countIsNaN = isNaN(count); 24042 24043 if (!countIsNaN && !(count in whens)) { 24044 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 24045 // Otherwise, check it against pluralization rules in $locale service. 24046 count = $locale.pluralCat(count - offset); 24047 } 24048 24049 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 24050 // In JS `NaN !== NaN`, so we have to exlicitly check. 24051 if ((count !== lastCount) && !(countIsNaN && isNaN(lastCount))) { 24052 watchRemover(); 24053 watchRemover = scope.$watch(whensExpFns[count], updateElementText); 24054 lastCount = count; 24055 } 24056 }); 24057 24058 function updateElementText(newText) { 24059 element.text(newText || ''); 24060 } 24061 } 24062 }; 24063}]; 24064 24065/** 24066 * @ngdoc directive 24067 * @name ngRepeat 24068 * 24069 * @description 24070 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 24071 * instance gets its own scope, where the given loop variable is set to the current collection item, 24072 * and `$index` is set to the item index or key. 24073 * 24074 * Special properties are exposed on the local scope of each template instance, including: 24075 * 24076 * | Variable | Type | Details | 24077 * |-----------|-----------------|-----------------------------------------------------------------------------| 24078 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 24079 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 24080 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 24081 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 24082 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 24083 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 24084 * 24085 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 24086 * This may be useful when, for instance, nesting ngRepeats. 24087 * 24088 * # Iterating over object properties 24089 * 24090 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 24091 * syntax: 24092 * 24093 * ```js 24094 * <div ng-repeat="(key, value) in myObj"> ... </div> 24095 * ``` 24096 * 24097 * You need to be aware that the JavaScript specification does not define what order 24098 * it will return the keys for an object. In order to have a guaranteed deterministic order 24099 * for the keys, Angular versions up to and including 1.3 **sort the keys alphabetically**. 24100 * 24101 * If this is not desired, the recommended workaround is to convert your object into an array 24102 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 24103 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 24104 * or implement a `$watch` on the object yourself. 24105 * 24106 * In version 1.4 we will remove the sorting, since it seems that browsers generally follow the 24107 * strategy of providing keys in the order in which they were defined, although there are exceptions 24108 * when keys are deleted and reinstated. 24109 * 24110 * 24111 * # Special repeat start and end points 24112 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 24113 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 24114 * 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) 24115 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 24116 * 24117 * The example below makes use of this feature: 24118 * ```html 24119 * <header ng-repeat-start="item in items"> 24120 * Header {{ item }} 24121 * </header> 24122 * <div class="body"> 24123 * Body {{ item }} 24124 * </div> 24125 * <footer ng-repeat-end> 24126 * Footer {{ item }} 24127 * </footer> 24128 * ``` 24129 * 24130 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 24131 * ```html 24132 * <header> 24133 * Header A 24134 * </header> 24135 * <div class="body"> 24136 * Body A 24137 * </div> 24138 * <footer> 24139 * Footer A 24140 * </footer> 24141 * <header> 24142 * Header B 24143 * </header> 24144 * <div class="body"> 24145 * Body B 24146 * </div> 24147 * <footer> 24148 * Footer B 24149 * </footer> 24150 * ``` 24151 * 24152 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 24153 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 24154 * 24155 * @animations 24156 * **.enter** - when a new item is added to the list or when an item is revealed after a filter 24157 * 24158 * **.leave** - when an item is removed from the list or when an item is filtered out 24159 * 24160 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 24161 * 24162 * @element ANY 24163 * @scope 24164 * @priority 1000 24165 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 24166 * formats are currently supported: 24167 * 24168 * * `variable in expression` â where variable is the user defined loop variable and `expression` 24169 * is a scope expression giving the collection to enumerate. 24170 * 24171 * For example: `album in artist.albums`. 24172 * 24173 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 24174 * and `expression` is the scope expression giving the collection to enumerate. 24175 * 24176 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 24177 * 24178 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking function 24179 * which can be used to associate the objects in the collection with the DOM elements. If no tracking function 24180 * is specified the ng-repeat associates elements by identity in the collection. It is an error to have 24181 * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are 24182 * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, 24183 * before specifying a tracking expression. 24184 * 24185 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 24186 * will be associated by item identity in the array. 24187 * 24188 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 24189 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 24190 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 24191 * element in the same way in the DOM. 24192 * 24193 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 24194 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 24195 * property is same. 24196 * 24197 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 24198 * to items in conjunction with a tracking expression. 24199 * 24200 * * `variable in expression as alias_expression` â You can also provide an optional alias expression which will then store the 24201 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 24202 * when a filter is active on the repeater, but the filtered result set is empty. 24203 * 24204 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 24205 * the items have been processed through the filter. 24206 * 24207 * @example 24208 * This example initializes the scope to a list of names and 24209 * then uses `ngRepeat` to display every person: 24210 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 24211 <file name="index.html"> 24212 <div ng-init="friends = [ 24213 {name:'John', age:25, gender:'boy'}, 24214 {name:'Jessie', age:30, gender:'girl'}, 24215 {name:'Johanna', age:28, gender:'girl'}, 24216 {name:'Joy', age:15, gender:'girl'}, 24217 {name:'Mary', age:28, gender:'girl'}, 24218 {name:'Peter', age:95, gender:'boy'}, 24219 {name:'Sebastian', age:50, gender:'boy'}, 24220 {name:'Erika', age:27, gender:'girl'}, 24221 {name:'Patrick', age:40, gender:'boy'}, 24222 {name:'Samantha', age:60, gender:'girl'} 24223 ]"> 24224 I have {{friends.length}} friends. They are: 24225 <input type="search" ng-model="q" placeholder="filter friends..." /> 24226 <ul class="example-animate-container"> 24227 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> 24228 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 24229 </li> 24230 <li class="animate-repeat" ng-if="results.length == 0"> 24231 <strong>No results found...</strong> 24232 </li> 24233 </ul> 24234 </div> 24235 </file> 24236 <file name="animations.css"> 24237 .example-animate-container { 24238 background:white; 24239 border:1px solid black; 24240 list-style:none; 24241 margin:0; 24242 padding:0 10px; 24243 } 24244 24245 .animate-repeat { 24246 line-height:40px; 24247 list-style:none; 24248 box-sizing:border-box; 24249 } 24250 24251 .animate-repeat.ng-move, 24252 .animate-repeat.ng-enter, 24253 .animate-repeat.ng-leave { 24254 -webkit-transition:all linear 0.5s; 24255 transition:all linear 0.5s; 24256 } 24257 24258 .animate-repeat.ng-leave.ng-leave-active, 24259 .animate-repeat.ng-move, 24260 .animate-repeat.ng-enter { 24261 opacity:0; 24262 max-height:0; 24263 } 24264 24265 .animate-repeat.ng-leave,
24266 .animate-repeat.ng-move.ng-move-active, 24267 .animate-repeat.ng-enter.ng-enter-active { 24268 opacity:1; 24269 max-height:40px; 24270 } 24271 </file> 24272 <file name="protractor.js" type="protractor"> 24273 var friends = element.all(by.repeater('friend in friends')); 24274 24275 it('should render initial data set', function() { 24276 expect(friends.count()).toBe(10); 24277 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 24278 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 24279 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 24280 expect(element(by.binding('friends.length')).getText()) 24281 .toMatch("I have 10 friends. They are:"); 24282 }); 24283 24284 it('should update repeater when filter predicate changes', function() { 24285 expect(friends.count()).toBe(10); 24286 24287 element(by.model('q')).sendKeys('ma'); 24288 24289 expect(friends.count()).toBe(2); 24290 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 24291 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 24292 }); 24293 </file> 24294 </example> 24295 */ 24296var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 24297 var NG_REMOVED = '$$NG_REMOVED'; 24298 var ngRepeatMinErr = minErr('ngRepeat'); 24299 24300 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 24301 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 24302 scope[valueIdentifier] = value; 24303 if (keyIdentifier) scope[keyIdentifier] = key; 24304 scope.$index = index; 24305 scope.$first = (index === 0); 24306 scope.$last = (index === (arrayLength - 1)); 24307 scope.$middle = !(scope.$first || scope.$last); 24308 // jshint bitwise: false 24309 scope.$odd = !(scope.$even = (index&1) === 0); 24310 // jshint bitwise: true 24311 }; 24312 24313 var getBlockStart = function(block) { 24314 return block.clone[0]; 24315 }; 24316 24317 var getBlockEnd = function(block) { 24318 return block.clone[block.clone.length - 1]; 24319 }; 24320 24321 24322 return { 24323 restrict: 'A', 24324 multiElement: true, 24325 transclude: 'element', 24326 priority: 1000, 24327 terminal: true, 24328 $$tlb: true, 24329 compile: function ngRepeatCompile($element, $attr) { 24330 var expression = $attr.ngRepeat; 24331 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' '); 24332 24333 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); 24334 24335 if (!match) { 24336 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 24337 expression); 24338 } 24339 24340 var lhs = match[1]; 24341 var rhs = match[2]; 24342 var aliasAs = match[3]; 24343 var trackByExp = match[4]; 24344 24345 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); 24346 24347 if (!match) { 24348 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 24349 lhs); 24350 } 24351 var valueIdentifier = match[3] || match[1]; 24352 var keyIdentifier = match[2]; 24353 24354 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 24355 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 24356 throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.", 24357 aliasAs); 24358 } 24359 24360 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; 24361 var hashFnLocals = {$id: hashKey}; 24362 24363 if (trackByExp) { 24364 trackByExpGetter = $parse(trackByExp); 24365 } else { 24366 trackByIdArrayFn = function(key, value) { 24367 return hashKey(value); 24368 }; 24369 trackByIdObjFn = function(key) { 24370 return key; 24371 }; 24372 } 24373 24374 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 24375 24376 if (trackByExpGetter) { 24377 trackByIdExpFn = function(key, value, index) { 24378 // assign key, value, and $index to the locals so that they can be used in hash functions 24379 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 24380 hashFnLocals[valueIdentifier] = value; 24381 hashFnLocals.$index = index; 24382 return trackByExpGetter($scope, hashFnLocals); 24383 }; 24384 } 24385 24386 // Store a list of elements from previous run. This is a hash where key is the item from the 24387 // iterator, and the value is objects with following properties. 24388 // - scope: bound scope 24389 // - element: previous element. 24390 // - index: position 24391 // 24392 // We are using no-proto object so that we don't need to guard against inherited props via 24393 // hasOwnProperty. 24394 var lastBlockMap = createMap(); 24395 24396 //watch props 24397 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 24398 var index, length, 24399 previousNode = $element[0], // node that cloned
vendor: 5,124 bytes, lines 24399-24511
24399nodes should be inserted after 24400 // initialized to the comment node anchor 24401 nextNode, 24402 // Same as lastBlockMap but it has the current state. It will become the 24403 // lastBlockMap on the next iteration. 24404 nextBlockMap = createMap(), 24405 collectionLength, 24406 key, value, // key/value of iteration 24407 trackById, 24408 trackByIdFn, 24409 collectionKeys, 24410 block, // last object information {scope, element, id} 24411 nextBlockOrder, 24412 elementsToRemove; 24413 24414 if (aliasAs) { 24415 $scope[aliasAs] = collection; 24416 } 24417 24418 if (isArrayLike(collection)) { 24419 collectionKeys = collection; 24420 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 24421 } else { 24422 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 24423 // if object, extract keys, sort them and use to determine order of iteration over obj props 24424 collectionKeys = []; 24425 for (var itemKey in collection) { 24426 if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) != '$') { 24427 collectionKeys.push(itemKey); 24428 } 24429 } 24430 collectionKeys.sort(); 24431 } 24432 24433 collectionLength = collectionKeys.length; 24434 nextBlockOrder = new Array(collectionLength); 24435 24436 // locate existing items 24437 for (index = 0; index < collectionLength; index++) { 24438 key = (collection === collectionKeys) ? index : collectionKeys[index]; 24439 value = collection[key]; 24440 trackById = trackByIdFn(key, value, index); 24441 if (lastBlockMap[trackById]) { 24442 // found previously seen block 24443 block = lastBlockMap[trackById]; 24444 delete lastBlockMap[trackById]; 24445 nextBlockMap[trackById] = block; 24446 nextBlockOrder[index] = block; 24447 } else if (nextBlockMap[trackById]) { 24448 // if collision detected. restore lastBlockMap and throw an error 24449 forEach(nextBlockOrder, function(block) { 24450 if (block && block.scope) lastBlockMap[block.id] = block; 24451 }); 24452 throw ngRepeatMinErr('dupes', 24453 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", 24454 expression, trackById, value); 24455 } else { 24456 // new never before seen block 24457 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 24458 nextBlockMap[trackById] = true; 24459 } 24460 } 24461 24462 // remove leftover items 24463 for (var blockKey in lastBlockMap) { 24464 block = lastBlockMap[blockKey]; 24465 elementsToRemove = getBlockNodes(block.clone); 24466 $animate.leave(elementsToRemove); 24467 if (elementsToRemove[0].parentNode) { 24468 // if the element was not removed yet because of pending animation, mark it as deleted 24469 // so that we can ignore it later 24470 for (index = 0, length = elementsToRemove.length; index < length; index++) { 24471 elementsToRemove[index][NG_REMOVED] = true; 24472 } 24473 } 24474 block.scope.$destroy(); 24475 } 24476 24477 // we are not using forEach for perf reasons (trying to avoid #call) 24478 for (index = 0; index < collectionLength; index++) { 24479 key = (collection === collectionKeys) ? index : collectionKeys[index]; 24480 value = collection[key]; 24481 block = nextBlockOrder[index]; 24482 24483 if (block.scope) { 24484 // if we have already seen this object, then we need to reuse the 24485 // associated scope/element 24486 24487 nextNode = previousNode; 24488 24489 // skip nodes that are already pending removal via leave animation 24490 do { 24491 nextNode = nextNode.nextSibling; 24492 } while (nextNode && nextNode[NG_REMOVED]); 24493 24494 if (getBlockStart(block) != nextNode) { 24495 // existing item which got moved 24496 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); 24497 } 24498 previousNode = getBlockEnd(block); 24499 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 24500 } else { 24501 // new item which we don't know about 24502 $transclude(function ngRepeatTransclude(clone, scope) { 24503 block.scope = scope; 24504 // http://jsperf.com/clone-vs-createcomment 24505 var endNode = ngRepeatEndComment.cloneNode(false); 24506 clone[clone.length++] = endNode; 24507 24508 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper? 24509 $animate.enter(clone, null, jqLite(previousNode)); 24510 previousNode = endNode; 24511 // Note: We only need the first/last node of the cloned
24511nodes. 24512 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 24513 // by a directive with templateUrl when its template arrives. 24514 block.clone = clone; 24515 nextBlockMap[block.id] = block; 24516 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 24517 }); 24518 } 24519 } 24520 lastBlockMap = nextBlockMap; 24521 }); 24522 }; 24523 } 24524 }; 24525}]; 24526 24527var NG_HIDE_CLASS = 'ng-hide'; 24528var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 24529/** 24530 * @ngdoc directive 24531 * @name ngShow 24532 * 24533 * @description 24534 * The `ngShow` directive shows or hides the given HTML element based on the expression 24535 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding 24536 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 24537 * in AngularJS and sets the display style to none (using an !important flag). 24538 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 24539 * 24540 * ```html 24541 * <!-- when $scope.myValue is truthy (element is visible) --> 24542 * <div ng-show="myValue"></div> 24543 * 24544 * <!-- when $scope.myValue is falsy (element is hidden) --> 24545 * <div ng-show="myValue" class="ng-hide"></div> 24546 * ``` 24547 * 24548 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class 24549 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed 24550 * from the element causing the element not to appear hidden. 24551 * 24552 * ## Why is !important used? 24553 * 24554 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 24555 * can be easily overridden by heavier selectors. For example, something as simple 24556 * as changing the display style on a HTML list item would make hidden elements appear visible. 24557 * This also becomes a bigger issue when dealing with CSS frameworks. 24558 * 24559 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 24560 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 24561 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 24562 * 24563 * ### Overriding `.ng-hide` 24564 * 24565 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 24566 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 24567 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope 24568 * with extra animation classes that can be added. 24569 * 24570 * ```css 24571 * .ng-hide:not(.ng-hide-animate) { 24572 * /* this is just another form of hiding an element */ 24573 * display: block!important; 24574 * position: absolute; 24575 * top: -9999px; 24576 * left: -9999px; 24577 * } 24578 * ``` 24579 * 24580 * By default you don't need to override in CSS anything and the animations will work around the display style. 24581 * 24582 * ## A note about animations with `ngShow` 24583 * 24584 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 24585 * is true and false. This system works like the animation system present with ngClass except that 24586 * you must also include the !important flag to override the display property 24587 * so that you can perform an animation when the element is hidden during the time of the animation. 24588 * 24589 * ```css 24590 * // 24591 * //a working example can be found at the bottom of this page 24592 * // 24593 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 24594 * /* this is required as of 1.3x to properly 24595 * apply all styling in a show/hide animation */ 24596 * transition: 0s linear all; 24597 * } 24598 * 24599 * .my-element.ng-hide-add-active, 24600 * .my-element.ng-hide-remove-active { 24601 * /* the transition is defined in the active class */ 24602 * transition: 1s linear all; 24603 * } 24604 * 24605 * .my-element.ng-hide-add { ... } 24606 * .my-element.ng-hide-add.ng-hide-add-active { ... } 24607 * .my-element.ng-hide-remove { ... } 24608 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 24609 * ``` 24610 * 24611 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 24612 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 24613 * 24614 * @animations 24615 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible 24616 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden 24617 * 24618 * @element ANY 24619 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 24620 * then the element is shown or hidden respectively. 24621 * 24622 * @example 24623 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 24624 <file name="index.html"> 24625 Click me: <input type="checkbox" ng-model="checked"><br/> 24626 <div> 24627 Show: 24628 <div class="check-element animate-show" ng-show="checked">
24629 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 24630 </div> 24631 </div> 24632 <div> 24633 Hide: 24634 <div class="check-element animate-show" ng-hide="checked"> 24635 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 24636 </div> 24637 </div> 24638 </file> 24639 <file name="glyphicons.css"> 24640 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 24641 </file> 24642 <file name="animations.css"> 24643 .animate-show { 24644 line-height: 20px; 24645 opacity: 1; 24646 padding: 10px; 24647 border: 1px solid black; 24648 background: white; 24649 } 24650 24651 .animate-show.ng-hide-add.ng-hide-add-active, 24652 .animate-show.ng-hide-remove.ng-hide-remove-active { 24653 -webkit-transition: all linear 0.5s; 24654 transition: all linear 0.5s; 24655 } 24656 24657 .animate-show.ng-hide { 24658 line-height: 0; 24659 opacity: 0; 24660 padding: 0 10px; 24661 } 24662 24663 .check-element { 24664 padding: 10px; 24665 border: 1px solid black; 24666 background: white; 24667 } 24668 </file> 24669 <file name="protractor.js" type="protractor"> 24670 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 24671 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 24672 24673 it('should check ng-show / ng-hide', function() { 24674 expect(thumbsUp.isDisplayed()).toBeFalsy(); 24675 expect(thumbsDown.isDisplayed()).toBeTruthy(); 24676 24677 element(by.model('checked')).click(); 24678 24679 expect(thumbsUp.isDisplayed()).toBeTruthy(); 24680 expect(thumbsDown.isDisplayed()).toBeFalsy(); 24681 }); 24682 </file> 24683 </example> 24684 */ 24685var ngShowDirective = ['$animate', function($animate) { 24686 return { 24687 restrict: 'A', 24688 multiElement: true, 24689 link: function(scope, element, attr) { 24690 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 24691 // we're adding a temporary, animation-specific class for ng-hide since this way 24692 // we can control when the element is actually displayed on screen without having 24693 // to have a global/greedy CSS selector that breaks when other animations are run. 24694 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 24695 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { 24696 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 24697 }); 24698 }); 24699 } 24700 }; 24701}]; 24702 24703 24704/** 24705 * @ngdoc directive 24706 * @name ngHide 24707 * 24708 * @description 24709 * The `ngHide` directive shows or hides the given HTML element based on the expression 24710 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding 24711 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 24712 * in AngularJS and sets the display style to none (using an !important flag). 24713 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 24714 * 24715 * ```html 24716 * <!-- when $scope.myValue is truthy (element is hidden) --> 24717 * <div ng-hide="myValue" class="ng-hide"></div> 24718 * 24719 * <!-- when $scope.myValue is falsy (element is visible) --> 24720 * <div ng-hide="myValue"></div> 24721 * ``` 24722 * 24723 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class 24724 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed 24725 * from the element causing the element not to appear hidden. 24726 * 24727 * ## Why is !important used? 24728 * 24729 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 24730 * can be easily overridden by heavier selectors. For example, something as simple 24731 * as changing the display style on a HTML list item would make hidden elements appear visible. 24732 * This also becomes a bigger issue when dealing with CSS frameworks. 24733 * 24734 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 24735 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 24736 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 24737 * 24738 * ### Overriding `.ng-hide` 24739 * 24740 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 24741 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 24742 * class in CSS: 24743 * 24744 * ```css 24745 * .ng-hide { 24746 * /* this is just another form of hiding an element */ 24747 * display: block!important; 24748 * position: absolute; 24749 * top: -9999px; 24750 * left: -9999px; 24751 * } 24752 * ``` 24753 * 24754 * By default you don't need to override in CSS anything and the animations will work around the display style. 24755 * 24756 * ## A note about animations with `ngHide` 24757 * 24758 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 24759 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 24760 * CSS class is added and removed for you instead of your own CSS class. 24761 * 24762 * ```css 24763 * // 24764 * //a working example can be found at the bottom of this page 24765 * // 24766 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 24767 * transition: 0.5s linear all; 24768 * } 24769 * 24770 * .my-element.ng-hide-add { ... } 24771 * .my-element.ng-hide-add.ng-hide-add-active { ... } 24772 * .my-element.ng-hide-remove { ... } 24773 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 24774 * ``` 24775 * 24776 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 24777 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 24778 * 24779 * @animations 24780 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden 24781 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible 24782 * 24783 * @element ANY 24784 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 24785 * the element is shown or hidden respectively. 24786 * 24787 * @example 24788 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 24789 <file name="index.html"> 24790 Click me: <input type="checkbox" ng-model="checked"><br/> 24791 <div> 24792 Show: 24793 <div class="check-element animate-hide" ng-show="checked">
24794 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 24795 </div> 24796 </div> 24797 <div> 24798 Hide: 24799 <div class="check-element animate-hide" ng-hide="checked"> 24800 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 24801 </div> 24802 </div> 24803 </file> 24804 <file name="glyphicons.css"> 24805 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 24806 </file> 24807 <file name="animations.css"> 24808 .animate-hide { 24809 -webkit-transition: all linear 0.5s; 24810 transition: all linear 0.5s; 24811 line-height: 20px; 24812 opacity: 1; 24813 padding: 10px; 24814 border: 1px solid black; 24815 background: white; 24816 } 24817 24818 .animate-hide.ng-hide { 24819 line-height: 0; 24820 opacity: 0; 24821 padding: 0 10px; 24822 } 24823 24824 .check-element { 24825 padding: 10px; 24826 border: 1px solid black; 24827 background: white; 24828 } 24829 </file> 24830 <file name="protractor.js" type="protractor"> 24831 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 24832 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 24833 24834 it('should check ng-show / ng-hide', function() { 24835 expect(thumbsUp.isDisplayed()).toBeFalsy(); 24836 expect(thumbsDown.isDisplayed()).toBeTruthy(); 24837 24838 element(by.model('checked')).click(); 24839 24840 expect(thumbsUp.isDisplayed()).toBeTruthy(); 24841 expect(thumbsDown.isDisplayed()).toBeFalsy(); 24842 }); 24843 </file> 24844 </example> 24845 */ 24846var ngHideDirective = ['$animate', function($animate) { 24847 return { 24848 restrict: 'A', 24849 multiElement: true, 24850 link: function(scope, element, attr) { 24851 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 24852 // The comment inside of the ngShowDirective explains why we add and 24853 // remove a temporary class for the show/hide animation 24854 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { 24855 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 24856 }); 24857 }); 24858 } 24859 }; 24860}]; 24861 24862/** 24863 * @ngdoc directive 24864 * @name ngStyle 24865 * @restrict AC 24866 * 24867 * @description 24868 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 24869 * 24870 * @element ANY 24871 * @param {expression} ngStyle 24872 * 24873 * {@link guide/expression Expression} which evals to an 24874 * object whose keys are CSS style names and values are corresponding values for those CSS 24875 * keys. 24876 * 24877 * Since some CSS style names are not valid keys for an object, they must be quoted. 24878 * See the 'background-color' style in the example below. 24879 * 24880 * @example 24881 <example> 24882 <file name="index.html"> 24883 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 24884 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 24885 <input type="button" value="clear" ng-click="myStyle={}"> 24886 <br/> 24887 <span ng-style="myStyle">Sample Text</span> 24888 <pre>myStyle={{myStyle}}</pre> 24889 </file> 24890 <file name="style.css"> 24891 span { 24892 color: black; 24893 } 24894 </file> 24895 <file name="protractor.js" type="protractor"> 24896 var colorSpan = element(by.css('span')); 24897 24898 it('should check ng-style', function() { 24899 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 24900 element(by.css('input[value=\'set color\']')).click(); 24901 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 24902 element(by.css('input[value=clear]')).click(); 24903 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 24904 }); 24905 </file> 24906 </example> 24907 */ 24908var ngStyleDirective = ngDirective(function(scope, element, attr) { 24909 scope.$watchCollection(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 24910 if (oldStyles && (newStyles !== oldStyles)) { 24911 forEach(oldStyles, function(val, style) { element.css(style, '');}); 24912 } 24913 if (newStyles) element.css(newStyles); 24914 }); 24915}); 24916 24917/** 24918 * @ngdoc directive 24919 * @name ngSwitch 24920 * @restrict EA 24921 * 24922 * @description 24923 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 24924 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 24925 * as specified in the template. 24926 * 24927 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 24928 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 24929 * matches the value obtained from the evaluated expression. In other words, you define a container element 24930 * (where you place the directive), place an expression on the **`on="..."` attribute** 24931 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 24932 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 24933 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 24934 * attribute is displayed. 24935 * 24936 * <div class="alert alert-info"> 24937 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 24938 * as literal string values to match against. 24939 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
24939omeVal"` not against the 24940 * value of the expression `$scope.someVal`. 24941 * </div> 24942 24943 * @animations 24944 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 24945 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 24946 * 24947 * @usage 24948 * 24949 * ``` 24950 * <ANY ng-switch="expression"> 24951 * <ANY ng-switch-when="matchValue1">...</ANY> 24952 * <ANY ng-switch-when="matchValue2">...</ANY> 24953 * <ANY ng-switch-default>...</ANY> 24954 * </ANY> 24955 * ``` 24956 * 24957 * 24958 * @scope 24959 * @priority 1200 24960 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>. 24961 * On child elements add: 24962 * 24963 * * `ngSwitchWhen`: the case statement to match against. If match then this 24964 * case will be displayed. If the same match appears multiple times, all the 24965 * elements will be displayed. 24966 * * `ngSwitchDefault`: the default case when no other case match. If there 24967 * are multiple default cases, all of them will be displayed when no other 24968 * case match. 24969 * 24970 * 24971 * @example 24972 <example module="switchExample" deps="angular-animate.js" animations="true"> 24973 <file name="index.html">
24974 <div ng-controller="ExampleController"> 24975 <select ng-model="selection" ng-options="item for item in items"> 24976 </select> 24977 <tt>selection={{selection}}</tt> 24978 <hr/> 24979 <div class="animate-switch-container" 24980 ng-switch on="selection"> 24981 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 24982 <div class="animate-switch" ng-switch-when="home">Home Span</div> 24983 <div class="animate-switch" ng-switch-default>default</div> 24984 </div> 24985 </div> 24986 </file> 24987 <file name="script.js"> 24988 angular.module('switchExample', ['ngAnimate']) 24989 .controller('ExampleController', ['$scope', function($scope) { 24990 $scope.items = ['settings', 'home', 'other']; 24991 $scope.selection = $scope.items[0]; 24992 }]); 24993 </file> 24994 <file name="animations.css"> 24995 .animate-switch-container { 24996 position:relative; 24997 background:white; 24998 border:1px solid black; 24999 height:40px; 25000 overflow:hidden; 25001 } 25002 25003 .animate-switch { 25004 padding:10px; 25005 } 25006 25007 .animate-switch.ng-animate { 25008 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 25009 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 25010 25011 position:absolute; 25012 top:0; 25013 left:0; 25014 right:0; 25015 bottom:0; 25016 } 25017 25018 .animate-switch.ng-leave.ng-leave-active, 25019 .animate-switch.ng-enter { 25020 top:-50px; 25021 } 25022 .animate-switch.ng-leave, 25023 .animate-switch.ng-enter.ng-enter-active { 25024 top:0; 25025 } 25026 </file> 25027 <file name="protractor.js" type="protractor"> 25028 var switchElem = element(by.css('[ng-switch]')); 25029 var select = element(by.model('selection')); 25030 25031 it('should start in settings', function() { 25032 expect(switchElem.getText()).toMatch(/Settings Div/); 25033 }); 25034 it('should change to home', function() { 25035 select.all(by.css('option')).get(1).click(); 25036 expect(switchElem.getText()).toMatch(/Home Span/); 25037 }); 25038 it('should select default', function() { 25039 select.all(by.css('option')).get(2).click(); 25040 expect(switchElem.getText()).toMatch(/default/); 25041 }); 25042 </file> 25043 </example> 25044 */ 25045var ngSwitchDirective = ['$animate', function($animate) { 25046 return { 25047 restrict: 'EA', 25048 require: 'ngSwitch', 25049 25050 // asks for $scope to fool the BC controller module 25051 controller: ['$scope', function ngSwitchController() { 25052 this.cases = {}; 25053 }], 25054 link: function(scope, element, attr, ngSwitchController) { 25055 var watchExpr = attr.ngSwitch || attr.on, 25056 selectedTranscludes = [], 25057 selectedElements = [], 25058 previousLeaveAnimations = [], 25059 selectedScopes = []; 25060 25061 var spliceFactory = function(array, index) { 25062 return function() { array.splice(index, 1); }; 25063 }; 25064 25065 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 25066 var i, ii; 25067 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { 25068 $animate.cancel(previousLeaveAnimations[i]); 25069 } 25070 previousLeaveAnimations.length = 0; 25071 25072 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 25073 var selected = getBlockNodes(selectedElements[i].clone); 25074 selectedScopes[i].$destroy(); 25075 var promise = previousLeaveAnimations[i] = $animate.leave(selected); 25076 promise.then(spliceFactory(previousLeaveAnimations, i)); 25077 } 25078 25079 selectedElements.length = 0; 25080 selectedScopes.length = 0; 25081 25082 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 25083 forEach(selectedTranscludes, function(selectedTransclude) { 25084 selectedTransclude.transclude(function(caseElement, selectedScope) { 25085 selectedScopes.push(selectedScope); 25086 var anchor = selectedTransclude.element; 25087 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: '); 25088 var block = { clone: caseElement }; 25089 25090 selectedElements.push(block);
25091 $animate.enter(caseElement, anchor.parent(), anchor); 25092 }); 25093 }); 25094 } 25095 }); 25096 } 25097 }; 25098}]; 25099 25100var ngSwitchWhenDirective = ngDirective({ 25101 transclude: 'element', 25102 priority: 1200, 25103 require: '^ngSwitch', 25104 multiElement: true, 25105 link: function(scope, element, attrs, ctrl, $transclude) { 25106 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 25107 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 25108 } 25109}); 25110 25111var ngSwitchDefaultDirective = ngDirective({ 25112 transclude: 'element', 25113 priority: 1200, 25114 require: '^ngSwitch', 25115 multiElement: true, 25116 link: function(scope, element, attr, ctrl, $transclude) { 25117 ctrl.cases['?'] = (ctrl.cases['?'] || []); 25118 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 25119 } 25120}); 25121 25122/** 25123 * @ngdoc directive 25124 * @name ngTransclude 25125 * @restrict EAC 25126 * 25127 * @description 25128 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 25129 * 25130 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 25131 * 25132 * @element ANY 25133 * 25134 * @example 25135 <example module="transcludeExample"> 25136 <file name="index.html"> 25137 <script> 25138 angular.module('transcludeExample', []) 25139 .directive('pane', function(){ 25140 return { 25141 restrict: 'E', 25142 transclude: true, 25143 scope: { title:'@' }, 25144 template: '<div style="border: 1px solid black;">' + 25145 '<div style="background-color: gray">{{title}}</div>' + 25146 '<ng-transclude></ng-transclude>' + 25147 '</div>' 25148 }; 25149 }) 25150 .controller('ExampleController', ['$scope', function($scope) { 25151 $scope.title = 'Lorem Ipsum'; 25152 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 25153 }]); 25154 </script>
25155 <div ng-controller="ExampleController"> 25156 <input ng-model="title"> <br/> 25157 <textarea ng-model="text"></textarea> <br/> 25158 <pane title="{{title}}">{{text}}</pane> 25159 </div> 25160 </file> 25161 <file name="protractor.js" type="protractor"> 25162 it('should have transcluded', function() { 25163 var titleElement = element(by.model('title')); 25164 titleElement.clear(); 25165 titleElement.sendKeys('TITLE'); 25166 var textElement = element(by.model('text')); 25167 textElement.clear(); 25168 textElement.sendKeys('TEXT'); 25169 expect(element(by.binding('title')).getText()).toEqual('TITLE'); 25170 expect(element(by.binding('text')).getText()).toEqual('TEXT'); 25171 }); 25172 </file> 25173 </example> 25174 * 25175 */ 25176var ngTranscludeDirective = ngDirective({ 25177 restrict: 'EAC', 25178 link: function($scope, $element, $attrs, controller, $transclude) { 25179 if (!$transclude) { 25180 throw minErr('ngTransclude')('orphan', 25181 'Illegal use of ngTransclude directive in the template! ' + 25182 'No parent directive that requires a transclusion found. ' + 25183 'Element: {0}', 25184 startingTag($element)); 25185 } 25186 25187 $transclude(function(clone) { 25188 $element.empty(); 25189 $element.append(clone); 25190 }); 25191 } 25192}); 25193 25194/** 25195 * @ngdoc directive 25196 * @name script 25197 * @restrict E 25198 * 25199 * @description 25200 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 25201 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 25202 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 25203 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 25204 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 25205 * 25206 * @param {string} type Must be set to `'text/ng-template'`. 25207 * @param {string} id Cache name of the template. 25208 * 25209 * @example 25210 <example> 25211 <file name="index.html"> 25212 <script type="text/ng-template" id="/tpl.html"> 25213 Content of the template. 25214 </script> 25215 25216 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 25217 <div id="tpl-content" ng-include src="currentTpl"></div> 25218 </file> 25219 <file name="protractor.js" type="protractor"> 25220 it('should load template defined inside script tag', function() { 25221 element(by.css('#tpl-link')).click(); 25222 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 25223 }); 25224 </file> 25225 </example> 25226 */ 25227var scriptDirective = ['$templateCache', function($templateCache) { 25228 return { 25229 restrict: 'E', 25230 terminal: true, 25231 compile: function(element, attr) { 25232 if (attr.type == 'text/ng-template') { 25233 var templateUrl = attr.id, 25234 text = element[0].text; 25235 25236 $templateCache.put(templateUrl, text); 25237 } 25238 } 25239 }; 25240}]; 25241 25242var ngOptionsMinErr = minErr('ngOptions'); 25243/** 25244 * @ngdoc directive 25245 * @name select 25246 * @restrict E 25247 * 25248 * @description 25249 * HTML `SELECT` element with angular data-binding. 25250 * 25251 * # `ngOptions` 25252 * 25253 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 25254 * elements for the `<select>` element using the array or object obtained by evaluating the 25255 * `ngOptions` comprehension expression. 25256 * 25257 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a 25258 * similar result. However, `ngOptions` provides some benefits such as reducing memory and 25259 * increasing speed by not creating a new scope for each repeated instance, as well as providing 25260 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 25261 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound 25262 * to a non-string value. This is because an option element can only be bound to string values at 25263 * present. 25264 * 25265 * When an item in the `<select>` menu is selected, the array element or object property 25266 * represented by the selected option will be bound to the model identified by the `ngModel` 25267 * directive. 25268 * 25269 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 25270 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 25271 * option. See example below for demonstration. 25272 * 25273 * <div class="alert alert-warning"> 25274 * **Note:** `ngModel` compares by reference, not value. This is important when binding to an 25275 * array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/). 25276 * </div> 25277 * 25278 * ## `select` **`as`** 25279 * 25280 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 25281 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 25282 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 25283 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 25284 * 25285 * 25286 * ### `select` **`as`** and **`track by`** 25287 * 25288 * <div class="alert alert-warning"> 25289 * Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together. 25290 * </div> 25291 * 25292 * Consider the following example: 25293 * 25294 * ```html 25295 * <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"> 25296 * ``` 25297 * 25298 * ```js 25299 * $scope.values = [{ 25300 * id: 1, 25301 * label: 'aLabel', 25302 * subItem: { name: 'aSubItem' } 25303 * }, { 25304 * id: 2, 25305 * label: 'bLabel', 25306 * subItem: { name: 'bSubItem' } 25307 * }]; 25308 * 25309 * $scope.selected = { name: 'aSubItem' }; 25310 * ``` 25311 * 25312 * With the purpose of preserving the selection, the **`track by`** expression is always applied to the element 25313 * of the data source (to `item` in this example). To calculate whether an element is selected, we do the 25314 * following: 25315 * 25316 * 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
25317 * 2. Apply **`track by`** to the already selected value in `ngModel`. 25318 * In the example: this is not possible as **`track by`** refers to `item.id`, but the selected 25319 * value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to 25320 * a wrong object, the selected element can't be found, `<select>` is always reset to the "not 25321 * selected" option. 25322 * 25323 * 25324 * @param {string} ngModel Assignable angular expression to data-bind to. 25325 * @param {string=} name Property name of the form under which the control is published. 25326 * @param {string=} required The control is considered valid only if value is entered. 25327 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25328 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25329 * `required` when you want to data-bind to the `required` attribute. 25330 * @param {comprehension_expression=} ngOptions in one of the following forms: 25331 * 25332 * * for array data sources: 25333 * * `label` **`for`** `value` **`in`** `array` 25334 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 25335 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 25336 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 25337 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 25338 * (for including a filter with `track by`) 25339 * * for object data sources: 25340 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 25341 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 25342 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 25343 * * `select` **`as`** `label` **`group by`** `group` 25344 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 25345 * 25346 * Where: 25347 * 25348 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 25349 * * `value`: local variable which will refer to each item in the `array` or each property value 25350 * of `object` during iteration. 25351 * * `key`: local variable which will refer to a property name in `object` during iteration. 25352 * * `label`: The result of this expression will be the label for `<option>` element. The 25353 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 25354 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 25355 * element. If not specified, `select` expression will default to `value`. 25356 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 25357 * DOM element. 25358 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 25359 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 25360 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 25361 * even when the options are recreated (e.g. reloaded from the server). 25362 * 25363 * @example 25364 <example module="selectExample"> 25365 <file name="index.html"> 25366 <script> 25367 angular.module('selectExample', []) 25368 .controller('ExampleController', ['$scope', function($scope) { 25369 $scope.colors = [ 25370 {name:'black', shade:'dark'}, 25371 {name:'white', shade:'light'}, 25372 {name:'red', shade:'dark'}, 25373 {name:'blue', shade:'dark'}, 25374 {name:'yellow', shade:'light'} 25375 ]; 25376 $scope.myColor = $scope.colors[2]; // red 25377 }]); 25378 </script>
25379 <div ng-controller="ExampleController"> 25380 <ul> 25381 <li ng-repeat="color in colors"> 25382 Name: <input ng-model="color.name"> 25383 [<a href ng-click="colors.splice($index, 1)">X</a>] 25384 </li> 25385 <li> 25386 [<a href ng-click="colors.push({})">add</a>] 25387 </li> 25388 </ul> 25389 <hr/> 25390 Color (null not allowed): 25391 <select ng-model="myColor" ng-options="color.name for color in colors"></select><br> 25392 25393 Color (null allowed): 25394 <span class="nullable"> 25395 <select ng-model="myColor" ng-options="color.name for color in colors"> 25396 <option value="">-- choose color --</option> 25397 </select> 25398 </span><br/> 25399 25400 Color grouped by shade: 25401 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 25402 </select><br/> 25403 25404 25405 Select <a href ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</a>.<br> 25406 <hr/> 25407 Currently selected: {{ {selected_color:myColor} }} 25408 <div style="border:solid 1px black; height:20px" 25409 ng-style="{'background-color':myColor.name}"> 25410 </div> 25411 </div> 25412 </file> 25413 <file name="protractor.js" type="protractor"> 25414 it('should check ng-options', function() { 25415 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 25416 element.all(by.model('myColor')).first().click(); 25417 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 25418 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 25419 element(by.css('.nullable select[ng-model="myColor"]')).click(); 25420 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 25421 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 25422 }); 25423 </file> 25424 </example> 25425 */ 25426 25427var ngOptionsDirective = valueFn({ 25428 restrict: 'A', 25429 terminal: true 25430}); 25431 25432// jshint maxlen: false 25433var selectDirective = ['$compile', '$parse', function($compile, $parse) { 25434 //000011111111110000000000022222222220000000000000000000003333333333000000000000004444444444444440000000005555555555555550000000666666666666666000000000000000777777777700000000000000000008888888888 25435 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]+?))?$/, 25436 nullModelCtrl = {$setViewValue: noop}; 25437// jshint maxlen: 100 25438 25439 return { 25440 restrict: 'E', 25441 require: ['select', '?ngModel'], 25442 controller: ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 25443 var self = this, 25444 optionsMap = {}, 25445 ngModelCtrl = nullModelCtrl, 25446 nullOption, 25447 unknownOption; 25448 25449 25450 self.databound = $attrs.ngModel; 25451 25452 25453 self.init = function(ngModelCtrl_, nullOption_, unknownOption_) { 25454 ngModelCtrl = ngModelCtrl_; 25455 nullOption = nullOption_; 25456 unknownOption = unknownOption_; 25457 }; 25458 25459 25460 self.addOption = function(value, element) { 25461 assertNotHasOwnProperty(value, '"option value"'); 25462 optionsMap[value] = true; 25463 25464 if (ngModelCtrl.$viewValue == value) { 25465 $element.val(value); 25466 if (unknownOption.parent()) unknownOption.remove(); 25467 } 25468 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 25469 // Adding an <option selected="selected"> element to a <select required="required"> should 25470 // automatically select the new element 25471 if (element && element[0].hasAttribute('selected')) { 25472 element[0].selected = true; 25473 } 25474 }; 25475 25476 25477 self.removeOption = function(value) { 25478 if (this.hasOption(value)) { 25479 delete optionsMap[value]; 25480 if (ngModelCtrl.$viewValue === value) { 25481 this.renderUnknownOption(value); 25482 } 25483 } 25484 }; 25485 25486 25487 self.renderUnknownOption = function(val) { 25488 var unknownVal = '? ' + hashKey(val) + ' ?'; 25489 unknownOption.val(unknownVal);
25490 $element.prepend(unknownOption); 25491 $element.val(unknownVal); 25492 unknownOption.prop('selected', true); // needed for IE 25493 }; 25494 25495 25496 self.hasOption = function(value) { 25497 return optionsMap.hasOwnProperty(value); 25498 }; 25499 25500 $scope.$on('$destroy', function() { 25501 // disable unknown option so that we don't do work when the whole select is being destroyed 25502 self.renderUnknownOption = noop; 25503 }); 25504 }], 25505 25506 link: function(scope, element, attr, ctrls) { 25507 // if ngModel is not defined, we don't need to do anything 25508 if (!ctrls[1]) return; 25509 25510 var selectCtrl = ctrls[0], 25511 ngModelCtrl = ctrls[1], 25512 multiple = attr.multiple, 25513 optionsExp = attr.ngOptions, 25514 nullOption = false, // if false, user will not be able to select it (used by ngOptions) 25515 emptyOption, 25516 renderScheduled = false, 25517 // we can't just jqLite('<option>') since jqLite is not smart enough 25518 // to create it in <select> and IE barfs otherwise. 25519 optionTemplate = jqLite(document.createElement('option')), 25520 optGroupTemplate =jqLite(document.createElement('optgroup')), 25521 unknownOption = optionTemplate.clone(); 25522 25523 // find "null" option 25524 for (var i = 0, children = element.children(), ii = children.length; i < ii; i++) { 25525 if (children[i].value === '') { 25526 emptyOption = nullOption = children.eq(i); 25527 break; 25528 } 25529 } 25530 25531 selectCtrl.init(ngModelCtrl, nullOption, unknownOption); 25532 25533 // required validator 25534 if (multiple) { 25535 ngModelCtrl.$isEmpty = function(value) { 25536 return !value || value.length === 0; 25537 }; 25538 } 25539 25540 if (optionsExp) setupAsOptions(scope, element, ngModelCtrl); 25541 else if (multiple) setupAsMultiple(scope, element, ngModelCtrl); 25542 else setupAsSingle(scope, element, ngModelCtrl, selectCtrl); 25543 25544 25545 //////////////////////////// 25546 25547 25548 25549 function setupAsSingle(scope, selectElement, ngModelCtrl, selectCtrl) { 25550 ngModelCtrl.$render = function() { 25551 var viewValue = ngModelCtrl.$viewValue; 25552 25553 if (selectCtrl.hasOption(viewValue)) { 25554 if (unknownOption.parent()) unknownOption.remove(); 25555 selectElement.val(viewValue); 25556 if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy 25557 } else { 25558 if (isUndefined(viewValue) && emptyOption) { 25559 selectElement.val(''); 25560 } else { 25561 selectCtrl.renderUnknownOption(viewValue); 25562 } 25563 } 25564 }; 25565 25566 selectElement.on('change', function() { 25567 scope.$apply(function() { 25568 if (unknownOption.parent()) unknownOption.remove(); 25569 ngModelCtrl.$setViewValue(selectElement.val()); 25570 }); 25571 }); 25572 } 25573 25574 function setupAsMultiple(scope, selectElement, ctrl) { 25575 var lastView; 25576 ctrl.$render = function() { 25577 var items = new HashMap(ctrl.$viewValue); 25578 forEach(selectElement.find('option'), function(option) { 25579 option.selected = isDefined(items.get(option.value)); 25580 }); 25581 }; 25582 25583 // we have to do it on each watch since ngModel watches reference, but 25584 // we need to work of an array, so we need to see if anything was inserted/removed 25585 scope.$watch(function selectMultipleWatch() { 25586 if (!equals(lastView, ctrl.$viewValue)) { 25587 lastView = shallowCopy(ctrl.$viewValue); 25588 ctrl.$render(); 25589 } 25590 }); 25591 25592 selectElement.on('change', function() { 25593 scope.$apply(function() { 25594 var array = []; 25595 forEach(selectElement.find('option'), function(option) { 25596 if (option.selected) { 25597 array.push(option.value); 25598 } 25599 }); 25600 ctrl.$setViewValue(array); 25601 }); 25602 }); 25603 } 25604 25605 function setupAsOptions(scope, selectElement, ctrl) { 25606 var match; 25607 25608 if (!(match = optionsExp.match(NG_OPTIONS_REGEXP))) { 25609 throw ngOptionsMinErr('iexp', 25610 "Expected expression in form of " + 25611 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 25612 " but got '{0}'. Element: {1}", 25613 optionsExp, startingTag(selectElement)); 25614 } 25615 25616 var displayFn = $parse(match[2] || match[1]), 25617 valueName = match[4] || match[6], 25618 selectAs = / as /.test(match[0]) && match[1], 25619 selectAsFn = selectAs ? $parse(selectAs) : null, 25620 keyName = match[5],
25621 groupByFn = $parse(match[3] || ''), 25622 valueFn = $parse(match[2] ? match[1] : valueName), 25623 valuesFn = $parse(match[7]), 25624 track = match[8], 25625 trackFn = track ? $parse(match[8]) : null, 25626 trackKeysCache = {}, 25627 // This is an array of array of existing option groups in DOM. 25628 // We try to reuse these if possible 25629 // - optionGroupsCache[0] is the options with no option group 25630 // - optionGroupsCache[?][0] is the parent: either the SELECT or OPTGROUP element 25631 optionGroupsCache = [[{element: selectElement, label:''}]], 25632 //re-usable object to represent option's locals 25633 locals = {}; 25634 25635 if (nullOption) { 25636 // compile the element since there might be bindings in it 25637 $compile(nullOption)(scope); 25638 25639 // remove the class, which is added automatically because we recompile the element and it 25640 // becomes the compilation root 25641 nullOption.removeClass('ng-scope'); 25642 25643 // we need to remove it before calling selectElement.empty() because otherwise IE will 25644 // remove the label from the element. wtf? 25645 nullOption.remove(); 25646 } 25647 25648 // clear contents, we'll add what's needed based on the model 25649 selectElement.empty(); 25650 25651 selectElement.on('change', selectionChanged); 25652 25653 ctrl.$render = render; 25654 25655 scope.$watchCollection(valuesFn, scheduleRendering); 25656 scope.$watchCollection(getLabels, scheduleRendering); 25657 25658 if (multiple) { 25659 scope.$watchCollection(function() { return ctrl.$modelValue; }, scheduleRendering); 25660 } 25661 25662 // ------------------------------------------------------------------ // 25663 25664 function callExpression(exprFn, key, value) { 25665 locals[valueName] = value; 25666 if (keyName) locals[keyName] = key; 25667 return exprFn(scope, locals); 25668 } 25669 25670 function selectionChanged() { 25671 scope.$apply(function() { 25672 var collection = valuesFn(scope) || []; 25673 var viewValue; 25674 if (multiple) { 25675 viewValue = []; 25676 forEach(selectElement.val(), function(selectedKey) { 25677 selectedKey = trackFn ? trackKeysCache[selectedKey] : selectedKey; 25678 viewValue.push(getViewValue(selectedKey, collection[selectedKey])); 25679 }); 25680 } else { 25681 var selectedKey = trackFn ? trackKeysCache[selectElement.val()] : selectElement.val(); 25682 viewValue = getViewValue(selectedKey, collection[selectedKey]); 25683 } 25684 ctrl.$setViewValue(viewValue); 25685 render(); 25686 }); 25687 } 25688 25689 function getViewValue(key, value) { 25690 if (key === '?') { 25691 return undefined; 25692 } else if (key === '') { 25693 return null; 25694 } else { 25695 var viewValueFn = selectAsFn ? selectAsFn : valueFn; 25696 return callExpression(viewValueFn, key, value); 25697 } 25698 } 25699 25700 function getLabels() { 25701 var values = valuesFn(scope); 25702 var toDisplay; 25703 if (values && isArray(values)) { 25704 toDisplay = new Array(values.length); 25705 for (var i = 0, ii = values.length; i < ii; i++) { 25706 toDisplay[i] = callExpression(displayFn, i, values[i]); 25707 } 25708 return toDisplay; 25709 } else if (values) { 25710 // TODO: Add a test for this case 25711 toDisplay = {}; 25712 for (var prop in values) { 25713 if (values.hasOwnProperty(prop)) { 25714 toDisplay[prop] = callExpression(displayFn, prop, values[prop]); 25715 } 25716 } 25717 } 25718 return toDisplay; 25719 } 25720 25721 function createIsSelectedFn(viewValue) { 25722 var selectedSet; 25723 if (multiple) { 25724 if (trackFn && isArray(viewValue)) { 25725 25726 selectedSet = new HashMap([]); 25727 for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) { 25728 // tracking by key 25729 selectedSet.put(callExpression(trackFn, null, viewValue[trackIndex]), true); 25730 } 25731 } else { 25732 selectedSet = new HashMap(viewValue); 25733 } 25734 } else if (trackFn) { 25735 viewValue = callExpression(trackFn, null, viewValue); 25736 } 25737 25738 return function isSelected(key, value) { 25739 var compareValueFn; 25740 if (trackFn) { 25741 compareValueFn = trackFn; 25742 } else if (selectAsFn) { 25743 compareValueFn = selectAsFn; 25744 } else { 25745 compareValueFn = valueFn; 25746 } 25747 25748 if (multiple) { 25749 return isDefined(selectedSet.remove(callExpression(compareValueFn, key, value))); 25750 } else { 25751 return viewValue === callExpression(compareValueFn, key, value); 25752 } 25753 }; 25754 } 25755 25756 function scheduleRendering() { 25757 if (!renderScheduled) { 25758 scope.$$postDigest(render); 25759 renderScheduled = true; 25760 } 25761 } 25762 25763 /** 25764 * A new labelMap is created with each render. 25765 * This function is called for each existing option with added=false, 25766 * and each new option with added=true. 25767 * - Labels that are passed to this method twice, 25768 * (once with added=true and once with added=false) will end up with a value of 0, and 25769 * will cause no change to happen to the corresponding option. 25770 * - Labels that are passed to this method only once with added=false will end up with a 25771 * value of -1 and will eventually be passed to selectCtrl.removeOption() 25772 * - Labels that are passed to this method only once with added=true will end up with a 25773 * value of 1 and will eventually be passed to selectCtrl.addOption() 25774 */ 25775 function updateLabelMap(labelMap, label, added) { 25776 labelMap[label] = labelMap[label] || 0; 25777 labelMap[label] += (added ? 1 : -1); 25778 } 25779 25780 function render() { 25781 renderScheduled = false;
25782 25783 // Temporary location for the option groups before we render them 25784 var optionGroups = {'':[]}, 25785 optionGroupNames = [''], 25786 optionGroupName, 25787 optionGroup, 25788 option, 25789 existingParent, existingOptions, existingOption, 25790 viewValue = ctrl.$viewValue, 25791 values = valuesFn(scope) || [], 25792 keys = keyName ? sortedKeys(values) : values, 25793 key, 25794 value, 25795 groupLength, length, 25796 groupIndex, index, 25797 labelMap = {}, 25798 selected, 25799 isSelected = createIsSelectedFn(viewValue), 25800 anySelected = false, 25801 lastElement, 25802 element, 25803 label, 25804 optionId; 25805 25806 trackKeysCache = {}; 25807 25808 // We now build up the list of options we need (we merge later) 25809 for (index = 0; length = keys.length, index < length; index++) { 25810 key = index; 25811 if (keyName) { 25812 key = keys[index]; 25813 if (key.charAt(0) === '$') continue; 25814 } 25815 value = values[key]; 25816 25817 optionGroupName = callExpression(groupByFn, key, value) || ''; 25818 if (!(optionGroup = optionGroups[optionGroupName])) { 25819 optionGroup = optionGroups[optionGroupName] = []; 25820 optionGroupNames.push(optionGroupName); 25821 } 25822 25823 selected = isSelected(key, value); 25824 anySelected = anySelected || selected; 25825 25826 label = callExpression(displayFn, key, value); // what will be seen by the user 25827 25828 // doing displayFn(scope, locals) || '' overwrites zero values 25829 label = isDefined(label) ? label : ''; 25830 optionId = trackFn ? trackFn(scope, locals) : (keyName ? keys[index] : index); 25831 if (trackFn) { 25832 trackKeysCache[optionId] = key; 25833 } 25834 25835 optionGroup.push({ 25836 // either the index into array or key from object 25837 id: optionId, 25838 label: label, 25839 selected: selected // determine if we should be selected 25840 }); 25841 } 25842 if (!multiple) { 25843 if (nullOption || viewValue === null) { 25844 // insert null option if we have a placeholder, or the model is null 25845 optionGroups[''].unshift({id:'', label:'', selected:!anySelected}); 25846 } else if (!anySelected) { 25847 // option could not be found, we have to insert the undefined item 25848 optionGroups[''].unshift({id:'?', label:'', selected:true}); 25849 } 25850 } 25851 25852 // Now we need to update the list of DOM nodes to match the optionGroups we computed above 25853 for (groupIndex = 0, groupLength = optionGroupNames.length; 25854 groupIndex < groupLength; 25855 groupIndex++) { 25856 // current option group name or '' if no group 25857 optionGroupName = optionGroupNames[groupIndex]; 25858 25859 // list of options for that group. (first item has the parent) 25860 optionGroup = optionGroups[optionGroupName]; 25861 25862 if (optionGroupsCache.length <= groupIndex) { 25863 // we need to grow the optionGroups 25864 existingParent = { 25865 element: optGroupTemplate.clone().attr('label', optionGroupName), 25866 label: optionGroup.label 25867 }; 25868 existingOptions = [existingParent]; 25869 optionGroupsCache.push(existingOptions); 25870 selectElement.append(existingParent.element); 25871 } else { 25872 existingOptions = optionGroupsCache[groupIndex]; 25873 existingParent = existingOptions[0]; // either SELECT (no group) or OPTGROUP element 25874 25875 // update the OPTGROUP label if not the same. 25876 if (existingParent.label != optionGroupName) { 25877 existingParent.element.attr('label', existingParent.label = optionGroupName); 25878 } 25879 } 25880 25881 lastElement = null; // start at the beginning 25882 for (index = 0, length = optionGroup.length; index < length; index++) { 25883 option = optionGroup[index]; 25884 if ((existingOption = existingOptions[index + 1])) { 25885 // reuse elements 25886 lastElement = existingOption.element; 25887 if (existingOption.label !== option.label) { 25888 updateLabelMap(labelMap, existingOption.label, false); 25889 updateLabelMap(labelMap, option.label, true); 25890 lastElement.text(existingOption.label = option.label); 25891 lastElement.prop('label', existingOption.label); 25892 } 25893 if (existingOption.id !== option.id) { 25894 lastElement.val(existingOption.id = option.id); 25895 } 25896 // lastElement.prop('selected') provided by jQuery has side-effects 25897 if (lastElement[0].selected !== option.selected) {
25898 lastElement.prop('selected', (existingOption.selected = option.selected)); 25899 if (msie) { 25900 // See #7692 25901 // The selected item wouldn't visually update on IE without this. 25902 // Tested on Win7: IE9, IE10 and IE11. Future IEs should be tested as well 25903 lastElement.prop('selected', existingOption.selected); 25904 } 25905 } 25906 } else { 25907 // grow elements 25908 25909 // if it's a null option 25910 if (option.id === '' && nullOption) { 25911 // put back the pre-compiled element 25912 element = nullOption; 25913 } else { 25914 // jQuery(v1.4.2) Bug: We should be able to chain the method calls, but 25915 // in this version of jQuery on some browser the .text() returns a string 25916 // rather then the element. 25917 (element = optionTemplate.clone()) 25918 .val(option.id) 25919 .prop('selected', option.selected) 25920 .attr('selected', option.selected) 25921 .prop('label', option.label) 25922 .text(option.label); 25923 } 25924 25925 existingOptions.push(existingOption = { 25926 element: element, 25927 label: option.label, 25928 id: option.id, 25929 selected: option.selected 25930 }); 25931 updateLabelMap(labelMap, option.label, true); 25932 if (lastElement) { 25933 lastElement.after(element); 25934 } else { 25935 existingParent.element.append(element); 25936 } 25937 lastElement = element; 25938 } 25939 } 25940 // remove any excessive OPTIONs in a group 25941 index++; // increment since the existingOptions[0] is parent element not OPTION 25942 while (existingOptions.length > index) { 25943 option = existingOptions.pop(); 25944 updateLabelMap(labelMap, option.label, false); 25945 option.element.remove(); 25946 } 25947 } 25948 // remove any excessive OPTGROUPs from select 25949 while (optionGroupsCache.length > groupIndex) { 25950 // remove all the labels in the option group 25951 optionGroup = optionGroupsCache.pop(); 25952 for (index = 1; index < optionGroup.length; ++index) { 25953 updateLabelMap(labelMap, optionGroup[index].label, false); 25954 } 25955 optionGroup[0].element.remove(); 25956 } 25957 forEach(labelMap, function(count, label) { 25958 if (count > 0) { 25959 selectCtrl.addOption(label); 25960 } else if (count < 0) { 25961 selectCtrl.removeOption(label); 25962 } 25963 }); 25964 } 25965 } 25966 } 25967 }; 25968}]; 25969 25970var optionDirective = ['$interpolate', function($interpolate) { 25971 var nullSelectCtrl = { 25972 addOption: noop, 25973 removeOption: noop 25974 }; 25975 25976 return { 25977 restrict: 'E', 25978 priority: 100, 25979 compile: function(element, attr) { 25980 if (isUndefined(attr.value)) { 25981 var interpolateFn = $interpolate(element.text(), true); 25982 if (!interpolateFn) { 25983 attr.$set('value', element.text()); 25984 } 25985 } 25986 25987 return function(scope, element, attr) { 25988 var selectCtrlName = '$selectController', 25989 parent = element.parent(), 25990 selectCtrl = parent.data(selectCtrlName) || 25991 parent.parent().data(selectCtrlName); // in case we are in optgroup 25992 25993 if (!selectCtrl || !selectCtrl.databound) { 25994 selectCtrl = nullSelectCtrl; 25995 } 25996 25997 if (interpolateFn) { 25998 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 25999 attr.$set('value', newVal); 26000 if (oldVal !== newVal) { 26001 selectCtrl.removeOption(oldVal); 26002 } 26003 selectCtrl.addOption(newVal, element); 26004 }); 26005 } else { 26006 selectCtrl.addOption(attr.value, element); 26007 } 26008 26009 element.on('$destroy', function() { 26010 selectCtrl.removeOption(attr.value); 26011 }); 26012 }; 26013 } 26014 }; 26015}]; 26016 26017var styleDirective = valueFn({ 26018 restrict: 'E', 26019 terminal: false 26020}); 26021 26022var requiredDirective = function() { 26023 return { 26024 restrict: 'A', 26025 require: '?ngModel', 26026 link: function(scope, elm, attr, ctrl) { 26027 if (!ctrl) return; 26028 attr.required = true; // force truthy in case we are on non input element 26029 26030 ctrl.$validators.required = function(modelValue, viewValue) { 26031 return !attr.required || !ctrl.$isEmpty(viewValue); 26032 }; 26033 26034 attr.$observe('required', function() { 26035 ctrl.$validate(); 26036 }); 26037 } 26038 }; 26039}; 26040 26041 26042var patternDirective = function() { 26043 return { 26044 restrict: 'A', 26045 require: '?ngModel', 26046 link: function(scope, elm, attr, ctrl) { 26047 if (!ctrl) return; 26048 26049 var regexp, patternExp = attr.ngPattern || attr.pattern; 26050 attr.$observe('pattern', function(regex) { 26051 if (isString(regex) && regex.length > 0) { 26052 regex = new RegExp('^' + regex + '$'); 26053 } 26054 26055 if (regex && !regex.test) { 26056 throw minErr('ngPattern')('noregexp', 26057 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 26058 regex, startingTag(elm)); 26059 } 26060 26061 regexp = regex || undefined; 26062 ctrl.$validate(); 26063 }); 26064 26065 ctrl.$validators.pattern = function(value) { 26066 return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value); 26067 }; 26068 } 26069 }; 26070}; 26071 26072 26073var maxlengthDirective = function() { 26074 return { 26075 restrict: 'A', 26076 require: '?ngModel', 26077 link: function(scope, elm, attr, ctrl) { 26078 if (!ctrl) return; 26079 26080 var maxlength = -1; 26081 attr.$observe('maxlength', function(value) { 26082 var intVal = int(value); 26083 maxlength = isNaN(intVal) ? -1 : intVal; 26084 ctrl.$validate(); 26085 }); 26086 ctrl.$validators.maxlength = function(modelValue, viewValue) { 26087 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); 26088 }; 26089 } 26090 }; 26091}; 26092 26093var minlengthDirective = function() { 26094 return { 26095 restrict: 'A', 26096 require: '?ngModel', 26097 link: function(scope, elm, attr, ctrl) { 26098 if (!ctrl) return; 26099 26100 var minlength = 0; 26101 attr.$observe('minlength', function(value) { 26102 minlength = int(value) || 0; 26103 ctrl.$validate(); 26104 }); 26105 ctrl.$validators.minlength = function(modelValue, viewValue) { 26106 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; 26107 }; 26108 } 26109 }; 26110}; 26111 26112 if (window.angular.bootstrap) { 26113 //AngularJS is already loaded, so we can return here... 26114 console.log('WARNING: Tried to load angular more than once.'); 26115 return; 26116 } 26117 26118 //try to bind to jquery now so that one can write jqLite(document).ready() 26119 //but we will rebind on bootstrap again. 26120 bindJQuery(); 26121 26122 publishExternalAPI(angular); 26123 26124 jqLite(document).ready(function() { 26125 angularInit(document, bootstrap); 26126 }); 26127 26128})(window, document); 26129 26130!window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}</style>');
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.