1/** 2 * @license AngularJS v1.4.1 3 * (c) 2010-2015 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 SKIP_INDEXES = 2; 42 43 var templateArgs = arguments, 44 code = templateArgs[0], 45 message = '[' + (module ? module + ':' : '') + code + '] ', 46 template = templateArgs[1], 47 paramPrefix, i; 48 49 message += template.replace(/\{\d+\}/g, function(match) { 50 var index = +match.slice(1, -1), 51 shiftedIndex = index + SKIP_INDEXES; 52 53 if (shiftedIndex < templateArgs.length) { 54 return toDebugString(templateArgs[shiftedIndex]); 55 } 56 57 return match; 58 }); 59 60 message += '\nhttp://errors.angularjs.org/1.4.1/' + 61 (module ? module + '/' : '') + code; 62 63 for (i = SKIP_INDEXES, paramPrefix = '?'; i < templateArgs.length; i++, paramPrefix = '&') { 64 message += paramPrefix + 'p' + (i - SKIP_INDEXES) + '=' + 65 encodeURIComponent(toDebugString(templateArgs[i])); 66 } 67 68 return new ErrorConstructor(message); 69 }; 70} 71 72/* We need to tell jshint what variables are being exported */ 73/* global angular: true, 74 msie: true, 75 jqLite: true, 76 jQuery: true, 77 slice: true, 78 splice: true, 79 push: true, 80 toString: true, 81 ngMinErr: true, 82 angularModule: true, 83 uid: true, 84 REGEX_STRING_REGEXP: true, 85 VALIDITY_STATE_PROPERTY: true, 86 87 lowercase: true, 88 uppercase: true, 89 manualLowercase: true, 90 manualUppercase: true, 91 nodeName_: true, 92 isArrayLike: true, 93 forEach: true, 94 forEachSorted: true, 95 reverseParams: true, 96 nextUid: true, 97 setHashKey: true, 98 extend: true, 99 toInt: true, 100 inherit: true, 101 merge: true, 102 noop: true, 103 identity: true, 104 valueFn: true, 105 isUndefined: true, 106 isDefined: true, 107 isObject: true, 108 isBlankObject: true, 109 isString: true, 110 isNumber: true, 111 isDate: true, 112 isArray: true, 113 isFunction: true, 114 isRegExp: true, 115 isWindow: true, 116 isScope: true, 117 isFile: true, 118 isFormData: true, 119 isBlob: true, 120 isBoolean: true, 121 isPromiseLike: true, 122 trim: true, 123 escapeForRegexp: true, 124 isElement: true, 125 makeMap: true, 126 includes: true, 127 arrayRemove: true, 128 copy: true, 129 shallowCopy: true, 130 equals: true, 131 csp: true, 132 jq: true, 133 concat: true, 134 sliceArgs: true, 135 bind: true, 136 toJsonReplacer: true, 137 toJson: true, 138 fromJson: true, 139 convertTimezoneToLocal: true, 140 timezoneToOffset: true, 141 startingTag: true, 142 tryDecodeURIComponent: true, 143 parseKeyValue: true, 144 toKeyValue: true, 145 encodeUriSegment: true, 146 encodeUriQuery: true, 147 angularInit: true, 148 bootstrap: true, 149 getTestability: true, 150 snake_case: true, 151 bindJQuery: true, 152 assertArg: true, 153 assertArgFn: true, 154 assertNotHasOwnProperty: true, 155 getter: true, 156 getBlockNodes: true, 157 hasOwnProperty: true, 158 createMap: true, 159 160 NODE_TYPE_ELEMENT: true, 161 NODE_TYPE_ATTRIBUTE: true, 162 NODE_TYPE_TEXT: true, 163 NODE_TYPE_COMMENT: true, 164 NODE_TYPE_DOCUMENT: true, 165 NODE_TYPE_DOCUMENT_FRAGMENT: true, 166*/ 167
168//////////////////////////////////// 169 170/** 171 * @ngdoc module 172 * @name ng 173 * @module ng 174 * @description 175 * 176 * # ng (core module) 177 * The ng module is loaded by default when an AngularJS application is started. The module itself 178 * contains the essential components for an AngularJS application to function. The table below 179 * lists a high level breakdown of each of the services/factories, filters, directives and testing 180 * components available within this core module. 181 * 182 * <div doc-module-components="ng"></div> 183 */ 184 185var REGEX_STRING_REGEXP = /^\/(.+)\/([a-z]*)$/; 186 187// The name of a form control's ValidityState property. 188// This is used so that it's possible for internal tests to create mock ValidityStates. 189var VALIDITY_STATE_PROPERTY = 'validity'; 190 191/** 192 * @ngdoc function 193 * @name angular.lowercase 194 * @module ng 195 * @kind function 196 * 197 * @description Converts the specified string to lowercase. 198 * @param {string} string String to be converted to lowercase. 199 * @returns {string} Lowercased string. 200 */ 201var lowercase = function(string) {return isString(string) ? string.toLowerCase() : string;}; 202var hasOwnProperty = Object.prototype.hasOwnProperty; 203 204/** 205 * @ngdoc function 206 * @name angular.uppercase 207 * @module ng 208 * @kind function 209 * 210 * @description Converts the specified string to uppercase. 211 * @param {string} string String to be converted to uppercase. 212 * @returns {string} Uppercased string. 213 */ 214var uppercase = function(string) {return isString(string) ? string.toUpperCase() : string;}; 215 216 217var manualLowercase = function(s) { 218 /* jshint bitwise: false */ 219 return isString(s) 220 ? s.replace(/[A-Z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) | 32);}) 221 : s; 222}; 223var manualUppercase = function(s) { 224 /* jshint bitwise: false */ 225 return isString(s) 226 ? s.replace(/[a-z]/g, function(ch) {return String.fromCharCode(ch.charCodeAt(0) & ~32);}) 227 : s; 228}; 229 230 231// String#toLowerCase and String#toUpperCase don't produce correct results in browsers with Turkish 232// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods 233// with correct but slower alternatives. 234if ('i' !== 'I'.toLowerCase()) { 235 lowercase = manualLowercase; 236 uppercase = manualUppercase; 237} 238 239 240var 241 msie, // holds major version number for IE, or NaN if UA is not IE. 242 jqLite, // delay binding since jQuery could be loaded after us. 243 jQuery, // delay binding 244 slice = [].slice, 245 splice = [].splice, 246 push = [].push, 247 toString = Object.prototype.toString, 248 getPrototypeOf = Object.getPrototypeOf, 249 ngMinErr = minErr('ng'), 250 251 /** @name angular */ 252 angular = window.angular || (window.angular = {}), 253 angularModule, 254 uid = 0; 255 256/** 257 * documentMode is an IE-only property 258 * http://msdn.microsoft.com/en-us/library/ie/cc196988(v=vs.85).aspx 259 */ 260msie = document.documentMode; 261 262 263/** 264 * @private 265 * @param {*} obj 266 * @return {boolean} Returns true if `obj` is an array or array-like object (NodeList, Arguments, 267 * String ...) 268 */ 269function isArrayLike(obj) { 270 if (obj == null || isWindow(obj)) { 271 return false; 272 } 273 274 // Support: iOS 8.2 (not reproducible in simulator) 275 // "length" in obj used to prevent JIT error (gh-11508) 276 var length = "length" in Object(obj) && obj.length; 277 278 if (obj.nodeType === NODE_TYPE_ELEMENT && length) { 279 return true; 280 } 281 282 return isString(obj) || isArray(obj) || length === 0 || 283 typeof length === 'number' && length > 0 && (length - 1) in obj; 284} 285 286/** 287 * @ngdoc function 288 * @name angular.forEach 289 * @module ng 290 * @kind function 291 * 292 * @description 293 * Invokes the `iterator` function once for each item in `obj` collection, which can be either an 294 * object or an array. The `iterator` function is invoked with `iterator(value, key, obj)`, where `value` 295 * is the value of an object property or an array element, `key` is the object property key or 296 * array element index and obj is the `obj` itself. Specifying a `context` for the function is optional. 297 * 298 * It is worth noting that `.forEach` does not iterate over inherited properties because it filters 299 * using the `hasOwnProperty` method. 300 * 301 * Unlike ES262's 302 * [Array.prototype.forEach](http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.18), 303 * Providing 'undefined' or 'null' values for `obj` will not throw a TypeError, but rather just 304 * return the value provided. 305 * 306 ```js 307 var values = {name: 'misko', gender: 'male'}; 308 var log = []; 309 angular.forEach(values, function(value, key) { 310 this.push(key + ': ' + value); 311 }, log); 312 expect(log).toEqual(['name: misko', 'gender: male']); 313 ``` 314 * 315 * @param {Object|Array} obj Object to iterate over. 316 * @param {Function} iterator Iterator function. 317 * @param {Object=} context Object to become context (`this`) for the iterator function. 318 * @returns {Object|Array} Reference to `obj`. 319 */ 320 321function forEach(obj, iterator, context) { 322 var key, length; 323 if (obj) { 324 if (isFunction(obj)) { 325 for (key in obj) { 326 // Need to check if hasOwnProperty exists, 327 // as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function 328 if (key != 'prototype' && key != 'length' && key != 'name' && (!obj.hasOwnProperty || obj.hasOwnProperty(key))) { 329 iterator.call(context, obj[key], key, obj); 330 } 331 } 332 } else if (isArray(obj) || isArrayLike(obj)) { 333 var isPrimitive = typeof obj !== 'object'; 334 for (key = 0, length = obj.length; key < length; key++) { 335 if (isPrimitive || key in obj) { 336 iterator.call(context, obj[key], key, obj); 337 } 338 } 339 }
vendor: 11,067 bytes, lines 339-804
339 else if (obj.forEach && obj.forEach !== forEach) { 340 obj.forEach(iterator, context, obj); 341 } else if (isBlankObject(obj)) { 342 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 343 for (key in obj) { 344 iterator.call(context, obj[key], key, obj); 345 } 346 } else if (typeof obj.hasOwnProperty === 'function') { 347 // Slow path for objects inheriting Object.prototype, hasOwnProperty check needed 348 for (key in obj) { 349 if (obj.hasOwnProperty(key)) { 350 iterator.call(context, obj[key], key, obj); 351 } 352 } 353 } else { 354 // Slow path for objects which do not have a method `hasOwnProperty` 355 for (key in obj) { 356 if (hasOwnProperty.call(obj, key)) { 357 iterator.call(context, obj[key], key, obj); 358 } 359 } 360 } 361 } 362 return obj; 363} 364 365function forEachSorted(obj, iterator, context) { 366 var keys = Object.keys(obj).sort(); 367 for (var i = 0; i < keys.length; i++) { 368 iterator.call(context, obj[keys[i]], keys[i]); 369 } 370 return keys; 371} 372 373 374/** 375 * when using forEach the params are value, key, but it is often useful to have key, value. 376 * @param {function(string, *)} iteratorFn 377 * @returns {function(*, string)} 378 */ 379function reverseParams(iteratorFn) { 380 return function(value, key) { iteratorFn(key, value); }; 381} 382 383/** 384 * A consistent way of creating unique IDs in angular. 385 * 386 * Using simple numbers allows us to generate 28.6 million unique ids per second for 10 years before 387 * we hit number precision issues in JavaScript. 388 * 389 * Math.pow(2,53) / 60 / 60 / 24 / 365 / 10 = 28.6M 390 * 391 * @returns {number} an unique alpha-numeric string 392 */ 393function nextUid() { 394 return ++uid; 395} 396 397 398/** 399 * Set or clear the hashkey for an object. 400 * @param obj object 401 * @param h the hashkey (!truthy to delete the hashkey) 402 */ 403function setHashKey(obj, h) { 404 if (h) { 405 obj.$$hashKey = h; 406 } else { 407 delete obj.$$hashKey; 408 } 409} 410 411 412function baseExtend(dst, objs, deep) { 413 var h = dst.$$hashKey; 414 415 for (var i = 0, ii = objs.length; i < ii; ++i) { 416 var obj = objs[i]; 417 if (!isObject(obj) && !isFunction(obj)) continue; 418 var keys = Object.keys(obj); 419 for (var j = 0, jj = keys.length; j < jj; j++) { 420 var key = keys[j]; 421 var src = obj[key]; 422 423 if (deep && isObject(src)) { 424 if (!isObject(dst[key])) dst[key] = isArray(src) ? [] : {}; 425 baseExtend(dst[key], [src], true); 426 } else { 427 dst[key] = src; 428 } 429 } 430 } 431 432 setHashKey(dst, h); 433 return dst; 434} 435 436/** 437 * @ngdoc function 438 * @name angular.extend 439 * @module ng 440 * @kind function 441 * 442 * @description 443 * Extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 444 * to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 445 * by passing an empty object as the target: `var object = angular.extend({}, object1, object2)`. 446 * 447 * **Note:** Keep in mind that `angular.extend` does not support recursive merge (deep copy). Use 448 * {@link angular.merge} for this. 449 * 450 * @param {Object} dst Destination object. 451 * @param {...Object} src Source object(s). 452 * @returns {Object} Reference to `dst`. 453 */ 454function extend(dst) { 455 return baseExtend(dst, slice.call(arguments, 1), false); 456} 457 458 459/** 460* @ngdoc function 461* @name angular.merge 462* @module ng 463* @kind function 464* 465* @description 466* Deeply extends the destination object `dst` by copying own enumerable properties from the `src` object(s) 467* to `dst`. You can specify multiple `src` objects. If you want to preserve original objects, you can do so 468* by passing an empty object as the target: `var object = angular.merge({}, object1, object2)`. 469* 470* Unlike {@link angular.extend extend()}, `merge()` recursively descends into object properties of source 471* objects, performing a deep copy. 472* 473* @param {Object} dst Destination object. 474* @param {...Object} src Source object(s). 475* @returns {Object} Reference to `dst`. 476*/ 477function merge(dst) { 478 return baseExtend(dst, slice.call(arguments, 1), true); 479} 480 481 482 483function toInt(str) { 484 return parseInt(str, 10); 485} 486 487 488function inherit(parent, extra) { 489 return extend(Object.create(parent), extra); 490} 491 492/** 493 * @ngdoc function 494 * @name angular.noop 495 * @module ng 496 * @kind function 497 * 498 * @description 499 * A function that performs no operations. This function can be useful when writing code in the 500 * functional style. 501 ```js 502 function foo(callback) { 503 var result = calculateResult(); 504 (callback || angular.noop)(result); 505 } 506 ``` 507 */ 508function noop() {} 509noop.$inject = []; 510 511 512/** 513 * @ngdoc function 514 * @name angular.identity 515 * @module ng 516 * @kind function 517 * 518 * @description 519 * A function that returns its first argument. This function is useful when writing code in the 520 * functional style. 521 * 522 ```js 523 function transformer(transformationFn, value) { 524 return (transformationFn || angular.identity)(value); 525 }; 526 ``` 527 * @param {*} value to be returned. 528 * @returns {*} the value passed in. 529 */ 530function identity($) {return $;} 531identity.$inject = []; 532 533 534function valueFn(value) {return function() {return value;};} 535 536/** 537 * @ngdoc function 538 * @name angular.isUndefined 539 * @module ng 540 * @kind function 541 * 542 * @description 543 * Determines if a reference is undefined. 544 * 545 * @param {*} value Reference to check. 546 * @returns {boolean} True if `value` is undefined. 547 */ 548function isUndefined(value) {return typeof value === 'undefined';} 549 550 551/** 552 * @ngdoc function 553 * @name angular.isDefined 554 * @module ng 555 * @kind function 556 * 557 * @description 558 * Determines if a reference is defined. 559 * 560 * @param {*} value Reference to check. 561 * @returns {boolean} True if `value` is defined. 562 */ 563function isDefined(value) {return typeof value !== 'undefined';} 564 565 566/** 567 * @ngdoc function 568 * @name angular.isObject 569 * @module ng 570 * @kind function 571 * 572 * @description 573 * Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not 574 * considered to be objects. Note that JavaScript arrays are objects. 575 * 576 * @param {*} value Reference to check. 577 * @returns {boolean} True if `value` is an `Object` but not `null`. 578 */ 579function isObject(value) { 580 // http://jsperf.com/isobject4 581 return value !== null && typeof value === 'object'; 582} 583 584 585/** 586 * Determine if a value is an object with a null prototype 587 * 588 * @returns {boolean} True if `value` is an `Object` with a null prototype 589 */ 590function isBlankObject(value) { 591 return value !== null && typeof value === 'object' && !getPrototypeOf(value); 592} 593 594 595/** 596 * @ngdoc function 597 * @name angular.isString 598 * @module ng 599 * @kind function 600 * 601 * @description 602 * Determines if a reference is a `String`. 603 * 604 * @param {*} value Reference to check. 605 * @returns {boolean} True if `value` is a `String`. 606 */ 607function isString(value) {return typeof value === 'string';} 608 609 610/** 611 * @ngdoc function 612 * @name angular.isNumber 613 * @module ng 614 * @kind function 615 * 616 * @description 617 * Determines if a reference is a `Number`. 618 * 619 * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. 620 * 621 * If you wish to exclude these then you can use the native 622 * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) 623 * method. 624 * 625 * @param {*} value Reference to check. 626 * @returns {boolean} True if `value` is a `Number`. 627 */ 628function isNumber(value) {return typeof value === 'number';} 629 630 631/** 632 * @ngdoc function 633 * @name angular.isDate 634 * @module ng 635 * @kind function 636 * 637 * @description 638 * Determines if a value is a date. 639 * 640 * @param {*} value Reference to check. 641 * @returns {boolean} True if `value` is a `Date`. 642 */ 643function isDate(value) { 644 return toString.call(value) === '[object Date]'; 645} 646 647 648/** 649 * @ngdoc function 650 * @name angular.isArray 651 * @module ng 652 * @kind function 653 * 654 * @description 655 * Determines if a reference is an `Array`. 656 * 657 * @param {*} value Reference to check. 658 * @returns {boolean} True if `value` is an `Array`. 659 */ 660var isArray = Array.isArray; 661 662/** 663 * @ngdoc function 664 * @name angular.isFunction 665 * @module ng 666 * @kind function 667 * 668 * @description 669 * Determines if a reference is a `Function`. 670 * 671 * @param {*} value Reference to check. 672 * @returns {boolean} True if `value` is a `Function`. 673 */ 674function isFunction(value) {return typeof value === 'function';} 675 676 677/** 678 * Determines if a value is a regular expression object. 679 * 680 * @private 681 * @param {*} value Reference to check. 682 * @returns {boolean} True if `value` is a `RegExp`. 683 */ 684function isRegExp(value) { 685 return toString.call(value) === '[object RegExp]'; 686} 687 688 689/** 690 * Checks if `obj` is a window object. 691 * 692 * @private 693 * @param {*} obj Object to check 694 * @returns {boolean} True if `obj` is a window obj. 695 */ 696function isWindow(obj) { 697 return obj && obj.window === obj; 698} 699 700 701function isScope(obj) { 702 return obj && obj.$evalAsync && obj.$watch; 703} 704 705 706function isFile(obj) { 707 return toString.call(obj) === '[object File]'; 708} 709 710 711function isFormData(obj) { 712 return toString.call(obj) === '[object FormData]'; 713} 714 715 716function isBlob(obj) { 717 return toString.call(obj) === '[object Blob]'; 718} 719 720 721function isBoolean(value) { 722 return typeof value === 'boolean'; 723} 724 725 726function isPromiseLike(obj) { 727 return obj && isFunction(obj.then); 728} 729 730 731var TYPED_ARRAY_REGEXP = /^\[object (Uint8(Clamped)?)|(Uint16)|(Uint32)|(Int8)|(Int16)|(Int32)|(Float(32)|(64))Array\]$/; 732function isTypedArray(value) { 733 return TYPED_ARRAY_REGEXP.test(toString.call(value)); 734} 735 736 737var trim = function(value) { 738 return isString(value) ? value.trim() : value; 739}; 740 741// Copied from: 742// http://docs.closure-library.googlecode.com/git/local_closure_goog_string_string.js.source.html#line1021 743// Prereq: s is a string. 744var escapeForRegexp = function(s) { 745 return s.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g, '\\$1'). 746 replace(/\x08/g, '\\x08'); 747}; 748 749 750/** 751 * @ngdoc function 752 * @name angular.isElement 753 * @module ng 754 * @kind function 755 * 756 * @description 757 * Determines if a reference is a DOM element (or wrapped jQuery element). 758 * 759 * @param {*} value Reference to check. 760 * @returns {boolean} True if `value` is a DOM element (or wrapped jQuery element). 761 */ 762function isElement(node) { 763 return !!(node && 764 (node.nodeName // we are a direct element 765 || (node.prop && node.attr && node.find))); // we have an on and find method part of jQuery API 766} 767 768/** 769 * @param str 'key1,key2,...' 770 * @returns {object} in the form of {key1:true, key2:true, ...} 771 */ 772function makeMap(str) { 773 var obj = {}, items = str.split(","), i; 774 for (i = 0; i < items.length; i++) { 775 obj[items[i]] = true; 776 } 777 return obj; 778} 779 780 781function nodeName_(element) { 782 return lowercase(element.nodeName || (element[0] && element[0].nodeName)); 783} 784 785function includes(array, obj) { 786 return Array.prototype.indexOf.call(array, obj) != -1; 787} 788 789function arrayRemove(array, value) { 790 var index = array.indexOf(value); 791 if (index >= 0) { 792 array.splice(index, 1); 793 } 794 return index; 795} 796 797/** 798 * @ngdoc function 799 * @name angular.copy 800 * @module ng 801 * @kind function 802 * 803 * @description 804 * Creates a deep copy of `source`, which should be an object or an array.
805 * 806 * * If no destination is supplied, a copy of the object or array is created. 807 * * If a destination is provided, all of its elements (for arrays) or properties (for objects) 808 * are deleted and then all elements/properties from the source are copied to it. 809 * * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned. 810 * * If `source` is identical to 'destination' an exception will be thrown. 811 * 812 * @param {*} source The source that will be used to make a copy. 813 * Can be any type, including primitives, `null`, and `undefined`. 814 * @param {(Object|Array)=} destination Destination into which the source is copied. If 815 * provided, must be of the same type as `source`. 816 * @returns {*} The copy or updated `destination`, if `destination` was specified. 817 * 818 * @example 819 <example module="copyExample"> 820 <file name="index.html"> 821 <div ng-controller="ExampleController"> 822 <form novalidate class="simple-form"> 823 Name: <input type="text" ng-model="user.name" /><br /> 824 E-mail: <input type="email" ng-model="user.email" /><br /> 825 Gender: <input type="radio" ng-model="user.gender" value="male" />male 826 <input type="radio" ng-model="user.gender" value="female" />female<br /> 827 <button ng-click="reset()">RESET</button> 828 <button ng-click="update(user)">SAVE</button> 829 </form> 830 <pre>form = {{user | json}}</pre> 831 <pre>master = {{master | json}}</pre> 832 </div> 833 834 <script> 835 angular.module('copyExample', []) 836 .controller('ExampleController', ['$scope', function($scope) { 837 $scope.master= {}; 838 839 $scope.update = function(user) { 840 // Example with 1 argument 841 $scope.master= angular.copy(user); 842 }; 843 844 $scope.reset = function() { 845 // Example with 2 arguments 846 angular.copy($scope.master, $scope.user); 847 }; 848 849 $scope.reset(); 850 }]); 851 </script> 852 </file> 853 </example> 854 */ 855function copy(source, destination, stackSource, stackDest) { 856 if (isWindow(source) || isScope(source)) { 857 throw ngMinErr('cpws', 858 "Can't copy! Making copies of Window or Scope instances is not supported."); 859 } 860 if (isTypedArray(destination)) { 861 throw ngMinErr('cpta', 862 "Can't copy! TypedArray destination cannot be mutated."); 863 } 864 865 if (!destination) { 866 destination = source; 867 if (isObject(source)) { 868 var index; 869 if (stackSource && (index = stackSource.indexOf(source)) !== -1) { 870 return stackDest[index]; 871 } 872 873 // TypedArray, Date and RegExp have specific copy functionality and must be 874 // pushed onto the stack before returning. 875 // Array and other objects create the base object and recurse to copy child 876 // objects. The array/object will be pushed onto the stack when recursed. 877 if (isArray(source)) { 878 return copy(source, [], stackSource, stackDest); 879 } else if (isTypedArray(source)) { 880 destination = new source.constructor(source); 881 } else if (isDate(source)) { 882 destination = new Date(source.getTime()); 883 } else if (isRegExp(source)) { 884 destination = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]); 885 destination.lastIndex = source.lastIndex; 886 } else { 887 var emptyObject = Object.create(getPrototypeOf(source)); 888 return copy(source, emptyObject, stackSource, stackDest); 889 } 890 891 if (stackDest) { 892 stackSource.push(source); 893 stackDest.push(destination); 894 } 895 } 896 } else { 897 if (source === destination) throw ngMinErr('cpi', 898 "Can't copy! Source and destination are identical."); 899 900 stackSource = stackSource || []; 901 stackDest = stackDest || []; 902 903 if (isObject(source)) { 904 stackSource.push(source); 905 stackDest.push(destination); 906 } 907 908 var result, key; 909 if (isArray(source)) { 910 destination.length = 0; 911 for (var i = 0; i < source.length; i++) { 912 destination.push(copy(source[i], null, stackSource, stackDest)); 913 } 914 } else { 915 var h = destination.$$hashKey; 916 if (isArray(destination)) { 917 destination.length = 0; 918 } else { 919 forEach(destination, function(value, key) { 920 delete destination[key]; 921 }); 922 } 923 if (isBlankObject(source)) { 924 // createMap() fast path --- Safe to avoid hasOwnProperty check because prototype chain is empty 925 for (key in source) { 926 destination[key] = copy(source[key], null, stackSource, stackDest); 927 } 928 } else if (source && typeof source.hasOwnProperty === 'function') { 929 // Slow path, which must rely on hasOwnProperty 930 for (key in source) { 931 if (source.hasOwnProperty(key)) { 932 destination[key] = copy(source[key], null, stackSource, stackDest); 933 } 934 } 935 } else { 936 // Slowest path --- hasOwnProperty can't be called as a method 937 for (key in source) { 938 if (hasOwnProperty.call(source, key)) { 939 destination[key] = copy(source[key], null, stackSource, stackDest); 940 } 941 } 942 } 943 setHashKey(destination,h); 944 } 945 } 946 return destination; 947} 948 949/** 950 * Creates a shallow copy of an object, an array or a primitive. 951 * 952 * Assumes that there are no proto properties for objects. 953 */ 954function shallowCopy(src, dst) { 955 if (isArray(src)) { 956 dst = dst || []; 957 958 for (var i = 0, ii = src.length; i < ii; i++) { 959 dst[i] = src[i]; 960 } 961 } else if (isObject(src)) { 962 dst = dst || {}; 963 964 for (var key in src) { 965 if (!(key.charAt(0) === '$' && key.charAt(1) === '$')) { 966 dst[key] = src[key]; 967 } 968 } 969 } 970 971 return dst || src; 972} 973 974 975/** 976 * @ngdoc function 977 * @name angular.equals 978 * @module ng 979 * @kind function 980 * 981 * @description 982 * Determines if two objects or two values are equivalent. Supports value types, regular 983 * expressions, arrays and objects. 984 * 985 * Two objects or values are considered equivalent if at least one of the following is true: 986 * 987 * * Both objects or values pass `===` comparison. 988 * * Both objects or values are of the same type and all of their properties are equal by 989 * comparing them with `angular.equals`. 990 * * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal) 991 * * Both values represent the same regular expression (In JavaScript,
992 * /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual 993 * representation matches). 994 * 995 * During a property comparison, properties of `function` type and properties with names 996 * that begin with `$` are ignored. 997 * 998 * Scope and DOMWindow objects are being compared only by identify (`===`). 999 * 1000 * @param {*} o1 Object or value to compare. 1001 * @param {*} o2 Object or value to compare. 1002 * @returns {boolean} True if arguments are equal. 1003 */ 1004function equals(o1, o2) { 1005 if (o1 === o2) return true; 1006 if (o1 === null || o2 === null) return false; 1007 if (o1 !== o1 && o2 !== o2) return true; // NaN === NaN 1008 var t1 = typeof o1, t2 = typeof o2, length, key, keySet; 1009 if (t1 == t2) { 1010 if (t1 == 'object') { 1011 if (isArray(o1)) { 1012 if (!isArray(o2)) return false; 1013 if ((length = o1.length) == o2.length) { 1014 for (key = 0; key < length; key++) { 1015 if (!equals(o1[key], o2[key])) return false; 1016 } 1017 return true; 1018 } 1019 } else if (isDate(o1)) { 1020 if (!isDate(o2)) return false; 1021 return equals(o1.getTime(), o2.getTime()); 1022 } else if (isRegExp(o1)) { 1023 return isRegExp(o2) ? o1.toString() == o2.toString() : false; 1024 } else { 1025 if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || 1026 isArray(o2) || isDate(o2) || isRegExp(o2)) return false; 1027 keySet = createMap(); 1028 for (key in o1) { 1029 if (key.charAt(0) === '$' || isFunction(o1[key])) continue; 1030 if (!equals(o1[key], o2[key])) return false; 1031 keySet[key] = true; 1032 } 1033 for (key in o2) { 1034 if (!(key in keySet) && 1035 key.charAt(0) !== '$' && 1036 o2[key] !== undefined && 1037 !isFunction(o2[key])) return false; 1038 } 1039 return true; 1040 } 1041 } 1042 } 1043 return false; 1044} 1045 1046var csp = function() { 1047 if (isDefined(csp.isActive_)) return csp.isActive_; 1048 1049 var active = !!(document.querySelector('[ng-csp]') || 1050 document.querySelector('[data-ng-csp]')); 1051 1052 if (!active) { 1053 try { 1054 /* jshint -W031, -W054 */ 1055 new Function(''); 1056 /* jshint +W031, +W054 */ 1057 } catch (e) { 1058 active = true; 1059 } 1060 } 1061 1062 return (csp.isActive_ = active); 1063}; 1064 1065/** 1066 * @ngdoc directive 1067 * @module ng 1068 * @name ngJq 1069 * 1070 * @element ANY 1071 * @param {string=} ngJq the name of the library available under `window` 1072 * to be used for angular.element 1073 * @description 1074 * Use this directive to force the angular.element library. This should be 1075 * used to force either jqLite by leaving ng-jq blank or setting the name of 1076 * the jquery variable under window (eg. jQuery). 1077 * 1078 * Since angular looks for this directive when it is loaded (doesn't wait for the 1079 * DOMContentLoaded event), it must be placed on an element that comes before the script 1080 * which loads angular. Also, only the first instance of `ng-jq` will be used and all 1081 * others ignored. 1082 * 1083 * @example 1084 * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. 1085 ```html 1086 <!doctype html> 1087 <html ng-app ng-jq> 1088 ... 1089 ... 1090 </html> 1091 ``` 1092 * @example 1093 * This example shows how to use a jQuery based library of a different name. 1094 * The library name must be available at the top most 'window'. 1095 ```html 1096 <!doctype html> 1097 <html ng-app ng-jq="jQueryLib"> 1098 ... 1099 ... 1100 </html> 1101 ``` 1102 */ 1103var jq = function() { 1104 if (isDefined(jq.name_)) return jq.name_; 1105 var el; 1106 var i, ii = ngAttrPrefixes.length, prefix, name; 1107 for (i = 0; i < ii; ++i) { 1108 prefix = ngAttrPrefixes[i]; 1109 if (el = document.querySelector('[' + prefix.replace(':', '\\:') + 'jq]')) { 1110 name = el.getAttribute(prefix + 'jq'); 1111 break; 1112 } 1113 } 1114 1115 return (jq.name_ = name); 1116}; 1117 1118function concat(array1, array2, index) { 1119 return array1.concat(slice.call(array2, index)); 1120} 1121 1122function sliceArgs(args, startIndex) { 1123 return slice.call(args, startIndex || 0); 1124} 1125 1126 1127/* jshint -W101 */ 1128/** 1129 * @ngdoc function 1130 * @name angular.bind 1131 * @module ng 1132 * @kind function 1133 * 1134 * @description 1135 * Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for 1136 * `fn`). You can supply optional `args` that are prebound to the function. This feature is also 1137 * known as [partial application](http://en.wikipedia.org/wiki/Partial_application), as 1138 * distinguished from [function currying](http://en.wikipedia.org/wiki/Currying#Contrast_with_partial_function_application). 1139 * 1140 * @param {Object} self Context which `fn` should be evaluated in. 1141 * @param {function()} fn Function to be bound. 1142 * @param {...*} args Optional arguments to be prebound to the `fn` function call. 1143 * @returns {function()} Function that wraps the `fn` with all the specified bindings. 1144 */ 1145/* jshint +W101 */ 1146function bind(self, fn) { 1147 var curryArgs = arguments.length > 2 ? sliceArgs(arguments, 2) : []; 1148 if (isFunction(fn) && !(fn instanceof RegExp)) { 1149 return curryArgs.length 1150 ? function() { 1151 return arguments.length 1152 ? fn.apply(self, concat(curryArgs, arguments, 0)) 1153 : fn.apply(self, curryArgs); 1154 } 1155 : function() { 1156 return arguments.length 1157 ? fn.apply(self, arguments) 1158 : fn.call(self); 1159 }; 1160 } else { 1161 // in IE, native methods are not functions so they cannot be bound (note: they don't need to be) 1162 return fn; 1163 } 1164} 1165 1166 1167function toJsonReplacer(key, value) { 1168 var val = value; 1169 1170 if (typeof key === 'string' && key.charAt(0) === '$' && key.charAt(1) === '$') { 1171 val = undefined; 1172 } else if (isWindow(value)) { 1173 val = '$WINDOW'; 1174 } else if (value && document === value) { 1175 val = '$DOCUMENT'; 1176 } else if (isScope(value)) { 1177 val = '$SCOPE'; 1178 } 1179 1180 return val; 1181} 1182 1183 1184/** 1185 * @ngdoc function 1186 * @name angular.toJson 1187 * @module ng 1188 * @kind function 1189 * 1190 * @description 1191 * Serializes input into a JSON-formatted string. Properties with leading $$ characters will be 1192 * stripped since angular uses this notation internally. 1193 * 1194 * @param {Object|Array|Date|string|number} obj Input to be serialized into JSON. 1195 * @param {boolean|number}
1195 [pretty=2] If set to true, the JSON output will contain newlines and whitespace. 1196 * If set to an integer, the JSON output will contain that many spaces per indentation. 1197 * @returns {string|undefined} JSON-ified string representing `obj`. 1198 */ 1199function toJson(obj, pretty) { 1200 if (typeof obj === 'undefined') return undefined; 1201 if (!isNumber(pretty)) { 1202 pretty = pretty ? 2 : null; 1203 } 1204 return JSON.stringify(obj, toJsonReplacer, pretty); 1205} 1206 1207 1208/** 1209 * @ngdoc function 1210 * @name angular.fromJson 1211 * @module ng 1212 * @kind function 1213 * 1214 * @description 1215 * Deserializes a JSON string. 1216 * 1217 * @param {string} json JSON string to deserialize. 1218 * @returns {Object|Array|string|number} Deserialized JSON string. 1219 */ 1220function fromJson(json) { 1221 return isString(json) 1222 ? JSON.parse(json) 1223 : json; 1224} 1225 1226 1227function timezoneToOffset(timezone, fallback) { 1228 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 1229 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 1230} 1231 1232 1233function addDateMinutes(date, minutes) { 1234 date = new Date(date.getTime()); 1235 date.setMinutes(date.getMinutes() + minutes); 1236 return date; 1237} 1238 1239 1240function convertTimezoneToLocal(date, timezone, reverse) { 1241 reverse = reverse ? -1 : 1; 1242 var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); 1243 return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset())); 1244} 1245 1246 1247/** 1248 * @returns {string} Returns the string representation of the element. 1249 */ 1250function startingTag(element) { 1251 element = jqLite(element).clone(); 1252 try { 1253 // turns out IE does not let you set .html() on elements which 1254 // are not allowed to have children. So we just ignore it. 1255 element.empty(); 1256 } catch (e) {} 1257 var elemHtml = jqLite('<div>').append(element).html(); 1258 try { 1259 return element[0].nodeType === NODE_TYPE_TEXT ? lowercase(elemHtml) : 1260 elemHtml. 1261 match(/^(<[^>]+>)/)[1]. 1262 replace(/^<([\w\-]+)/, function(match, nodeName) { return '<' + lowercase(nodeName); }); 1263 } catch (e) { 1264 return lowercase(elemHtml); 1265 } 1266 1267} 1268 1269 1270///////////////////////////////////////////////// 1271 1272/** 1273 * Tries to decode the URI component without throwing an exception. 1274 * 1275 * @private 1276 * @param str value potential URI component to check. 1277 * @returns {boolean} True if `value` can be decoded 1278 * with the decodeURIComponent function. 1279 */ 1280function tryDecodeURIComponent(value) { 1281 try { 1282 return decodeURIComponent(value); 1283 } catch (e) { 1284 // Ignore any invalid uri component 1285 } 1286} 1287 1288 1289/** 1290 * Parses an escaped url query string into key-value pairs. 1291 * @returns {Object.<string,boolean|Array>} 1292 */ 1293function parseKeyValue(/**string*/keyValue) { 1294 var obj = {}, key_value, key; 1295 forEach((keyValue || "").split('&'), function(keyValue) { 1296 if (keyValue) { 1297 key_value = keyValue.replace(/\+/g,'%20').split('='); 1298 key = tryDecodeURIComponent(key_value[0]); 1299 if (isDefined(key)) { 1300 var val = isDefined(key_value[1]) ? tryDecodeURIComponent(key_value[1]) : true; 1301 if (!hasOwnProperty.call(obj, key)) { 1302 obj[key] = val; 1303 } else if (isArray(obj[key])) { 1304 obj[key].push(val); 1305 } else { 1306 obj[key] = [obj[key],val]; 1307 } 1308 } 1309 } 1310 }); 1311 return obj; 1312} 1313 1314function toKeyValue(obj) { 1315 var parts = []; 1316 forEach(obj, function(value, key) { 1317 if (isArray(value)) { 1318 forEach(value, function(arrayValue) { 1319 parts.push(encodeUriQuery(key, true) + 1320 (arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true))); 1321 }); 1322 } else { 1323 parts.push(encodeUriQuery(key, true) + 1324 (value === true ? '' : '=' + encodeUriQuery(value, true))); 1325 } 1326 }); 1327 return parts.length ? parts.join('&') : ''; 1328} 1329 1330 1331/** 1332 * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 1333 * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path 1334 * segments: 1335 * segment = *pchar 1336 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1337 * pct-encoded = "%" HEXDIG HEXDIG 1338 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1339 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1340 * / "*" / "+" / "," / ";" / "=" 1341 */ 1342function encodeUriSegment(val) { 1343 return encodeUriQuery(val, true). 1344 replace(/%26/gi, '&'). 1345 replace(/%3D/gi, '='). 1346 replace(/%2B/gi, '+'); 1347} 1348 1349 1350/** 1351 * This method is intended for encoding *key* or *value* parts of query component. We need a custom 1352 * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be 1353 * encoded per http://tools.ietf.org/html/rfc3986: 1354 * query = *( pchar / "/" / "?" ) 1355 * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 1356 * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 1357 * pct-encoded = "%" HEXDIG HEXDIG 1358 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 1359 * / "*" / "+" / "," / ";" / "=" 1360 */ 1361function encodeUriQuery(val, pctEncodeSpaces) { 1362 return encodeURIComponent(val). 1363 replace(/%40/gi, '@'). 1364 replace(/%3A/gi, ':'). 1365 replace(/%24/g, '$'). 1366 replace(/%2C/gi, ','). 1367 replace(/%3B/gi, ';'). 1368 replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 1369} 1370 1371var ngAttrPrefixes = ['ng-', 'data-ng-', 'ng:', 'x-ng-']; 1372 1373function getNgAttribute(element, ngAttr) { 1374 var attr, i, ii = ngAttrPrefixes.length; 1375 for (i = 0; i < ii; ++i) { 1376 attr = ngAttrPrefixes[i] + ngAttr; 1377 if (isString(attr = element.getAttribute(attr))) { 1378 return attr; 1379 } 1380 } 1381 return null; 1382} 1383 1384/** 1385 * @ngdoc directive 1386 * @name ngApp 1387 * @module ng 1388 * 1389 * @element ANY 1390 * @param {angular.Module} ngApp an optional application 1391 * {@link angular.module module} name to load. 1392 * @param {boolean=} ngStrictDi if this attribute is present on the app element, the injector will be 1393 * created in "strict-di" mode. This means that the application will fail to invoke functions which 1394 * do not use explicit function annotation (and are thus unsuitable for minification), as de
1394scribed 1395 * in {@link guide/di the Dependency Injection guide}, and useful debugging info will assist in 1396 * tracking down the root of these bugs. 1397 * 1398 * @description 1399 * 1400 * Use this directive to **auto-bootstrap** an AngularJS application. The `ngApp` directive 1401 * designates the **root element** of the application and is typically placed near the root element 1402 * of the page - e.g. on the `<body>` or `<html>` tags. 1403 * 1404 * Only one AngularJS application can be auto-bootstrapped per HTML document. The first `ngApp` 1405 * found in the document will be used to define the root element to auto-bootstrap as an 1406 * application. To run multiple applications in an HTML document you must manually bootstrap them using 1407 * {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other. 1408 * 1409 * You can specify an **AngularJS module** to be used as the root module for the application. This 1410 * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It 1411 * should contain the application code needed or have dependencies on other modules that will 1412 * contain the code. See {@link angular.module} for more information. 1413 * 1414 * In the example below if the `ngApp` directive were not placed on the `html` element then the 1415 * document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}` 1416 * would not be resolved to `3`. 1417 * 1418 * `ngApp` is the easiest, and most common way to bootstrap an application. 1419 * 1420 <example module="ngAppDemo"> 1421 <file name="index.html"> 1422 <div ng-controller="ngAppDemoController"> 1423 I can add: {{a}} + {{b}} = {{ a+b }} 1424 </div> 1425 </file> 1426 <file name="script.js"> 1427 angular.module('ngAppDemo', []).controller('ngAppDemoController', function($scope) { 1428 $scope.a = 1; 1429 $scope.b = 2; 1430 }); 1431 </file> 1432 </example> 1433 * 1434 * Using `ngStrictDi`, you would see something like this: 1435 * 1436 <example ng-app-included="true"> 1437 <file name="index.html"> 1438 <div ng-app="ngAppStrictDemo" ng-strict-di> 1439 <div ng-controller="GoodController1"> 1440 I can add: {{a}} + {{b}} = {{ a+b }} 1441 1442 <p>This renders because the controller does not fail to 1443 instantiate, by using explicit annotation style (see 1444 script.js for details) 1445 </p> 1446 </div> 1447 1448 <div ng-controller="GoodController2"> 1449 Name: <input ng-model="name"><br /> 1450 Hello, {{name}}! 1451 1452 <p>This renders because the controller does not fail to 1453 instantiate, by using explicit annotation style 1454 (see script.js for details) 1455 </p> 1456 </div> 1457 1458 <div ng-controller="BadController"> 1459 I can add: {{a}} + {{b}} = {{ a+b }} 1460 1461 <p>The controller could not be instantiated, due to relying 1462 on automatic function annotations (which are disabled in 1463 strict mode). As such, the content of this section is not 1464 interpolated, and there should be an error in your web console. 1465 </p> 1466 </div> 1467 </div> 1468 </file> 1469 <file name="script.js"> 1470 angular.module('ngAppStrictDemo', []) 1471 // BadController will fail to instantiate, due to relying on automatic function annotation, 1472 // rather than an explicit annotation 1473 .controller('BadController', function($scope) { 1474 $scope.a = 1; 1475 $scope.b = 2; 1476 }) 1477 // Unlike BadController, GoodController1 and GoodController2 will not fail to be instantiated, 1478 // due to using explicit annotations using the array style and $inject property, respectively. 1479 .controller('GoodController1', ['$scope', function($scope) { 1480 $scope.a = 1; 1481 $scope.b = 2; 1482 }]) 1483 .controller('GoodController2', GoodController2); 1484 function GoodController2($scope) { 1485 $scope.name = "World"; 1486 } 1487 GoodController2.$inject = ['$scope']; 1488 </file> 1489 <file name="style.css"> 1490 div[ng-controller] { 1491 margin-bottom: 1em; 1492 -webkit-border-radius: 4px; 1493 border-radius: 4px; 1494 border: 1px solid; 1495 padding: .5em; 1496 } 1497 div[ng-controller^=Good] { 1498 border-color: #d6e9c6; 1499 background-color: #dff0d8; 1500 color: #3c763d; 1501 } 1502 div[ng-controller^=Bad] { 1503 border-color: #ebccd1; 1504 background-color: #f2dede; 1505 color: #a94442; 1506 margin-bottom: 0; 1507 } 1508 </file> 1509 </example> 1510 */ 1511function angularInit(element, bootstrap) { 1512 var appElement, 1513 module, 1514 config = {}; 1515 1516 // The element `element` has priority over any other element 1517 forEach(ngAttrPrefixes, function(prefix) { 1518 var name = prefix + 'app'; 1519 1520 if (!appElement && element.hasAttribute && element.hasAttribute(name)) { 1521 appElement = element; 1522 module = element.getAttribute(name); 1523 } 1524 }); 1525 forEach(ngAttrPrefixes, function(prefix) { 1526 var name = prefix + 'app'; 1527 var candidate; 1528 1529 if (!appElement && (candidate = element.querySelector('[' + name.replace(':', '\\:') + ']'))) { 1530 appElement = candidate; 1531 module = candidate.getAttribute(name); 1532 } 1533 }); 1534 if (appElement) { 1535 config.strictDi = getNgAttribute(appElement, "strict-di") !== null; 1536 bootstrap(appElement, module ? [module] : [], config); 1537 } 1538} 1539 1540/** 1541 * @ngdoc function 1542 * @name angular.bootstrap 1543 * @module ng 1544 * @description 1545 * Use this function to manually start up angular application. 1546 * 1547 * See: {@link guide/bootstrap Bootstrap} 1548 *
1549 * Note that Protractor based end-to-end tests cannot use this function to bootstrap manually. 1550 * They must use {@link ng.directive:ngApp ngApp}. 1551 * 1552 * Angular will detect if it has been loaded into the browser more than once and only allow the 1553 * first loaded script to be bootstrapped and will report a warning to the browser console for 1554 * each of the subsequent scripts. This prevents strange results in applications, where otherwise 1555 * multiple instances of Angular try to work on the DOM. 1556 * 1557 * ```html 1558 * <!doctype html> 1559 * <html> 1560 * <body> 1561 * <div ng-controller="WelcomeController"> 1562 * {{greeting}} 1563 * </div> 1564 * 1565 * <script src="angular.js"></script> 1566 * <script> 1567 * var app = angular.module('demo', []) 1568 * .controller('WelcomeController', function($scope) { 1569 * $scope.greeting = 'Welcome!'; 1570 * }); 1571 * angular.bootstrap(document, ['demo']); 1572 * </script> 1573 * </body> 1574 * </html> 1575 * ``` 1576 * 1577 * @param {DOMElement} element DOM element which is the root of angular application. 1578 * @param {Array<String|Function|Array>=} modules an array of modules to load into the application. 1579 * Each item in the array should be the name of a predefined module or a (DI annotated) 1580 * function that will be invoked by the injector as a `config` block. 1581 * See: {@link angular.module modules} 1582 * @param {Object=} config an object for defining configuration options for the application. The 1583 * following keys are supported: 1584 * 1585 * * `strictDi` - disable automatic function annotation for the application. This is meant to 1586 * assist in finding bugs which break minified code. Defaults to `false`. 1587 * 1588 * @returns {auto.$injector} Returns the newly created injector for this app. 1589 */ 1590function bootstrap(element, modules, config) { 1591 if (!isObject(config)) config = {}; 1592 var defaultConfig = { 1593 strictDi: false 1594 }; 1595 config = extend(defaultConfig, config); 1596 var doBootstrap = function() { 1597 element = jqLite(element); 1598 1599 if (element.injector()) { 1600 var tag = (element[0] === document) ? 'document' : startingTag(element); 1601 //Encode angle brackets to prevent input from being sanitized to empty string #8683 1602 throw ngMinErr( 1603 'btstrpd', 1604 "App Already Bootstrapped with this Element '{0}'", 1605 tag.replace(/</,'<').replace(/>/,'>')); 1606 } 1607 1608 modules = modules || []; 1609 modules.unshift(['$provide', function($provide) { 1610 $provide.value('$rootElement', element); 1611 }]); 1612 1613 if (config.debugInfoEnabled) { 1614 // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`. 1615 modules.push(['$compileProvider', function($compileProvider) { 1616 $compileProvider.debugInfoEnabled(true); 1617 }]); 1618 } 1619 1620 modules.unshift('ng'); 1621 var injector = createInjector(modules, config.strictDi); 1622 injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', 1623 function bootstrapApply(scope, element, compile, injector) { 1624 scope.$apply(function() { 1625 element.data('$injector', injector); 1626 compile(element)(scope); 1627 }); 1628 }] 1629 ); 1630 return injector; 1631 }; 1632 1633 var NG_ENABLE_DEBUG_INFO = /^NG_ENABLE_DEBUG_INFO!/; 1634 var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/; 1635 1636 if (window && NG_ENABLE_DEBUG_INFO.test(window.name)) { 1637 config.debugInfoEnabled = true; 1638 window.name = window.name.replace(NG_ENABLE_DEBUG_INFO, ''); 1639 } 1640 1641 if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) { 1642 return doBootstrap(); 1643 } 1644 1645 window.name = window.name.replace(NG_DEFER_BOOTSTRAP, ''); 1646 angular.resumeBootstrap = function(extraModules) { 1647 forEach(extraModules, function(module) { 1648 modules.push(module); 1649 }); 1650 return doBootstrap(); 1651 }; 1652 1653 if (isFunction(angular.resumeDeferredBootstrap)) { 1654 angular.resumeDeferredBootstrap(); 1655 } 1656} 1657 1658/** 1659 * @ngdoc function 1660 * @name angular.reloadWithDebugInfo 1661 * @module ng 1662 * @description 1663 * Use this function to reload the current application with debug information turned on. 1664 * This takes precedence over a call to `$compileProvider.debugInfoEnabled(false)`. 1665 * 1666 * See {@link ng.$compileProvider#debugInfoEnabled} for more. 1667 */ 1668function reloadWithDebugInfo() { 1669 window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name; 1670 window.location.reload(); 1671} 1672 1673/** 1674 * @name angular.getTestability 1675 * @module ng 1676 * @description 1677 * Get the testability service for the instance of Angular on the given 1678 * element. 1679 * @param {DOMElement} element DOM element which is the root of angular application. 1680 */ 1681function getTestability(rootElement) { 1682 var injector = angular.element(rootElement).injector(); 1683 if (!injector) { 1684 throw ngMinErr('test', 1685 'no injector found for element argument to getTestability'); 1686 } 1687 return injector.get('$$testability'); 1688} 1689 1690var SNAKE_CASE_REGEXP = /[A-Z]/g; 1691function snake_case(name, separator) { 1692 separator = separator || '_'; 1693 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { 1694 return (pos ? separator : '') + letter.toLowerCase(); 1695 }); 1696} 1697 1698var bindJQueryFired = false;
1699var skipDestroyOnNextJQueryCleanData; 1700function bindJQuery() { 1701 var originalCleanData; 1702 1703 if (bindJQueryFired) { 1704 return; 1705 } 1706 1707 // bind to jQuery if present; 1708 var jqName = jq(); 1709 jQuery = window.jQuery; // use default jQuery. 1710 if (isDefined(jqName)) { // `ngJq` present 1711 jQuery = jqName === null ? undefined : window[jqName]; // if empty; use jqLite. if not empty, use jQuery specified by `ngJq`. 1712 } 1713 1714 // Use jQuery if it exists with proper functionality, otherwise default to us. 1715 // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. 1716 // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older 1717 // versions. It will not work for sure with jQuery <1.7, though. 1718 if (jQuery && jQuery.fn.on) { 1719 jqLite = jQuery; 1720 extend(jQuery.fn, { 1721 scope: JQLitePrototype.scope, 1722 isolateScope: JQLitePrototype.isolateScope, 1723 controller: JQLitePrototype.controller, 1724 injector: JQLitePrototype.injector, 1725 inheritedData: JQLitePrototype.inheritedData 1726 }); 1727 1728 // All nodes removed from the DOM via various jQuery APIs like .remove() 1729 // are passed through jQuery.cleanData. Monkey-patch this method to fire 1730 // the $destroy event on all removed nodes. 1731 originalCleanData = jQuery.cleanData; 1732 jQuery.cleanData = function(elems) { 1733 var events; 1734 if (!skipDestroyOnNextJQueryCleanData) { 1735 for (var i = 0, elem; (elem = elems[i]) != null; i++) { 1736 events = jQuery._data(elem, "events"); 1737 if (events && events.$destroy) { 1738 jQuery(elem).triggerHandler('$destroy'); 1739 } 1740 } 1741 } else { 1742 skipDestroyOnNextJQueryCleanData = false; 1743 } 1744 originalCleanData(elems); 1745 }; 1746 } else { 1747 jqLite = JQLite; 1748 } 1749 1750 angular.element = jqLite; 1751 1752 // Prevent double-proxying. 1753 bindJQueryFired = true; 1754} 1755 1756/** 1757 * throw error if the argument is falsy. 1758 */ 1759function assertArg(arg, name, reason) { 1760 if (!arg) { 1761 throw ngMinErr('areq', "Argument '{0}' is {1}", (name || '?'), (reason || "required")); 1762 } 1763 return arg; 1764} 1765 1766function assertArgFn(arg, name, acceptArrayAnnotation) { 1767 if (acceptArrayAnnotation && isArray(arg)) { 1768 arg = arg[arg.length - 1]; 1769 } 1770 1771 assertArg(isFunction(arg), name, 'not a function, got ' + 1772 (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg)); 1773 return arg; 1774} 1775 1776/** 1777 * throw error if the name given is hasOwnProperty 1778 * @param {String} name the name to test 1779 * @param {String} context the context in which the name is used, such as module or directive 1780 */ 1781function assertNotHasOwnProperty(name, context) { 1782 if (name === 'hasOwnProperty') { 1783 throw ngMinErr('badname', "hasOwnProperty is not a valid {0} name", context); 1784 } 1785} 1786 1787/** 1788 * Return the value accessible from the object by path. Any undefined traversals are ignored 1789 * @param {Object} obj starting object 1790 * @param {String} path path to traverse 1791 * @param {boolean} [bindFnToScope=true] 1792 * @returns {Object} value as accessible by path 1793 */ 1794//TODO(misko): this function needs to be removed 1795function getter(obj, path, bindFnToScope) { 1796 if (!path) return obj; 1797 var keys = path.split('.'); 1798 var key; 1799 var lastInstance = obj; 1800 var len = keys.length; 1801 1802 for (var i = 0; i < len; i++) { 1803 key = keys[i]; 1804 if (obj) { 1805 obj = (lastInstance = obj)[key]; 1806 } 1807 } 1808 if (!bindFnToScope && isFunction(obj)) { 1809 return bind(lastInstance, obj); 1810 } 1811 return obj; 1812} 1813 1814/** 1815 * Return the DOM siblings between the first and last node in the given array. 1816 * @param {Array} array like object 1817 * @returns {jqLite} jqLite collection containing the nodes 1818 */ 1819function getBlockNodes(nodes) { 1820 // TODO(perf): just check if all items in `nodes` are siblings and if they are return the original 1821 // collection, otherwise update the original collection. 1822 var node = nodes[0]; 1823 var endNode = nodes[nodes.length - 1]; 1824 var blockNodes = [node]; 1825 1826 do { 1827 node = node.nextSibling; 1828 if (!node) break; 1829 blockNodes.push(node); 1830 } while (node !== endNode); 1831 1832 return jqLite(blockNodes); 1833} 1834 1835 1836/** 1837 * Creates a new object without a prototype. This object is useful for lookup without having to 1838 * guard against prototypically inherited properties via hasOwnProperty. 1839 * 1840 * Related micro-benchmarks: 1841 * - http://jsperf.com/object-create2 1842 * - http://jsperf.com/proto-map-lookup/2 1843 * - http://jsperf.com/for-in-vs-object-keys2 1844 * 1845 * @returns {Object} 1846 */ 1847function createMap() { 1848 return Object.create(null); 1849} 1850 1851var NODE_TYPE_ELEMENT = 1; 1852var NODE_TYPE_ATTRIBUTE = 2; 1853var NODE_TYPE_TEXT = 3; 1854var NODE_TYPE_COMMENT = 8; 1855var NODE_TYPE_DOCUMENT = 9; 1856var NODE_TYPE_DOCUMENT_FRAGMENT = 11; 1857 1858/** 1859 * @ngdoc type 1860 * @name angular.Module 1861 * @module ng 1862 * @description 1863 * 1864 * Interface for configuring angular {@link angular.module modules}. 1865 */ 1866 1867function setupModuleLoader(window) { 1868 1869 var $injectorMinErr = minErr('$injector'); 1870 var ngMinErr = minErr('ng'); 1871 1872 function ensure(obj, name, factory) {
vendor: 4,138 bytes, lines 1873-1987
1873 return obj[name] || (obj[name] = factory()); 1874 } 1875 1876 var angular = ensure(window, 'angular', Object); 1877 1878 // We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap 1879 angular.$$minErr = angular.$$minErr || minErr; 1880 1881 return ensure(angular, 'module', function() { 1882 /** @type {Object.<string, angular.Module>} */ 1883 var modules = {}; 1884 1885 /** 1886 * @ngdoc function 1887 * @name angular.module 1888 * @module ng 1889 * @description 1890 * 1891 * The `angular.module` is a global place for creating, registering and retrieving Angular 1892 * modules. 1893 * All modules (angular core or 3rd party) that should be available to an application must be 1894 * registered using this mechanism. 1895 * 1896 * When passed two or more arguments, a new module is created. If passed only one argument, an 1897 * existing module (the name passed as the first argument to `module`) is retrieved. 1898 * 1899 * 1900 * # Module 1901 * 1902 * A module is a collection of services, directives, controllers, filters, and configuration information. 1903 * `angular.module` is used to configure the {@link auto.$injector $injector}. 1904 * 1905 * ```js 1906 * // Create a new module 1907 * var myModule = angular.module('myModule', []); 1908 * 1909 * // register a new service 1910 * myModule.value('appName', 'MyCoolApp'); 1911 * 1912 * // configure existing services inside initialization blocks. 1913 * myModule.config(['$locationProvider', function($locationProvider) { 1914 * // Configure existing providers 1915 * $locationProvider.hashPrefix('!'); 1916 * }]); 1917 * ``` 1918 * 1919 * Then you can create an injector and load your modules like this: 1920 * 1921 * ```js 1922 * var injector = angular.injector(['ng', 'myModule']) 1923 * ``` 1924 * 1925 * However it's more likely that you'll just use 1926 * {@link ng.directive:ngApp ngApp} or 1927 * {@link angular.bootstrap} to simplify this process for you. 1928 * 1929 * @param {!string} name The name of the module to create or retrieve. 1930 * @param {!Array.<string>=} requires If specified then new module is being created. If 1931 * unspecified then the module is being retrieved for further configuration. 1932 * @param {Function=} configFn Optional configuration function for the module. Same as 1933 * {@link angular.Module#config Module#config()}. 1934 * @returns {module} new module with the {@link angular.Module} api. 1935 */ 1936 return function module(name, requires, configFn) { 1937 var assertNotHasOwnProperty = function(name, context) { 1938 if (name === 'hasOwnProperty') { 1939 throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); 1940 } 1941 }; 1942 1943 assertNotHasOwnProperty(name, 'module'); 1944 if (requires && modules.hasOwnProperty(name)) { 1945 modules[name] = null; 1946 } 1947 return ensure(modules, name, function() { 1948 if (!requires) { 1949 throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + 1950 "the module name or forgot to load it. If registering a module ensure that you " + 1951 "specify the dependencies as the second argument.", name); 1952 } 1953 1954 /** @type {!Array.<Array.<*>>} */ 1955 var invokeQueue = []; 1956 1957 /** @type {!Array.<Function>} */ 1958 var configBlocks = []; 1959 1960 /** @type {!Array.<Function>} */ 1961 var runBlocks = []; 1962 1963 var config = invokeLater('$injector', 'invoke', 'push', configBlocks); 1964 1965 /** @type {angular.Module} */ 1966 var moduleInstance = { 1967 // Private state 1968 _invokeQueue: invokeQueue, 1969 _configBlocks: configBlocks, 1970 _runBlocks: runBlocks, 1971 1972 /** 1973 * @ngdoc property 1974 * @name angular.Module#requires 1975 * @module ng 1976 * 1977 * @description 1978 * Holds the list of modules which the injector will load before the current module is 1979 * loaded. 1980 */ 1981 requires: requires, 1982 1983 /** 1984 * @ngdoc property 1985 * @name angular.Module#name 1986 * @module ng 1987 *
1988 * @description 1989 * Name of the module. 1990 */ 1991 name: name, 1992 1993 1994 /** 1995 * @ngdoc method 1996 * @name angular.Module#provider 1997 * @module ng 1998 * @param {string} name service name 1999 * @param {Function} providerType Construction function for creating new instance of the 2000 * service. 2001 * @description 2002 * See {@link auto.$provide#provider $provide.provider()}. 2003 */ 2004 provider: invokeLaterAndSetModuleName('$provide', 'provider'), 2005 2006 /** 2007 * @ngdoc method 2008 * @name angular.Module#factory 2009 * @module ng 2010 * @param {string} name service name 2011 * @param {Function} providerFunction Function for creating new instance of the service. 2012 * @description 2013 * See {@link auto.$provide#factory $provide.factory()}. 2014 */ 2015 factory: invokeLaterAndSetModuleName('$provide', 'factory'), 2016 2017 /** 2018 * @ngdoc method 2019 * @name angular.Module#service 2020 * @module ng 2021 * @param {string} name service name 2022 * @param {Function} constructor A constructor function that will be instantiated. 2023 * @description 2024 * See {@link auto.$provide#service $provide.service()}. 2025 */ 2026 service: invokeLaterAndSetModuleName('$provide', 'service'), 2027 2028 /** 2029 * @ngdoc method 2030 * @name angular.Module#value 2031 * @module ng 2032 * @param {string} name service name 2033 * @param {*} object Service instance object. 2034 * @description 2035 * See {@link auto.$provide#value $provide.value()}. 2036 */ 2037 value: invokeLater('$provide', 'value'), 2038 2039 /** 2040 * @ngdoc method 2041 * @name angular.Module#constant 2042 * @module ng 2043 * @param {string} name constant name 2044 * @param {*} object Constant value. 2045 * @description 2046 * Because the constant are fixed, they get applied before other provide methods. 2047 * See {@link auto.$provide#constant $provide.constant()}. 2048 */ 2049 constant: invokeLater('$provide', 'constant', 'unshift'), 2050 2051 /** 2052 * @ngdoc method 2053 * @name angular.Module#decorator 2054 * @module ng 2055 * @param {string} The name of the service to decorate. 2056 * @param {Function} This function will be invoked when the service needs to be 2057 * instantiated and should return the decorated service instance. 2058 * @description 2059 * See {@link auto.$provide#decorator $provide.decorator()}. 2060 */ 2061 decorator: invokeLaterAndSetModuleName('$provide', 'decorator'), 2062 2063 /** 2064 * @ngdoc method 2065 * @name angular.Module#animation 2066 * @module ng 2067 * @param {string} name animation name 2068 * @param {Function} animationFactory Factory function for creating new instance of an 2069 * animation. 2070 * @description 2071 * 2072 * **NOTE**: animations take effect only if the **ngAnimate** module is loaded. 2073 * 2074 * 2075 * Defines an animation hook that can be later used with 2076 * {@link $animate $animate} service and directives that use this service. 2077 * 2078 * ```js 2079 * module.animation('.animation-name', function($inject1, $inject2) { 2080 * return { 2081 * eventName : function(element, done) { 2082 * //code to run the animation 2083 * //once complete, then run done() 2084 * return function cancellationFunction(element) { 2085 * //code to cancel the animation 2086 * } 2087 * } 2088 * } 2089 * }) 2090 * ``` 2091 * 2092 * See {@link ng.$animateProvider#register $animateProvider.register()} and 2093 * {@link ngAnimate ngAnimate module} for more information. 2094 */ 2095 animation: invokeLaterAndSetModuleName('$animateProvider', 'register'), 2096 2097 /** 2098 * @ngdoc method 2099 * @name angular.Module#filter 2100 * @module ng 2101 * @param {string} name Filter name - this must be a valid angular expression identifier 2102 * @param {Function} filterFactory Factory function for creating new instance of filter.
2103 * @description 2104 * See {@link ng.$filterProvider#register $filterProvider.register()}. 2105 * 2106 * <div class="alert alert-warning"> 2107 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 2108 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 2109 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 2110 * (`myapp_subsection_filterx`). 2111 * </div> 2112 */ 2113 filter: invokeLaterAndSetModuleName('$filterProvider', 'register'), 2114 2115 /** 2116 * @ngdoc method 2117 * @name angular.Module#controller 2118 * @module ng 2119 * @param {string|Object} name Controller name, or an object map of controllers where the 2120 * keys are the names and the values are the constructors. 2121 * @param {Function} constructor Controller constructor function. 2122 * @description 2123 * See {@link ng.$controllerProvider#register $controllerProvider.register()}. 2124 */ 2125 controller: invokeLaterAndSetModuleName('$controllerProvider', 'register'), 2126 2127 /** 2128 * @ngdoc method 2129 * @name angular.Module#directive 2130 * @module ng 2131 * @param {string|Object} name Directive name, or an object map of directives where the 2132 * keys are the names and the values are the factories. 2133 * @param {Function} directiveFactory Factory function for creating new instance of 2134 * directives. 2135 * @description 2136 * See {@link ng.$compileProvider#directive $compileProvider.directive()}. 2137 */ 2138 directive: invokeLaterAndSetModuleName('$compileProvider', 'directive'), 2139 2140 /** 2141 * @ngdoc method 2142 * @name angular.Module#config 2143 * @module ng 2144 * @param {Function} configFn Execute this function on module load. Useful for service 2145 * configuration. 2146 * @description 2147 * Use this method to register work which needs to be performed on module loading. 2148 * For more about how to configure services, see 2149 * {@link providers#provider-recipe Provider Recipe}. 2150 */ 2151 config: config, 2152 2153 /** 2154 * @ngdoc method 2155 * @name angular.Module#run 2156 * @module ng 2157 * @param {Function} initializationFn Execute this function after injector creation. 2158 * Useful for application initialization. 2159 * @description 2160 * Use this method to register work which should be performed when the injector is done 2161 * loading all modules. 2162 */ 2163 run: function(block) { 2164 runBlocks.push(block); 2165 return this; 2166 } 2167 }; 2168 2169 if (configFn) { 2170 config(configFn); 2171 } 2172 2173 return moduleInstance; 2174 2175 /** 2176 * @param {string} provider 2177 * @param {string} method 2178 * @param {String=} insertMethod 2179 * @returns {angular.Module} 2180 */ 2181 function invokeLater(provider, method, insertMethod, queue) { 2182 if (!queue) queue = invokeQueue; 2183 return function() { 2184 queue[insertMethod || 'push']([provider, method, arguments]); 2185 return moduleInstance; 2186 }; 2187 } 2188 2189 /** 2190 * @param {string} provider 2191 * @param {string} method 2192 * @returns {angular.Module} 2193 */ 2194 function invokeLaterAndSetModuleName(provider, method) { 2195 return function(recipeName, factoryFunction) { 2196 if (factoryFunction && isFunction(factoryFunction)) factoryFunction.$$moduleName = name; 2197 invokeQueue.push([provider, method, arguments]); 2198 return moduleInstance; 2199 }; 2200 } 2201 }); 2202 }; 2203 }); 2204 2205} 2206 2207/* global: toDebugString: true */ 2208 2209function serializeObject(obj) { 2210 var seen = []; 2211 2212 return JSON.stringify(obj, function(key, val) { 2213 val = toJsonReplacer(key, val); 2214 if (isObject(val)) { 2215 2216 if (seen.indexOf(val) >= 0) return '<<already seen>>'; 2217 2218 seen.push(val); 2219 } 2220 return val; 2221 }); 2222} 2223 2224function toDebugString(obj) { 2225 if (typeof obj === 'function') { 2226 return obj.toString().replace(/ \{[\s\S]*$/, ''); 2227 } else if (typeof obj === 'undefined') { 2228 return 'undefined'; 2229 } else if (typeof obj !== 'string') { 2230 return serializeObject(obj); 2231 } 2232 return obj; 2233} 2234 2235/* global angularModule: true, 2236 version: true, 2237 2238 $LocaleProvider, 2239 $CompileProvider, 2240 2241 htmlAnchorDirective, 2242 inputDirective, 2243 inputDirective, 2244 formDirective, 2245 scriptDirective, 2246 selectDirective, 2247 styleDirective, 2248 optionDirective, 2249 ngBindDirective, 2250 ngBindHtmlDirective, 2251 ngBindTemplateDirective, 2252 ngClassDirective, 2253 ngClassEvenDirective, 2254 ngClassOddDirective, 2255 ngCspDirective, 2256 ngCloakDirective, 2257 ngControllerDirective, 2258 ngFormDirective, 2259 ngHideDirective, 2260 ngIfDirective, 2261 ngIncludeDirective, 2262 ngIncludeFillContentDirective, 2263 ngInitDirective, 2264 ngNonBindableDirective, 2265 ngPluralizeDirective, 2266 ngRepeatDirective, 2267 ngShowDirective, 2268 ngStyleDirective, 2269 ngSwitchDirective, 2270 ngSwitchWhenDirective, 2271 ngSwitchDefaultDirective, 2272 ngOptionsDirective, 2273 ngTranscludeDirective, 2274 ngModelDirective, 2275 ngListDirective, 2276 ngChangeDirective, 2277 patternDirective, 2278 patternDirective, 2279 requiredDirective, 2280 requiredDirective, 2281 minlengthDirective, 2282 minlengthDirective, 2283 maxlengthDirective, 2284 maxlengthDirective, 2285 ngValueDirective, 2286 ngModelOptionsDirective, 2287 ngAttributeAliasDirectives, 2288 ngEventDirectives, 2289 2290 $AnchorScrollProvider, 2291 $AnimateProvider, 2292 $$CoreAnimateQueueProvider, 2293 $$CoreAnimateRunnerProvider, 2294 $BrowserProvider,
2295 $CacheFactoryProvider, 2296 $ControllerProvider, 2297 $DocumentProvider, 2298 $ExceptionHandlerProvider, 2299 $FilterProvider, 2300 $InterpolateProvider, 2301 $IntervalProvider, 2302 $$HashMapProvider, 2303 $HttpProvider, 2304 $HttpParamSerializerProvider, 2305 $HttpParamSerializerJQLikeProvider, 2306 $HttpBackendProvider, 2307 $LocationProvider, 2308 $LogProvider, 2309 $ParseProvider, 2310 $RootScopeProvider, 2311 $QProvider, 2312 $$QProvider, 2313 $$SanitizeUriProvider, 2314 $SceProvider, 2315 $SceDelegateProvider, 2316 $SnifferProvider, 2317 $TemplateCacheProvider, 2318 $TemplateRequestProvider, 2319 $$TestabilityProvider, 2320 $TimeoutProvider, 2321 $$RAFProvider, 2322 $$AsyncCallbackProvider, 2323 $WindowProvider, 2324 $$jqLiteProvider, 2325 $$CookieReaderProvider 2326*/ 2327 2328 2329/** 2330 * @ngdoc object 2331 * @name angular.version 2332 * @module ng 2333 * @description 2334 * An object that contains information about the current AngularJS version. This object has the 2335 * following properties: 2336 * 2337 * - `full` â `{string}` â Full version string, such as "0.9.18". 2338 * - `major` â `{number}` â Major version number, such as "0". 2339 * - `minor` â `{number}` â Minor version number, such as "9". 2340 * - `dot` â `{number}` â Dot version number, such as "18". 2341 * - `codeName` â `{string}` â Code name of the release, such as "jiggling-armfat". 2342 */ 2343var version = { 2344 full: '1.4.1', // all of these placeholder strings will be replaced by grunt's 2345 major: 1, // package task 2346 minor: 4, 2347 dot: 1, 2348 codeName: 'hyperionic-illumination' 2349}; 2350 2351 2352function publishExternalAPI(angular) { 2353 extend(angular, { 2354 'bootstrap': bootstrap, 2355 'copy': copy, 2356 'extend': extend, 2357 'merge': merge, 2358 'equals': equals, 2359 'element': jqLite, 2360 'forEach': forEach, 2361 'injector': createInjector, 2362 'noop': noop, 2363 'bind': bind, 2364 'toJson': toJson, 2365 'fromJson': fromJson, 2366 'identity': identity, 2367 'isUndefined': isUndefined, 2368 'isDefined': isDefined, 2369 'isString': isString, 2370 'isFunction': isFunction, 2371 'isObject': isObject, 2372 'isNumber': isNumber, 2373 'isElement': isElement, 2374 'isArray': isArray, 2375 'version': version, 2376 'isDate': isDate, 2377 'lowercase': lowercase, 2378 'uppercase': uppercase, 2379 'callbacks': {counter: 0}, 2380 'getTestability': getTestability, 2381 '$$minErr': minErr, 2382 '$$csp': csp, 2383 'reloadWithDebugInfo': reloadWithDebugInfo 2384 }); 2385 2386 angularModule = setupModuleLoader(window); 2387 try { 2388 angularModule('ngLocale'); 2389 } catch (e) { 2390 angularModule('ngLocale', []).provider('$locale', $LocaleProvider); 2391 } 2392 2393 angularModule('ng', ['ngLocale'], ['$provide', 2394 function ngModule($provide) { 2395 // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. 2396 $provide.provider({ 2397 $$sanitizeUri: $$SanitizeUriProvider 2398 }); 2399 $provide.provider('$compile', $CompileProvider). 2400 directive({ 2401 a: htmlAnchorDirective, 2402 input: inputDirective, 2403 textarea: inputDirective, 2404 form: formDirective, 2405 script: scriptDirective, 2406 select: selectDirective, 2407 style: styleDirective, 2408 option: optionDirective, 2409 ngBind: ngBindDirective, 2410 ngBindHtml: ngBindHtmlDirective, 2411 ngBindTemplate: ngBindTemplateDirective, 2412 ngClass: ngClassDirective, 2413 ngClassEven: ngClassEvenDirective, 2414 ngClassOdd: ngClassOddDirective, 2415 ngCloak: ngCloakDirective, 2416 ngController: ngControllerDirective, 2417 ngForm: ngFormDirective, 2418 ngHide: ngHideDirective, 2419 ngIf: ngIfDirective, 2420 ngInclude: ngIncludeDirective, 2421 ngInit: ngInitDirective, 2422 ngNonBindable: ngNonBindableDirective, 2423 ngPluralize: ngPluralizeDirective, 2424 ngRepeat: ngRepeatDirective, 2425 ngShow: ngShowDirective, 2426 ngStyle: ngStyleDirective, 2427 ngSwitch: ngSwitchDirective, 2428 ngSwitchWhen: ngSwitchWhenDirective, 2429 ngSwitchDefault: ngSwitchDefaultDirective, 2430 ngOptions: ngOptionsDirective, 2431 ngTransclude: ngTranscludeDirective, 2432 ngModel: ngModelDirective, 2433 ngList: ngListDirective, 2434 ngChange: ngChangeDirective, 2435 pattern: patternDirective, 2436 ngPattern: patternDirective, 2437 required: requiredDirective, 2438 ngRequired: requiredDirective, 2439 minlength: minlengthDirective, 2440 ngMinlength: minlengthDirective, 2441 maxlength: maxlengthDirective, 2442 ngMaxlength: maxlengthDirective, 2443 ngValue: ngValueDirective, 2444 ngModelOptions: ngModelOptionsDirective 2445 }). 2446 directive({ 2447 ngInclude: ngIncludeFillContentDirective 2448 }). 2449 directive(ngAttributeAliasDirectives). 2450 directive(ngEventDirectives); 2451 $provide.provider({ 2452 $anchorScroll: $AnchorScrollProvider, 2453 $animate: $AnimateProvider, 2454 $$animateQueue: $$CoreAnimateQueueProvider, 2455 $$AnimateRunner: $$CoreAnimateRunnerProvider, 2456 $browser: $BrowserProvider,
2457 $cacheFactory: $CacheFactoryProvider, 2458 $controller: $ControllerProvider, 2459 $document: $DocumentProvider, 2460 $exceptionHandler: $ExceptionHandlerProvider, 2461 $filter: $FilterProvider, 2462 $interpolate: $InterpolateProvider, 2463 $interval: $IntervalProvider, 2464 $http: $HttpProvider, 2465 $httpParamSerializer: $HttpParamSerializerProvider, 2466 $httpParamSerializerJQLike: $HttpParamSerializerJQLikeProvider, 2467 $httpBackend: $HttpBackendProvider, 2468 $location: $LocationProvider, 2469 $log: $LogProvider, 2470 $parse: $ParseProvider, 2471 $rootScope: $RootScopeProvider, 2472 $q: $QProvider, 2473 $$q: $$QProvider, 2474 $sce: $SceProvider, 2475 $sceDelegate: $SceDelegateProvider, 2476 $sniffer: $SnifferProvider, 2477 $templateCache: $TemplateCacheProvider, 2478 $templateRequest: $TemplateRequestProvider, 2479 $$testability: $$TestabilityProvider, 2480 $timeout: $TimeoutProvider, 2481 $window: $WindowProvider, 2482 $$rAF: $$RAFProvider, 2483 $$asyncCallback: $$AsyncCallbackProvider, 2484 $$jqLite: $$jqLiteProvider, 2485 $$HashMap: $$HashMapProvider, 2486 $$cookieReader: $$CookieReaderProvider 2487 }); 2488 } 2489 ]); 2490} 2491 2492/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 2493 * Any commits to this file should be reviewed with security in mind. * 2494 * Changes to this file can potentially create security vulnerabilities. * 2495 * An approval from 2 Core members with history of modifying * 2496 * this file is required. * 2497 * * 2498 * Does the change somehow allow for arbitrary javascript to be executed? * 2499 * Or allows for someone to change the prototype of built-in objects? * 2500 * Or gives undesired access to variables likes document or window? * 2501 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 2502 2503/* global JQLitePrototype: true, 2504 addEventListenerFn: true, 2505 removeEventListenerFn: true, 2506 BOOLEAN_ATTR: true, 2507 ALIASED_ATTR: true, 2508*/ 2509 2510////////////////////////////////// 2511//JQLite 2512////////////////////////////////// 2513 2514/** 2515 * @ngdoc function 2516 * @name angular.element 2517 * @module ng 2518 * @kind function 2519 * 2520 * @description 2521 * Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element. 2522 * 2523 * If jQuery is available, `angular.element` is an alias for the 2524 * [jQuery](http://api.jquery.com/jQuery/) function. If jQuery is not available, `angular.element` 2525 * delegates to Angular's built-in subset of jQuery, called "jQuery lite" or "jqLite." 2526 * 2527 * <div class="alert alert-success">jqLite is a tiny, API-compatible subset of jQuery that allows 2528 * Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most 2529 * commonly needed functionality with the goal of having a very small footprint.</div> 2530 * 2531 * To use `jQuery`, simply ensure it is loaded before the `angular.js` file. 2532 * 2533 * <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or 2534 * jqLite; they are never raw DOM references.</div> 2535 * 2536 * ## Angular's jqLite 2537 * jqLite provides only the following jQuery methods: 2538 * 2539 * - [`addClass()`](http://api.jquery.com/addClass/) 2540 * - [`after()`](http://api.jquery.com/after/) 2541 * - [`append()`](http://api.jquery.com/append/) 2542 * - [`attr()`](http://api.jquery.com/attr/) - Does not support functions as parameters 2543 * - [`bind()`](http://api.jquery.com/bind/) - Does not support namespaces, selectors or eventData 2544 * - [`children()`](http://api.jquery.com/children/) - Does not support selectors 2545 * - [`clone()`](http://api.jquery.com/clone/) 2546 * - [`contents()`](http://api.jquery.com/contents/) 2547 * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'. 2548 * - [`data()`](http://api.jquery.com/data/) 2549 * - [`detach()`](http://api.jquery.com/detach/) 2550 * - [`empty()`](http://api.jquery.com/empty/) 2551 * - [`eq()`](http://api.jquery.com/eq/) 2552 * - [`find()`](http://api.jquery.com/find/) - Limited to lookups by tag name 2553 * - [`hasClass()`](http://api.jquery.com/hasClass/) 2554 * - [`html()`](http://api.jquery.com/html/) 2555 * - [`next()`](http://api.jquery.com/next/) - Does not support selectors 2556 * - [`on()`](http://api.jquery.com/on/) - Does not support namespaces, selectors or eventData 2557 * - [`off()`](http://api.jquery.com/off/) - Does not support namespaces or selectors 2558 * - [`one()`](http://api.jquery.com/one/) - Does not support namespaces or selectors 2559 * - [`parent()`](http://api.jquery.com/parent/) - Does not support selectors 2560 * - [`prepend()`](http://api.jquery.com/prepend/) 2561 * - [`prop()`](http://api.jquery.com/prop/) 2562 * - [`ready()`](http://api.jquery.com/ready/) 2563 * - [`remove()`](http://api.jquery.com/remove/) 2564 * - [`removeAttr()`](http://api.jquery.com/removeAttr/) 2565 * - [`removeClass()`](http://api.jquery.com/removeClass/) 2566 * - [`removeData()`](http://api.jquery.com/removeData/) 2567 * - [`replaceWith()`](http://api.jquery.com/replaceWith/) 2568 * - [`text()`](http://api.jquery.com/text/) 2569 * - [`toggleClass()`](http://api.jquery.com/toggleClass/) 2570 * - [`triggerHandler()`](http://api.jquery.com/triggerHandler/) - Passes a dummy event object to handlers. 2571 * - [`unbind()`](http://api.jquery.com/unbind/) - Does not support namespaces 2572 * - [`val()`](http://api.jquery.com/val/) 2573 * - [`wrap()`](http://api.jquery.com/wrap/) 2574 * 2575 * ## jQuery/jqLite Extras 2576 * Angular also provides the following additional methods and events to both jQuery and jqLite: 2577 * 2578 * ### Events 2579 * - `$destroy` - AngularJS intercepts all jqLite/jQuery's DOM destruction a
2579pis and fires this event 2580 * on all DOM nodes being removed. This can be used to clean up any 3rd party bindings to the DOM 2581 * element before it is removed. 2582 * 2583 * ### Methods 2584 * - `controller(name)` - retrieves the controller of the current element or its parent. By default 2585 * retrieves controller associated with the `ngController` directive. If `name` is provided as 2586 * camelCase directive name, then the controller for this directive will be retrieved (e.g. 2587 * `'ngModel'`). 2588 * - `injector()` - retrieves the injector of the current element or its parent. 2589 * - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current 2590 * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to 2591 * be enabled. 2592 * - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the 2593 * current element. This getter should be used only on elements that contain a directive which starts a new isolate 2594 * scope. Calling `scope()` on this element always returns the original non-isolate scope. 2595 * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled. 2596 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top 2597 * parent element is reached. 2598 * 2599 * @param {string|DOMElement} element HTML string or DOMElement to be wrapped into jQuery. 2600 * @returns {Object} jQuery object. 2601 */ 2602 2603JQLite.expando = 'ng339'; 2604 2605var jqCache = JQLite.cache = {}, 2606 jqId = 1, 2607 addEventListenerFn = function(element, type, fn) { 2608 element.addEventListener(type, fn, false); 2609 }, 2610 removeEventListenerFn = function(element, type, fn) { 2611 element.removeEventListener(type, fn, false); 2612 }; 2613 2614/* 2615 * !!! This is an undocumented "private" function !!! 2616 */ 2617JQLite._data = function(node) { 2618 //jQuery always returns an object on cache miss 2619 return this.cache[node[this.expando]] || {}; 2620}; 2621 2622function jqNextId() { return ++jqId; } 2623 2624 2625var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g; 2626var MOZ_HACK_REGEXP = /^moz([A-Z])/; 2627var MOUSE_EVENT_MAP= { mouseleave: "mouseout", mouseenter: "mouseover"}; 2628var jqLiteMinErr = minErr('jqLite'); 2629 2630/** 2631 * Converts snake_case to camelCase. 2632 * Also there is special case for Moz prefix starting with upper case letter. 2633 * @param name Name to normalize 2634 */ 2635function camelCase(name) { 2636 return name. 2637 replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) { 2638 return offset ? letter.toUpperCase() : letter; 2639 }). 2640 replace(MOZ_HACK_REGEXP, 'Moz$1'); 2641} 2642 2643var SINGLE_TAG_REGEXP = /^<(\w+)\s*\/?>(?:<\/\1>|)$/; 2644var HTML_REGEXP = /<|&#?\w+;/; 2645var TAG_NAME_REGEXP = /<([\w:]+)/; 2646var XHTML_TAG_REGEXP = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi; 2647 2648var wrapMap = { 2649 'option': [1, '<select multiple="multiple">', '</select>'], 2650 2651 'thead': [1, '<table>', '</table>'], 2652 'col': [2, '<table><colgroup>', '</colgroup></table>'], 2653 'tr': [2, '<table><tbody>', '</tbody></table>'], 2654 'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2655 '_default': [0, "", ""] 2656}; 2657 2658wrapMap.optgroup = wrapMap.option; 2659wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; 2660wrapMap.th = wrapMap.td; 2661 2662 2663function jqLiteIsTextNode(html) { 2664 return !HTML_REGEXP.test(html); 2665} 2666 2667function jqLiteAcceptsData(node) { 2668 // The window object can accept data but has no nodeType 2669 // Otherwise we are only interested in elements (1) and documents (9) 2670 var nodeType = node.nodeType; 2671 return nodeType === NODE_TYPE_ELEMENT || !nodeType || nodeType === NODE_TYPE_DOCUMENT; 2672} 2673 2674function jqLiteHasData(node) { 2675 for (var key in jqCache[node.ng339]) { 2676 return true; 2677 } 2678 return false; 2679} 2680 2681function jqLiteBuildFragment(html, context) { 2682 var tmp, tag, wrap, 2683 fragment = context.createDocumentFragment(), 2684 nodes = [], i; 2685 2686 if (jqLiteIsTextNode(html)) { 2687 // Convert non-html into a text node 2688 nodes.push(context.createTextNode(html)); 2689 } else { 2690 // Convert html into DOM nodes 2691 tmp = tmp || fragment.appendChild(context.createElement("div")); 2692 tag = (TAG_NAME_REGEXP.exec(html) || ["", ""])[1].toLowerCase(); 2693 wrap = wrapMap[tag] || wrapMap._default; 2694 tmp.innerHTML = wrap[1] + html.replace(XHTML_TAG_REGEXP, "<$1></$2>") + wrap[2]; 2695 2696 // Descend through wrappers to the right content 2697 i = wrap[0]; 2698 while (i--) { 2699 tmp = tmp.lastChild; 2700 } 2701 2702 nodes = concat(nodes, tmp.childNodes); 2703 2704 tmp = fragment.firstChild; 2705 tmp.textContent = ""; 2706 } 2707 2708 // Remove wrapper from fragment 2709 fragment.textContent = ""; 2710 fragment.innerHTML = ""; // Clear inner HTML 2711 forEach(nodes, function(node) { 2712 fragment.appendChild(node); 2713 }); 2714 2715 return fragment; 2716} 2717 2718function jqLiteParseHTML(html, context) { 2719 context = context || document; 2720 var parsed; 2721 2722 if ((parsed = SINGLE_TAG_REGEXP.exec(html))) { 2723 return [context.createElement(parsed[1])]; 2724 } 2725 2726 if ((parsed = jqLiteBuildFragment(html, context))) { 2727 return parsed.childNodes; 2728 } 2729 2730 return []; 2731} 2732
vendor: 1,065 bytes, lines 2733-2774
2733///////////////////////////////////////////// 2734function JQLite(element) { 2735 if (element instanceof JQLite) { 2736 return element; 2737 } 2738 2739 var argIsString; 2740 2741 if (isString(element)) { 2742 element = trim(element); 2743 argIsString = true; 2744 } 2745 if (!(this instanceof JQLite)) { 2746 if (argIsString && element.charAt(0) != '<') { 2747 throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element'); 2748 } 2749 return new JQLite(element); 2750 } 2751 2752 if (argIsString) { 2753 jqLiteAddNodes(this, jqLiteParseHTML(element)); 2754 } else { 2755 jqLiteAddNodes(this, element); 2756 } 2757} 2758 2759function jqLiteClone(element) { 2760 return element.cloneNode(true); 2761} 2762 2763function jqLiteDealoc(element, onlyDescendants) { 2764 if (!onlyDescendants) jqLiteRemoveData(element); 2765 2766 if (element.querySelectorAll) { 2767 var descendants = element.querySelectorAll('*'); 2768 for (var i = 0, l = descendants.length; i < l; i++) { 2769 jqLiteRemoveData(descendants[i]); 2770 } 2771 } 2772} 2773 2774function jqLiteOff(element, type, fn, unsupported) {
2775 if (isDefined(unsupported)) throw jqLiteMinErr('offargs', 'jqLite#off() does not support the `selector` argument'); 2776 2777 var expandoStore = jqLiteExpandoStore(element); 2778 var events = expandoStore && expandoStore.events; 2779 var handle = expandoStore && expandoStore.handle; 2780 2781 if (!handle) return; //no listeners registered 2782 2783 if (!type) { 2784 for (type in events) { 2785 if (type !== '$destroy') { 2786 removeEventListenerFn(element, type, handle); 2787 } 2788 delete events[type]; 2789 } 2790 } else { 2791 forEach(type.split(' '), function(type) { 2792 if (isDefined(fn)) { 2793 var listenerFns = events[type]; 2794 arrayRemove(listenerFns || [], fn); 2795 if (listenerFns && listenerFns.length > 0) { 2796 return; 2797 } 2798 } 2799 2800 removeEventListenerFn(element, type, handle); 2801 delete events[type]; 2802 }); 2803 } 2804} 2805 2806function jqLiteRemoveData(element, name) { 2807 var expandoId = element.ng339; 2808 var expandoStore = expandoId && jqCache[expandoId]; 2809 2810 if (expandoStore) { 2811 if (name) { 2812 delete expandoStore.data[name]; 2813 return; 2814 } 2815 2816 if (expandoStore.handle) { 2817 if (expandoStore.events.$destroy) { 2818 expandoStore.handle({}, '$destroy'); 2819 } 2820 jqLiteOff(element); 2821 } 2822 delete jqCache[expandoId]; 2823 element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it 2824 } 2825} 2826 2827 2828function jqLiteExpandoStore(element, createIfNecessary) { 2829 var expandoId = element.ng339, 2830 expandoStore = expandoId && jqCache[expandoId]; 2831 2832 if (createIfNecessary && !expandoStore) { 2833 element.ng339 = expandoId = jqNextId(); 2834 expandoStore = jqCache[expandoId] = {events: {}, data: {}, handle: undefined}; 2835 } 2836 2837 return expandoStore; 2838} 2839 2840 2841function jqLiteData(element, key, value) { 2842 if (jqLiteAcceptsData(element)) { 2843 2844 var isSimpleSetter = isDefined(value); 2845 var isSimpleGetter = !isSimpleSetter && key && !isObject(key); 2846 var massGetter = !key; 2847 var expandoStore = jqLiteExpandoStore(element, !isSimpleGetter); 2848 var data = expandoStore && expandoStore.data; 2849 2850 if (isSimpleSetter) { // data('key', value) 2851 data[key] = value; 2852 } else { 2853 if (massGetter) { // data() 2854 return data; 2855 } else { 2856 if (isSimpleGetter) { // data('key') 2857 // don't force creation of expandoStore if it doesn't exist yet 2858 return data && data[key]; 2859 } else { // mass-setter: data({key1: val1, key2: val2}) 2860 extend(data, key); 2861 } 2862 } 2863 } 2864 } 2865} 2866 2867function jqLiteHasClass(element, selector) { 2868 if (!element.getAttribute) return false; 2869 return ((" " + (element.getAttribute('class') || '') + " ").replace(/[\n\t]/g, " "). 2870 indexOf(" " + selector + " ") > -1); 2871} 2872 2873function jqLiteRemoveClass(element, cssClasses) { 2874 if (cssClasses && element.setAttribute) { 2875 forEach(cssClasses.split(' '), function(cssClass) { 2876 element.setAttribute('class', trim( 2877 (" " + (element.getAttribute('class') || '') + " ") 2878 .replace(/[\n\t]/g, " ") 2879 .replace(" " + trim(cssClass) + " ", " ")) 2880 ); 2881 }); 2882 } 2883} 2884 2885function jqLiteAddClass(element, cssClasses) { 2886 if (cssClasses && element.setAttribute) { 2887 var existingClasses = (' ' + (element.getAttribute('class') || '') + ' ') 2888 .replace(/[\n\t]/g, " "); 2889 2890 forEach(cssClasses.split(' '), function(cssClass) { 2891 cssClass = trim(cssClass); 2892 if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) { 2893 existingClasses += cssClass + ' '; 2894 } 2895 }); 2896 2897 element.setAttribute('class', trim(existingClasses)); 2898 } 2899} 2900 2901 2902function jqLiteAddNodes(root, elements) { 2903 // THIS CODE IS VERY HOT. Don't make changes without benchmarking. 2904 2905 if (elements) { 2906 2907 // if a Node (the most common case) 2908 if (elements.nodeType) { 2909 root[root.length++] = elements; 2910 } else { 2911 var length = elements.length; 2912 2913 // if an Array or NodeList and not a Window 2914 if (typeof length === 'number' && elements.window !== elements) { 2915 if (length) { 2916 for (var i = 0; i < length; i++) { 2917 root[root.length++] = elements[i]; 2918 } 2919 } 2920 } else { 2921 root[root.length++] = elements; 2922 } 2923 } 2924 } 2925} 2926 2927 2928function jqLiteController(element, name) { 2929 return jqLiteInheritedData(element, '$' + (name || 'ngController') + 'Controller'); 2930} 2931 2932function jqLiteInheritedData(element, name, value) { 2933 // if element is the document object work with the html element instead 2934 // this makes $(document).scope() possible 2935 if (element.nodeType == NODE_TYPE_DOCUMENT) { 2936 element = element.documentElement; 2937 } 2938 var names = isArray(name) ? name : [name]; 2939 2940 while (element) { 2941 for (var i = 0, ii = names.length; i < ii; i++) { 2942 if ((value = jqLite.data(element, names[i])) !== undefined) return value; 2943 } 2944 2945 // If dealing with a document fragment node with a host element, and no parent, use the host 2946 // element as the parent. This enables directives within a Shadow DOM or polyfilled Shadow DOM 2947 // to lookup parent controllers. 2948 element = element.parentNode || (element.nodeType === NODE_TYPE_DOCUMENT_FRAGMENT && element.host); 2949 } 2950} 2951 2952function jqLiteEmpty(element) { 2953 jqLiteDealoc(element, true); 2954 while (element.firstChild) { 2955 element.removeChild(element.firstChild); 2956 } 2957} 2958 2959function jqLiteRemove(element, keepData) { 2960 if (!keepData) jqLiteDealoc(element); 2961 var parent = element.parentNode; 2962 if (parent) parent.removeChild(element); 2963} 2964 2965 2966function jqLiteDocumentLoaded(action, win) { 2967 win = win || window; 2968 if (win.document.readyState === 'complete') { 2969 // Force the action to be run async for consistent behaviour 2970 // from the action's point of view 2971 // i.e. it will definitely not be in a $apply 2972 win.setTimeout(action); 2973 } else { 2974 // No need to unbind this handler as load is only ever called once 2975 jqLite(win).on('load', action); 2976 } 2977} 2978
2979////////////////////////////////////////// 2980// Functions which are declared directly. 2981////////////////////////////////////////// 2982var JQLitePrototype = JQLite.prototype = { 2983 ready: function(fn) { 2984 var fired = false; 2985 2986 function trigger() { 2987 if (fired) return; 2988 fired = true; 2989 fn(); 2990 } 2991 2992 // check if document is already loaded 2993 if (document.readyState === 'complete') { 2994 setTimeout(trigger); 2995 } else { 2996 this.on('DOMContentLoaded', trigger); // works for modern browsers and IE9 2997 // we can not use jqLite since we are not done loading and jQuery could be loaded later. 2998 // jshint -W064 2999 JQLite(window).on('load', trigger); // fallback to window.onload for others 3000 // jshint +W064 3001 } 3002 }, 3003 toString: function() { 3004 var value = []; 3005 forEach(this, function(e) { value.push('' + e);}); 3006 return '[' + value.join(', ') + ']'; 3007 }, 3008 3009 eq: function(index) { 3010 return (index >= 0) ? jqLite(this[index]) : jqLite(this[this.length + index]); 3011 }, 3012 3013 length: 0, 3014 push: push, 3015 sort: [].sort, 3016 splice: [].splice 3017}; 3018 3019////////////////////////////////////////// 3020// Functions iterating getter/setters. 3021// these functions return self on setter and 3022// value on get. 3023////////////////////////////////////////// 3024var BOOLEAN_ATTR = {}; 3025forEach('multiple,selected,checked,disabled,readOnly,required,open'.split(','), function(value) { 3026 BOOLEAN_ATTR[lowercase(value)] = value; 3027}); 3028var BOOLEAN_ELEMENTS = {}; 3029forEach('input,select,option,textarea,button,form,details'.split(','), function(value) { 3030 BOOLEAN_ELEMENTS[value] = true; 3031}); 3032var ALIASED_ATTR = { 3033 'ngMinlength': 'minlength', 3034 'ngMaxlength': 'maxlength', 3035 'ngMin': 'min', 3036 'ngMax': 'max', 3037 'ngPattern': 'pattern' 3038}; 3039 3040function getBooleanAttrName(element, name) { 3041 // check dom last since we will most likely fail on name 3042 var booleanAttr = BOOLEAN_ATTR[name.toLowerCase()]; 3043 3044 // booleanAttr is here twice to minimize DOM access 3045 return booleanAttr && BOOLEAN_ELEMENTS[nodeName_(element)] && booleanAttr; 3046} 3047 3048function getAliasedAttrName(element, name) { 3049 var nodeName = element.nodeName; 3050 return (nodeName === 'INPUT' || nodeName === 'TEXTAREA') && ALIASED_ATTR[name]; 3051} 3052 3053forEach({ 3054 data: jqLiteData, 3055 removeData: jqLiteRemoveData, 3056 hasData: jqLiteHasData 3057}, function(fn, name) { 3058 JQLite[name] = fn; 3059}); 3060 3061forEach({ 3062 data: jqLiteData, 3063 inheritedData: jqLiteInheritedData, 3064 3065 scope: function(element) { 3066 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3067 return jqLite.data(element, '$scope') || jqLiteInheritedData(element.parentNode || element, ['$isolateScope', '$scope']); 3068 }, 3069 3070 isolateScope: function(element) { 3071 // Can't use jqLiteData here directly so we stay compatible with jQuery! 3072 return jqLite.data(element, '$isolateScope') || jqLite.data(element, '$isolateScopeNoTemplate'); 3073 }, 3074 3075 controller: jqLiteController, 3076 3077 injector: function(element) { 3078 return jqLiteInheritedData(element, '$injector'); 3079 }, 3080 3081 removeAttr: function(element, name) { 3082 element.removeAttribute(name); 3083 }, 3084 3085 hasClass: jqLiteHasClass, 3086 3087 css: function(element, name, value) { 3088 name = camelCase(name); 3089 3090 if (isDefined(value)) { 3091 element.style[name] = value; 3092 } else { 3093 return element.style[name]; 3094 } 3095 }, 3096 3097 attr: function(element, name, value) { 3098 var nodeType = element.nodeType; 3099 if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) { 3100 return; 3101 } 3102 var lowercasedName = lowercase(name); 3103 if (BOOLEAN_ATTR[lowercasedName]) { 3104 if (isDefined(value)) { 3105 if (!!value) { 3106 element[name] = true; 3107 element.setAttribute(name, lowercasedName); 3108 } else { 3109 element[name] = false; 3110 element.removeAttribute(lowercasedName); 3111 } 3112 } else { 3113 return (element[name] || 3114 (element.attributes.getNamedItem(name) || noop).specified) 3115 ? lowercasedName 3116 : undefined; 3117 } 3118 } else if (isDefined(value)) { 3119 element.setAttribute(name, value); 3120 } else if (element.getAttribute) { 3121 // the extra argument "2" is to get the right thing for a.href in IE, see jQuery code 3122 // some elements (e.g. Document) don't have get attribute, so return undefined 3123 var ret = element.getAttribute(name, 2); 3124 // normalize non-existing attributes to undefined (as jQuery) 3125 return ret === null ? undefined : ret; 3126 } 3127 }, 3128 3129 prop: function(element, name, value) { 3130 if (isDefined(value)) { 3131 element[name] = value; 3132 } else { 3133 return element[name]; 3134 } 3135 }, 3136 3137 text: (function() { 3138 getText.$dv = ''; 3139 return getText; 3140 3141 function getText(element, value) { 3142 if (isUndefined(value)) { 3143 var nodeType = element.nodeType; 3144 return (nodeType === NODE_TYPE_ELEMENT || nodeType === NODE_TYPE_TEXT) ? element.textContent : ''; 3145 } 3146 element.textContent = value; 3147 } 3148 })(), 3149 3150 val: function(element, value) { 3151 if (isUndefined(value)) { 3152 if (element.multiple && nodeName_(element) === 'select') { 3153 var result = [];
vendor: 3,900 bytes, lines 3154-3283
3154 forEach(element.options, function(option) { 3155 if (option.selected) { 3156 result.push(option.value || option.text); 3157 } 3158 }); 3159 return result.length === 0 ? null : result; 3160 } 3161 return element.value; 3162 } 3163 element.value = value; 3164 }, 3165 3166 html: function(element, value) { 3167 if (isUndefined(value)) { 3168 return element.innerHTML; 3169 } 3170 jqLiteDealoc(element, true); 3171 element.innerHTML = value; 3172 }, 3173 3174 empty: jqLiteEmpty 3175}, function(fn, name) { 3176 /** 3177 * Properties: writes return selection, reads return first value 3178 */ 3179 JQLite.prototype[name] = function(arg1, arg2) { 3180 var i, key; 3181 var nodeCount = this.length; 3182 3183 // jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it 3184 // in a way that survives minification. 3185 // jqLiteEmpty takes no arguments but is a setter. 3186 if (fn !== jqLiteEmpty && 3187 (((fn.length == 2 && (fn !== jqLiteHasClass && fn !== jqLiteController)) ? arg1 : arg2) === undefined)) { 3188 if (isObject(arg1)) { 3189 3190 // we are a write, but the object properties are the key/values 3191 for (i = 0; i < nodeCount; i++) { 3192 if (fn === jqLiteData) { 3193 // data() takes the whole object in jQuery 3194 fn(this[i], arg1); 3195 } else { 3196 for (key in arg1) { 3197 fn(this[i], key, arg1[key]); 3198 } 3199 } 3200 } 3201 // return self for chaining 3202 return this; 3203 } else { 3204 // we are a read, so read the first child. 3205 // TODO: do we still need this? 3206 var value = fn.$dv; 3207 // Only if we have $dv do we iterate over all, otherwise it is just the first element. 3208 var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount; 3209 for (var j = 0; j < jj; j++) { 3210 var nodeValue = fn(this[j], arg1, arg2); 3211 value = value ? value + nodeValue : nodeValue; 3212 } 3213 return value; 3214 } 3215 } else { 3216 // we are a write, so apply to all children 3217 for (i = 0; i < nodeCount; i++) { 3218 fn(this[i], arg1, arg2); 3219 } 3220 // return self for chaining 3221 return this; 3222 } 3223 }; 3224}); 3225 3226function createEventHandler(element, events) { 3227 var eventHandler = function(event, type) { 3228 // jQuery specific api 3229 event.isDefaultPrevented = function() { 3230 return event.defaultPrevented; 3231 }; 3232 3233 var eventFns = events[type || event.type]; 3234 var eventFnsLength = eventFns ? eventFns.length : 0; 3235 3236 if (!eventFnsLength) return; 3237 3238 if (isUndefined(event.immediatePropagationStopped)) { 3239 var originalStopImmediatePropagation = event.stopImmediatePropagation; 3240 event.stopImmediatePropagation = function() { 3241 event.immediatePropagationStopped = true; 3242 3243 if (event.stopPropagation) { 3244 event.stopPropagation(); 3245 } 3246 3247 if (originalStopImmediatePropagation) { 3248 originalStopImmediatePropagation.call(event); 3249 } 3250 }; 3251 } 3252 3253 event.isImmediatePropagationStopped = function() { 3254 return event.immediatePropagationStopped === true; 3255 }; 3256 3257 // Copy event handlers in case event handlers array is modified during execution. 3258 if ((eventFnsLength > 1)) { 3259 eventFns = shallowCopy(eventFns); 3260 } 3261 3262 for (var i = 0; i < eventFnsLength; i++) { 3263 if (!event.isImmediatePropagationStopped()) { 3264 eventFns[i].call(element, event); 3265 } 3266 } 3267 }; 3268 3269 // TODO: this is a hack for angularMocks/clearDataCache that makes it possible to deregister all 3270 // events on `element` 3271 eventHandler.elem = element; 3272 return eventHandler; 3273} 3274 3275////////////////////////////////////////// 3276// Functions iterating traversal. 3277// These functions chain results into a single 3278// selector. 3279////////////////////////////////////////// 3280forEach({ 3281 removeData: jqLiteRemoveData, 3282 3283 on: function jqLiteOn(element, type, fn, unsupported) {
3284 if (isDefined(unsupported)) throw jqLiteMinErr('onargs', 'jqLite#on() does not support the `selector` or `eventData` parameters'); 3285 3286 // Do not add event handlers to non-elements because they will not be cleaned up. 3287 if (!jqLiteAcceptsData(element)) { 3288 return; 3289 } 3290 3291 var expandoStore = jqLiteExpandoStore(element, true); 3292 var events = expandoStore.events; 3293 var handle = expandoStore.handle; 3294 3295 if (!handle) { 3296 handle = expandoStore.handle = createEventHandler(element, events); 3297 } 3298 3299 // http://jsperf.com/string-indexof-vs-split 3300 var types = type.indexOf(' ') >= 0 ? type.split(' ') : [type]; 3301 var i = types.length; 3302 3303 while (i--) { 3304 type = types[i]; 3305 var eventFns = events[type]; 3306 3307 if (!eventFns) { 3308 events[type] = []; 3309 3310 if (type === 'mouseenter' || type === 'mouseleave') { 3311 // Refer to jQuery's implementation of mouseenter & mouseleave 3312 // Read about mouseenter and mouseleave: 3313 // http://www.quirksmode.org/js/events_mouse.html#link8 3314 3315 jqLiteOn(element, MOUSE_EVENT_MAP[type], function(event) { 3316 var target = this, related = event.relatedTarget; 3317 // For mousenter/leave call the handler if related is outside the target. 3318 // NB: No relatedTarget if the mouse left/entered the browser window 3319 if (!related || (related !== target && !target.contains(related))) { 3320 handle(event, type); 3321 } 3322 }); 3323 3324 } else { 3325 if (type !== '$destroy') { 3326 addEventListenerFn(element, type, handle); 3327 } 3328 } 3329 eventFns = events[type]; 3330 } 3331 eventFns.push(fn); 3332 } 3333 }, 3334 3335 off: jqLiteOff, 3336 3337 one: function(element, type, fn) { 3338 element = jqLite(element); 3339 3340 //add the listener twice so that when it is called 3341 //you can remove the original function and still be 3342 //able to call element.off(ev, fn) normally 3343 element.on(type, function onFn() { 3344 element.off(type, fn); 3345 element.off(type, onFn); 3346 }); 3347 element.on(type, fn); 3348 }, 3349 3350 replaceWith: function(element, replaceNode) { 3351 var index, parent = element.parentNode; 3352 jqLiteDealoc(element); 3353 forEach(new JQLite(replaceNode), function(node) { 3354 if (index) { 3355 parent.insertBefore(node, index.nextSibling); 3356 } else { 3357 parent.replaceChild(node, element); 3358 } 3359 index = node; 3360 }); 3361 }, 3362 3363 children: function(element) { 3364 var children = []; 3365 forEach(element.childNodes, function(element) { 3366 if (element.nodeType === NODE_TYPE_ELEMENT) { 3367 children.push(element); 3368 } 3369 }); 3370 return children; 3371 }, 3372 3373 contents: function(element) { 3374 return element.contentDocument || element.childNodes || []; 3375 }, 3376 3377 append: function(element, node) { 3378 var nodeType = element.nodeType; 3379 if (nodeType !== NODE_TYPE_ELEMENT && nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT) return; 3380 3381 node = new JQLite(node); 3382 3383 for (var i = 0, ii = node.length; i < ii; i++) { 3384 var child = node[i]; 3385 element.appendChild(child); 3386 } 3387 }, 3388 3389 prepend: function(element, node) { 3390 if (element.nodeType === NODE_TYPE_ELEMENT) { 3391 var index = element.firstChild; 3392 forEach(new JQLite(node), function(child) { 3393 element.insertBefore(child, index); 3394 }); 3395 } 3396 }, 3397 3398 wrap: function(element, wrapNode) { 3399 wrapNode = jqLite(wrapNode).eq(0).clone()[0]; 3400 var parent = element.parentNode; 3401 if (parent) { 3402 parent.replaceChild(wrapNode, element); 3403 } 3404 wrapNode.appendChild(element); 3405 }, 3406 3407 remove: jqLiteRemove, 3408 3409 detach: function(element) { 3410 jqLiteRemove(element, true); 3411 }, 3412 3413 after: function(element, newElement) { 3414 var index = element, parent = element.parentNode; 3415 newElement = new JQLite(newElement); 3416 3417 for (var i = 0, ii = newElement.length; i < ii; i++) { 3418 var node = newElement[i]; 3419 parent.insertBefore(node, index.nextSibling); 3420 index = node; 3421 } 3422 }, 3423 3424 addClass: jqLiteAddClass, 3425 removeClass: jqLiteRemoveClass, 3426 3427 toggleClass: function(element, selector, condition) { 3428 if (selector) {
vendor: 5,441 bytes, lines 3429-3630
3429 forEach(selector.split(' '), function(className) { 3430 var classCondition = condition; 3431 if (isUndefined(classCondition)) { 3432 classCondition = !jqLiteHasClass(element, className); 3433 } 3434 (classCondition ? jqLiteAddClass : jqLiteRemoveClass)(element, className); 3435 }); 3436 } 3437 }, 3438 3439 parent: function(element) { 3440 var parent = element.parentNode; 3441 return parent && parent.nodeType !== NODE_TYPE_DOCUMENT_FRAGMENT ? parent : null; 3442 }, 3443 3444 next: function(element) { 3445 return element.nextElementSibling; 3446 }, 3447 3448 find: function(element, selector) { 3449 if (element.getElementsByTagName) { 3450 return element.getElementsByTagName(selector); 3451 } else { 3452 return []; 3453 } 3454 }, 3455 3456 clone: jqLiteClone, 3457 3458 triggerHandler: function(element, event, extraParameters) { 3459 3460 var dummyEvent, eventFnsCopy, handlerArgs; 3461 var eventName = event.type || event; 3462 var expandoStore = jqLiteExpandoStore(element); 3463 var events = expandoStore && expandoStore.events; 3464 var eventFns = events && events[eventName]; 3465 3466 if (eventFns) { 3467 // Create a dummy event to pass to the handlers 3468 dummyEvent = { 3469 preventDefault: function() { this.defaultPrevented = true; }, 3470 isDefaultPrevented: function() { return this.defaultPrevented === true; }, 3471 stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, 3472 isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, 3473 stopPropagation: noop, 3474 type: eventName, 3475 target: element 3476 }; 3477 3478 // If a custom event was provided then extend our dummy event with it 3479 if (event.type) { 3480 dummyEvent = extend(dummyEvent, event); 3481 } 3482 3483 // Copy event handlers in case event handlers array is modified during execution. 3484 eventFnsCopy = shallowCopy(eventFns); 3485 handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; 3486 3487 forEach(eventFnsCopy, function(fn) { 3488 if (!dummyEvent.isImmediatePropagationStopped()) { 3489 fn.apply(element, handlerArgs); 3490 } 3491 }); 3492 } 3493 } 3494}, function(fn, name) { 3495 /** 3496 * chaining functions 3497 */ 3498 JQLite.prototype[name] = function(arg1, arg2, arg3) { 3499 var value; 3500 3501 for (var i = 0, ii = this.length; i < ii; i++) { 3502 if (isUndefined(value)) { 3503 value = fn(this[i], arg1, arg2, arg3); 3504 if (isDefined(value)) { 3505 // any function which returns a value needs to be wrapped 3506 value = jqLite(value); 3507 } 3508 } else { 3509 jqLiteAddNodes(value, fn(this[i], arg1, arg2, arg3)); 3510 } 3511 } 3512 return isDefined(value) ? value : this; 3513 }; 3514 3515 // bind legacy bind/unbind to on/off 3516 JQLite.prototype.bind = JQLite.prototype.on; 3517 JQLite.prototype.unbind = JQLite.prototype.off; 3518}); 3519 3520 3521// Provider for private $$jqLite service 3522function $$jqLiteProvider() { 3523 this.$get = function $$jqLite() { 3524 return extend(JQLite, { 3525 hasClass: function(node, classes) { 3526 if (node.attr) node = node[0]; 3527 return jqLiteHasClass(node, classes); 3528 }, 3529 addClass: function(node, classes) { 3530 if (node.attr) node = node[0]; 3531 return jqLiteAddClass(node, classes); 3532 }, 3533 removeClass: function(node, classes) { 3534 if (node.attr) node = node[0]; 3535 return jqLiteRemoveClass(node, classes); 3536 } 3537 }); 3538 }; 3539} 3540 3541/** 3542 * Computes a hash of an 'obj'. 3543 * Hash of a: 3544 * string is string 3545 * number is number as string 3546 * object is either result of calling $$hashKey function on the object or uniquely generated id, 3547 * that is also assigned to the $$hashKey property of the object. 3548 * 3549 * @param obj 3550 * @returns {string} hash string such that the same input will have the same hash string. 3551 * The resulting string key is in 'type:hashKey' format. 3552 */ 3553function hashKey(obj, nextUidFn) { 3554 var key = obj && obj.$$hashKey; 3555 3556 if (key) { 3557 if (typeof key === 'function') { 3558 key = obj.$$hashKey(); 3559 } 3560 return key; 3561 } 3562 3563 var objType = typeof obj; 3564 if (objType == 'function' || (objType == 'object' && obj !== null)) { 3565 key = obj.$$hashKey = objType + ':' + (nextUidFn || nextUid)(); 3566 } else { 3567 key = objType + ':' + obj; 3568 } 3569 3570 return key; 3571} 3572 3573/** 3574 * HashMap which can use objects as keys 3575 */ 3576function HashMap(array, isolatedUid) { 3577 if (isolatedUid) { 3578 var uid = 0; 3579 this.nextUid = function() { 3580 return ++uid; 3581 }; 3582 } 3583 forEach(array, this.put, this); 3584} 3585HashMap.prototype = { 3586 /** 3587 * Store key value pair 3588 * @param key key to store can be any type 3589 * @param value value to store can be any type 3590 */ 3591 put: function(key, value) { 3592 this[hashKey(key, this.nextUid)] = value; 3593 }, 3594 3595 /** 3596 * @param key 3597 * @returns {Object} the value for the key 3598 */ 3599 get: function(key) { 3600 return this[hashKey(key, this.nextUid)]; 3601 }, 3602 3603 /** 3604 * Remove the key/value pair 3605 * @param key 3606 */ 3607 remove: function(key) { 3608 var value = this[key = hashKey(key, this.nextUid)]; 3609 delete this[key]; 3610 return value; 3611 } 3612}; 3613 3614var $$HashMapProvider = [function() { 3615 this.$get = [function() { 3616 return HashMap; 3617 }]; 3618}]; 3619 3620/** 3621 * @ngdoc function 3622 * @module ng 3623 * @name angular.injector 3624 * @kind function 3625 * 3626 * @description 3627 * Creates an injector object that can be used for retrieving services as well as for 3628 * dependency injection (see {@link guide/di dependency injection}). 3629 * 3630 * @param {Array.<string|Function>}
3630 modules A list of module functions or their aliases. See 3631 * {@link angular.module}. The `ng` module must be explicitly added. 3632 * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which 3633 * disallows argument name annotation inference. 3634 * @returns {injector} Injector object. See {@link auto.$injector $injector}. 3635 * 3636 * @example 3637 * Typical usage 3638 * ```js 3639 * // create an injector 3640 * var $injector = angular.injector(['ng']); 3641 * 3642 * // use the injector to kick off your application 3643 * // use the type inference to auto inject arguments, or use implicit injection 3644 * $injector.invoke(function($rootScope, $compile, $document) { 3645 * $compile($document)($rootScope); 3646 * $rootScope.$digest(); 3647 * }); 3648 * ``` 3649 * 3650 * Sometimes you want to get access to the injector of a currently running Angular app 3651 * from outside Angular. Perhaps, you want to inject and compile some markup after the 3652 * application has been bootstrapped. You can do this using the extra `injector()` added 3653 * to JQuery/jqLite elements. See {@link angular.element}. 3654 * 3655 * *This is fairly rare but could be the case if a third party library is injecting the 3656 * markup.* 3657 * 3658 * In the following example a new block of HTML containing a `ng-controller` 3659 * directive is added to the end of the document body by JQuery. We then compile and link 3660 * it into the current AngularJS scope. 3661 * 3662 * ```js 3663 * var $div = $('<div ng-controller="MyCtrl">{{content.label}}</div>'); 3664 * $(document.body).append($div); 3665 * 3666 * angular.element(document).injector().invoke(function($compile) { 3667 * var scope = angular.element($div).scope(); 3668 * $compile($div)(scope); 3669 * }); 3670 * ``` 3671 */ 3672 3673 3674/** 3675 * @ngdoc module 3676 * @name auto 3677 * @description 3678 * 3679 * Implicit module which gets automatically added to each {@link auto.$injector $injector}. 3680 */ 3681 3682var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m; 3683var FN_ARG_SPLIT = /,/; 3684var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/; 3685var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg; 3686var $injectorMinErr = minErr('$injector'); 3687 3688function anonFn(fn) { 3689 // For anonymous functions, showing at the very least the function signature can help in 3690 // debugging. 3691 var fnText = fn.toString().replace(STRIP_COMMENTS, ''), 3692 args = fnText.match(FN_ARGS); 3693 if (args) { 3694 return 'function(' + (args[1] || '').replace(/[\s\r\n]+/, ' ') + ')'; 3695 } 3696 return 'fn'; 3697} 3698 3699function annotate(fn, strictDi, name) { 3700 var $inject, 3701 fnText, 3702 argDecl, 3703 last; 3704 3705 if (typeof fn === 'function') { 3706 if (!($inject = fn.$inject)) { 3707 $inject = []; 3708 if (fn.length) { 3709 if (strictDi) { 3710 if (!isString(name) || !name) { 3711 name = fn.name || anonFn(fn); 3712 } 3713 throw $injectorMinErr('strictdi', 3714 '{0} is not using explicit annotation and cannot be invoked in strict mode', name); 3715 } 3716 fnText = fn.toString().replace(STRIP_COMMENTS, ''); 3717 argDecl = fnText.match(FN_ARGS); 3718 forEach(argDecl[1].split(FN_ARG_SPLIT), function(arg) { 3719 arg.replace(FN_ARG, function(all, underscore, name) { 3720 $inject.push(name); 3721 }); 3722 }); 3723 } 3724 fn.$inject = $inject; 3725 } 3726 } else if (isArray(fn)) { 3727 last = fn.length - 1; 3728 assertArgFn(fn[last], 'fn'); 3729 $inject = fn.slice(0, last); 3730 } else { 3731 assertArgFn(fn, 'fn', true); 3732 } 3733 return $inject; 3734} 3735 3736/////////////////////////////////////// 3737 3738/** 3739 * @ngdoc service 3740 * @name $injector 3741 * 3742 * @description 3743 * 3744 * `$injector` is used to retrieve object instances as defined by 3745 * {@link auto.$provide provider}, instantiate types, invoke methods, 3746 * and load modules. 3747 * 3748 * The following always holds true: 3749 * 3750 * ```js 3751 * var $injector = angular.injector(); 3752 * expect($injector.get('$injector')).toBe($injector); 3753 * expect($injector.invoke(function($injector) { 3754 * return $injector; 3755 * })).toBe($injector); 3756 * ``` 3757 * 3758 * # Injection Function Annotation 3759 * 3760 * JavaScript does not have annotations, and annotations are needed for dependency injection. The 3761 * following are all valid ways of annotating function with injection arguments and are equivalent. 3762 * 3763 * ```js 3764 * // inferred (only works if code not minified/obfuscated) 3765 * $injector.invoke(function(serviceA){}); 3766 * 3767 * // annotated 3768 * function explicit(serviceA) {}; 3769 * explicit.$inject = ['serviceA']; 3770 * $injector.invoke(explicit); 3771 * 3772 * // inline 3773 * $injector.invoke(['serviceA', function(serviceA){}]); 3774 * ``` 3775 * 3776 * ## Inference 3777 * 3778 * In JavaScript calling `toString()` on a function returns the function definition. The definition 3779 * can then be parsed and the function arguments can be extracted. This method of discovering 3780 * annotations is disallowed when the injector is in strict mode. 3781 * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the 3782 * argument names. 3783 * 3784 * ## `$inject` Annotation 3785 * By adding an `$inject` property onto a function the injection parameters can be specified. 3786 * 3787 * ## Inline 3788 * As an array of injection names, where the last item in the array is the function to call. 3789 */ 3790 3791/** 3792 * @ngdoc method 3793 * @name $injector#get 3794 * 3795 * @description 3796 * Return an instance of the service. 3797 * 3798 * @param {string} name The name of the instance to retrieve. 3799 * @param {string=} caller An optional string to provide the origin of the function call for error messages. 3800 * @return {*} The instance. 3801 */ 3802 3803/** 3804 * @ngdoc method 3805 * @name $injector#invoke 3806 * 3807 * @description 3808 * Invoke the method and supply the method arguments from the `$injector`. 3809 * 3810 * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are 3811 * injected according to the {@link guide/di $inject Annotation} rules. 3812 * @param {Object=} self The `this` for the invoked method. 3813 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3814 * object first, before the `$injector` is consulted. 3815 * @returns {*} the value returned by the invoked `fn` function. 3816 */ 3817 3818/** 3819 * @ngdoc method 3820 * @name $injector#has 3821 * 3822 * @description 3823 * Allows the user to query if the particular service exists. 3824 * 3825 * @param {string} name Name of the service to query. 3826 * @returns {boolean} `true` if injector has given service. 3827 */ 3828 3829/** 3830 * @ngdoc method 3831 * @name $injector#instantiate 3832 * @description 3833 * Create a new instance of JS type. The method takes a constructor function, invokes the new 3834 * operator, and supplies all of the arguments to the constructor function as specified by the 3835 * constructor annotation. 3836 * 3837 * @param {Function} Type Annotated constructor function. 3838 * @param {Object=} locals Optional object. If preset then any argument names are read from this 3839 * object first, before the `$injector` is consulted. 3840 * @returns {Object} new instance of `Type`. 3841 */ 3842 3843/** 3844 * @ngdoc method 3845 * @name $injector#annotate 3846 * 3847 * @description 3848 * Returns an array of service names which the function is requesting for injection. This API is 3849 * used by the injector to determine which services need to be injected into the function when the 3850 * function is invoked. There are three ways in which the function can be annotated with the needed 3851 * dependencies. 3852 * 3853 * # Argument names 3854 * 3855 * The simplest form is to extract the dependencies from the arguments of the function. This is done
3856 * by converting the function into a string using `toString()` method and extracting the argument 3857 * names. 3858 * ```js 3859 * // Given 3860 * function MyController($scope, $route) { 3861 * // ... 3862 * } 3863 * 3864 * // Then 3865 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3866 * ``` 3867 * 3868 * You can disallow this method by using strict injection mode. 3869 * 3870 * This method does not work with code minification / obfuscation. For this reason the following 3871 * annotation strategies are supported. 3872 * 3873 * # The `$inject` property 3874 * 3875 * If a function has an `$inject` property and its value is an array of strings, then the strings 3876 * represent names of services to be injected into the function. 3877 * ```js 3878 * // Given 3879 * var MyController = function(obfuscatedScope, obfuscatedRoute) { 3880 * // ... 3881 * } 3882 * // Define function dependencies 3883 * MyController['$inject'] = ['$scope', '$route']; 3884 * 3885 * // Then 3886 * expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); 3887 * ``` 3888 * 3889 * # The array notation 3890 * 3891 * It is often desirable to inline Injected functions and that's when setting the `$inject` property 3892 * is very inconvenient. In these situations using the array notation to specify the dependencies in 3893 * a way that survives minification is a better choice: 3894 * 3895 * ```js 3896 * // We wish to write this (not minification / obfuscation safe) 3897 * injector.invoke(function($compile, $rootScope) { 3898 * // ... 3899 * }); 3900 * 3901 * // We are forced to write break inlining 3902 * var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) { 3903 * // ... 3904 * }; 3905 * tmpFn.$inject = ['$compile', '$rootScope']; 3906 * injector.invoke(tmpFn); 3907 * 3908 * // To better support inline function the inline annotation is supported 3909 * injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) { 3910 * // ... 3911 * }]); 3912 * 3913 * // Therefore 3914 * expect(injector.annotate( 3915 * ['$compile', '$rootScope', function(obfus_$compile, obfus_$rootScope) {}]) 3916 * ).toEqual(['$compile', '$rootScope']); 3917 * ``` 3918 * 3919 * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to 3920 * be retrieved as described above. 3921 * 3922 * @param {boolean=} [strictDi=false] Disallow argument name annotation inference. 3923 * 3924 * @returns {Array.<string>} The names of the services which the function requires. 3925 */ 3926 3927 3928 3929 3930/** 3931 * @ngdoc service 3932 * @name $provide 3933 * 3934 * @description 3935 * 3936 * The {@link auto.$provide $provide} service has a number of methods for registering components 3937 * with the {@link auto.$injector $injector}. Many of these functions are also exposed on 3938 * {@link angular.Module}. 3939 * 3940 * An Angular **service** is a singleton object created by a **service factory**. These **service 3941 * factories** are functions which, in turn, are created by a **service provider**. 3942 * The **service providers** are constructor functions. When instantiated they must contain a 3943 * property called `$get`, which holds the **service factory** function. 3944 * 3945 * When you request a service, the {@link auto.$injector $injector} is responsible for finding the 3946 * correct **service provider**, instantiating it and then calling its `$get` **service factory** 3947 * function to get the instance of the **service**. 3948 * 3949 * Often services have no configuration options and there is no need to add methods to the service 3950 * provider. The provider will be no more than a constructor function with a `$get` property. For 3951 * these cases the {@link auto.$provide $provide} service has additional helper methods to register 3952 * services without specifying a provider. 3953 * 3954 * * {@link auto.$provide#provider provider(provider)} - registers a **service provider** with the 3955 * {@link auto.$injector $injector} 3956 * * {@link auto.$provide#constant constant(obj)} - registers a value/object that can be accessed by 3957 * providers and services. 3958 * * {@link auto.$provide#value value(obj)} - registers a value/object that can only be accessed by 3959 * services, not providers. 3960 * * {@link auto.$provide#factory factory(fn)} - registers a service **factory function**, `fn`, 3961 * that will be wrapped in a **service provider** object, whose `$get` property will contain the 3962 * given factory function. 3963 * * {@link auto.$provide#service service(class)} - registers a **constructor function**, `class` 3964 * that will be wrapped in a **service provider** object, whose `$get` property will instantiate 3965 * a new object using the given constructor function. 3966 * 3967 * See the individual methods for more information and examples. 3968 */ 3969 3970/** 3971 * @ngdoc method 3972 * @name $provide#provider 3973 * @description 3974 * 3975 * Register a **provider function** with the {@link auto.$injector $injector}. Provider functions 3976 * are constructor functions, whose instances are responsible for "providing" a factory for a 3977 * service. 3978 * 3979 * Service provider names start with the name of the service they provide followed by `Provider`. 3980 * For example, the {@link ng.$log $log} service has a provider called 3981 * {@link ng.$logProvider $logProvider}. 3982 * 3983 * Service provider objects can have additional methods which allow configuration of the provider 3984 * and its service. Importantly, you can configure what kind of service is created by the `$get` 3985 * method, or how that service will act. For example, the {@link ng.$logProvider $logProvider} has a 3986 * method {@link ng.$logProvider#debugEnabled debugEnabled} 3987 * which lets you specify whether the {@link ng.$log $log} service will log debug messages to the 3988 * console or not. 3989 * 3990 * @param {string} name The name of the instance. NOTE: the provider will be available under `name + 3991 'Provider'` key. 3992 * @param {(Object|function())} provider If the provider is: 3993 * 3994 * - `Object`: then it should have a `$get` method. The `$get` method will be invoked using 3995 * {@link auto.$injector#invoke $injector.invoke()} when an instance needs to be created. 3996 * - `Constructor`: a new instance of the provider will be created using 3997 * {@link auto.$injector#instantiate $injector.instantiate()}, then treated as `object`. 3998 * 3999 * @returns {Object} registered provider instance 4000 4001 * @example 4002 * 4003 * The following example shows how to create a simple event tracking service and register it using 4004 * {@link auto.$provide#provider $provide.provider()}. 4005 * 4006 * ```js 4007 * // Define the eventTracker provider 4008 * function EventTrackerProvider() { 4009 * var trackingUrl = '/track'; 4010 * 4011 * // A provider method for configuring where the tracked events should been saved 4012 * this.setTrackingUrl = function(url) { 4013 * trackingUrl = url; 4014 * }; 4015 * 4016 * // The service factory function 4017 * this.$get = ['$http', function($http) { 4018 * var trackedEvents = {}; 4019 * return { 4020 * // Call this to track an event 4021 * event: function(event) { 4022 * var count = trackedEvents[event] || 0; 4023 * count += 1; 4024 * trackedEvents[event] = count; 4025 * return count; 4026 * }, 4027 * // Call this to save the tracked events to the trackingUrl 4028 * save: function() { 4029 * $http.post(trackingUrl, trackedEvents); 4030 * } 4031 * }; 4032 * }]; 4033 * } 4034 * 4035 * describe('eventTracker', function() { 4036 * var postSpy; 4037 * 4038 * beforeEach(module(function($provide) { 4039 * // Register the eventTracker provider 4040 * $provide.provider('eventTracker', EventTrackerProvider); 4041 * })); 4042 * 4043 * beforeEach(module(function(eventTrackerProvider) { 4044 * // Configure eventTracker provider 4045 * eventTrackerProvider.setTrackingUrl('/custom-track'); 4046 * })); 4047 * 4048 * it('tracks events', inject(function(eventTracker) { 4049 * expect(eventTracker.event('login')).toEqual(1); 4050 * expect(eventTracker.event('login')).toEqual(2); 4051 * })); 4052 * 4053 * it('saves to the tracking url', inject(function(eventTracker, $http) { 4054 * postSpy = spyOn($http, 'post'); 4055 * eventTracker.event('login'); 4056 * eventTracker.save(); 4057 * expect(postSpy).toHaveBeenCalled(); 4058 * expect(postSpy.mostRecentCall.args[0]).not.toEqual('/track'); 4059 * expect(postSpy.mostRecentCall.args[0]).toEqual('/custom-track'); 4060 * expect(postSpy.mostRecentCall.args[1]).toEqual({ 'login': 1 }); 4061 * })); 4062 * }); 4063 * ``` 4064 */ 4065 4066/** 4067 * @ngdoc method 4068 * @name $provide#factory 4069 * @description 4070 * 4071 * Register a **service factory**, which will be called to return the service instance. 4072 * This is short for registering a service where its provider consists of only a `$get` property, 4073 * which is the given service factory function. 4074 * You should use {@link auto.$provide#factory $provide.factory(getFn)} if you do not need to 4075 * configure your service in a provider. 4076 * 4077 * @param {string} name The name of the instance. 4078 * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation. 4079 * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`. 4080 * @returns {Object} registered provider instance 4081 * 4082 * @example 4083 * Here is an example of registering a service 4084 * ```js 4085 * $provide.factory('ping', ['$http', function($http) { 4086 * return function ping() { 4087 * return $http.send('/ping'); 4088 * }; 4089 * }]); 4090 * ``` 4091 * You would then inject and use this service like this: 4092 * ```js 4093 * someModule.controller('Ctrl', ['ping', function(ping) { 4094 * ping(); 4095 * }]); 4096 * ``` 4097 */ 4098 4099 4100/** 4101 * @ngdoc method 4102 * @name $provide#service 4103 * @description 4104 * 4105 * Register a **service constructor**, which will be invoked with `new` to create the service 4106 * instance. 4107 * This is short for registering a service where its provider's `$get` property is the service 4108 * constructor function that will be used to instantiate the service instance. 4109 * 4110 * You should use {@link auto.$provide#service $provide.service(class)} if you define your service 4111 * as a type/class. 4112 * 4113 * @param {string} name The name of the instance. 4114 * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function) 4115 * that will be instantiated. 4116 * @returns {Object} registered provider instance 4117 * 4118 * @example 4119 * Here is an example of registering a service using 4120 * {@link auto.$provide#service $provide.service(class)}. 4121 * ```js 4122 * var Ping = function($http) { 4123 * this.$http = $http; 4124 * }; 4125 * 4126 * Ping.$inject = ['$http']; 4127 * 4128 * Ping.prototype.send = function() { 4129 * return this.$http.get('/ping'); 4130 * }; 4131 * $provide.service('ping', Ping); 4132 * ``` 4133 * You would then inject and use this service like this: 4134 * ```js 4135 * someModule.controller('Ctrl', ['ping', function(ping) { 4136 * ping.send(); 4137 * }]); 4138 * ``` 4139 */ 4140 4141 4142/** 4143 * @ngdoc method 4144 * @name $provide#value 4145 * @description 4146 * 4147 * Register a **value service** with the {@link auto.$injector $injector}, such as a string, a 4148 * number, an array, an object or a function. This is short for registering a service where its 4149 * provider's `$get` property is a factory function that takes no arguments and returns the **value 4150 * service**. 4151 * 4152 * Value services are similar to constant services, except that they cannot be injected into a 4153 * module configuration function (see {@link angular.Module#config}) but they can be overridden by 4154 * an Angular 4155 * {@link auto.$provide#decorator decorator}. 4156 * 4157 * @param {string} name The name of the instance. 4158 * @param {*} value The value. 4159 * @returns {Object} registered provider instance 4160 * 4161 * @example 4162 * Here are some examples of creating value services. 4163 * ```js 4164 * $provide.value('ADMIN_USER', 'admin'); 4165 *
4166 * $provide.value('RoleLookup', { admin: 0, writer: 1, reader: 2 }); 4167 * 4168 * $provide.value('halfOf', function(value) { 4169 * return value / 2; 4170 * }); 4171 * ``` 4172 */ 4173 4174 4175/** 4176 * @ngdoc method 4177 * @name $provide#constant 4178 * @description 4179 * 4180 * Register a **constant service**, such as a string, a number, an array, an object or a function, 4181 * with the {@link auto.$injector $injector}. Unlike {@link auto.$provide#value value} it can be 4182 * injected into a module configuration function (see {@link angular.Module#config}) and it cannot 4183 * be overridden by an Angular {@link auto.$provide#decorator decorator}. 4184 * 4185 * @param {string} name The name of the constant. 4186 * @param {*} value The constant value. 4187 * @returns {Object} registered instance 4188 * 4189 * @example 4190 * Here a some examples of creating constants: 4191 * ```js 4192 * $provide.constant('SHARD_HEIGHT', 306); 4193 * 4194 * $provide.constant('MY_COLOURS', ['red', 'blue', 'grey']); 4195 * 4196 * $provide.constant('double', function(value) { 4197 * return value * 2; 4198 * }); 4199 * ``` 4200 */ 4201 4202 4203/** 4204 * @ngdoc method 4205 * @name $provide#decorator 4206 * @description 4207 * 4208 * Register a **service decorator** with the {@link auto.$injector $injector}. A service decorator 4209 * intercepts the creation of a service, allowing it to override or modify the behaviour of the 4210 * service. The object returned by the decorator may be the original service, or a new service 4211 * object which replaces or wraps and delegates to the original service. 4212 * 4213 * @param {string} name The name of the service to decorate. 4214 * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be 4215 * instantiated and should return the decorated service instance. The function is called using 4216 * the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable. 4217 * Local injection arguments: 4218 * 4219 * * `$delegate` - The original service instance, which can be monkey patched, configured, 4220 * decorated or delegated to. 4221 * 4222 * @example 4223 * Here we decorate the {@link ng.$log $log} service to convert warnings to errors by intercepting 4224 * calls to {@link ng.$log#error $log.warn()}. 4225 * ```js 4226 * $provide.decorator('$log', ['$delegate', function($delegate) { 4227 * $delegate.warn = $delegate.error; 4228 * return $delegate; 4229 * }]); 4230 * ``` 4231 */ 4232 4233 4234function createInjector(modulesToLoad, strictDi) { 4235 strictDi = (strictDi === true); 4236 var INSTANTIATING = {}, 4237 providerSuffix = 'Provider', 4238 path = [], 4239 loadedModules = new HashMap([], true), 4240 providerCache = { 4241 $provide: { 4242 provider: supportObject(provider), 4243 factory: supportObject(factory), 4244 service: supportObject(service), 4245 value: supportObject(value), 4246 constant: supportObject(constant), 4247 decorator: decorator 4248 } 4249 }, 4250 providerInjector = (providerCache.$injector = 4251 createInternalInjector(providerCache, function(serviceName, caller) { 4252 if (angular.isString(caller)) { 4253 path.push(caller); 4254 } 4255 throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- ')); 4256 })), 4257 instanceCache = {}, 4258 instanceInjector = (instanceCache.$injector = 4259 createInternalInjector(instanceCache, function(serviceName, caller) { 4260 var provider = providerInjector.get(serviceName + providerSuffix, caller); 4261 return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); 4262 })); 4263 4264 4265 forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); }); 4266 4267 return instanceInjector; 4268 4269 //////////////////////////////////// 4270 // $provider 4271 //////////////////////////////////// 4272 4273 function supportObject(delegate) { 4274 return function(key, value) { 4275 if (isObject(key)) { 4276 forEach(key, reverseParams(delegate)); 4277 } else { 4278 return delegate(key, value); 4279 } 4280 }; 4281 } 4282 4283 function provider(name, provider_) { 4284 assertNotHasOwnProperty(name, 'service'); 4285 if (isFunction(provider_) || isArray(provider_)) { 4286 provider_ = providerInjector.instantiate(provider_); 4287 } 4288 if (!provider_.$get) { 4289 throw $injectorMinErr('pget', "Provider '{0}' must define $get factory method.", name); 4290 } 4291 return providerCache[name + providerSuffix] = provider_; 4292 } 4293 4294 function enforceReturnValue(name, factory) { 4295 return function enforcedReturnValue() { 4296 var result = instanceInjector.invoke(factory, this); 4297 if (isUndefined(result)) { 4298 throw $injectorMinErr('undef', "Provider '{0}' must return a value from $get factory method.", name); 4299 } 4300 return result; 4301 }; 4302 } 4303 4304 function factory(name, factoryFn, enforce) { 4305 return provider(name, { 4306 $get: enforce !== false ? enforceReturnValue(name, factoryFn) : factoryFn 4307 }); 4308 } 4309 4310 function service(name, constructor) { 4311 return factory(name, ['$injector', function($injector) { 4312 return $injector.instantiate(constructor); 4313 }]); 4314 } 4315 4316 function value(name, val) { return factory(name, valueFn(val), false); } 4317 4318 function constant(name, value) { 4319 assertNotHasOwnProperty(name, 'constant'); 4320 providerCache[name] = value; 4321 instanceCache[name] = value; 4322 } 4323 4324 function decorator(serviceName, decorFn) { 4325 var origProvider = providerInjector.get(serviceName + providerSuffix), 4326 orig$get = origProvider.$get; 4327 4328 origProvider.$get = function() { 4329 var origInstance = instanceInjector.invoke(orig$get, origProvider); 4330 return instanceInjector.invoke(decorFn, null, {$delegate: origInstance}); 4331 }; 4332 } 4333 4334 //////////////////////////////////// 4335 // Module Loading 4336 //////////////////////////////////// 4337 function loadModules(modulesToLoad) { 4338 var runBlocks = [], moduleFn;
vendor: 5,290 bytes, lines 4339-4496
4339 forEach(modulesToLoad, function(module) { 4340 if (loadedModules.get(module)) return; 4341 loadedModules.put(module, true); 4342 4343 function runInvokeQueue(queue) { 4344 var i, ii; 4345 for (i = 0, ii = queue.length; i < ii; i++) { 4346 var invokeArgs = queue[i], 4347 provider = providerInjector.get(invokeArgs[0]); 4348 4349 provider[invokeArgs[1]].apply(provider, invokeArgs[2]); 4350 } 4351 } 4352 4353 try { 4354 if (isString(module)) { 4355 moduleFn = angularModule(module); 4356 runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks); 4357 runInvokeQueue(moduleFn._invokeQueue); 4358 runInvokeQueue(moduleFn._configBlocks); 4359 } else if (isFunction(module)) { 4360 runBlocks.push(providerInjector.invoke(module)); 4361 } else if (isArray(module)) { 4362 runBlocks.push(providerInjector.invoke(module)); 4363 } else { 4364 assertArgFn(module, 'module'); 4365 } 4366 } catch (e) { 4367 if (isArray(module)) { 4368 module = module[module.length - 1]; 4369 } 4370 if (e.message && e.stack && e.stack.indexOf(e.message) == -1) { 4371 // Safari & FF's stack traces don't contain error.message content 4372 // unlike those of Chrome and IE 4373 // So if stack doesn't contain message, we create a new string that contains both. 4374 // Since error.stack is read-only in Safari, I'm overriding e and not e.stack here. 4375 /* jshint -W022 */ 4376 e = e.message + '\n' + e.stack; 4377 } 4378 throw $injectorMinErr('modulerr', "Failed to instantiate module {0} due to:\n{1}", 4379 module, e.stack || e.message || e); 4380 } 4381 }); 4382 return runBlocks; 4383 } 4384 4385 //////////////////////////////////// 4386 // internal Injector 4387 //////////////////////////////////// 4388 4389 function createInternalInjector(cache, factory) { 4390 4391 function getService(serviceName, caller) { 4392 if (cache.hasOwnProperty(serviceName)) { 4393 if (cache[serviceName] === INSTANTIATING) { 4394 throw $injectorMinErr('cdep', 'Circular dependency found: {0}', 4395 serviceName + ' <- ' + path.join(' <- ')); 4396 } 4397 return cache[serviceName]; 4398 } else { 4399 try { 4400 path.unshift(serviceName); 4401 cache[serviceName] = INSTANTIATING; 4402 return cache[serviceName] = factory(serviceName, caller); 4403 } catch (err) { 4404 if (cache[serviceName] === INSTANTIATING) { 4405 delete cache[serviceName]; 4406 } 4407 throw err; 4408 } finally { 4409 path.shift(); 4410 } 4411 } 4412 } 4413 4414 function invoke(fn, self, locals, serviceName) { 4415 if (typeof locals === 'string') { 4416 serviceName = locals; 4417 locals = null; 4418 } 4419 4420 var args = [], 4421 $inject = createInjector.$$annotate(fn, strictDi, serviceName), 4422 length, i, 4423 key; 4424 4425 for (i = 0, length = $inject.length; i < length; i++) { 4426 key = $inject[i]; 4427 if (typeof key !== 'string') { 4428 throw $injectorMinErr('itkn', 4429 'Incorrect injection token! Expected service name as string, got {0}', key); 4430 } 4431 args.push( 4432 locals && locals.hasOwnProperty(key) 4433 ? locals[key] 4434 : getService(key, serviceName) 4435 ); 4436 } 4437 if (isArray(fn)) { 4438 fn = fn[length]; 4439 } 4440 4441 // http://jsperf.com/angularjs-invoke-apply-vs-switch 4442 // #5388 4443 return fn.apply(self, args); 4444 } 4445 4446 function instantiate(Type, locals, serviceName) { 4447 // Check if Type is annotated and use just the given function at n-1 as parameter 4448 // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]); 4449 // Object creation: http://jsperf.com/create-constructor/2 4450 var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null); 4451 var returnedValue = invoke(Type, instance, locals, serviceName); 4452 4453 return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; 4454 } 4455 4456 return { 4457 invoke: invoke, 4458 instantiate: instantiate, 4459 get: getService, 4460 annotate: createInjector.$$annotate, 4461 has: function(name) { 4462 return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name); 4463 } 4464 }; 4465 } 4466} 4467 4468createInjector.$$annotate = annotate; 4469 4470/** 4471 * @ngdoc provider 4472 * @name $anchorScrollProvider 4473 * 4474 * @description 4475 * Use `$anchorScrollProvider` to disable automatic scrolling whenever 4476 * {@link ng.$location#hash $location.hash()} changes. 4477 */ 4478function $AnchorScrollProvider() { 4479 4480 var autoScrollingEnabled = true; 4481 4482 /** 4483 * @ngdoc method 4484 * @name $anchorScrollProvider#disableAutoScrolling 4485 * 4486 * @description 4487 * By default, {@link ng.$anchorScroll $anchorScroll()} will automatically detect changes to 4488 * {@link ng.$location#hash $location.hash()} and scroll to the element matching the new hash.<br /> 4489 * Use this method to disable automatic scrolling. 4490 * 4491 * If automatic scrolling is disabled, one must explicitly call 4492 * {@link ng.$anchorScroll $anchorScroll()} in order to scroll to the element related to the 4493 * current hash. 4494 */ 4495 this.disableAutoScrolling = function() { 4496 autoScrollingEnabled = false;
4497 }; 4498 4499 /** 4500 * @ngdoc service 4501 * @name $anchorScroll 4502 * @kind function 4503 * @requires $window 4504 * @requires $location 4505 * @requires $rootScope 4506 * 4507 * @description 4508 * When called, it scrolls to the element related to the specified `hash` or (if omitted) to the 4509 * current value of {@link ng.$location#hash $location.hash()}, according to the rules specified 4510 * in the 4511 * [HTML5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document). 4512 * 4513 * It also watches the {@link ng.$location#hash $location.hash()} and automatically scrolls to 4514 * match any anchor whenever it changes. This can be disabled by calling 4515 * {@link ng.$anchorScrollProvider#disableAutoScrolling $anchorScrollProvider.disableAutoScrolling()}. 4516 * 4517 * Additionally, you can use its {@link ng.$anchorScroll#yOffset yOffset} property to specify a 4518 * vertical scroll-offset (either fixed or dynamic). 4519 * 4520 * @param {string=} hash The hash specifying the element to scroll to. If omitted, the value of 4521 * {@link ng.$location#hash $location.hash()} will be used. 4522 * 4523 * @property {(number|function|jqLite)} yOffset 4524 * If set, specifies a vertical scroll-offset. This is often useful when there are fixed 4525 * positioned elements at the top of the page, such as navbars, headers etc. 4526 * 4527 * `yOffset` can be specified in various ways: 4528 * - **number**: A fixed number of pixels to be used as offset.<br /><br /> 4529 * - **function**: A getter function called everytime `$anchorScroll()` is executed. Must return 4530 * a number representing the offset (in pixels).<br /><br /> 4531 * - **jqLite**: A jqLite/jQuery element to be used for specifying the offset. The distance from 4532 * the top of the page to the element's bottom will be used as offset.<br /> 4533 * **Note**: The element will be taken into account only as long as its `position` is set to 4534 * `fixed`. This option is useful, when dealing with responsive navbars/headers that adjust 4535 * their height and/or positioning according to the viewport's size. 4536 * 4537 * <br /> 4538 * <div class="alert alert-warning"> 4539 * In order for `yOffset` to work properly, scrolling should take place on the document's root and 4540 * not some child element. 4541 * </div> 4542 * 4543 * @example 4544 <example module="anchorScrollExample"> 4545 <file name="index.html"> 4546 <div id="scrollArea" ng-controller="ScrollController"> 4547 <a ng-click="gotoBottom()">Go to bottom</a> 4548 <a id="bottom"></a> You're at the bottom! 4549 </div> 4550 </file> 4551 <file name="script.js"> 4552 angular.module('anchorScrollExample', []) 4553 .controller('ScrollController', ['$scope', '$location', '$anchorScroll', 4554 function ($scope, $location, $anchorScroll) { 4555 $scope.gotoBottom = function() { 4556 // set the location.hash to the id of 4557 // the element you wish to scroll to. 4558 $location.hash('bottom'); 4559 4560 // call $anchorScroll() 4561 $anchorScroll(); 4562 }; 4563 }]); 4564 </file> 4565 <file name="style.css"> 4566 #scrollArea { 4567 height: 280px; 4568 overflow: auto; 4569 } 4570 4571 #bottom { 4572 display: block; 4573 margin-top: 2000px; 4574 } 4575 </file> 4576 </example> 4577 * 4578 * <hr /> 4579 * The example below illustrates the use of a vertical scroll-offset (specified as a fixed value). 4580 * See {@link ng.$anchorScroll#yOffset $anchorScroll.yOffset} for more details. 4581 * 4582 * @example 4583 <example module="anchorScrollOffsetExample"> 4584 <file name="index.html"> 4585 <div class="fixed-header" ng-controller="headerCtrl"> 4586 <a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]"> 4587 Go to anchor {{x}} 4588 </a> 4589 </div> 4590 <div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]"> 4591 Anchor {{x}} of 5 4592 </div> 4593 </file> 4594 <file name="script.js"> 4595 angular.module('anchorScrollOffsetExample', []) 4596 .run(['$anchorScroll', function($anchorScroll) { 4597 $anchorScroll.yOffset = 50; // always scroll by 50 extra pixels 4598 }]) 4599 .controller('headerCtrl', ['$anchorScroll', '$location', '$scope', 4600 function ($anchorScroll, $location, $scope) {
4601 $scope.gotoAnchor = function(x) { 4602 var newHash = 'anchor' + x; 4603 if ($location.hash() !== newHash) { 4604 // set the $location.hash to `newHash` and 4605 // $anchorScroll will automatically scroll to it 4606 $location.hash('anchor' + x); 4607 } else { 4608 // call $anchorScroll() explicitly, 4609 // since $location.hash hasn't changed 4610 $anchorScroll(); 4611 } 4612 }; 4613 } 4614 ]); 4615 </file> 4616 <file name="style.css"> 4617 body { 4618 padding-top: 50px; 4619 } 4620 4621 .anchor { 4622 border: 2px dashed DarkOrchid; 4623 padding: 10px 10px 200px 10px; 4624 } 4625 4626 .fixed-header { 4627 background-color: rgba(0, 0, 0, 0.2); 4628 height: 50px; 4629 position: fixed; 4630 top: 0; left: 0; right: 0; 4631 } 4632 4633 .fixed-header > a { 4634 display: inline-block; 4635 margin: 5px 15px; 4636 } 4637 </file> 4638 </example> 4639 */ 4640 this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) { 4641 var document = $window.document; 4642 4643 // Helper function to get first anchor from a NodeList 4644 // (using `Array#some()` instead of `angular#forEach()` since it's more performant 4645 // and working in all supported browsers.) 4646 function getFirstAnchor(list) { 4647 var result = null; 4648 Array.prototype.some.call(list, function(element) { 4649 if (nodeName_(element) === 'a') { 4650 result = element; 4651 return true; 4652 } 4653 }); 4654 return result; 4655 } 4656 4657 function getYOffset() { 4658 4659 var offset = scroll.yOffset; 4660 4661 if (isFunction(offset)) { 4662 offset = offset(); 4663 } else if (isElement(offset)) { 4664 var elem = offset[0]; 4665 var style = $window.getComputedStyle(elem); 4666 if (style.position !== 'fixed') { 4667 offset = 0; 4668 } else { 4669 offset = elem.getBoundingClientRect().bottom; 4670 } 4671 } else if (!isNumber(offset)) { 4672 offset = 0; 4673 } 4674 4675 return offset; 4676 } 4677 4678 function scrollTo(elem) { 4679 if (elem) { 4680 elem.scrollIntoView(); 4681 4682 var offset = getYOffset(); 4683 4684 if (offset) { 4685 // `offset` is the number of pixels we should scroll UP in order to align `elem` properly. 4686 // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the 4687 // top of the viewport. 4688 // 4689 // IF the number of pixels from the top of `elem` to the end of the page's content is less 4690 // than the height of the viewport, then `elem.scrollIntoView()` will align the `elem` some 4691 // way down the page. 4692 // 4693 // This is often the case for elements near the bottom of the page. 4694 // 4695 // In such cases we do not need to scroll the whole `offset` up, just the difference between 4696 // the top of the element and the offset, which is enough to align the top of `elem` at the 4697 // desired position. 4698 var elemTop = elem.getBoundingClientRect().top; 4699 $window.scrollBy(0, elemTop - offset); 4700 } 4701 } else { 4702 $window.scrollTo(0, 0); 4703 } 4704 } 4705 4706 function scroll(hash) { 4707 hash = isString(hash) ? hash : $location.hash(); 4708 var elm; 4709 4710 // empty hash, scroll to the top of the page 4711 if (!hash) scrollTo(null); 4712 4713 // element with given id 4714 else if ((elm = document.getElementById(hash))) scrollTo(elm); 4715 4716 // first anchor with given name :-D 4717 else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm); 4718 4719 // no element and hash == 'top', scroll to the top of the page 4720 else if (hash === 'top') scrollTo(null); 4721 } 4722 4723 // does not scroll when user clicks on anchor link that is currently on 4724 // (no url change, no $location.hash() change), browser native does scroll 4725 if (autoScrollingEnabled) { 4726 $rootScope.$watch(function autoScrollWatch() {return $location.hash();}, 4727 function autoScrollWatchAction(newVal, oldVal) { 4728 // skip the initial scroll if $location.hash is empty 4729 if (newVal === oldVal && newVal === '') return; 4730 4731 jqLiteDocumentLoaded(function() { 4732 $rootScope.$evalAsync(scroll); 4733 }); 4734 }); 4735 } 4736 4737 return scroll; 4738 }]; 4739} 4740 4741var $animateMinErr = minErr('$animate'); 4742var ELEMENT_NODE = 1; 4743var NG_ANIMATE_CLASSNAME = 'ng-animate'; 4744 4745function mergeClasses(a,b) { 4746 if (!a && !b) return ''; 4747 if (!a) return b; 4748 if (!b) return a; 4749 if (isArray(a)) a = a.join(' '); 4750 if (isArray(b)) b = b.join(' '); 4751 return a + ' ' + b; 4752} 4753 4754function extractElementNode(element) { 4755 for (var i = 0; i < element.length; i++) { 4756 var elm = element[i]; 4757 if (elm.nodeType === ELEMENT_NODE) { 4758 return elm; 4759 } 4760 } 4761} 4762 4763function splitClasses(classes) { 4764 if (isString(classes)) { 4765 classes = classes.split(' '); 4766 } 4767 4768 // Use createMap() to prevent class assumptions involving property names in 4769 // Object.prototype 4770 var obj = createMap(); 4771 forEach(classes, function(klass) { 4772 // sometimes the split leaves empty string values 4773 // incase extra spaces were applied to the options 4774 if (klass.length) { 4775 obj[klass] = true; 4776 } 4777 }); 4778 return obj; 4779} 4780 4781// if any other type of options value besides an Object value is 4782// passed into the $animate.method() animation then this helper code 4783// will be run which will ignore it. While this patch is not the 4784// greatest solution to this, a lot of existing plugins depend on 4785// $animate to either call the callback (< 1.2) or return a promise 4786// that can be changed. This helper function ensures that the options 4787// are wiped clean incase a callback function is provided. 4788function prepareAnimateOptions(options) { 4789 return isObject(options) 4790 ? options 4791 : {}; 4792} 4793 4794var $$CoreAnimateRunnerProvider = function() { 4795 this.$get = ['$q', '$$rAF', function($q, $$rAF) { 4796 function AnimateRunner() {} 4797 AnimateRunner.all = noop; 4798 AnimateRunner.chain = noop; 4799 AnimateRunner.prototype = { 4800 end: noop, 4801 cancel: noop, 4802 resume: noop, 4803 pause: noop, 4804 complete: noop, 4805 then: function(pass, fail) { 4806 return $q(function(resolve) { 4807 $$rAF(function() { 4808 resolve(); 4809 }); 4810 }).then(pass, fail); 4811 } 4812 }; 4813 return AnimateRunner; 4814 }]; 4815}; 4816 4817// this is prefixed with Core since it conflicts with 4818// the animateQueueProvider defined in ngAnimate/animateQueue.js 4819var $$CoreAnimateQueueProvider = function() { 4820 var postDigestQueue = new HashMap(); 4821 var postDigestElements = []; 4822 4823 this.$get = ['$$AnimateRunner', '$rootScope', 4824 function($$AnimateRunner, $rootScope) { 4825 return { 4826 enabled: noop, 4827 on: noop, 4828 off: noop, 4829 pin: noop, 4830 4831 push: function(element, event, options, domOperation) { 4832 domOperation && domOperation(); 4833 4834 options = options || {}; 4835 options.from && element.css(options.from); 4836 options.to && element.css(options.to); 4837 4838 if (options.addClass || options.removeClass) { 4839 addRemoveClassesPostDigest(element, options.addClass, options.removeClass); 4840 } 4841 4842 return new $$AnimateRunner(); // jshint ignore:line 4843 } 4844 }; 4845 4846 function addRemoveClassesPostDigest(element, add, remove) { 4847 var data = postDigestQueue.get(element); 4848 var classVal; 4849 4850 if (!data) { 4851 postDigestQueue.put(element, data = {}); 4852 postDigestElements.push(element); 4853 } 4854 4855 if (add) {
4856 forEach(add.split(' '), function(className) { 4857 if (className) { 4858 data[className] = true; 4859 } 4860 }); 4861 } 4862 4863 if (remove) { 4864 forEach(remove.split(' '), function(className) { 4865 if (className) { 4866 data[className] = false; 4867 } 4868 }); 4869 } 4870 4871 if (postDigestElements.length > 1) return; 4872 4873 $rootScope.$$postDigest(function() { 4874 forEach(postDigestElements, function(element) { 4875 var data = postDigestQueue.get(element); 4876 if (data) { 4877 var existing = splitClasses(element.attr('class')); 4878 var toAdd = ''; 4879 var toRemove = ''; 4880 forEach(data, function(status, className) { 4881 var hasClass = !!existing[className]; 4882 if (status !== hasClass) { 4883 if (status) { 4884 toAdd += (toAdd.length ? ' ' : '') + className; 4885 } else { 4886 toRemove += (toRemove.length ? ' ' : '') + className; 4887 } 4888 } 4889 }); 4890 4891 forEach(element, function(elm) { 4892 toAdd && jqLiteAddClass(elm, toAdd); 4893 toRemove && jqLiteRemoveClass(elm, toRemove); 4894 }); 4895 postDigestQueue.remove(element); 4896 } 4897 }); 4898 4899 postDigestElements.length = 0; 4900 }); 4901 } 4902 }]; 4903}; 4904 4905/** 4906 * @ngdoc provider 4907 * @name $animateProvider 4908 * 4909 * @description 4910 * Default implementation of $animate that doesn't perform any animations, instead just 4911 * synchronously performs DOM updates and resolves the returned runner promise. 4912 * 4913 * In order to enable animations the `ngAnimate` module has to be loaded. 4914 * 4915 * To see the functional implementation check out `src/ngAnimate/animate.js`. 4916 */ 4917var $AnimateProvider = ['$provide', function($provide) { 4918 var provider = this; 4919 4920 this.$$registeredAnimations = Object.create(null); 4921 4922 /** 4923 * @ngdoc method 4924 * @name $animateProvider#register 4925 * 4926 * @description 4927 * Registers a new injectable animation factory function. The factory function produces the 4928 * animation object which contains callback functions for each event that is expected to be 4929 * animated. 4930 * 4931 * * `eventFn`: `function(element, ... , doneFunction, options)` 4932 * The element to animate, the `doneFunction` and the options fed into the animation. Depending 4933 * on the type of animation additional arguments will be injected into the animation function. The 4934 * list below explains the function signatures for the different animation methods: 4935 * 4936 * - setClass: function(element, addedClasses, removedClasses, doneFunction, options) 4937 * - addClass: function(element, addedClasses, doneFunction, options) 4938 * - removeClass: function(element, removedClasses, doneFunction, options) 4939 * - enter, leave, move: function(element, doneFunction, options) 4940 * - animate: function(element, fromStyles, toStyles, doneFunction, options) 4941 * 4942 * Make sure to trigger the `doneFunction` once the animation is fully complete. 4943 * 4944 * ```js 4945 * return { 4946 * //enter, leave, move signature 4947 * eventFn : function(element, done, options) { 4948 * //code to run the animation 4949 * //once complete, then run done() 4950 * return function endFunction(wasCancelled) { 4951 * //code to cancel the animation 4952 * } 4953 * } 4954 * } 4955 * ``` 4956 * 4957 * @param {string} name The name of the animation (this is what the class-based CSS value will be compared to). 4958 * @param {Function} factory The factory function that will be executed to return the animation 4959 * object. 4960 */ 4961 this.register = function(name, factory) { 4962 if (name && name.charAt(0) !== '.') { 4963 throw $animateMinErr('notcsel', "Expecting class selector starting with '.' got '{0}'.", name); 4964 } 4965 4966 var key = name + '-animation'; 4967 provider.$$registeredAnimations[name.substr(1)] = key; 4968 $provide.factory(key, factory); 4969 }; 4970 4971 /** 4972 * @ngdoc method 4973 * @name $animateProvider#classNameFilter 4974 * 4975 * @description 4976 * Sets and/or returns the CSS class regular expression that is checked when performing 4977 * an animation. Upon bootstrap the classNameFilter value is not set at all and will 4978 * therefore enable $animate to attempt to perform an animation on any element that is triggered. 4979 * When setting the `classNameFilter` value, animations will only be performed on elements 4980 * that successfully match the filter expression. This in turn can boost performance 4981 * for low-powered devices as well as applications containing a lot of structural operations. 4982 * @param {RegExp=} expression The className expression which will be checked against all animations 4983 * @return {RegExp} The current CSS className expression value. If null then there is no expression value 4984 */ 4985 this.classNameFilter = function(expression) { 4986 if (arguments.length === 1) { 4987 this.$$classNameFilter = (expression instanceof RegExp) ? expression : null; 4988 if (this.$$classNameFilter) { 4989 var reservedRegex = new RegExp("(\\s+|\\/)" + NG_ANIMATE_CLASSNAME + "(\\s+|\\/)"); 4990 if (reservedRegex.test(this.$$classNameFilter.toString())) { 4991 throw $animateMinErr('nongcls','$animateProvider.classNameFilter(regex) prohibits accepting a regex value which matches/contains the "{0}" CSS class.', NG_ANIMATE_CLASSNAME); 4992 4993 } 4994 } 4995 } 4996 return this.$$classNameFilter; 4997 }; 4998 4999 this.$get = ['$$animateQueue', function($$animateQueue) { 5000 function domInsert(element, parentElement, afterElement) { 5001 // if for some reason the previous element was removed 5002 // from the dom sometime before this code runs then let's
5003 // just stick to using the parent element as the anchor 5004 if (afterElement) { 5005 var afterNode = extractElementNode(afterElement); 5006 if (afterNode && !afterNode.parentNode && !afterNode.previousElementSibling) { 5007 afterElement = null; 5008 } 5009 } 5010 afterElement ? afterElement.after(element) : parentElement.prepend(element); 5011 } 5012 5013 /** 5014 * @ngdoc service 5015 * @name $animate 5016 * @description The $animate service exposes a series of DOM utility methods that provide support 5017 * for animation hooks. The default behavior is the application of DOM operations, however, 5018 * when an animation is detected (and animations are enabled), $animate will do the heavy lifting 5019 * to ensure that animation runs with the triggered DOM operation. 5020 * 5021 * By default $animate doesn't trigger an animations. This is because the `ngAnimate` module isn't 5022 * included and only when it is active then the animation hooks that `$animate` triggers will be 5023 * functional. Once active then all structural `ng-` directives will trigger animations as they perform 5024 * their DOM-related operations (enter, leave and move). Other directives such as `ngClass`, 5025 * `ngShow`, `ngHide` and `ngMessages` also provide support for animations. 5026 * 5027 * It is recommended that the`$animate` service is always used when executing DOM-related procedures within directives. 5028 * 5029 * To learn more about enabling animation support, click here to visit the 5030 * {@link ngAnimate ngAnimate module page}. 5031 */ 5032 return { 5033 // we don't call it directly since non-existant arguments may 5034 // be interpreted as null within the sub enabled function 5035 5036 /** 5037 * 5038 * @ngdoc method 5039 * @name $animate#on 5040 * @kind function 5041 * @description Sets up an event listener to fire whenever the animation event (enter, leave, move, etc...) 5042 * has fired on the given element or among any of its children. Once the listener is fired, the provided callback 5043 * is fired with the following params: 5044 * 5045 * ```js 5046 * $animate.on('enter', container, 5047 * function callback(element, phase) { 5048 * // cool we detected an enter animation within the container 5049 * } 5050 * ); 5051 * ``` 5052 * 5053 * @param {string} event the animation event that will be captured (e.g. enter, leave, move, addClass, removeClass, etc...) 5054 * @param {DOMElement} container the container element that will capture each of the animation events that are fired on itself 5055 * as well as among its children 5056 * @param {Function} callback the callback function that will be fired when the listener is triggered 5057 * 5058 * The arguments present in the callback function are: 5059 * * `element` - The captured DOM element that the animation was fired on. 5060 * * `phase` - The phase of the animation. The two possible phases are **start** (when the animation starts) and **close** (when it ends). 5061 */ 5062 on: $$animateQueue.on, 5063 5064 /** 5065 * 5066 * @ngdoc method 5067 * @name $animate#off 5068 * @kind function 5069 * @description Deregisters an event listener based on the event which has been associated with the provided element. This method 5070 * can be used in three different ways depending on the arguments: 5071 * 5072 * ```js 5073 * // remove all the animation event listeners listening for `enter` 5074 * $animate.off('enter'); 5075 * 5076 * // remove all the animation event listeners listening for `enter` on the given element and its children 5077 * $animate.off('enter', container); 5078 * 5079 * // remove the event listener function provided by `listenerFn` that is set 5080 * // to listen for `enter` on the given `element` as well as its children 5081 * $animate.off('enter', container, callback); 5082 * ``` 5083 * 5084 * @param {string} event the animation event (e.g. enter, leave, move, addClass, removeClass, etc...) 5085 * @param {DOMElement=} container the container element the event listener was placed on 5086 * @param {Function=} callback the callback function that was registered as the listener 5087 */ 5088 off: $$animateQueue.off, 5089 5090 /** 5091 * @ngdoc method 5092 * @name $animate#pin 5093 * @kind function
5094 * @description Associates the provided element with a host parent element to allow the element to be animated even if it exists 5095 * outside of the DOM structure of the Angular application. By doing so, any animation triggered via `$animate` can be issued on the 5096 * element despite being outside the realm of the application or within another application. Say for example if the application 5097 * was bootstrapped on an element that is somewhere inside of the `<body>` tag, but we wanted to allow for an element to be situated 5098 * as a direct child of `document.body`, then this can be achieved by pinning the element via `$animate.pin(element)`. Keep in mind 5099 * that calling `$animate.pin(element, parentElement)` will not actually insert into the DOM anywhere; it will just create the association. 5100 * 5101 * Note that this feature is only active when the `ngAnimate` module is used. 5102 * 5103 * @param {DOMElement} element the external element that will be pinned 5104 * @param {DOMElement} parentElement the host parent element that will be associated with the external element 5105 */ 5106 pin: $$animateQueue.pin, 5107 5108 /** 5109 * 5110 * @ngdoc method 5111 * @name $animate#enabled 5112 * @kind function 5113 * @description Used to get and set whether animations are enabled or not on the entire application or on an element and its children. This 5114 * function can be called in four ways: 5115 * 5116 * ```js 5117 * // returns true or false 5118 * $animate.enabled(); 5119 * 5120 * // changes the enabled state for all animations 5121 * $animate.enabled(false); 5122 * $animate.enabled(true); 5123 * 5124 * // returns true or false if animations are enabled for an element 5125 * $animate.enabled(element); 5126 * 5127 * // changes the enabled state for an element and its children 5128 * $animate.enabled(element, true); 5129 * $animate.enabled(element, false); 5130 * ``` 5131 * 5132 * @param {DOMElement=} element the element that will be considered for checking/setting the enabled state 5133 * @param {boolean=} enabled whether or not the animations will be enabled for the element 5134 * 5135 * @return {boolean} whether or not animations are enabled 5136 */ 5137 enabled: $$animateQueue.enabled, 5138 5139 /** 5140 * @ngdoc method 5141 * @name $animate#cancel 5142 * @kind function 5143 * @description Cancels the provided animation. 5144 * 5145 * @param {Promise} animationPromise The animation promise that is returned when an animation is started. 5146 */ 5147 cancel: function(runner) { 5148 runner.end && runner.end(); 5149 }, 5150 5151 /** 5152 * 5153 * @ngdoc method 5154 * @name $animate#enter 5155 * @kind function 5156 * @description Inserts the element into the DOM either after the `after` element (if provided) or 5157 * as the first child within the `parent` element and then triggers an animation. 5158 * A promise is returned that will be resolved during the next digest once the animation 5159 * has completed. 5160 * 5161 * @param {DOMElement} element the element which will be inserted into the DOM 5162 * @param {DOMElement} parent the parent element which will append the element as 5163 * a child (so long as the after element is not present) 5164 * @param {DOMElement=} after the sibling element after which the element will be appended 5165 * @param {object=} options an optional collection of options/styles that will be applied to the element 5166 * 5167 * @return {Promise} the animation callback promise 5168 */ 5169 enter: function(element, parent, after, options) { 5170 parent = parent && jqLite(parent); 5171 after = after && jqLite(after); 5172 parent = parent || after.parent(); 5173 domInsert(element, parent, after); 5174 return $$animateQueue.push(element, 'enter', prepareAnimateOptions(options)); 5175 }, 5176 5177 /** 5178 * 5179 * @ngdoc method 5180 * @name $animate#move 5181 * @kind function
5182 * @description Inserts (moves) the element into its new position in the DOM either after 5183 * the `after` element (if provided) or as the first child within the `parent` element 5184 * and then triggers an animation. A promise is returned that will be resolved 5185 * during the next digest once the animation has completed. 5186 * 5187 * @param {DOMElement} element the element which will be moved into the new DOM position 5188 * @param {DOMElement} parent the parent element which will append the element as 5189 * a child (so long as the after element is not present) 5190 * @param {DOMElement=} after the sibling element after which the element will be appended 5191 * @param {object=} options an optional collection of options/styles that will be applied to the element 5192 * 5193 * @return {Promise} the animation callback promise 5194 */ 5195 move: function(element, parent, after, options) { 5196 parent = parent && jqLite(parent); 5197 after = after && jqLite(after); 5198 parent = parent || after.parent(); 5199 domInsert(element, parent, after); 5200 return $$animateQueue.push(element, 'move', prepareAnimateOptions(options)); 5201 }, 5202 5203 /** 5204 * @ngdoc method 5205 * @name $animate#leave 5206 * @kind function 5207 * @description Triggers an animation and then removes the element from the DOM. 5208 * When the function is called a promise is returned that will be resolved during the next 5209 * digest once the animation has completed. 5210 * 5211 * @param {DOMElement} element the element which will be removed from the DOM 5212 * @param {object=} options an optional collection of options/styles that will be applied to the element 5213 * 5214 * @return {Promise} the animation callback promise 5215 */ 5216 leave: function(element, options) { 5217 return $$animateQueue.push(element, 'leave', prepareAnimateOptions(options), function() { 5218 element.remove(); 5219 }); 5220 }, 5221 5222 /** 5223 * @ngdoc method 5224 * @name $animate#addClass 5225 * @kind function 5226 * 5227 * @description Triggers an addClass animation surrounding the addition of the provided CSS class(es). Upon 5228 * execution, the addClass operation will only be handled after the next digest and it will not trigger an 5229 * animation if element already contains the CSS class or if the class is removed at a later step. 5230 * Note that class-based animations are treated differently compared to structural animations 5231 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5232 * depending if CSS or JavaScript animations are used. 5233 * 5234 * @param {DOMElement} element the element which the CSS classes will be applied to 5235 * @param {string} className the CSS class(es) that will be added (multiple classes are separated via spaces) 5236 * @param {object=} options an optional collection of options/styles that will be applied to the element 5237 * 5238 * @return {Promise} the animation callback promise 5239 */ 5240 addClass: function(element, className, options) { 5241 options = prepareAnimateOptions(options); 5242 options.addClass = mergeClasses(options.addclass, className); 5243 return $$animateQueue.push(element, 'addClass', options); 5244 }, 5245 5246 /** 5247 * @ngdoc method 5248 * @name $animate#removeClass 5249 * @kind function 5250 * 5251 * @description Triggers a removeClass animation surrounding the removal of the provided CSS class(es). Upon 5252 * execution, the removeClass operation will only be handled after the next digest and it will not trigger an 5253 * animation if element does not contain the CSS class or if the class is added at a later step. 5254 * Note that class-based animations are treated differently compared to structural animations 5255 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5256 * depending if CSS or JavaScript animations are used. 5257 * 5258 * @param {DOMElement} element the element which the CSS classes will be applied to 5259 * @param {string} className the CSS class(es) that will be removed (multiple classes are separated via spaces) 5260 * @param {object=} options an optional collection of options/styles that will be applied to the element 5261 * 5262 * @return {Promise} the animation callback promise 5263 */ 5264 removeClass: function(element, className, options) { 5265 options = prepareAnimateOptions(options); 5266 options.removeClass = mergeClasses(options.removeClass, className); 5267 return $$animateQueue.push(element, 'removeClass', options); 5268 }, 5269 5270 /** 5271 * @ngdoc method 5272 * @name $animate#setClass 5273 * @kind function 5274 *
5275 * @description Performs both the addition and removal of a CSS classes on an element and (during the process) 5276 * triggers an animation surrounding the class addition/removal. Much like `$animate.addClass` and 5277 * `$animate.removeClass`, `setClass` will only evaluate the classes being added/removed once a digest has 5278 * passed. Note that class-based animations are treated differently compared to structural animations 5279 * (like enter, move and leave) since the CSS classes may be added/removed at different points 5280 * depending if CSS or JavaScript animations are used. 5281 * 5282 * @param {DOMElement} element the element which the CSS classes will be applied to 5283 * @param {string} add the CSS class(es) that will be added (multiple classes are separated via spaces) 5284 * @param {string} remove the CSS class(es) that will be removed (multiple classes are separated via spaces) 5285 * @param {object=} options an optional collection of options/styles that will be applied to the element 5286 * 5287 * @return {Promise} the animation callback promise 5288 */ 5289 setClass: function(element, add, remove, options) { 5290 options = prepareAnimateOptions(options); 5291 options.addClass = mergeClasses(options.addClass, add); 5292 options.removeClass = mergeClasses(options.removeClass, remove); 5293 return $$animateQueue.push(element, 'setClass', options); 5294 }, 5295 5296 /** 5297 * @ngdoc method 5298 * @name $animate#animate 5299 * @kind function 5300 * 5301 * @description Performs an inline animation on the element which applies the provided to and from CSS styles to the element. 5302 * If any detected CSS transition, keyframe or JavaScript matches the provided className value then the animation will take 5303 * on the provided styles. For example, if a transition animation is set for the given className then the provided from and 5304 * to styles will be applied alongside the given transition. If a JavaScript animation is detected then the provided styles 5305 * will be given in as function paramters into the `animate` method (or as apart of the `options` parameter). 5306 * 5307 * @param {DOMElement} element the element which the CSS styles will be applied to 5308 * @param {object} from the from (starting) CSS styles that will be applied to the element and across the animation. 5309 * @param {object} to the to (destination) CSS styles that will be applied to the element and across the animation. 5310 * @param {string=} className an optional CSS class that will be applied to the element for the duration of the animation. If 5311 * this value is left as empty then a CSS class of `ng-inline-animate` will be applied to the element. 5312 * (Note that if no animation is detected then this value will not be appplied to the element.) 5313 * @param {object=} options an optional collection of options/styles that will be applied to the element 5314 * 5315 * @return {Promise} the animation callback promise 5316 */ 5317 animate: function(element, from, to, className, options) { 5318 options = prepareAnimateOptions(options); 5319 options.from = options.from ? extend(options.from, from) : from; 5320 options.to = options.to ? extend(options.to, to) : to; 5321 5322 className = className || 'ng-inline-animate'; 5323 options.tempClasses = mergeClasses(options.tempClasses, className); 5324 return $$animateQueue.push(element, 'animate', options); 5325 } 5326 }; 5327 }]; 5328}]; 5329 5330function $$AsyncCallbackProvider() { 5331 this.$get = ['$$rAF', '$timeout', function($$rAF, $timeout) { 5332 return $$rAF.supported 5333 ? function(fn) { return $$rAF(fn); } 5334 : function(fn) { 5335 return $timeout(fn, 0, false); 5336 }; 5337 }]; 5338} 5339 5340/* global stripHash: true */ 5341 5342/** 5343 * ! This is a private undocumented service ! 5344 * 5345 * @name $browser 5346 * @requires $log 5347 * @description 5348 * This object has two goals: 5349 * 5350 * - hide all the global state in the browser caused by the window object 5351 * - abstract away all the browser specific features and inconsistencies 5352 * 5353 * For tests we provide {@link ngMock.$browser mock implementation} of the `$browser`
5354 * service, which can be used for convenient testing of the application without the interaction with 5355 * the real browser apis. 5356 */ 5357/** 5358 * @param {object} window The global window object. 5359 * @param {object} document jQuery wrapped document. 5360 * @param {object} $log window.console or an object with the same interface. 5361 * @param {object} $sniffer $sniffer service 5362 */ 5363function Browser(window, document, $log, $sniffer) { 5364 var self = this, 5365 rawDocument = document[0], 5366 location = window.location, 5367 history = window.history, 5368 setTimeout = window.setTimeout, 5369 clearTimeout = window.clearTimeout, 5370 pendingDeferIds = {}; 5371 5372 self.isMock = false; 5373 5374 var outstandingRequestCount = 0; 5375 var outstandingRequestCallbacks = []; 5376 5377 // TODO(vojta): remove this temporary api 5378 self.$$completeOutstandingRequest = completeOutstandingRequest; 5379 self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; }; 5380 5381 /** 5382 * Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks` 5383 * counter. If the counter reaches 0, all the `outstandingRequestCallbacks` are executed. 5384 */ 5385 function completeOutstandingRequest(fn) { 5386 try { 5387 fn.apply(null, sliceArgs(arguments, 1)); 5388 } finally { 5389 outstandingRequestCount--; 5390 if (outstandingRequestCount === 0) { 5391 while (outstandingRequestCallbacks.length) { 5392 try { 5393 outstandingRequestCallbacks.pop()(); 5394 } catch (e) { 5395 $log.error(e); 5396 } 5397 } 5398 } 5399 } 5400 } 5401 5402 function getHash(url) { 5403 var index = url.indexOf('#'); 5404 return index === -1 ? '' : url.substr(index + 1); 5405 } 5406 5407 /** 5408 * @private 5409 * Note: this method is used only by scenario runner 5410 * TODO(vojta): prefix this method with $$ ? 5411 * @param {function()} callback Function that will be called when no outstanding request 5412 */ 5413 self.notifyWhenNoOutstandingRequests = function(callback) { 5414 if (outstandingRequestCount === 0) { 5415 callback(); 5416 } else { 5417 outstandingRequestCallbacks.push(callback); 5418 } 5419 }; 5420 5421 ////////////////////////////////////////////////////////////// 5422 // URL API 5423 ////////////////////////////////////////////////////////////// 5424 5425 var cachedState, lastHistoryState, 5426 lastBrowserUrl = location.href, 5427 baseElement = document.find('base'), 5428 reloadLocation = null; 5429 5430 cacheState(); 5431 lastHistoryState = cachedState; 5432 5433 /** 5434 * @name $browser#url 5435 * 5436 * @description 5437 * GETTER: 5438 * Without any argument, this method just returns current value of location.href. 5439 * 5440 * SETTER: 5441 * With at least one argument, this method sets url to new value. 5442 * If html5 history api supported, pushState/replaceState is used, otherwise 5443 * location.href/location.replace is used. 5444 * Returns its own instance to allow chaining 5445 * 5446 * NOTE: this api is intended for use only by the $location service. Please use the 5447 * {@link ng.$location $location service} to change url. 5448 * 5449 * @param {string} url New url (when used as setter) 5450 * @param {boolean=} replace Should new url replace current history record? 5451 * @param {object=} state object to use with pushState/replaceState 5452 */ 5453 self.url = function(url, replace, state) { 5454 // In modern browsers `history.state` is `null` by default; treating it separately 5455 // from `undefined` would cause `$browser.url('/foo')` to change `history.state` 5456 // to undefined via `pushState`. Instead, let's change `undefined` to `null` here. 5457 if (isUndefined(state)) { 5458 state = null; 5459 } 5460 5461 // Android Browser BFCache causes location, history reference to become stale. 5462 if (location !== window.location) location = window.location; 5463 if (history !== window.history) history = window.history; 5464 5465 // setter 5466 if (url) { 5467 var sameState = lastHistoryState === state; 5468 5469 // Don't change anything if previous and current URLs and states match. This also prevents 5470 // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. 5471 // See https://github.com/angular/angular.js/commit/ffb2701 5472 if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { 5473 return self; 5474 } 5475 var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); 5476 lastBrowserUrl = url; 5477 lastHistoryState = state; 5478 // Don't use history API if only the hash changed 5479 // due to a bug in IE10/IE11 which leads 5480 // to not firing a `hashchange` nor `popstate` event 5481 // in some cases (see #9143). 5482 if ($sniffer.history && (!sameBase || !sameState)) { 5483 history[replace ? 'replaceState' : 'pushState'](state, '', url); 5484 cacheState(); 5485 // Do the assignment again so that those two variables are referentially identical. 5486 lastHistoryState = cachedState; 5487 } else { 5488 if (!sameBase || reloadLocation) { 5489 reloadLocation = url; 5490 } 5491 if (replace) { 5492 location.replace(url); 5493 } else if (!sameBase) { 5494 location.href = url; 5495 } else { 5496 location.hash = getHash(url); 5497 } 5498 } 5499 return self; 5500 // getter 5501 } else { 5502 // - reloadLocation is needed as browsers don't allow to read out 5503 // the new location.href if a reload happened.
5504 // - the replacement is a workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=407172 5505 return reloadLocation || location.href.replace(/%27/g,"'"); 5506 } 5507 }; 5508 5509 /** 5510 * @name $browser#state 5511 * 5512 * @description 5513 * This method is a getter. 5514 * 5515 * Return history.state or null if history.state is undefined. 5516 * 5517 * @returns {object} state 5518 */ 5519 self.state = function() { 5520 return cachedState; 5521 }; 5522 5523 var urlChangeListeners = [], 5524 urlChangeInit = false; 5525 5526 function cacheStateAndFireUrlChange() { 5527 cacheState(); 5528 fireUrlChange(); 5529 } 5530 5531 function getCurrentState() { 5532 try { 5533 return history.state; 5534 } catch (e) { 5535 // MSIE can reportedly throw when there is no state (UNCONFIRMED). 5536 } 5537 } 5538 5539 // This variable should be used *only* inside the cacheState function. 5540 var lastCachedState = null; 5541 function cacheState() { 5542 // This should be the only place in $browser where `history.state` is read. 5543 cachedState = getCurrentState(); 5544 cachedState = isUndefined(cachedState) ? null : cachedState; 5545 5546 // Prevent callbacks fo fire twice if both hashchange & popstate were fired. 5547 if (equals(cachedState, lastCachedState)) { 5548 cachedState = lastCachedState; 5549 } 5550 lastCachedState = cachedState; 5551 } 5552 5553 function fireUrlChange() { 5554 if (lastBrowserUrl === self.url() && lastHistoryState === cachedState) { 5555 return; 5556 } 5557 5558 lastBrowserUrl = self.url(); 5559 lastHistoryState = cachedState; 5560 forEach(urlChangeListeners, function(listener) { 5561 listener(self.url(), cachedState); 5562 }); 5563 } 5564 5565 /** 5566 * @name $browser#onUrlChange 5567 * 5568 * @description 5569 * Register callback function that will be called, when url changes. 5570 * 5571 * It's only called when the url is changed from outside of angular: 5572 * - user types different url into address bar 5573 * - user clicks on history (forward/back) button 5574 * - user clicks on a link 5575 * 5576 * It's not called when url is changed by $browser.url() method 5577 * 5578 * The listener gets called with new url as parameter. 5579 * 5580 * NOTE: this api is intended for use only by the $location service. Please use the 5581 * {@link ng.$location $location service} to monitor url changes in angular apps. 5582 * 5583 * @param {function(string)} listener Listener function to be called when url changes. 5584 * @return {function(string)} Returns the registered listener fn - handy if the fn is anonymous. 5585 */ 5586 self.onUrlChange = function(callback) { 5587 // TODO(vojta): refactor to use node's syntax for events 5588 if (!urlChangeInit) { 5589 // We listen on both (hashchange/popstate) when available, as some browsers (e.g. Opera) 5590 // don't fire popstate when user change the address bar and don't fire hashchange when url 5591 // changed by push/replaceState 5592 5593 // html5 history api - popstate event 5594 if ($sniffer.history) jqLite(window).on('popstate', cacheStateAndFireUrlChange); 5595 // hashchange event 5596 jqLite(window).on('hashchange', cacheStateAndFireUrlChange); 5597 5598 urlChangeInit = true; 5599 } 5600 5601 urlChangeListeners.push(callback); 5602 return callback; 5603 }; 5604 5605 /** 5606 * @private 5607 * Remove popstate and hashchange handler from window. 5608 * 5609 * NOTE: this api is intended for use only by $rootScope. 5610 */ 5611 self.$$applicationDestroyed = function() { 5612 jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); 5613 }; 5614 5615 /** 5616 * Checks whether the url has changed outside of Angular. 5617 * Needs to be exported to be able to check for changes that have been done in sync, 5618 * as hashchange/popstate events fire in async. 5619 */ 5620 self.$$checkUrlChange = fireUrlChange; 5621 5622 ////////////////////////////////////////////////////////////// 5623 // Misc API 5624 ////////////////////////////////////////////////////////////// 5625 5626 /** 5627 * @name $browser#baseHref 5628 * 5629 * @description 5630 * Returns current <base href> 5631 * (always relative - without domain) 5632 * 5633 * @returns {string} The current base href 5634 */ 5635 self.baseHref = function() { 5636 var href = baseElement.attr('href'); 5637 return href ? href.replace(/^(https?\:)?\/\/[^\/]*/, '') : ''; 5638 }; 5639 5640 /** 5641 * @name $browser#defer 5642 * @param {function()} fn A function, who's execution should be deferred. 5643 * @param {number=} [delay=0] of milliseconds to defer the function execution. 5644 * @returns {*} DeferId that can be used to cancel the task via `$browser.defer.cancel()`. 5645 * 5646 * @description 5647 * Executes a fn asynchronously via `setTimeout(fn, delay)`. 5648 * 5649 * Unlike when calling `setTimeout` directly, in test this function is mocked and instead of using 5650 * `setTimeout` in tests, the fns are queued in an array, which can be programmatically flushed 5651 * via `$browser.defer.flush()`. 5652 * 5653 */ 5654 self.defer = function(fn, delay) { 5655 var timeoutId; 5656 outstandingRequestCount++; 5657 timeoutId = setTimeout(function() {
5658 delete pendingDeferIds[timeoutId]; 5659 completeOutstandingRequest(fn); 5660 }, delay || 0); 5661 pendingDeferIds[timeoutId] = true; 5662 return timeoutId; 5663 }; 5664 5665 5666 /** 5667 * @name $browser#defer.cancel 5668 * 5669 * @description 5670 * Cancels a deferred task identified with `deferId`. 5671 * 5672 * @param {*} deferId Token returned by the `$browser.defer` function. 5673 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 5674 * canceled. 5675 */ 5676 self.defer.cancel = function(deferId) { 5677 if (pendingDeferIds[deferId]) { 5678 delete pendingDeferIds[deferId]; 5679 clearTimeout(deferId); 5680 completeOutstandingRequest(noop); 5681 return true; 5682 } 5683 return false; 5684 }; 5685 5686} 5687 5688function $BrowserProvider() { 5689 this.$get = ['$window', '$log', '$sniffer', '$document', 5690 function($window, $log, $sniffer, $document) { 5691 return new Browser($window, $document, $log, $sniffer); 5692 }]; 5693} 5694 5695/** 5696 * @ngdoc service 5697 * @name $cacheFactory 5698 * 5699 * @description 5700 * Factory that constructs {@link $cacheFactory.Cache Cache} objects and gives access to 5701 * them. 5702 * 5703 * ```js 5704 * 5705 * var cache = $cacheFactory('cacheId'); 5706 * expect($cacheFactory.get('cacheId')).toBe(cache); 5707 * expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined(); 5708 * 5709 * cache.put("key", "value"); 5710 * cache.put("another key", "another value"); 5711 * 5712 * // We've specified no options on creation 5713 * expect(cache.info()).toEqual({id: 'cacheId', size: 2}); 5714 * 5715 * ``` 5716 * 5717 * 5718 * @param {string} cacheId Name or id of the newly created cache. 5719 * @param {object=} options Options object that specifies the cache behavior. Properties: 5720 * 5721 * - `{number=}` `capacity` â turns the cache into LRU cache. 5722 * 5723 * @returns {object} Newly created cache object with the following set of methods: 5724 * 5725 * - `{object}` `info()` â Returns id, size, and options of cache. 5726 * - `{{*}}` `put({string} key, {*} value)` â Puts a new key-value pair into the cache and returns 5727 * it. 5728 * - `{{*}}` `get({string} key)` â Returns cached value for `key` or undefined for cache miss. 5729 * - `{void}` `remove({string} key)` â Removes a key-value pair from the cache. 5730 * - `{void}` `removeAll()` â Removes all cached values. 5731 * - `{void}` `destroy()` â Removes references to this cache from $cacheFactory. 5732 * 5733 * @example 5734 <example module="cacheExampleApp"> 5735 <file name="index.html"> 5736 <div ng-controller="CacheController"> 5737 <input ng-model="newCacheKey" placeholder="Key"> 5738 <input ng-model="newCacheValue" placeholder="Value"> 5739 <button ng-click="put(newCacheKey, newCacheValue)">Cache</button> 5740 5741 <p ng-if="keys.length">Cached Values</p> 5742 <div ng-repeat="key in keys"> 5743 <span ng-bind="key"></span> 5744 <span>: </span> 5745 <b ng-bind="cache.get(key)"></b> 5746 </div> 5747 5748 <p>Cache Info</p> 5749 <div ng-repeat="(key, value) in cache.info()"> 5750 <span ng-bind="key"></span> 5751 <span>: </span> 5752 <b ng-bind="value"></b> 5753 </div> 5754 </div> 5755 </file> 5756 <file name="script.js"> 5757 angular.module('cacheExampleApp', []). 5758 controller('CacheController', ['$scope', '$cacheFactory', function($scope, $cacheFactory) { 5759 $scope.keys = []; 5760 $scope.cache = $cacheFactory('cacheId'); 5761 $scope.put = function(key, value) { 5762 if ($scope.cache.get(key) === undefined) { 5763 $scope.keys.push(key); 5764 } 5765 $scope.cache.put(key, value === undefined ? null : value); 5766 }; 5767 }]); 5768 </file> 5769 <file name="style.css"> 5770 p { 5771 margin: 10px 0 3px; 5772 } 5773 </file> 5774 </example> 5775 */ 5776function $CacheFactoryProvider() { 5777 5778 this.$get = function() { 5779 var caches = {}; 5780 5781 function cacheFactory(cacheId, options) { 5782 if (cacheId in caches) { 5783 throw minErr('$cacheFactory')('iid', "CacheId '{0}' is already taken!", cacheId); 5784 } 5785 5786 var size = 0, 5787 stats = extend({}, options, {id: cacheId}), 5788 data = {}, 5789 capacity = (options && options.capacity) || Number.MAX_VALUE, 5790 lruHash = {}, 5791 freshEnd = null, 5792 staleEnd = null; 5793 5794 /** 5795 * @ngdoc type 5796 * @name $cacheFactory.Cache 5797 *
5798 * @description 5799 * A cache object used to store and retrieve data, primarily used by 5800 * {@link $http $http} and the {@link ng.directive:script script} directive to cache 5801 * templates and other data. 5802 * 5803 * ```js 5804 * angular.module('superCache') 5805 * .factory('superCache', ['$cacheFactory', function($cacheFactory) { 5806 * return $cacheFactory('super-cache'); 5807 * }]); 5808 * ``` 5809 * 5810 * Example test: 5811 * 5812 * ```js 5813 * it('should behave like a cache', inject(function(superCache) { 5814 * superCache.put('key', 'value'); 5815 * superCache.put('another key', 'another value'); 5816 * 5817 * expect(superCache.info()).toEqual({ 5818 * id: 'super-cache', 5819 * size: 2 5820 * }); 5821 * 5822 * superCache.remove('another key'); 5823 * expect(superCache.get('another key')).toBeUndefined(); 5824 * 5825 * superCache.removeAll(); 5826 * expect(superCache.info()).toEqual({ 5827 * id: 'super-cache', 5828 * size: 0 5829 * }); 5830 * })); 5831 * ``` 5832 */ 5833 return caches[cacheId] = { 5834 5835 /** 5836 * @ngdoc method 5837 * @name $cacheFactory.Cache#put 5838 * @kind function 5839 * 5840 * @description 5841 * Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be 5842 * retrieved later, and incrementing the size of the cache if the key was not already 5843 * present in the cache. If behaving like an LRU cache, it will also remove stale 5844 * entries from the set. 5845 * 5846 * It will not insert undefined values into the cache. 5847 * 5848 * @param {string} key the key under which the cached data is stored. 5849 * @param {*} value the value to store alongside the key. If it is undefined, the key 5850 * will not be stored. 5851 * @returns {*} the value stored. 5852 */ 5853 put: function(key, value) { 5854 if (isUndefined(value)) return; 5855 if (capacity < Number.MAX_VALUE) { 5856 var lruEntry = lruHash[key] || (lruHash[key] = {key: key}); 5857 5858 refresh(lruEntry); 5859 } 5860 5861 if (!(key in data)) size++; 5862 data[key] = value; 5863 5864 if (size > capacity) { 5865 this.remove(staleEnd.key); 5866 } 5867 5868 return value; 5869 }, 5870 5871 /** 5872 * @ngdoc method 5873 * @name $cacheFactory.Cache#get 5874 * @kind function 5875 * 5876 * @description 5877 * Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object. 5878 * 5879 * @param {string} key the key of the data to be retrieved 5880 * @returns {*} the value stored. 5881 */ 5882 get: function(key) { 5883 if (capacity < Number.MAX_VALUE) { 5884 var lruEntry = lruHash[key]; 5885 5886 if (!lruEntry) return; 5887 5888 refresh(lruEntry); 5889 } 5890 5891 return data[key]; 5892 }, 5893 5894 5895 /** 5896 * @ngdoc method 5897 * @name $cacheFactory.Cache#remove 5898 * @kind function 5899 * 5900 * @description 5901 * Removes an entry from the {@link $cacheFactory.Cache Cache} object. 5902 * 5903 * @param {string} key the key of the entry to be removed 5904 */ 5905 remove: function(key) { 5906 if (capacity < Number.MAX_VALUE) { 5907 var lruEntry = lruHash[key]; 5908 5909 if (!lruEntry) return; 5910 5911 if (lruEntry == freshEnd) freshEnd = lruEntry.p; 5912 if (lruEntry == staleEnd) staleEnd = lruEntry.n; 5913 link(lruEntry.n,lruEntry.p); 5914 5915 delete lruHash[key]; 5916 } 5917 5918 delete data[key]; 5919 size--; 5920 }, 5921 5922 5923 /** 5924 * @ngdoc method 5925 * @name $cacheFactory.Cache#removeAll 5926 * @kind function 5927 * 5928 * @description 5929 * Clears the cache object of any entries. 5930 */ 5931 removeAll: function() { 5932 data = {}; 5933 size = 0; 5934 lruHash = {}; 5935 freshEnd = staleEnd = null; 5936 }, 5937 5938 5939 /** 5940 * @ngdoc method 5941 * @name $cacheFactory.Cache#destroy 5942 * @kind function 5943 *
5944 * @description 5945 * Destroys the {@link $cacheFactory.Cache Cache} object entirely, 5946 * removing it from the {@link $cacheFactory $cacheFactory} set. 5947 */ 5948 destroy: function() { 5949 data = null; 5950 stats = null; 5951 lruHash = null; 5952 delete caches[cacheId]; 5953 }, 5954 5955 5956 /** 5957 * @ngdoc method 5958 * @name $cacheFactory.Cache#info 5959 * @kind function 5960 * 5961 * @description 5962 * Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}. 5963 * 5964 * @returns {object} an object with the following properties: 5965 * <ul> 5966 * <li>**id**: the id of the cache instance</li> 5967 * <li>**size**: the number of entries kept in the cache instance</li> 5968 * <li>**...**: any additional properties from the options object when creating the 5969 * cache.</li> 5970 * </ul> 5971 */ 5972 info: function() { 5973 return extend({}, stats, {size: size}); 5974 } 5975 }; 5976 5977 5978 /** 5979 * makes the `entry` the freshEnd of the LRU linked list 5980 */ 5981 function refresh(entry) { 5982 if (entry != freshEnd) { 5983 if (!staleEnd) { 5984 staleEnd = entry; 5985 } else if (staleEnd == entry) { 5986 staleEnd = entry.n; 5987 } 5988 5989 link(entry.n, entry.p); 5990 link(entry, freshEnd); 5991 freshEnd = entry; 5992 freshEnd.n = null; 5993 } 5994 } 5995 5996 5997 /** 5998 * bidirectionally links two entries of the LRU linked list 5999 */ 6000 function link(nextEntry, prevEntry) { 6001 if (nextEntry != prevEntry) { 6002 if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify 6003 if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify 6004 } 6005 } 6006 } 6007 6008 6009 /** 6010 * @ngdoc method 6011 * @name $cacheFactory#info 6012 * 6013 * @description 6014 * Get information about all the caches that have been created 6015 * 6016 * @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info` 6017 */ 6018 cacheFactory.info = function() { 6019 var info = {}; 6020 forEach(caches, function(cache, cacheId) { 6021 info[cacheId] = cache.info(); 6022 }); 6023 return info; 6024 }; 6025 6026 6027 /** 6028 * @ngdoc method 6029 * @name $cacheFactory#get 6030 * 6031 * @description 6032 * Get access to a cache object by the `cacheId` used when it was created. 6033 * 6034 * @param {string} cacheId Name or id of a cache to access. 6035 * @returns {object} Cache object identified by the cacheId or undefined if no such cache. 6036 */ 6037 cacheFactory.get = function(cacheId) { 6038 return caches[cacheId]; 6039 }; 6040 6041 6042 return cacheFactory; 6043 }; 6044} 6045 6046/** 6047 * @ngdoc service 6048 * @name $templateCache 6049 * 6050 * @description 6051 * The first time a template is used, it is loaded in the template cache for quick retrieval. You 6052 * can load templates directly into the cache in a `script` tag, or by consuming the 6053 * `$templateCache` service directly. 6054 * 6055 * Adding via the `script` tag: 6056 * 6057 * ```html 6058 * <script type="text/ng-template" id="templateId.html"> 6059 * <p>This is the content of the template</p> 6060 * </script> 6061 * ``` 6062 * 6063 * **Note:** the `script` tag containing the template does not need to be included in the `head` of 6064 * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, 6065 * element with ng-app attribute), otherwise the template will be ignored. 6066 * 6067 * Adding via the `$templateCache` service: 6068 * 6069 * ```js 6070 * var myApp = angular.module('myApp', []); 6071 * myApp.run(function($templateCache) { 6072 * $templateCache.put('templateId.html', 'This is the content of the template'); 6073 * }); 6074 * ``` 6075 * 6076 * To retrieve the template later, simply use it in your HTML: 6077 * ```html 6078 * <div ng-include=" 'templateId.html' "></div> 6079 * ``` 6080 * 6081 * or get it via Javascript: 6082 * ```js 6083 * $templateCache.get('templateId.html') 6084 * ``` 6085 * 6086 * See {@link ng.$cacheFactory $cacheFactory}. 6087 * 6088 */ 6089function $TemplateCacheProvider() { 6090 this.$get = ['$cacheFactory', function($cacheFactory) { 6091 return $cacheFactory('templates'); 6092 }]; 6093} 6094 6095/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 6096 * Any commits to this file should be reviewed with security in mind. * 6097 * Changes to this file can potentially create security vulnerabilities. * 6098 * An approval from 2 Core members with history of modifying * 6099 * this file is required. * 6100 * * 6101 * Does the change somehow allow for arbitrary javascript to be executed? * 6102 * Or allows for someone to change the prototype of built-in objects? * 6103 * Or gives undesired access to variables likes document or window? * 6104 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 6105
6106/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! 6107 * 6108 * DOM-related variables: 6109 * 6110 * - "node" - DOM Node 6111 * - "element" - DOM Element or Node 6112 * - "$node" or "$element" - jqLite-wrapped node or element 6113 * 6114 * 6115 * Compiler related stuff: 6116 * 6117 * - "linkFn" - linking fn of a single directive 6118 * - "nodeLinkFn" - function that aggregates all linking fns for a particular node 6119 * - "childLinkFn" - function that aggregates all linking fns for child nodes of a particular node 6120 * - "compositeLinkFn" - function that aggregates all linking fns for a compilation root (nodeList) 6121 */ 6122 6123 6124/** 6125 * @ngdoc service 6126 * @name $compile 6127 * @kind function 6128 * 6129 * @description 6130 * Compiles an HTML string or DOM into a template and produces a template function, which 6131 * can then be used to link {@link ng.$rootScope.Scope `scope`} and the template together. 6132 * 6133 * The compilation is a process of walking the DOM tree and matching DOM elements to 6134 * {@link ng.$compileProvider#directive directives}. 6135 * 6136 * <div class="alert alert-warning"> 6137 * **Note:** This document is an in-depth reference of all directive options. 6138 * For a gentle introduction to directives with examples of common use cases, 6139 * see the {@link guide/directive directive guide}. 6140 * </div> 6141 * 6142 * ## Comprehensive Directive API 6143 * 6144 * There are many different options for a directive. 6145 * 6146 * The difference resides in the return value of the factory function. 6147 * You can either return a "Directive Definition Object" (see below) that defines the directive properties, 6148 * or just the `postLink` function (all other properties will have the default values). 6149 * 6150 * <div class="alert alert-success"> 6151 * **Best Practice:** It's recommended to use the "directive definition object" form. 6152 * </div> 6153 * 6154 * Here's an example directive declared with a Directive Definition Object: 6155 * 6156 * ```js 6157 * var myModule = angular.module(...); 6158 * 6159 * myModule.directive('directiveName', function factory(injectables) { 6160 * var directiveDefinitionObject = { 6161 * priority: 0, 6162 * template: '<div></div>
6162', // or // function(tElement, tAttrs) { ... }, 6163 * // or 6164 * // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... }, 6165 * transclude: false, 6166 * restrict: 'A', 6167 * templateNamespace: 'html', 6168 * scope: false, 6169 * controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... }, 6170 * controllerAs: 'stringIdentifier', 6171 * bindToController: false, 6172 * require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'], 6173 * compile: function compile(tElement, tAttrs, transclude) { 6174 * return { 6175 * pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6176 * post: function postLink(scope, iElement, iAttrs, controller) { ... } 6177 * } 6178 * // or 6179 * // return function postLink( ... ) { ... } 6180 * }, 6181 * // or 6182 * // link: { 6183 * // pre: function preLink(scope, iElement, iAttrs, controller) { ... }, 6184 * // post: function postLink(scope, iElement, iAttrs, controller) { ... } 6185 * // } 6186 * // or 6187 * // link: function postLink( ... ) { ... } 6188 * }; 6189 * return directiveDefinitionObject; 6190 * }); 6191 * ``` 6192 * 6193 * <div class="alert alert-warning"> 6194 * **Note:** Any unspecified options will use the default value. You can see the default values below. 6195 * </div> 6196 * 6197 * Therefore the above can be simplified as: 6198 * 6199 * ```js 6200 * var myModule = angular.module(...); 6201 * 6202 * myModule.directive('directiveName', function factory(injectables) { 6203 * var directiveDefinitionObject = { 6204 * link: function postLink(scope, iElement, iAttrs) { ... } 6205 * }; 6206 * return directiveDefinitionObject; 6207 * // or 6208 * // return function postLink(scope, iElement, iAttrs) { ... } 6209 * }); 6210 * ``` 6211 * 6212 * 6213 * 6214 * ### Directive Definition Object 6215 * 6216 * The directive definition object provides instructions to the {@link ng.$compile 6217 * compiler}. The attributes are: 6218 * 6219 * #### `multiElement` 6220 * When this property is set to true, the HTML compiler will collect DOM nodes between 6221 * nodes with the attributes `directive-name-start` and `directive-name-end`, and group them 6222 * together as the directive elements. It is recommended that this feature be used on directives 6223 * which are not strictly behavioural (such as {@link ngClick}), and which 6224 * do not manipulate or replace child nodes (such as {@link ngInclude}). 6225 * 6226 * #### `priority` 6227 * When there are multiple directives defined on a single DOM element, sometimes it 6228 * is necessary to specify the order in which the directives are applied. The `priority` is used 6229 * to sort the directives before their `compile` functions get called. Priority is defined as a 6230 * number. Directives with greater numerical `priority` are compiled first. Pre-link functions 6231 * are also run in priority order, but post-link functions are run in reverse order. The order 6232 * of directives with the same priority is undefined. The default priority is `0`. 6233 * 6234 * #### `terminal` 6235 * If set to true then the current `priority` will be the last set of directives 6236 * which will execute (any directives at the current priority will still execute 6237 * as the order of execution on same `priority` is undefined). Note that expressions 6238 * and other directives used in the directive's template will also be excluded from execution. 6239 * 6240 * #### `scope` 6241 * **If set to `true`,** then a new scope will be created for this directive. If multiple directives on the 6242 * same element request a new scope, only one new scope is created. The new scope rule does not 6243 * apply for the root of the template since the root of the template always gets a new scope. 6244 * 6245 * **If set to `{}` (object hash),** then a new "isolate" scope is created. The 'isolate' scope differs from 6246 * normal scope in that it does not prototypically inherit from the parent scope. This is useful 6247 * when creating reusable components, which should not accidentally read or modify data in the 6248 * parent scope. 6249 * 6250 * The 'isolate' scope takes an object hash which defines a set of local scope properties 6251 * derived from the parent scope. These local properties are useful for aliasing values for 6252 * templates. Locals definition is a hash of local scope property to its source: 6253 * 6254 * * `@` or `@attr` - bind a local scope property to the value of DOM attribute. The result is
6255 * always a string since DOM attributes are strings. If no `attr` name is specified then the 6256 * attribute name is assumed to be the same as the local name. 6257 * Given `<widget my-attr="hello {{name}}">` and widget definition 6258 * of `scope: { localName:'@myAttr' }`, then widget scope property `localName` will reflect 6259 * the interpolated value of `hello {{name}}`. As the `name` attribute changes so will the 6260 * `localName` property on the widget scope. The `name` is read from the parent scope (not 6261 * component scope). 6262 * 6263 * * `=` or `=attr` - set up bi-directional binding between a local scope property and the 6264 * parent scope property of name defined via the value of the `attr` attribute. If no `attr` 6265 * name is specified then the attribute name is assumed to be the same as the local name. 6266 * Given `<widget my-attr="parentModel">` and widget definition of 6267 * `scope: { localModel:'=myAttr' }`, then widget scope property `localModel` will reflect the 6268 * value of `parentModel` on the parent scope. Any changes to `parentModel` will be reflected 6269 * in `localModel` and any changes in `localModel` will reflect in `parentModel`. If the parent 6270 * scope property doesn't exist, it will throw a NON_ASSIGNABLE_MODEL_EXPRESSION exception. You 6271 * can avoid this behavior using `=?` or `=?attr` in order to flag the property as optional. If 6272 * you want to shallow watch for changes (i.e. $watchCollection instead of $watch) you can use 6273 * `=*` or `=*attr` (`=*?` or `=*?attr` if the property is optional). 6274 * 6275 * * `&` or `&attr` - provides a way to execute an expression in the context of the parent scope. 6276 * If no `attr` name is specified then the attribute name is assumed to be the same as the 6277 * local name. Given `<widget my-attr="count = count + value">` and widget definition of 6278 * `scope: { localFn:'&myAttr' }`, then isolate scope property `localFn` will point to 6279 * a function wrapper for the `count = count + value` expression. Often it's desirable to 6280 * pass data from the isolated scope via an expression to the parent scope, this can be 6281 * done by passing a map of local variable names and values into the expression wrapper fn. 6282 * For example, if the expression is `increment(amount)` then we can specify the amount value 6283 * by calling the `localFn` as `localFn({amount: 22})`. 6284 * 6285 * 6286 * #### `bindToController` 6287 * When an isolate scope is used for a component (see above), and `controllerAs` is used, `bindToController: true` will 6288 * allow a component to have its properties bound to the controller, rather than to scope. When the controller 6289 * is instantiated, the initial values of the isolate scope bindings are already available. 6290 * 6291 * #### `controller` 6292 * Controller constructor function. The controller is instantiated before the 6293 * pre-linking phase and it is shared with other directives (see 6294 * `require` attribute). This allows the directives to communicate with each other and augment 6295 * each other's behavior. The controller is injectable (and supports bracket notation) with the following locals: 6296 * 6297 * * `$scope` - Current scope associated with the element 6298 * * `$element` - Current element 6299 * * `$attrs` - Current attributes object for the element 6300 * * `$transclude` - A transclude linking function pre-bound to the correct transclusion scope: 6301 * `function([scope], cloneLinkingFn, futureParentElement)`. 6302 * * `scope`: optional argument to override the scope. 6303 * * `cloneLinkingFn`: optional argument to create clones of the original transcluded content. 6304 * * `futureParentElement`: 6305 * * defines the parent to which the `cloneLinkingFn` will add the cloned elements. 6306 * * default: `$element.parent()` resp. `$element` for `transclude:'element'` resp. `transclude:true`. 6307 * * only needed for transcludes that are allowed to contain non html elements (e.g. SVG elements) 6308 * and when the `cloneLinkinFn` is passed, 6309 * as those elements need to created and cloned in a special way when they are defined outside their 6310 * usual containers (e.g. like `<svg>`). 6311 * * See also the `directive.templateNamespace` property. 6312 * 6313 * 6314 * #### `require` 6315 * Require another directive and inject its controller as the fourth argument to the linking function. The
6316 * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the 6317 * injected argument will be an array in corresponding order. If no such directive can be 6318 * found, or if the directive does not have a controller, then an error is raised (unless no link function 6319 * is specified, in which case error checking is skipped). The name can be prefixed with: 6320 * 6321 * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. 6322 * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. 6323 * * `^` - Locate the required controller by searching the element and its parents. Throw an error if not found. 6324 * * `^^` - Locate the required controller by searching the element's parents. Throw an error if not found. 6325 * * `?^` - Attempt to locate the required controller by searching the element and its parents or pass 6326 * `null` to the `link` fn if not found. 6327 * * `?^^` - Attempt to locate the required controller by searching the element's parents, or pass 6328 * `null` to the `link` fn if not found. 6329 * 6330 * 6331 * #### `controllerAs` 6332 * Identifier name for a reference to the controller in the directive's scope. 6333 * This allows the controller to be referenced from the directive template. The directive 6334 * needs to define a scope for this configuration to be used. Useful in the case when 6335 * directive is used as component. 6336 * 6337 * 6338 * #### `restrict` 6339 * String of subset of `EACM` which restricts the directive to a specific directive 6340 * declaration style. If omitted, the defaults (elements and attributes) are used. 6341 * 6342 * * `E` - Element name (default): `<my-directive></my-directive>` 6343 * * `A` - Attribute (default): `<div my-directive="exp"></div>` 6344 * * `C` - Class: `<div class="my-directive: exp;"></div>` 6345 * * `M` - Comment: `<!-- directive: my-directive exp -->` 6346 * 6347 * 6348 * #### `templateNamespace` 6349 * String representing the document type used by the markup in the template. 6350 * AngularJS needs this information as those elements need to be created and cloned 6351 * in a special way when they are defined outside their usual containers like `<svg>` and `<math>`. 6352 * 6353 * * `html` - All root nodes in the template are HTML. Root nodes may also be 6354 * top-level elements such as `<svg>` or `<math>`. 6355 * * `svg` - The root nodes in the template are SVG elements (excluding `<math>`). 6356 * * `math` - The root nodes in the template are MathML elements (excluding `<svg>`). 6357 * 6358 * If no `templateNamespace` is specified, then the namespace is considered to be `html`. 6359 * 6360 * #### `template` 6361 * HTML markup that may: 6362 * * Replace the contents of the directive's element (default). 6363 * * Replace the directive's element itself (if `replace` is true - DEPRECATED). 6364 * * Wrap the contents of the directive's element (if `transclude` is true). 6365 * 6366 * Value may be: 6367 * 6368 * * A string. For example `<div red-on-hover>{{delete_str}}</div>`. 6369 * * A function which takes two arguments `tElement` and `tAttrs` (described in the `compile` 6370 * function api below) and returns a string value. 6371 * 6372 * 6373 * #### `templateUrl` 6374 * This is similar to `template` but the template is loaded from the specified URL, asynchronously. 6375 * 6376 * Because template loading is asynchronous the compiler will suspend compilation of directives on that element 6377 * for later when the template has been resolved. In the meantime it will continue to compile and link 6378 * sibling and parent elements as though this element had not contained any directives. 6379 * 6380 * The compiler does not suspend the entire compilation to wait for templates to be loaded because this 6381 * would result in the whole app "stalling" until all templates are loaded asynchronously - even in the 6382 * case when only one deeply nested directive has `templateUrl`. 6383 * 6384 * Template loading is asynchronous even if the template has been preloaded into the {@link $templateCache} 6385 * 6386 * You can specify `templateUrl` as a string representing the URL or as a function which takes two 6387 * arguments `tElement` and `tAttrs` (described in the `compile` function api below) and returns 6388 * a string value representing the url. In either case, the template URL is passed through {@link 6389 * $sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}. 6390 * 6391 * 6392 * #### `replace` ([*DEPRECATED*!], will be removed in next major release - i.e. v2.0) 6393 * specify what the template should replace. Defaults to `false`. 6394 * 6395 * * `true` - the template will replace the directive's element. 6396 * * `false` - the template will replace the contents of the directive's element. 6397 * 6398 * The replacement process migrates all of the attributes / classes from the old element to the new 6399 * one. See the {@link guide/directive#template-expanding-directive 6400 * Directives Guide} for an example. 6401 * 6402 * There are very few scenarios where element replacement is required for the application function, 6403 * the main one being reusable custom components that are used within SVG contexts 6404 * (because SVG doesn't work with custom elements in the DOM tree). 6405 * 6406 * #### `transclude` 6407 * Extract the contents of the element where the directive appears and make it available to the directive. 6408 * The contents are compiled and provided to the directive as a **transclusion function**. See the 6409 * {@link $compile#transclusion Transclusion} section below. 6410 * 6411 * There are two kinds of transclusion depending upon whether you want to transclude just the contents of the 6412 * directive's element or the entire element: 6413 * 6414 * * `true` - transclude the content (i.e. the child nodes) of the directive's element. 6415 * * `'element'` - transclude the whole of the directive's element including any directives on this 6416 * element that defined at a lower priority than this directive. When used, the `template` 6417 * property is ignored. 6418 * 6419 * 6420 * #### `compile` 6421 * 6422 * ```js 6423 * function compile(tElement, tAttrs, transclude) { ... } 6424 * ``` 6425 * 6426 * The compile function deals with transforming the template DOM. Since most directives do not do 6427 * template transformation, it is not used often. The compile function takes the following arguments: 6428 * 6429 * * `tElement` - template element - The element where the directive has been declared. It is 6430 * safe to do template transformation on the element and child elements only. 6431 * 6432 * * `tAttrs` - template attributes - Normalized list of attributes declared on this element shared 6433 * between all directive compile functions. 6434 * 6435 * * `transclude` - [*DEPRECATED*!] A transclude linking function: `function(scope, cloneLinkingFn)` 6436 * 6437 * <div class="alert alert-warning"> 6438 * **Note:** The template instance and the link instance may be different objects if the template has 6439 * been cloned. For this reason it is **not** safe to do anything other than DOM transformations that 6440 * apply to all cloned DOM nodes within the compile function. Specifically, DOM listener registration
6441 * should be done in a linking function rather than in a compile function. 6442 * </div> 6443 6444 * <div class="alert alert-warning"> 6445 * **Note:** The compile function cannot handle directives that recursively use themselves in their 6446 * own templates or compile functions. Compiling these directives results in an infinite loop and a 6447 * stack overflow errors. 6448 * 6449 * This can be avoided by manually using $compile in the postLink function to imperatively compile 6450 * a directive's template instead of relying on automatic template compilation via `template` or 6451 * `templateUrl` declaration or manual compilation inside the compile function. 6452 * </div> 6453 * 6454 * <div class="alert alert-danger"> 6455 * **Note:** The `transclude` function that is passed to the compile function is deprecated, as it 6456 * e.g. does not know about the right outer scope. Please use the transclude function that is passed 6457 * to the link function instead. 6458 * </div> 6459 6460 * A compile function can have a return value which can be either a function or an object. 6461 * 6462 * * returning a (post-link) function - is equivalent to registering the linking function via the 6463 * `link` property of the config object when the compile function is empty. 6464 * 6465 * * returning an object with function(s) registered via `pre` and `post` properties - allows you to 6466 * control when a linking function should be called during the linking phase. See info about 6467 * pre-linking and post-linking functions below. 6468 * 6469 * 6470 * #### `link` 6471 * This property is used only if the `compile` property is not defined. 6472 * 6473 * ```js 6474 * function link(scope, iElement, iAttrs, controller, transcludeFn) { ... } 6475 * ``` 6476 * 6477 * The link function is responsible for registering DOM listeners as well as updating the DOM. It is 6478 * executed after the template has been cloned. This is where most of the directive logic will be 6479 * put. 6480 * 6481 * * `scope` - {@link ng.$rootScope.Scope Scope} - The scope to be used by the 6482 * directive for registering {@link ng.$rootScope.Scope#$watch watches}. 6483 * 6484 * * `iElement` - instance element - The element where the directive is to be used. It is safe to 6485 * manipulate the children of the element only in `postLink` function since the children have 6486 * already been linked. 6487 * 6488 * * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared 6489 * between all directive linking functions. 6490 * 6491 * * `controller` - the directive's required controller instance(s) - Instances are shared 6492 * among all directives, which allows the directives to use the controllers as a communication 6493 * channel. The exact value depends on the directive's `require` property: 6494 * * no controller(s) required: the directive's own controller, or `undefined` if it doesn't have one 6495 * * `string`: the controller instance 6496 * * `array`: array of controller instances 6497 * 6498 * If a required controller cannot be found, and it is optional, the instance is `null`, 6499 * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown. 6500 * 6501 * Note that you can also require the directive's own controller - it will be made available like 6502 * like any other controller. 6503 * 6504 * * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope. 6505 * This is the same as the `$transclude` 6506 * parameter of directive controllers, see there for details. 6507 * `function([scope], cloneLinkingFn, futureParentElement)`. 6508 * 6509 * #### Pre-linking function 6510 * 6511 * Executed before the child elements are linked. Not safe to do DOM transformation since the 6512 * compiler linking function will fail to locate the correct elements for linking. 6513 * 6514 * #### Post-linking function 6515 * 6516 * Executed after the child elements are linked. 6517 *
6518 * Note that child elements that contain `templateUrl` directives will not have been compiled 6519 * and linked since they are waiting for their template to load asynchronously and their own 6520 * compilation and linking has been suspended until that occurs. 6521 * 6522 * It is safe to do DOM transformation in the post-linking function on elements that are not waiting 6523 * for their async templates to be resolved. 6524 * 6525 * 6526 * ### Transclusion 6527 * 6528 * Transclusion is the process of extracting a collection of DOM element from one part of the DOM and 6529 * copying them to another part of the DOM, while maintaining their connection to the original AngularJS 6530 * scope from where they were taken. 6531 * 6532 * Transclusion is used (often with {@link ngTransclude}) to insert the 6533 * original contents of a directive's element into a specified place in the template of the directive. 6534 * The benefit of transclusion, over simply moving the DOM elements manually, is that the transcluded 6535 * content has access to the properties on the scope from which it was taken, even if the directive 6536 * has isolated scope. 6537 * See the {@link guide/directive#creating-a-directive-that-wraps-other-elements Directives Guide}. 6538 * 6539 * This makes it possible for the widget to have private state for its template, while the transcluded 6540 * content has access to its originating scope. 6541 * 6542 * <div class="alert alert-warning"> 6543 * **Note:** When testing an element transclude directive you must not place the directive at the root of the 6544 * DOM fragment that is being compiled. See {@link guide/unit-testing#testing-transclusion-directives 6545 * Testing Transclusion Directives}. 6546 * </div> 6547 * 6548 * #### Transclusion Functions 6549 * 6550 * When a directive requests transclusion, the compiler extracts its contents and provides a **transclusion 6551 * function** to the directive's `link` function and `controller`. This transclusion function is a special 6552 * **linking function** that will return the compiled contents linked to a new transclusion scope. 6553 * 6554 * <div class="alert alert-info"> 6555 * If you are just using {@link ngTransclude} then you don't need to worry about this function, since 6556 * ngTransclude will deal with it for us. 6557 * </div> 6558 * 6559 * If you want to manually control the insertion and removal of the transcluded content in your directive 6560 * then you must use this transclude function. When you call a transclude function it returns a a jqLite/JQuery 6561 * object that contains the compiled DOM, which is linked to the correct transclusion scope. 6562 * 6563 * When you call a transclusion function you can pass in a **clone attach function**. This function accepts 6564 * two parameters, `function(clone, scope) { ... }`, where the `clone` is a fresh compiled copy of your transcluded 6565 * content and the `scope` is the newly created transclusion scope, to which the clone is bound. 6566 * 6567 * <div class="alert alert-info"> 6568 * **Best Practice**: Always provide a `cloneFn` (clone attach function) when you call a translude function 6569 * since you then get a fresh clone of the original DOM and also have access to the new transclusion scope. 6570 * </div> 6571 * 6572 * It is normal practice to attach your transcluded content (`clone`) to the DOM inside your **clone 6573 * attach function**: 6574 * 6575 * ```js 6576 * var transcludedContent, transclusionScope; 6577 * 6578 * $transclude(function(clone, scope) { 6579 * element.append(clone); 6580 * transcludedContent = clone; 6581 * transclusionScope = scope; 6582 * }); 6583 * ``` 6584 * 6585 * Later, if you want to remove the transcluded content from your DOM then you should also destroy the 6586 * associated transclusion scope: 6587 * 6588 * ```js 6589 * transcludedContent.remove(); 6590 * transclusionScope.$destroy(); 6591 * ``` 6592 * 6593 * <div class="alert alert-info"> 6594 * **Best Practice**: if you intend to add and remove transcluded content manually in your directive 6595 * (by calling the transclude function to get the DOM and calling `element.remove()` to remove it), 6596 * then you are also responsible for calling `$destroy` on the transclusion scope. 6597 * </div> 6598 * 6599 * The built-in DOM manipulation directives, such as {@link ngIf}, {@link ngSwitch} and {@link ngRepeat} 6600 * automatically destroy their transluded clones as necessary so you do not need to worry about this if 6601 * you are simply using {@link ngTransclude} to inject the transclusion into your directive. 6602 * 6603 * 6604 * #### Transclusion Scopes 6605 * 6606 * When you call a transclude function it returns a DOM fragment that is pre-bound to a **transclusion 6607 * scope**. This scope is special, in that it is a child of the directive's scope (and so gets destroyed 6608 * when the directive's scope gets destroyed) but it inherits the properties of the scope from which it 6609 * was taken. 6610 * 6611 * For example consider a directive that uses transclusion and isolated scope. The DOM hierarchy might look 6612 * like this: 6613 * 6614 * ```html 6615 * <div ng-app> 6616 * <div isolate> 6617 * <div transclusion>
6618 * </div> 6619 * </div> 6620 * </div> 6621 * ``` 6622 * 6623 * The `$parent` scope hierarchy will look like this: 6624 * 6625 * ``` 6626 * - $rootScope 6627 * - isolate 6628 * - transclusion 6629 * ``` 6630 * 6631 * but the scopes will inherit prototypically from different scopes to their `$parent`. 6632 * 6633 * ``` 6634 * - $rootScope 6635 * - transclusion 6636 * - isolate 6637 * ``` 6638 * 6639 * 6640 * ### Attributes 6641 * 6642 * The {@link ng.$compile.directive.Attributes Attributes} object - passed as a parameter in the 6643 * `link()` or `compile()` functions. It has a variety of uses. 6644 * 6645 * accessing *Normalized attribute names:* 6646 * Directives like 'ngBind' can be expressed in many ways: 'ng:bind', `data-ng-bind`, or 'x-ng-bind'. 6647 * the attributes object allows for normalized access to 6648 * the attributes. 6649 * 6650 * * *Directive inter-communication:* All directives share the same instance of the attributes 6651 * object which allows the directives to use the attributes object as inter directive 6652 * communication. 6653 * 6654 * * *Supports interpolation:* Interpolation attributes are assigned to the attribute object 6655 * allowing other directives to read the interpolated value. 6656 * 6657 * * *Observing interpolated attributes:* Use `$observe` to observe the value changes of attributes 6658 * that contain interpolation (e.g. `src="{{bar}}"`). Not only is this very efficient but it's also 6659 * the only way to easily get the actual value because during the linking phase the interpolation 6660 * hasn't been evaluated yet and so the value is at this time set to `undefined`. 6661 * 6662 * ```js 6663 * function linkingFn(scope, elm, attrs, ctrl) { 6664 * // get the attribute value 6665 * console.log(attrs.ngModel); 6666 * 6667 * // change the attribute 6668 * attrs.$set('ngModel', 'new value'); 6669 * 6670 * // observe changes to interpolated attribute 6671 * attrs.$observe('ngModel', function(value) { 6672 * console.log('ngModel has changed value to ' + value); 6673 * }); 6674 * } 6675 * ``` 6676 * 6677 * ## Example 6678 * 6679 * <div class="alert alert-warning"> 6680 * **Note**: Typically directives are registered with `module.directive`. The example below is 6681 * to illustrate how `$compile` works. 6682 * </div> 6683 * 6684 <example module="compileExample"> 6685 <file name="index.html"> 6686 <script> 6687 angular.module('compileExample', [], function($compileProvider) { 6688 // configure new 'compile' directive by passing a directive 6689 // factory function. The factory function injects the '$compile' 6690 $compileProvider.directive('compile', function($compile) { 6691 // directive factory creates a link function 6692 return function(scope, element, attrs) { 6693 scope.$watch( 6694 function(scope) { 6695 // watch the 'compile' expression for changes 6696 return scope.$eval(attrs.compile); 6697 }, 6698 function(value) { 6699 // when the 'compile' expression changes 6700 // assign it into the current DOM 6701 element.html(value); 6702 6703 // compile the new DOM and link it to the current 6704 // scope. 6705 // NOTE: we only compile .childNodes so that 6706 // we don't get into infinite loop compiling ourselves 6707 $compile(element.contents())(scope); 6708 } 6709 ); 6710 }; 6711 }); 6712 }) 6713 .controller('GreeterController', ['$scope', function($scope) { 6714 $scope.name = 'Angular'; 6715 $scope.html = 'Hello {{name}}'; 6716 }]); 6717 </script> 6718 <div ng-controller="GreeterController"> 6719 <input ng-model="name"> <br/> 6720 <textarea ng-model="html"></textarea> <br/> 6721 <div compile="html"></div> 6722 </div> 6723 </file> 6724 <file name="protractor.js" type="protractor"> 6725 it('should auto compile', function() { 6726 var textarea = $('textarea'); 6727 var output = $('div[compile]'); 6728 // The initial state reads 'Hello Angular'. 6729 expect(output.getText()).toBe('Hello Angular'); 6730 textarea.clear(); 6731 textarea.sendKeys('{{name}}!'); 6732 expect(output.getText()).toBe('Angular!'); 6733 }); 6734 </file> 6735 </example> 6736 6737 * 6738 * 6739 * @param {string|DOMElement} element Element or HTML string to compile into a template function. 6740 * @param {function(angular.Scope, cloneAttachFn=)} transclude function available to directives - DEPRECATED. 6741 * 6742 * <div class="alert alert-danger"> 6743 * **Note:** Passing a `transclude` function to the $compile function is deprecated, as it 6744 * e.g. will not use the right outer scope. Please pass the transclude function as a 6745 * `parentBoundTranscludeFn` to the link function instead. 6746 * </div> 6747 * 6748 * @param {number} maxPriority only apply directives lower than given priority (Only effects the
6749 * root element(s), not their children) 6750 * @returns {function(scope, cloneAttachFn=, options=)} a link function which is used to bind template 6751 * (a DOM element/tree) to a scope. Where: 6752 * 6753 * * `scope` - A {@link ng.$rootScope.Scope Scope} to bind to. 6754 * * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the 6755 * `template` and call the `cloneAttachFn` function allowing the caller to attach the 6756 * cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is 6757 * called as: <br/> `cloneAttachFn(clonedElement, scope)` where: 6758 * 6759 * * `clonedElement` - is a clone of the original `element` passed into the compiler. 6760 * * `scope` - is the current scope with which the linking function is working with. 6761 * 6762 * * `options` - An optional object hash with linking options. If `options` is provided, then the following 6763 * keys may be used to control linking behavior: 6764 * 6765 * * `parentBoundTranscludeFn` - the transclude function made available to 6766 * directives; if given, it will be passed through to the link functions of 6767 * directives found in `element` during compilation. 6768 * * `transcludeControllers` - an object hash with keys that map controller names 6769 * to controller instances; if given, it will make the controllers 6770 * available to directives. 6771 * * `futureParentElement` - defines the parent to which the `cloneAttachFn` will add 6772 * the cloned elements; only needed for transcludes that are allowed to contain non html 6773 * elements (e.g. SVG elements). See also the directive.controller property. 6774 * 6775 * Calling the linking function returns the element of the template. It is either the original 6776 * element passed in, or the clone of the element if the `cloneAttachFn` is provided. 6777 * 6778 * After linking the view is not updated until after a call to $digest which typically is done by 6779 * Angular automatically. 6780 * 6781 * If you need access to the bound view, there are two ways to do it: 6782 * 6783 * - If you are not asking the linking function to clone the template, create the DOM element(s) 6784 * before you send them to the compiler and keep this reference around. 6785 * ```js 6786 * var element = $compile('<p>{{total}}</p>')(scope); 6787 * ``` 6788 * 6789 * - if on the other hand, you need the element to be cloned, the view reference from the original 6790 * example would not point to the clone, but rather to the original template that was cloned. In 6791 * this case, you can access the clone via the cloneAttachFn: 6792 * ```js 6793 * var templateElement = angular.element('<p>{{total}}</p>'), 6794 * scope = ....; 6795 * 6796 * var clonedElement = $compile(templateElement)(scope, function(clonedElement, scope) { 6797 * //attach the clone to DOM document at the right place 6798 * }); 6799 * 6800 * //now we have reference to the cloned DOM via `clonedElement` 6801 * ``` 6802 * 6803 * 6804 * For information on how the compiler works, see the 6805 * {@link guide/compiler Angular HTML Compiler} section of the Developer Guide. 6806 */ 6807 6808var $compileMinErr = minErr('$compile'); 6809 6810/** 6811 * @ngdoc provider 6812 * @name $compileProvider 6813 * 6814 * @description 6815 */ 6816$CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider']; 6817function $CompileProvider($provide, $$sanitizeUriProvider) { 6818 var hasDirectives = {}, 6819 Suffix = 'Directive', 6820 COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\w\-]+)\s+(.*)$/, 6821 CLASS_DIRECTIVE_REGEXP = /(([\w\-]+)(?:\:([^;]+))?;?)/, 6822 ALL_OR_NOTHING_ATTRS = makeMap('ngSrc,ngSrcset,src,srcset'), 6823 REQUIRE_PREFIX_REGEXP = /^(?:(\^\^?)?(\?)?(\^\^?)?)?/; 6824 6825 // Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes 6826 // The assumption is that future DOM event attribute names will begin with 6827 // 'on' and be composed of only English letters. 6828 var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; 6829 6830 function parseIsolateBindings(scope, directiveName, isController) { 6831 var LOCAL_REGEXP = /^\s*([@&]|=(\*?))(\??)\s*(\w*)\s*$/; 6832 6833 var bindings = {}; 6834 6835 forEach(scope, function(definition, scopeName) { 6836 var match = definition.match(LOCAL_REGEXP); 6837 6838 if (!match) { 6839 throw $compileMinErr('iscp', 6840 "Invalid {3} for directive '{0}'." + 6841 " Definition: {... {1}: '{2}' ...}", 6842 directiveName, scopeName, definition, 6843 (isController ? "controller bindings definition" : 6844 "isolate scope definition")); 6845 } 6846 6847 bindings[scopeName] = { 6848 mode: match[1][0], 6849 collection: match[2] === '*', 6850 optional: match[3] === '?', 6851 attrName: match[4] || scopeName 6852 }; 6853 }); 6854 6855 return bindings; 6856 } 6857 6858 function parseDirectiveBindings(directive, directiveName) { 6859 var bindings = { 6860 isolateScope: null, 6861 bindToController: null 6862 }; 6863 if (isObject(directive.scope)) { 6864 if (directive.bindToController === true) { 6865 bindings.bindToController = parseIsolateBindings(directive.scope, 6866 directiveName, true); 6867 bindings.isolateScope = {}; 6868 } else { 6869 bindings.isolateScope = parseIsolateBindings(directive.scope, 6870 directiveName, false); 6871 } 6872 } 6873 if (isObject(directive.bindToController)) { 6874 bindings.bindToController = 6875 parseIsolateBindings(directive.bindToController, directiveName, true); 6876 } 6877 if (isObject(bindings.bindToController)) { 6878 var controller = directive.controller; 6879 var controllerAs = directive.controllerAs; 6880 if (!controller) { 6881 // There is no controller, there may or may not be a controllerAs property 6882 throw $compileMinErr('noctrl', 6883 "Cannot bind to controller without directive '{0}'s controller.", 6884 directiveName); 6885 } else if (!identifierForController(controller, controllerAs)) { 6886 // There is a controller, but no identifier or controllerAs property 6887 throw $compileMinErr('noident', 6888 "Cannot bind to controller without identifier for directive '{0}'.", 6889 directiveName); 6890 } 6891 } 6892 return bindings; 6893 } 6894 6895 function assertValidDirectiveName(name) { 6896 var letter = name.charAt(0); 6897 if (!letter || letter !== lowercase(letter)) { 6898 throw $compileMinErr('baddir', "Directive name '{0}
vendor: 4,630 bytes, lines 6898-7010
6898' is invalid. The first character must be a lowercase letter", name); 6899 } 6900 if (name !== name.trim()) { 6901 throw $compileMinErr('baddir', 6902 "Directive name '{0}' is invalid. The name should not contain leading or trailing whitespaces", 6903 name); 6904 } 6905 } 6906 6907 /** 6908 * @ngdoc method 6909 * @name $compileProvider#directive 6910 * @kind function 6911 * 6912 * @description 6913 * Register a new directive with the compiler. 6914 * 6915 * @param {string|Object} name Name of the directive in camel-case (i.e. <code>ngBind</code> which 6916 * will match as <code>ng-bind</code>), or an object map of directives where the keys are the 6917 * names and the values are the factories. 6918 * @param {Function|Array} directiveFactory An injectable directive factory function. See 6919 * {@link guide/directive} for more info. 6920 * @returns {ng.$compileProvider} Self for chaining. 6921 */ 6922 this.directive = function registerDirective(name, directiveFactory) { 6923 assertNotHasOwnProperty(name, 'directive'); 6924 if (isString(name)) { 6925 assertValidDirectiveName(name); 6926 assertArg(directiveFactory, 'directiveFactory'); 6927 if (!hasDirectives.hasOwnProperty(name)) { 6928 hasDirectives[name] = []; 6929 $provide.factory(name + Suffix, ['$injector', '$exceptionHandler', 6930 function($injector, $exceptionHandler) { 6931 var directives = []; 6932 forEach(hasDirectives[name], function(directiveFactory, index) { 6933 try { 6934 var directive = $injector.invoke(directiveFactory); 6935 if (isFunction(directive)) { 6936 directive = { compile: valueFn(directive) }; 6937 } else if (!directive.compile && directive.link) { 6938 directive.compile = valueFn(directive.link); 6939 } 6940 directive.priority = directive.priority || 0; 6941 directive.index = index; 6942 directive.name = directive.name || name; 6943 directive.require = directive.require || (directive.controller && directive.name); 6944 directive.restrict = directive.restrict || 'EA'; 6945 var bindings = directive.$$bindings = 6946 parseDirectiveBindings(directive, directive.name); 6947 if (isObject(bindings.isolateScope)) { 6948 directive.$$isolateBindings = bindings.isolateScope; 6949 } 6950 directive.$$moduleName = directiveFactory.$$moduleName; 6951 directives.push(directive); 6952 } catch (e) { 6953 $exceptionHandler(e); 6954 } 6955 }); 6956 return directives; 6957 }]); 6958 } 6959 hasDirectives[name].push(directiveFactory); 6960 } else { 6961 forEach(name, reverseParams(registerDirective)); 6962 } 6963 return this; 6964 }; 6965 6966 6967 /** 6968 * @ngdoc method 6969 * @name $compileProvider#aHrefSanitizationWhitelist 6970 * @kind function 6971 * 6972 * @description 6973 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 6974 * urls during a[href] sanitization. 6975 * 6976 * The sanitization is a security measure aimed at preventing XSS attacks via html links. 6977 * 6978 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 6979 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 6980 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 6981 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 6982 * 6983 * @param {RegExp=} regexp New regexp to whitelist urls with. 6984 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 6985 * chaining otherwise. 6986 */ 6987 this.aHrefSanitizationWhitelist = function(regexp) { 6988 if (isDefined(regexp)) { 6989 $$sanitizeUriProvider.aHrefSanitizationWhitelist(regexp); 6990 return this; 6991 } else { 6992 return $$sanitizeUriProvider.aHrefSanitizationWhitelist(); 6993 } 6994 }; 6995 6996 6997 /** 6998 * @ngdoc method 6999 * @name $compileProvider#imgSrcSanitizationWhitelist 7000 * @kind function 7001 * 7002 * @description 7003 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 7004 * urls during img[src] sanitization. 7005 * 7006 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 7007 * 7008 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 7009 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 7010 * regular expression. If a match is found, the original url is
7010written into the dom. Otherwise, 7011 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 7012 * 7013 * @param {RegExp=} regexp New regexp to whitelist urls with. 7014 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 7015 * chaining otherwise. 7016 */ 7017 this.imgSrcSanitizationWhitelist = function(regexp) { 7018 if (isDefined(regexp)) { 7019 $$sanitizeUriProvider.imgSrcSanitizationWhitelist(regexp); 7020 return this; 7021 } else { 7022 return $$sanitizeUriProvider.imgSrcSanitizationWhitelist(); 7023 } 7024 }; 7025 7026 /** 7027 * @ngdoc method 7028 * @name $compileProvider#debugInfoEnabled 7029 * 7030 * @param {boolean=} enabled update the debugInfoEnabled state if provided, otherwise just return the 7031 * current debugInfoEnabled state 7032 * @returns {*} current value if used as getter or itself (chaining) if used as setter 7033 * 7034 * @kind function 7035 * 7036 * @description 7037 * Call this method to enable/disable various debug runtime information in the compiler such as adding 7038 * binding information and a reference to the current scope on to DOM elements. 7039 * If enabled, the compiler will add the following to DOM elements that have been bound to the scope 7040 * * `ng-binding` CSS class 7041 * * `$binding` data property containing an array of the binding expressions 7042 * 7043 * You may want to disable this in production for a significant performance boost. See 7044 * {@link guide/production#disabling-debug-data Disabling Debug Data} for more. 7045 * 7046 * The default value is true. 7047 */ 7048 var debugInfoEnabled = true; 7049 this.debugInfoEnabled = function(enabled) { 7050 if (isDefined(enabled)) { 7051 debugInfoEnabled = enabled; 7052 return this; 7053 } 7054 return debugInfoEnabled; 7055 }; 7056 7057 this.$get = [ 7058 '$injector', '$interpolate', '$exceptionHandler', '$templateRequest', '$parse', 7059 '$controller', '$rootScope', '$document', '$sce', '$animate', '$$sanitizeUri', 7060 function($injector, $interpolate, $exceptionHandler, $templateRequest, $parse, 7061 $controller, $rootScope, $document, $sce, $animate, $$sanitizeUri) { 7062 7063 var Attributes = function(element, attributesToCopy) { 7064 if (attributesToCopy) { 7065 var keys = Object.keys(attributesToCopy); 7066 var i, l, key; 7067 7068 for (i = 0, l = keys.length; i < l; i++) { 7069 key = keys[i]; 7070 this[key] = attributesToCopy[key]; 7071 } 7072 } else { 7073 this.$attr = {}; 7074 } 7075 7076 this.$$element = element; 7077 }; 7078 7079 Attributes.prototype = { 7080 /** 7081 * @ngdoc method 7082 * @name $compile.directive.Attributes#$normalize 7083 * @kind function 7084 * 7085 * @description 7086 * Converts an attribute name (e.g. dash/colon/underscore-delimited string, optionally prefixed with `x-` or 7087 * `data-`) to its normalized, camelCase form. 7088 * 7089 * Also there is special case for Moz prefix starting with upper case letter. 7090 * 7091 * For further information check out the guide on {@link guide/directive#matching-directives Matching Directives} 7092 * 7093 * @param {string} name Name to normalize 7094 */ 7095 $normalize: directiveNormalize, 7096 7097 7098 /** 7099 * @ngdoc method 7100 * @name $compile.directive.Attributes#$addClass 7101 * @kind function 7102 * 7103 * @description 7104 * Adds the CSS class value specified by the classVal parameter to the element. If animations 7105 * are enabled then an animation will be triggered for the class addition. 7106 * 7107 * @param {string} classVal The className value that will be added to the element 7108 */ 7109 $addClass: function(classVal) { 7110 if (classVal && classVal.length > 0) { 7111 $animate.addClass(this.$$element, classVal); 7112 } 7113 }, 7114 7115 /** 7116 * @ngdoc method 7117 * @name $compile.directive.Attributes#$removeClass 7118 * @kind function 7119 * 7120 * @description 7121 * Removes the CSS class value specified by the classVal parameter from the element. If 7122 * animations are enabled then an animation will be triggered for the class removal. 7123 * 7124 * @param {string} classVal The className value that will be removed from the element 7125 */ 7126 $removeClass: function(classVal) { 7127 if (classVal && classVal.length > 0) { 7128 $animate.removeClass(this.$$element, classVal); 7129 } 7130 }, 7131 7132 /** 7133 * @ngdoc method 7134 * @name $compile.directive.Attributes#$updateClass 7135 * @kind function 7136 *
7137 * @description 7138 * Adds and removes the appropriate CSS class values to the element based on the difference 7139 * between the new and old CSS class values (specified as newClasses and oldClasses). 7140 * 7141 * @param {string} newClasses The current CSS className value 7142 * @param {string} oldClasses The former CSS className value 7143 */ 7144 $updateClass: function(newClasses, oldClasses) { 7145 var toAdd = tokenDifference(newClasses, oldClasses); 7146 if (toAdd && toAdd.length) { 7147 $animate.addClass(this.$$element, toAdd); 7148 } 7149 7150 var toRemove = tokenDifference(oldClasses, newClasses); 7151 if (toRemove && toRemove.length) { 7152 $animate.removeClass(this.$$element, toRemove); 7153 } 7154 }, 7155 7156 /** 7157 * Set a normalized attribute on the element in a way such that all directives 7158 * can share the attribute. This function properly handles boolean attributes. 7159 * @param {string} key Normalized key. (ie ngAttribute) 7160 * @param {string|boolean} value The value to set. If `null` attribute will be deleted. 7161 * @param {boolean=} writeAttr If false, does not write the value to DOM element attribute. 7162 * Defaults to true. 7163 * @param {string=} attrName Optional none normalized name. Defaults to key. 7164 */ 7165 $set: function(key, value, writeAttr, attrName) { 7166 // TODO: decide whether or not to throw an error if "class" 7167 //is set through this function since it may cause $updateClass to 7168 //become unstable. 7169 7170 var node = this.$$element[0], 7171 booleanKey = getBooleanAttrName(node, key), 7172 aliasedKey = getAliasedAttrName(node, key), 7173 observer = key, 7174 nodeName; 7175 7176 if (booleanKey) { 7177 this.$$element.prop(key, value); 7178 attrName = booleanKey; 7179 } else if (aliasedKey) { 7180 this[aliasedKey] = value; 7181 observer = aliasedKey; 7182 } 7183 7184 this[key] = value; 7185 7186 // translate normalized key to actual key 7187 if (attrName) { 7188 this.$attr[key] = attrName; 7189 } else { 7190 attrName = this.$attr[key]; 7191 if (!attrName) { 7192 this.$attr[key] = attrName = snake_case(key, '-'); 7193 } 7194 } 7195 7196 nodeName = nodeName_(this.$$element); 7197 7198 if ((nodeName === 'a' && key === 'href') || 7199 (nodeName === 'img' && key === 'src')) { 7200 // sanitize a[href] and img[src] values 7201 this[key] = value = $$sanitizeUri(value, key === 'src'); 7202 } else if (nodeName === 'img' && key === 'srcset') { 7203 // sanitize img[srcset] values 7204 var result = ""; 7205 7206 // first check if there are spaces because it's not the same pattern 7207 var trimmedSrcset = trim(value); 7208 // ( 999x ,| 999w ,| ,|, ) 7209 var srcPattern = /(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/; 7210 var pattern = /\s/.test(trimmedSrcset) ? srcPattern : /(,)/; 7211 7212 // split srcset into tuple of uri and descriptor except for the last item 7213 var rawUris = trimmedSrcset.split(pattern); 7214 7215 // for each tuples 7216 var nbrUrisWith2parts = Math.floor(rawUris.length / 2); 7217 for (var i = 0; i < nbrUrisWith2parts; i++) { 7218 var innerIdx = i * 2; 7219 // sanitize the uri 7220 result += $$sanitizeUri(trim(rawUris[innerIdx]), true); 7221 // add the descriptor 7222 result += (" " + trim(rawUris[innerIdx + 1])); 7223 } 7224 7225 // split the last item into uri and descriptor 7226 var lastTuple = trim(rawUris[i * 2]).split(/\s/); 7227 7228 // sanitize the last uri 7229 result += $$sanitizeUri(trim(lastTuple[0]), true); 7230 7231 // and add the last descriptor if any 7232 if (lastTuple.length === 2) { 7233 result += (" " + trim(lastTuple[1])); 7234 } 7235 this[key] = value = result; 7236 } 7237 7238 if (writeAttr !== false) { 7239 if (value === null || value === undefined) { 7240 this.$$element.removeAttr(attrName); 7241 } else { 7242 this.$$element.attr(attrName, value); 7243 } 7244 } 7245 7246 // fire observers 7247 var $$observers = this.$$observers;
7248 $$observers && forEach($$observers[observer], function(fn) { 7249 try { 7250 fn(value); 7251 } catch (e) { 7252 $exceptionHandler(e); 7253 } 7254 }); 7255 }, 7256 7257 7258 /** 7259 * @ngdoc method 7260 * @name $compile.directive.Attributes#$observe 7261 * @kind function 7262 * 7263 * @description 7264 * Observes an interpolated attribute. 7265 * 7266 * The observer function will be invoked once during the next `$digest` following 7267 * compilation. The observer is then invoked whenever the interpolated value 7268 * changes. 7269 * 7270 * @param {string} key Normalized key. (ie ngAttribute) . 7271 * @param {function(interpolatedValue)} fn Function that will be called whenever 7272 the interpolated value of the attribute changes. 7273 * See the {@link guide/directive#text-and-attribute-bindings Directives} guide for more info. 7274 * @returns {function()} Returns a deregistration function for this observer. 7275 */ 7276 $observe: function(key, fn) { 7277 var attrs = this, 7278 $$observers = (attrs.$$observers || (attrs.$$observers = createMap())), 7279 listeners = ($$observers[key] || ($$observers[key] = [])); 7280 7281 listeners.push(fn); 7282 $rootScope.$evalAsync(function() { 7283 if (!listeners.$$inter && attrs.hasOwnProperty(key)) { 7284 // no one registered attribute interpolation function, so lets call it manually 7285 fn(attrs[key]); 7286 } 7287 }); 7288 7289 return function() { 7290 arrayRemove(listeners, fn); 7291 }; 7292 } 7293 }; 7294 7295 7296 function safeAddClass($element, className) { 7297 try { 7298 $element.addClass(className); 7299 } catch (e) { 7300 // ignore, since it means that we are trying to set class on 7301 // SVG element, where class name is read-only. 7302 } 7303 } 7304 7305 7306 var startSymbol = $interpolate.startSymbol(), 7307 endSymbol = $interpolate.endSymbol(), 7308 denormalizeTemplate = (startSymbol == '{{' || endSymbol == '}}') 7309 ? identity 7310 : function denormalizeTemplate(template) { 7311 return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol); 7312 }, 7313 NG_ATTR_BINDING = /^ngAttr[A-Z]/; 7314 7315 compile.$$addBindingInfo = debugInfoEnabled ? function $$addBindingInfo($element, binding) { 7316 var bindings = $element.data('$binding') || []; 7317 7318 if (isArray(binding)) { 7319 bindings = bindings.concat(binding); 7320 } else { 7321 bindings.push(binding); 7322 } 7323 7324 $element.data('$binding', bindings); 7325 } : noop; 7326 7327 compile.$$addBindingClass = debugInfoEnabled ? function $$addBindingClass($element) { 7328 safeAddClass($element, 'ng-binding'); 7329 } : noop; 7330 7331 compile.$$addScopeInfo = debugInfoEnabled ? function $$addScopeInfo($element, scope, isolated, noTemplate) { 7332 var dataName = isolated ? (noTemplate ? '$isolateScopeNoTemplate' : '$isolateScope') : '$scope'; 7333 $element.data(dataName, scope); 7334 } : noop; 7335 7336 compile.$$addScopeClass = debugInfoEnabled ? function $$addScopeClass($element, isolated) { 7337 safeAddClass($element, isolated ? 'ng-isolate-scope' : 'ng-scope'); 7338 } : noop; 7339 7340 return compile; 7341 7342 //================================ 7343 7344 function compile($compileNodes, transcludeFn, maxPriority, ignoreDirective, 7345 previousCompileContext) { 7346 if (!($compileNodes instanceof jqLite)) { 7347 // jquery always rewraps, whereas we need to preserve the original selector so that we can 7348 // modify it. 7349 $compileNodes = jqLite($compileNodes); 7350 } 7351 // We can not compile top level text elements since text nodes can be merged and we will 7352 // not be able to attach scope data to them, so we will wrap them in <span> 7353 forEach($compileNodes, function(node, index) { 7354 if (node.nodeType == NODE_TYPE_TEXT && node.nodeValue.match(/\S+/) /* non-empty */ ) { 7355 $compileNodes[index] = jqLite(node).wrap('<span></span>').parent()[0]; 7356 } 7357 }); 7358 var compositeLinkFn = 7359 compileNodes($compileNodes, transcludeFn, $compileNodes, 7360 maxPriority, ignoreDirective, previousCompileContext); 7361 compile.$$addScopeClass($compileNodes); 7362 var namespace = null; 7363 return function publicLinkFn(scope, cloneConnectFn, options) { 7364 assertArg(scope, 'scope'); 7365 7366 options = options || {}; 7367 var parentBoundTranscludeFn = options.parentBoundTranscludeFn, 7368 transcludeControllers = options.transcludeControllers, 7369 futureParentElement = options.futureParentElement; 7370 7371 // When `parentBoundTranscludeFn` is passed, it is a 7372 // `controllersBoundTransclude` function (it was previously passed 7373 // as `transclude` to directive.link) so we must unwrap it to get 7374 // its `boundTranscludeFn` 7375 if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) { 7376 parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude; 7377 } 7378 7379 if (!namespace) { 7380 namespace = detectNamespaceForChildElements(futureParentElement); 7381 } 7382 var $linkNode; 7383 if (namespace !== 'html') { 7384 // When using a directive with replace:true and templateUrl the $compileNodes 7385 // (or a child element inside of them) 7386 // might change, so we need to recreate the namespace adapted compileNodes 7387 // for call to the link function. 7388 // Note: This will already clone the nodes... 7389 $linkNode = jqLite( 7390 wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html()) 7391 ); 7392 } else if (cloneConnectFn) { 7393 // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
vendor: 7,988 bytes, lines 7394-7588
7394 // and sometimes changes the structure of the DOM. 7395 $linkNode = JQLitePrototype.clone.call($compileNodes); 7396 } else { 7397 $linkNode = $compileNodes; 7398 } 7399 7400 if (transcludeControllers) { 7401 for (var controllerName in transcludeControllers) { 7402 $linkNode.data('$' + controllerName + 'Controller', transcludeControllers[controllerName].instance); 7403 } 7404 } 7405 7406 compile.$$addScopeInfo($linkNode, scope); 7407 7408 if (cloneConnectFn) cloneConnectFn($linkNode, scope); 7409 if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn); 7410 return $linkNode; 7411 }; 7412 } 7413 7414 function detectNamespaceForChildElements(parentElement) { 7415 // TODO: Make this detect MathML as well... 7416 var node = parentElement && parentElement[0]; 7417 if (!node) { 7418 return 'html'; 7419 } else { 7420 return nodeName_(node) !== 'foreignobject' && node.toString().match(/SVG/) ? 'svg' : 'html'; 7421 } 7422 } 7423 7424 /** 7425 * Compile function matches each node in nodeList against the directives. Once all directives 7426 * for a particular node are collected their compile functions are executed. The compile 7427 * functions return values - the linking functions - are combined into a composite linking 7428 * function, which is the a linking function for the node. 7429 * 7430 * @param {NodeList} nodeList an array of nodes or NodeList to compile 7431 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 7432 * scope argument is auto-generated to the new child of the transcluded parent scope. 7433 * @param {DOMElement=} $rootElement If the nodeList is the root of the compilation tree then 7434 * the rootElement must be set the jqLite collection of the compile root. This is 7435 * needed so that the jqLite collection items can be replaced with widgets. 7436 * @param {number=} maxPriority Max directive priority. 7437 * @returns {Function} A composite linking function of all of the matched directives or null. 7438 */ 7439 function compileNodes(nodeList, transcludeFn, $rootElement, maxPriority, ignoreDirective, 7440 previousCompileContext) { 7441 var linkFns = [], 7442 attrs, directives, nodeLinkFn, childNodes, childLinkFn, linkFnFound, nodeLinkFnFound; 7443 7444 for (var i = 0; i < nodeList.length; i++) { 7445 attrs = new Attributes(); 7446 7447 // we must always refer to nodeList[i] since the nodes can be replaced underneath us. 7448 directives = collectDirectives(nodeList[i], [], attrs, i === 0 ? maxPriority : undefined, 7449 ignoreDirective); 7450 7451 nodeLinkFn = (directives.length) 7452 ? applyDirectivesToNode(directives, nodeList[i], attrs, transcludeFn, $rootElement, 7453 null, [], [], previousCompileContext) 7454 : null; 7455 7456 if (nodeLinkFn && nodeLinkFn.scope) { 7457 compile.$$addScopeClass(attrs.$$element); 7458 } 7459 7460 childLinkFn = (nodeLinkFn && nodeLinkFn.terminal || 7461 !(childNodes = nodeList[i].childNodes) || 7462 !childNodes.length) 7463 ? null 7464 : compileNodes(childNodes, 7465 nodeLinkFn ? ( 7466 (nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement) 7467 && nodeLinkFn.transclude) : transcludeFn); 7468 7469 if (nodeLinkFn || childLinkFn) { 7470 linkFns.push(i, nodeLinkFn, childLinkFn); 7471 linkFnFound = true; 7472 nodeLinkFnFound = nodeLinkFnFound || nodeLinkFn; 7473 } 7474 7475 //use the previous context only for the first element in the virtual group 7476 previousCompileContext = null; 7477 } 7478 7479 // return a linking function if we have found anything, null otherwise 7480 return linkFnFound ? compositeLinkFn : null; 7481 7482 function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) { 7483 var nodeLinkFn, childLinkFn, node, childScope, i, ii, idx, childBoundTranscludeFn; 7484 var stableNodeList; 7485 7486 7487 if (nodeLinkFnFound) { 7488 // copy nodeList so that if a nodeLinkFn removes or adds an element at this DOM level our 7489 // offsets don't get screwed up 7490 var nodeListLength = nodeList.length; 7491 stableNodeList = new Array(nodeListLength); 7492 7493 // create a sparse array by only copying the elements which have a linkFn 7494 for (i = 0; i < linkFns.length; i+=3) { 7495 idx = linkFns[i]; 7496 stableNodeList[idx] = nodeList[idx]; 7497 } 7498 } else { 7499 stableNodeList = nodeList; 7500 } 7501 7502 for (i = 0, ii = linkFns.length; i < ii;) { 7503 node = stableNodeList[linkFns[i++]]; 7504 nodeLinkFn = linkFns[i++]; 7505 childLinkFn = linkFns[i++]; 7506 7507 if (nodeLinkFn) { 7508 if (nodeLinkFn.scope) { 7509 childScope = scope.$new(); 7510 compile.$$addScopeInfo(jqLite(node), childScope); 7511 var destroyBindings = nodeLinkFn.$$destroyBindings; 7512 if (destroyBindings) { 7513 nodeLinkFn.$$destroyBindings = null; 7514 childScope.$on('$destroyed', destroyBindings); 7515 } 7516 } else { 7517 childScope = scope; 7518 } 7519 7520 if (nodeLinkFn.transcludeOnThisElement) { 7521 childBoundTranscludeFn = createBoundTranscludeFn( 7522 scope, nodeLinkFn.transclude, parentBoundTranscludeFn); 7523 7524 } else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) { 7525 childBoundTranscludeFn = parentBoundTranscludeFn; 7526 7527 } else if (!parentBoundTranscludeFn && transcludeFn) { 7528 childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn); 7529 7530 } else { 7531 childBoundTranscludeFn = null; 7532 } 7533 7534 nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn, 7535 nodeLinkFn); 7536 7537 } else if (childLinkFn) { 7538 childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn); 7539 } 7540 } 7541 } 7542 } 7543 7544 function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) { 7545 7546 var boundTranscludeFn = function(transcludedScope, cloneFn, controllers, futureParentElement, containingScope) { 7547 7548 if (!transcludedScope) { 7549 transcludedScope = scope.$new(false, containingScope); 7550 transcludedScope.$$transcluded = true; 7551 } 7552 7553 return transcludeFn(transcludedScope, cloneFn, { 7554 parentBoundTranscludeFn: previousBoundTranscludeFn, 7555 transcludeControllers: controllers, 7556 futureParentElement: futureParentElement 7557 }); 7558 }; 7559 7560 return boundTranscludeFn; 7561 } 7562 7563 /** 7564 * Looks for directives on the given node and adds them to the directive collection which is 7565 * sorted. 7566 * 7567 * @param node Node to search. 7568 * @param directives An array to which the directives are added to. This array is sorted before 7569 * the function returns. 7570 * @param attrs The shared attrs object which is used to populate the normalized attributes. 7571 * @param {number=} maxPriority Max directive priority. 7572 */ 7573 function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) { 7574 var nodeType = node.nodeType, 7575 attrsMap = attrs.$attr, 7576 match, 7577 className; 7578 7579 switch (nodeType) { 7580 case NODE_TYPE_ELEMENT: /* Element */ 7581 // use the node name: <directive> 7582 addDirective(directives, 7583 directiveNormalize(nodeName_(node)), 'E', maxPriority, ignoreDirective); 7584 7585 // iterate over the attributes 7586 for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, 7587 j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { 7588 var attrStartName = false;
7589 var attrEndName = false; 7590 7591 attr = nAttrs[j]; 7592 name = attr.name; 7593 value = trim(attr.value); 7594 7595 // support ngAttr attribute binding 7596 ngAttrName = directiveNormalize(name); 7597 if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { 7598 name = name.replace(PREFIX_REGEXP, '') 7599 .substr(8).replace(/_(.)/g, function(match, letter) { 7600 return letter.toUpperCase(); 7601 }); 7602 } 7603 7604 var directiveNName = ngAttrName.replace(/(Start|End)$/, ''); 7605 if (directiveIsMultiElement(directiveNName)) { 7606 if (ngAttrName === directiveNName + 'Start') { 7607 attrStartName = name; 7608 attrEndName = name.substr(0, name.length - 5) + 'end'; 7609 name = name.substr(0, name.length - 6); 7610 } 7611 } 7612 7613 nName = directiveNormalize(name.toLowerCase()); 7614 attrsMap[nName] = name; 7615 if (isNgAttr || !attrs.hasOwnProperty(nName)) { 7616 attrs[nName] = value; 7617 if (getBooleanAttrName(node, nName)) { 7618 attrs[nName] = true; // presence means true 7619 } 7620 } 7621 addAttrInterpolateDirective(node, directives, value, nName, isNgAttr); 7622 addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, 7623 attrEndName); 7624 } 7625 7626 // use class as directive 7627 className = node.className; 7628 if (isObject(className)) { 7629 // Maybe SVGAnimatedString 7630 className = className.animVal; 7631 } 7632 if (isString(className) && className !== '') { 7633 while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) { 7634 nName = directiveNormalize(match[2]); 7635 if (addDirective(directives, nName, 'C', maxPriority, ignoreDirective)) { 7636 attrs[nName] = trim(match[3]); 7637 } 7638 className = className.substr(match.index + match[0].length); 7639 } 7640 } 7641 break; 7642 case NODE_TYPE_TEXT: /* Text Node */ 7643 if (msie === 11) { 7644 // Workaround for #11781 7645 while (node.parentNode && node.nextSibling && node.nextSibling.nodeType === NODE_TYPE_TEXT) { 7646 node.nodeValue = node.nodeValue + node.nextSibling.nodeValue; 7647 node.parentNode.removeChild(node.nextSibling); 7648 } 7649 } 7650 addTextInterpolateDirective(directives, node.nodeValue); 7651 break; 7652 case NODE_TYPE_COMMENT: /* Comment */ 7653 try { 7654 match = COMMENT_DIRECTIVE_REGEXP.exec(node.nodeValue); 7655 if (match) { 7656 nName = directiveNormalize(match[1]); 7657 if (addDirective(directives, nName, 'M', maxPriority, ignoreDirective)) { 7658 attrs[nName] = trim(match[2]); 7659 } 7660 } 7661 } catch (e) { 7662 // turns out that under some circumstances IE9 throws errors when one attempts to read 7663 // comment's node value. 7664 // Just ignore it and continue. (Can't seem to reproduce in test case.) 7665 } 7666 break; 7667 } 7668 7669 directives.sort(byPriority); 7670 return directives; 7671 } 7672 7673 /** 7674 * Given a node with an directive-start it collects all of the siblings until it finds 7675 * directive-end. 7676 * @param node 7677 * @param attrStart 7678 * @param attrEnd 7679 * @returns {*} 7680 */ 7681 function groupScan(node, attrStart, attrEnd) { 7682 var nodes = []; 7683 var depth = 0; 7684 if (attrStart && node.hasAttribute && node.hasAttribute(attrStart)) { 7685 do { 7686 if (!node) { 7687 throw $compileMinErr('uterdir', 7688 "Unterminated attribute, found '{0}' but no matching '{1}' found.", 7689 attrStart, attrEnd); 7690 } 7691 if (node.nodeType == NODE_TYPE_ELEMENT) { 7692 if (node.hasAttribute(attrStart)) depth++; 7693 if (node.hasAttribute(attrEnd)) depth--; 7694 } 7695 nodes.push(node); 7696 node = node.nextSibling; 7697 } while (depth > 0); 7698 } else { 7699 nodes.push(node); 7700 } 7701 7702 return jqLite(nodes); 7703 } 7704 7705 /** 7706 * Wrapper for linking function which converts normal linking function into a grouped 7707 * linking function. 7708 * @param linkFn 7709 * @param attrStart 7710 * @param attrEnd 7711 * @returns {Function} 7712 */ 7713 function groupElementsLinkFnWrapper(linkFn, attrStart, attrEnd) { 7714 return function(scope, element, attrs, controllers, transcludeFn) { 7715 element = groupScan(element[0], attrStart, attrEnd); 7716 return linkFn(scope, element, attrs, controllers, transcludeFn); 7717 }; 7718 } 7719 7720 /** 7721 * Once the directives have been collected, their compile functions are executed. This method 7722 * is responsible for inlining directive templates as well as terminating the application 7723 * of the directives if the terminal directive has been reached. 7724 * 7725 * @param {Array} directives Array of collected directives to execute their compile function. 7726 * this needs to be pre-sorted by priority order. 7727 * @param {Node} compileNode The raw DOM node to apply the compile functions to 7728 * @param {Object} templateAttrs The shared attribute function 7729 * @param {function(angular.Scope, cloneAttachFn=)} transcludeFn A linking function, where the 7730 * scope argument is auto-generated to the new 7731 * child of the transcluded parent scope. 7732 * @param {JQLite} jqCollection If we are working on the root of the compile tree then this 7733 * argument has the root jqLite array so that we can replace nodes 7734 * on it. 7735 * @param {Object=} originalReplaceDirective An optional directive that will be ignored when 7736 * compiling the transclusion. 7737 * @param {Array.<Function>} preLinkFns 7738 * @param {Array.<Function>} postLinkFns 7739 * @param {Object} previousCompileContext Context used for previous compilation of the current 7740 * node 7741 * @returns {Function} linkFn 7742 */ 7743 function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn, 7744 jqCollection, originalReplaceDirective, preLinkFns, postLinkFns, 7745 previousCompileContext) { 7746 previousCompileContext = previousCompileContext || {}; 7747 7748 var terminalPriority = -Number.MAX_VALUE, 7749 newScopeDirective, 7750 controllerDirectives = previousCompileContext.controllerDirectives, 7751 newIsolateScopeDirective = previousCompileContext.newIsolateScopeDirective, 7752 templateDirective = previousCompileContext.templateDirective, 7753 nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective, 7754 hasTranscludeDirective = false, 7755 hasTemplate = false, 7756 hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective, 7757 $compileNode = templateAttrs.$$element = jqLite(compileNode), 7758 directive, 7759 directiveName, 7760 $template, 7761 replaceDirective = originalReplaceDirective, 7762 childTranscludeFn = transcludeFn, 7763 linkFn, 7764 directiveValue; 7765 7766 // executes all directives on the current element 7767 for (var i = 0, ii = directives.length; i < ii; i++) { 7768 directive = directives[i]; 7769 var attrStart = directive.$$start; 7770 var attrEnd = directive.$$end; 7771 7772 // collect multiblock sections 7773 if (attrStart) { 7774 $compileNode = groupScan(compileNode, attrStart, attrEnd); 7775 } 7776 $template = undefined; 7777 7778 if (terminalPriority > directive.priority) { 7779 break; // prevent further processing of directives 7780 } 7781 7782 if (directiveValue = directive.scope) { 7783 7784 // skip the check for directives with async templates, we'll check the derived sync 7785 // directive when the template arrives 7786 if (!directive.templateUrl) { 7787 if (isObject(directiveValue)) { 7788 // This directive is trying to add an isolated scope. 7789 // Check that there is no scope of any kind already 7790 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective || newScopeDirective, 7791 directive, $compileNode); 7792 newIsolateScopeDirective = directive; 7793 } else { 7794 // This directive is trying to add a child scope. 7795 // Check that there is no isolated scope already 7796 assertNoDuplicate('new/isolated scope', newIsolateScopeDirective, directive, 7797 $compileNode); 7798 } 7799 } 7800 7801 newScopeDirective = newScopeDirective || directive; 7802 } 7803 7804 directiveName = directive.name; 7805 7806 if (!directive.templateUrl && directive.controller) { 7807 directiveValue = directive.controller; 7808 controllerDirectives = controllerDirectives || createMap(); 7809 assertNoDuplicate("'" + directiveName + "' controller", 7810 controllerDirectives[directiveName], directive, $compileNode); 7811 controllerDirectives[directiveName] = directive; 7812 } 7813 7814 if (directiveValue = directive.transclude) { 7815 hasTranscludeDirective = true; 7816 7817 // Special case ngIf and ngRepeat so that we don't complain about duplicate transclusion. 7818 // This option should only be used by directives that know how to safely handle element transclusion, 7819 // where the transcluded nodes are added or replaced after linking. 7820 if (!directive.$$tlb) { 7821 assertNoDuplicate('transclusion', nonTlbTranscludeDirective, directive, $compileNode); 7822 nonTlbTranscludeDirective = directive; 7823 } 7824 7825 if (directiveValue == 'element') { 7826 hasElementTranscludeDirective = true; 7827 terminalPriority = directive.priority; 7828 $template = $compileNode; 7829 $compileNode = templateAttrs.$$element = 7830 jqLite(document.createComment(' ' + directiveName + ': ' + 7831 templateAttrs[directiveName] + ' ')); 7832 compileNode = $compileNode[0]; 7833 replaceWith(jqCollection, sliceArgs($template), compileNode); 7834 7835 childTranscludeFn = compile($template, transcludeFn, terminalPriority, 7836 replaceDirective && replaceDirective.name, { 7837 // Don't pass in: 7838 // - controllerDirectives - otherwise we'll create duplicates controllers 7839 // - newIsolateScopeDirective or templateDirective - comb
vendor: 4,670 bytes, lines 7839-7946
7839ining templates with 7840 // element transclusion doesn't make sense. 7841 // 7842 // We need only nonTlbTranscludeDirective so that we prevent putting transclusion 7843 // on the same element more than once. 7844 nonTlbTranscludeDirective: nonTlbTranscludeDirective 7845 }); 7846 } else { 7847 $template = jqLite(jqLiteClone(compileNode)).contents(); 7848 $compileNode.empty(); // clear contents 7849 childTranscludeFn = compile($template, transcludeFn); 7850 } 7851 } 7852 7853 if (directive.template) { 7854 hasTemplate = true; 7855 assertNoDuplicate('template', templateDirective, directive, $compileNode); 7856 templateDirective = directive; 7857 7858 directiveValue = (isFunction(directive.template)) 7859 ? directive.template($compileNode, templateAttrs) 7860 : directive.template; 7861 7862 directiveValue = denormalizeTemplate(directiveValue); 7863 7864 if (directive.replace) { 7865 replaceDirective = directive; 7866 if (jqLiteIsTextNode(directiveValue)) { 7867 $template = []; 7868 } else { 7869 $template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue))); 7870 } 7871 compileNode = $template[0]; 7872 7873 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 7874 throw $compileMinErr('tplrt', 7875 "Template for directive '{0}' must have exactly one root element. {1}", 7876 directiveName, ''); 7877 } 7878 7879 replaceWith(jqCollection, $compileNode, compileNode); 7880 7881 var newTemplateAttrs = {$attr: {}}; 7882 7883 // combine directives from the original node and from the template: 7884 // - take the array of directives for this element 7885 // - split it into two parts, those that already applied (processed) and those that weren't (unprocessed) 7886 // - collect directives from the template and sort them by priority 7887 // - combine directives as: processed + template + unprocessed 7888 var templateDirectives = collectDirectives(compileNode, [], newTemplateAttrs); 7889 var unprocessedDirectives = directives.splice(i + 1, directives.length - (i + 1)); 7890 7891 if (newIsolateScopeDirective) { 7892 markDirectivesAsIsolate(templateDirectives); 7893 } 7894 directives = directives.concat(templateDirectives).concat(unprocessedDirectives); 7895 mergeTemplateAttributes(templateAttrs, newTemplateAttrs); 7896 7897 ii = directives.length; 7898 } else { 7899 $compileNode.html(directiveValue); 7900 } 7901 } 7902 7903 if (directive.templateUrl) { 7904 hasTemplate = true; 7905 assertNoDuplicate('template', templateDirective, directive, $compileNode); 7906 templateDirective = directive; 7907 7908 if (directive.replace) { 7909 replaceDirective = directive; 7910 } 7911 7912 nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode, 7913 templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, { 7914 controllerDirectives: controllerDirectives, 7915 newIsolateScopeDirective: newIsolateScopeDirective, 7916 templateDirective: templateDirective, 7917 nonTlbTranscludeDirective: nonTlbTranscludeDirective 7918 }); 7919 ii = directives.length; 7920 } else if (directive.compile) { 7921 try { 7922 linkFn = directive.compile($compileNode, templateAttrs, childTranscludeFn); 7923 if (isFunction(linkFn)) { 7924 addLinkFns(null, linkFn, attrStart, attrEnd); 7925 } else if (linkFn) { 7926 addLinkFns(linkFn.pre, linkFn.post, attrStart, attrEnd); 7927 } 7928 } catch (e) { 7929 $exceptionHandler(e, startingTag($compileNode)); 7930 } 7931 } 7932 7933 if (directive.terminal) { 7934 nodeLinkFn.terminal = true; 7935 terminalPriority = Math.max(terminalPriority, directive.priority); 7936 } 7937 7938 } 7939 7940 nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true; 7941 nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective; 7942 nodeLinkFn.templateOnThisElement = hasTemplate; 7943 nodeLinkFn.transclude = childTranscludeFn; 7944 7945 previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective; 7946
7947 // might be normal or delayed nodeLinkFn depending on if templateUrl is present 7948 return nodeLinkFn; 7949 7950 //////////////////// 7951 7952 function addLinkFns(pre, post, attrStart, attrEnd) { 7953 if (pre) { 7954 if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd); 7955 pre.require = directive.require; 7956 pre.directiveName = directiveName; 7957 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 7958 pre = cloneAndAnnotateFn(pre, {isolateScope: true}); 7959 } 7960 preLinkFns.push(pre); 7961 } 7962 if (post) { 7963 if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd); 7964 post.require = directive.require; 7965 post.directiveName = directiveName; 7966 if (newIsolateScopeDirective === directive || directive.$$isolateScope) { 7967 post = cloneAndAnnotateFn(post, {isolateScope: true}); 7968 } 7969 postLinkFns.push(post); 7970 } 7971 } 7972 7973 7974 function getControllers(directiveName, require, $element, elementControllers) { 7975 var value; 7976 7977 if (isString(require)) { 7978 var match = require.match(REQUIRE_PREFIX_REGEXP); 7979 var name = require.substring(match[0].length); 7980 var inheritType = match[1] || match[3]; 7981 var optional = match[2] === '?'; 7982 7983 //If only parents then start at the parent element 7984 if (inheritType === '^^') { 7985 $element = $element.parent(); 7986 //Otherwise attempt getting the controller from elementControllers in case 7987 //the element is transcluded (and has no data) and to avoid .data if possible 7988 } else { 7989 value = elementControllers && elementControllers[name]; 7990 value = value && value.instance; 7991 } 7992 7993 if (!value) { 7994 var dataName = '$' + name + 'Controller'; 7995 value = inheritType ? $element.inheritedData(dataName) : $element.data(dataName); 7996 } 7997 7998 if (!value && !optional) { 7999 throw $compileMinErr('ctreq', 8000 "Controller '{0}', required by directive '{1}', can't be found!", 8001 name, directiveName); 8002 } 8003 } else if (isArray(require)) { 8004 value = []; 8005 for (var i = 0, ii = require.length; i < ii; i++) { 8006 value[i] = getControllers(directiveName, require[i], $element, elementControllers); 8007 } 8008 } 8009 8010 return value || null; 8011 } 8012 8013 function setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope) { 8014 var elementControllers = createMap(); 8015 for (var controllerKey in controllerDirectives) { 8016 var directive = controllerDirectives[controllerKey]; 8017 var locals = { 8018 $scope: directive === newIsolateScopeDirective || directive.$$isolateScope ? isolateScope : scope, 8019 $element: $element, 8020 $attrs: attrs, 8021 $transclude: transcludeFn 8022 }; 8023 8024 var controller = directive.controller; 8025 if (controller == '@') { 8026 controller = attrs[directive.name]; 8027 } 8028 8029 var controllerInstance = $controller(controller, locals, true, directive.controllerAs); 8030 8031 // For directives with element transclusion the element is a comment, 8032 // but jQuery .data doesn't support attaching data to comment nodes as it's hard to 8033 // clean up (http://bugs.jquery.com/ticket/8335). 8034 // Instead, we save the controllers for the element in a local hash and attach to .data 8035 // later, once we have the actual element. 8036 elementControllers[directive.name] = controllerInstance; 8037 if (!hasElementTranscludeDirective) { 8038 $element.data('$' + directive.name + 'Controller', controllerInstance.instance); 8039 } 8040 } 8041 return elementControllers; 8042 } 8043 8044 function nodeLinkFn(childLinkFn, scope, linkNode, $rootElement, boundTranscludeFn, 8045 thisLinkFn) { 8046 var i, ii, linkFn, controller, isolateScope, elementControllers, transcludeFn, $element, 8047 attrs; 8048 8049 if (compileNode === linkNode) { 8050 attrs = templateAttrs; 8051 $element = templateAttrs.$$element; 8052 } else { 8053 $element = jqLite(linkNode); 8054 attrs = new Attributes($element, templateAttrs); 8055 } 8056 8057 if (newIsolateScopeDirective) { 8058 isolateScope = scope.$new(true); 8059 } 8060 8061 if (boundTranscludeFn) { 8062 // track `boundTranscludeFn` so it can be unwrapped if `transcludeFn` 8063 // is later passed as `parentBoundTranscludeFn` to `publicLinkFn` 8064 transcludeFn = controllersBoundTransclude; 8065 transcludeFn.$$boundTransclude = boundTranscludeFn; 8066 } 8067 8068 if (controllerDirectives) { 8069 elementControllers = setupControllers($element, attrs, transcludeFn, controllerDirectives, isolateScope, scope); 8070 } 8071 8072 if (newIsolateScopeDirective) { 8073 // Initialize isolate scope bindings for new isolate scope directive. 8074 compile.$$addScopeInfo($element, isolateScope, true, !(templateDirective && (templateDirective === newIsolateScopeDirective || 8075 templateDirective === newIsolateScopeDirective.$$originalDirective))); 8076 compile.$$addScopeClass($element, true); 8077 isolateScope.$$isolateBindings = 8078 newIsolateScopeDirective.$$isolateBindings; 8079 initializeDirectiveBindings(scope, attrs, isolateScope, 8080 isolateScope.$$isolateBindings, 8081 newIsolateScopeDirective, isolateScope); 8082 } 8083 if (elementControllers) { 8084 // Initialize bindToController bindings for new/isolate scopes 8085 var scopeDirective = newIsolateScopeDirective || newScopeDirective; 8086 var bindings; 8087 var controllerForBindings; 8088 if (scopeDirective && elementControllers[scopeDirective.name]) { 8089 bindings = scopeDirective.$$bindings.bindToController; 8090 controller = elementControllers[scopeDirective.name]; 8091 8092 if (controller && controller.identifier && bindings) { 8093 controllerForBindings = controller; 8094 thisLinkFn.$$destroyBindings = 8095 initializeDirectiveBindings(scope, attrs, controller.instance, 8096 bindings, scopeDirective); 8097 } 8098 } 8099 for (i in elementControllers) { 8100 controller = elementControllers[i]; 8101 var controllerResult = controller(); 8102 8103 if (controllerResult !== controller.instance) { 8104 // If the controller constructor has a return value, overwrite the instance 8105 // from setupControllers and update the element data 8106 controller.instance = controllerResult; 8107 $element.data('$' + i + 'Controller', controllerResult); 8108 if (controller === controllerForBindings) { 8109 // Remove and re-install bindToController bindings 8110 thisLinkFn.$$destroyBindings(); 8111 thisLinkFn.$$destroyBindings = 8112 initializeDirectiveBindings(scope, attrs, controllerResult, bindings, scopeDirective); 8113 } 8114 } 8115 } 8116 } 8117 8118 // PRELINKING 8119 for (i = 0, ii = preLinkFns.length; i < ii; i++) { 8120 linkFn = preLinkFns[i]; 8121 invokeLinkFn(linkFn, 8122 linkFn.isolateScope ? isolateScope : scope, 8123 $element, 8124 attrs, 8125 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 8126 transcludeFn 8127 ); 8128 } 8129 8130 // RECURSION 8131 // We only pass the isolate scope, if the isolate directive has a template, 8132 // otherwise the child elements do not belong to the isolate directive. 8133 var scopeToChild = scope; 8134 if (newIsolateScopeDirective && (newIsolateScopeDirective.template || newIsolateScopeDirective.templateUrl === null)) { 8135 scopeToChild = isolateScope; 8136 } 8137 childLinkFn && childLinkFn(scopeToChild, linkNode.childNodes, undefined, boundTranscludeFn); 8138 8139 // POSTLINKING 8140 for (i = postLinkFns.length - 1; i >= 0; i--) { 8141 linkFn = postLinkFns[i]; 8142 invokeLinkFn(linkFn, 8143 linkFn.isolateScope ? isolateScope : scope, 8144 $element, 8145 attrs, 8146 linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), 8147 transcludeFn 8148 ); 8149 } 8150 8151 // This is the function that is injected as `$transclude`. 8152 // Note: all arguments are optional! 8153 function controllersBoundTransclude(scope, cloneAttachFn, futureParentElement) { 8154 var transcludeControllers; 8155 8156 // No scope passed in: 8157 if (!isScope(scope)) { 8158 futureParentElement = cloneAttachFn; 8159 cloneAttachFn = scope; 8160 scope = undefined; 8161 } 8162 8163 if (hasElementTranscludeDirective) { 8164 transcludeControllers = elementControllers; 8165 } 8166 if (!futureParentElement) { 8167 futureParentElement = hasElementTranscludeDirective ? $element.parent() : $element; 8168 } 8169 return boundTranscludeFn(scope, cloneAttachFn, transcludeControllers, futureParentElement, scopeToChild); 8170 } 8171 } 8172 } 8173 8174 function markDirectivesAsIsolate(directives) { 8175 // mark all directives as needing isolate scope. 8176 for (var j = 0, jj = directives.length; j < jj; j++) { 8177 directives[j] = inherit(directives[j], {$$isolateScope: true}); 8178 } 8179 } 8180 8181 /** 8182 * looks up the directive and decorates it with exception handling and proper parameters. We 8183 * call this the boundDirective. 8184 * 8185 * @param {string} name name of the directive to look up. 8186 * @param {string} location The directive must be found in specific format. 8187 * String containing any of theses characters: 8188 * 8189 * * `E`: element name 8190 * * `A': attribute 8191 * * `C`: class 8192 * * `M`: comment 8193 * @returns {boolean} true if directive was added. 8194 */ 8195 function addDirective(tDirectives, name, location, maxPriority, ignoreDirective, startAttrName, 8196 endAttrName) { 8197 if (name === ignoreDirective) return null; 8198 var match = null; 8199 if (hasDirectives.hasOwnProperty(name)) { 8200 for (var directive, directives = $injector.get(name + Suffix), 8201 i = 0, ii = directives.length; i < ii; i++) { 8202 try { 8203 directive = directives[i]; 8204 if ((maxPriority === undefined || maxPriority > directive.priority) && 8205 directive.restrict.indexOf(location) != -1) { 8206 if (startAttrName) { 8207 directive = inherit(directive, {$$start: startAttrName, $$end: endAttrName}); 8208 } 8209 tDirectives.push(directive); 8210 match = directive; 8211 } 8212 } catch (e) { $exceptionHandler(e); } 8213 } 8214 } 8215 return match; 8216 } 8217 8218 8219 /** 8220 * looks up the directive and returns true if it is a multi-element directive, 8221 * and therefore requires DOM nodes between -start and -end markers to be grouped 8222 * together. 8223 * 8224 * @param {string} name name of the directive to look up. 8225 * @returns true if directive was registered as multi-element. 8226 */ 8227 function directiveIsMultiElement(name) { 8228 if (hasDirectives.hasOwnProperty(name)) { 8229 for (var directive, directives = $injector.get(name + Suffix), 8230 i = 0, ii = directives.length; i < ii; i++) { 8231 directive = directives[i]; 8232 if (directive.multiElement) { 8233 return true; 8234 } 8235 } 8236 } 8237 return false;
8238 } 8239 8240 /** 8241 * When the element is replaced with HTML template then the new attributes 8242 * on the template need to be merged with the existing attributes in the DOM. 8243 * The desired effect is to have both of the attributes present. 8244 * 8245 * @param {object} dst destination attributes (original DOM) 8246 * @param {object} src source attributes (from the directive template) 8247 */ 8248 function mergeTemplateAttributes(dst, src) { 8249 var srcAttr = src.$attr, 8250 dstAttr = dst.$attr, 8251 $element = dst.$$element; 8252 8253 // reapply the old attributes to the new element 8254 forEach(dst, function(value, key) { 8255 if (key.charAt(0) != '$') { 8256 if (src[key] && src[key] !== value) { 8257 value += (key === 'style' ? ';' : ' ') + src[key]; 8258 } 8259 dst.$set(key, value, true, srcAttr[key]); 8260 } 8261 }); 8262 8263 // copy the new attributes on the old attrs object 8264 forEach(src, function(value, key) { 8265 if (key == 'class') { 8266 safeAddClass($element, value); 8267 dst['class'] = (dst['class'] ? dst['class'] + ' ' : '') + value; 8268 } else if (key == 'style') { 8269 $element.attr('style', $element.attr('style') + ';' + value); 8270 dst['style'] = (dst['style'] ? dst['style'] + ';' : '') + value; 8271 // `dst` will never contain hasOwnProperty as DOM parser won't let it. 8272 // You will get an "InvalidCharacterError: DOM Exception 5" error if you 8273 // have an attribute like "has-own-property" or "data-has-own-property", etc. 8274 } else if (key.charAt(0) != '$' && !dst.hasOwnProperty(key)) { 8275 dst[key] = value; 8276 dstAttr[key] = srcAttr[key]; 8277 } 8278 }); 8279 } 8280 8281 8282 function compileTemplateUrl(directives, $compileNode, tAttrs, 8283 $rootElement, childTranscludeFn, preLinkFns, postLinkFns, previousCompileContext) { 8284 var linkQueue = [], 8285 afterTemplateNodeLinkFn, 8286 afterTemplateChildLinkFn, 8287 beforeTemplateCompileNode = $compileNode[0], 8288 origAsyncDirective = directives.shift(), 8289 derivedSyncDirective = inherit(origAsyncDirective, { 8290 templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective 8291 }), 8292 templateUrl = (isFunction(origAsyncDirective.templateUrl)) 8293 ? origAsyncDirective.templateUrl($compileNode, tAttrs) 8294 : origAsyncDirective.templateUrl, 8295 templateNamespace = origAsyncDirective.templateNamespace; 8296 8297 $compileNode.empty(); 8298 8299 $templateRequest($sce.getTrustedResourceUrl(templateUrl)) 8300 .then(function(content) { 8301 var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn; 8302 8303 content = denormalizeTemplate(content); 8304 8305 if (origAsyncDirective.replace) { 8306 if (jqLiteIsTextNode(content)) { 8307 $template = []; 8308 } else { 8309 $template = removeComments(wrapTemplate(templateNamespace, trim(content))); 8310 } 8311 compileNode = $template[0]; 8312 8313 if ($template.length != 1 || compileNode.nodeType !== NODE_TYPE_ELEMENT) { 8314 throw $compileMinErr('tplrt', 8315 "Template for directive '{0}' must have exactly one root element. {1}", 8316 origAsyncDirective.name, templateUrl); 8317 } 8318 8319 tempTemplateAttrs = {$attr: {}}; 8320 replaceWith($rootElement, $compileNode, compileNode); 8321 var templateDirectives = collectDirectives(compileNode, [], tempTemplateAttrs); 8322 8323 if (isObject(origAsyncDirective.scope)) { 8324 markDirectivesAsIsolate(templateDirectives); 8325 } 8326 directives = templateDirectives.concat(directives); 8327 mergeTemplateAttributes(tAttrs, tempTemplateAttrs); 8328 } else { 8329 compileNode = beforeTemplateCompileNode; 8330 $compileNode.html(content); 8331 } 8332 8333 directives.unshift(derivedSyncDirective); 8334 8335 afterTemplateNodeLinkFn = applyDirectivesToNode(directives, compileNode, tAttrs, 8336 childTranscludeFn, $compileNode, origAsyncDirective, preLinkFns, postLinkFns, 8337 previousCompileContext);
vendor: 11,852 bytes, lines 8338-8615
8338 forEach($rootElement, function(node, i) { 8339 if (node == compileNode) { 8340 $rootElement[i] = $compileNode[0]; 8341 } 8342 }); 8343 afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn); 8344 8345 while (linkQueue.length) { 8346 var scope = linkQueue.shift(), 8347 beforeTemplateLinkNode = linkQueue.shift(), 8348 linkRootElement = linkQueue.shift(), 8349 boundTranscludeFn = linkQueue.shift(), 8350 linkNode = $compileNode[0]; 8351 8352 if (scope.$$destroyed) continue; 8353 8354 if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 8355 var oldClasses = beforeTemplateLinkNode.className; 8356 8357 if (!(previousCompileContext.hasElementTranscludeDirective && 8358 origAsyncDirective.replace)) { 8359 // it was cloned therefore we have to clone as well. 8360 linkNode = jqLiteClone(compileNode); 8361 } 8362 replaceWith(linkRootElement, jqLite(beforeTemplateLinkNode), linkNode); 8363 8364 // Copy in CSS classes from original node 8365 safeAddClass(jqLite(linkNode), oldClasses); 8366 } 8367 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 8368 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 8369 } else { 8370 childBoundTranscludeFn = boundTranscludeFn; 8371 } 8372 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, linkNode, $rootElement, 8373 childBoundTranscludeFn, afterTemplateNodeLinkFn); 8374 } 8375 linkQueue = null; 8376 }); 8377 8378 return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) { 8379 var childBoundTranscludeFn = boundTranscludeFn; 8380 if (scope.$$destroyed) return; 8381 if (linkQueue) { 8382 linkQueue.push(scope, 8383 node, 8384 rootElement, 8385 childBoundTranscludeFn); 8386 } else { 8387 if (afterTemplateNodeLinkFn.transcludeOnThisElement) { 8388 childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); 8389 } 8390 afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn, 8391 afterTemplateNodeLinkFn); 8392 } 8393 }; 8394 } 8395 8396 8397 /** 8398 * Sorting function for bound directives. 8399 */ 8400 function byPriority(a, b) { 8401 var diff = b.priority - a.priority; 8402 if (diff !== 0) return diff; 8403 if (a.name !== b.name) return (a.name < b.name) ? -1 : 1; 8404 return a.index - b.index; 8405 } 8406 8407 function assertNoDuplicate(what, previousDirective, directive, element) { 8408 8409 function wrapModuleNameIfDefined(moduleName) { 8410 return moduleName ? 8411 (' (module: ' + moduleName + ')') : 8412 ''; 8413 } 8414 8415 if (previousDirective) { 8416 throw $compileMinErr('multidir', 'Multiple directives [{0}{1}, {2}{3}] asking for {4} on: {5}', 8417 previousDirective.name, wrapModuleNameIfDefined(previousDirective.$$moduleName), 8418 directive.name, wrapModuleNameIfDefined(directive.$$moduleName), what, startingTag(element)); 8419 } 8420 } 8421 8422 8423 function addTextInterpolateDirective(directives, text) { 8424 var interpolateFn = $interpolate(text, true); 8425 if (interpolateFn) { 8426 directives.push({ 8427 priority: 0, 8428 compile: function textInterpolateCompileFn(templateNode) { 8429 var templateNodeParent = templateNode.parent(), 8430 hasCompileParent = !!templateNodeParent.length; 8431 8432 // When transcluding a template that has bindings in the root 8433 // we don't have a parent and thus need to add the class during linking fn. 8434 if (hasCompileParent) compile.$$addBindingClass(templateNodeParent); 8435 8436 return function textInterpolateLinkFn(scope, node) { 8437 var parent = node.parent(); 8438 if (!hasCompileParent) compile.$$addBindingClass(parent); 8439 compile.$$addBindingInfo(parent, interpolateFn.expressions); 8440 scope.$watch(interpolateFn, function interpolateFnWatchAction(value) { 8441 node[0].nodeValue = value; 8442 }); 8443 }; 8444 } 8445 }); 8446 } 8447 } 8448 8449 8450 function wrapTemplate(type, template) { 8451 type = lowercase(type || 'html'); 8452 switch (type) { 8453 case 'svg': 8454 case 'math': 8455 var wrapper = document.createElement('div'); 8456 wrapper.innerHTML = '<' + type + '>' + template + '</' + type + '>'; 8457 return wrapper.childNodes[0].childNodes; 8458 default: 8459 return template; 8460 } 8461 } 8462 8463 8464 function getTrustedContext(node, attrNormalizedName) { 8465 if (attrNormalizedName == "srcdoc") { 8466 return $sce.HTML; 8467 } 8468 var tag = nodeName_(node); 8469 // maction[xlink:href] can source SVG. It's not limited to <maction>. 8470 if (attrNormalizedName == "xlinkHref" || 8471 (tag == "form" && attrNormalizedName == "action") || 8472 (tag != "img" && (attrNormalizedName == "src" || 8473 attrNormalizedName == "ngSrc"))) { 8474 return $sce.RESOURCE_URL; 8475 } 8476 } 8477 8478 8479 function addAttrInterpolateDirective(node, directives, value, name, allOrNothing) { 8480 var trustedContext = getTrustedContext(node, name); 8481 allOrNothing = ALL_OR_NOTHING_ATTRS[name] || allOrNothing; 8482 8483 var interpolateFn = $interpolate(value, true, trustedContext, allOrNothing); 8484 8485 // no interpolation found -> ignore 8486 if (!interpolateFn) return; 8487 8488 8489 if (name === "multiple" && nodeName_(node) === "select") { 8490 throw $compileMinErr("selmulti", 8491 "Binding to the 'multiple' attribute is not supported. Element: {0}", 8492 startingTag(node)); 8493 } 8494 8495 directives.push({ 8496 priority: 100, 8497 compile: function() { 8498 return { 8499 pre: function attrInterpolatePreLinkFn(scope, element, attr) { 8500 var $$observers = (attr.$$observers || (attr.$$observers = {})); 8501 8502 if (EVENT_HANDLER_ATTR_REGEXP.test(name)) { 8503 throw $compileMinErr('nodomevents', 8504 "Interpolations for HTML DOM event attributes are disallowed. Please use the " + 8505 "ng- versions (such as ng-click instead of onclick) instead."); 8506 } 8507 8508 // If the attribute has changed since last $interpolate()ed 8509 var newValue = attr[name]; 8510 if (newValue !== value) { 8511 // we need to interpolate again since the attribute value has been updated 8512 // (e.g. by another directive's compile function) 8513 // ensure unset/empty values make interpolateFn falsy 8514 interpolateFn = newValue && $interpolate(newValue, true, trustedContext, allOrNothing); 8515 value = newValue; 8516 } 8517 8518 // if attribute was updated so that there is no interpolation going on we don't want to 8519 // register any observers 8520 if (!interpolateFn) return; 8521 8522 // initialize attr object so that it's ready in case we need the value for isolate 8523 // scope initialization, otherwise the value would not be available from isolate 8524 // directive's linking fn during linking phase 8525 attr[name] = interpolateFn(scope); 8526 8527 ($$observers[name] || ($$observers[name] = [])).$$inter = true; 8528 (attr.$$observers && attr.$$observers[name].$$scope || scope). 8529 $watch(interpolateFn, function interpolateFnWatchAction(newValue, oldValue) { 8530 //special case for class attribute addition + removal 8531 //so that class changes can tap into the animation 8532 //hooks provided by the $animate service. Be sure to 8533 //skip animations when the first digest occurs (when 8534 //both the new and the old values are the same) since 8535 //the CSS classes are the non-interpolated values 8536 if (name === 'class' && newValue != oldValue) { 8537 attr.$updateClass(newValue, oldValue); 8538 } else { 8539 attr.$set(name, newValue); 8540 } 8541 }); 8542 } 8543 }; 8544 } 8545 }); 8546 } 8547 8548 8549 /** 8550 * This is a special jqLite.replaceWith, which can replace items which 8551 * have no parents, provided that the containing jqLite collection is provided. 8552 * 8553 * @param {JqLite=} $rootElement The root of the compile tree. Used so that we can replace nodes 8554 * in the root of the tree. 8555 * @param {JqLite} elementsToRemove The jqLite element which we are going to replace. We keep 8556 * the shell, but replace its DOM node reference. 8557 * @param {Node} newNode The new DOM node. 8558 */ 8559 function replaceWith($rootElement, elementsToRemove, newNode) { 8560 var firstElementToRemove = elementsToRemove[0], 8561 removeCount = elementsToRemove.length, 8562 parent = firstElementToRemove.parentNode, 8563 i, ii; 8564 8565 if ($rootElement) { 8566 for (i = 0, ii = $rootElement.length; i < ii; i++) { 8567 if ($rootElement[i] == firstElementToRemove) { 8568 $rootElement[i++] = newNode; 8569 for (var j = i, j2 = j + removeCount - 1, 8570 jj = $rootElement.length; 8571 j < jj; j++, j2++) { 8572 if (j2 < jj) { 8573 $rootElement[j] = $rootElement[j2]; 8574 } else { 8575 delete $rootElement[j]; 8576 } 8577 } 8578 $rootElement.length -= removeCount - 1; 8579 8580 // If the replaced element is also the jQuery .context then replace it 8581 // .context is a deprecated jQuery api, so we should set it only when jQuery set it 8582 // http://api.jquery.com/context/ 8583 if ($rootElement.context === firstElementToRemove) { 8584 $rootElement.context = newNode; 8585 } 8586 break; 8587 } 8588 } 8589 } 8590 8591 if (parent) { 8592 parent.replaceChild(newNode, firstElementToRemove); 8593 } 8594 8595 // TODO(perf): what's this document fragment for? is it needed? can we at least reuse it? 8596 var fragment = document.createDocumentFragment(); 8597 fragment.appendChild(firstElementToRemove); 8598 8599 if (jqLite.hasData(firstElementToRemove)) { 8600 // Copy over user data (that includes Angular's $scope etc.). Don't copy private 8601 // data here because there's no public interface in jQuery to do that and copying over 8602 // event listeners (which is the main use of private data) wouldn't work anyway. 8603 jqLite(newNode).data(jqLite(firstElementToRemove).data()); 8604 8605 // Remove data of the replaced element. We cannot just call .remove() 8606 // on the element it since that would deallocate scope that is needed 8607 // for the new node. Instead, remove the data "manually". 8608 if (!jQuery) { 8609 delete jqLite.cache[firstElementToRemove[jqLite.expando]]; 8610 } else { 8611 // jQuery 2.x doesn't expose the data storage. Use jQuery.cleanData to clean up after 8612 // the replaced element. The cleanData version monkey-patched by Angular would cause 8613 // the scope to be trashed and we do need the very same scope to work with the new 8614 // element. However, we cannot just cache the non-patched version and use it here as 8615 // that would break if another library patches the method after Angular does (one
8616 // example is jQuery UI). Instead, set a flag indicating scope destroying should be 8617 // skipped this one time. 8618 skipDestroyOnNextJQueryCleanData = true; 8619 jQuery.cleanData([firstElementToRemove]); 8620 } 8621 } 8622 8623 for (var k = 1, kk = elementsToRemove.length; k < kk; k++) { 8624 var element = elementsToRemove[k]; 8625 jqLite(element).remove(); // must do this way to clean up expando 8626 fragment.appendChild(element); 8627 delete elementsToRemove[k]; 8628 } 8629 8630 elementsToRemove[0] = newNode; 8631 elementsToRemove.length = 1; 8632 } 8633 8634 8635 function cloneAndAnnotateFn(fn, annotation) { 8636 return extend(function() { return fn.apply(null, arguments); }, fn, annotation); 8637 } 8638 8639 8640 function invokeLinkFn(linkFn, scope, $element, attrs, controllers, transcludeFn) { 8641 try { 8642 linkFn(scope, $element, attrs, controllers, transcludeFn); 8643 } catch (e) { 8644 $exceptionHandler(e, startingTag($element)); 8645 } 8646 } 8647 8648 8649 // Set up $watches for isolate scope and controller bindings. This process 8650 // only occurs for isolate scopes and new scopes with controllerAs. 8651 function initializeDirectiveBindings(scope, attrs, destination, bindings, 8652 directive, newScope) { 8653 var onNewScopeDestroyed; 8654 forEach(bindings, function(definition, scopeName) { 8655 var attrName = definition.attrName, 8656 optional = definition.optional, 8657 mode = definition.mode, // @, =, or & 8658 lastValue, 8659 parentGet, parentSet, compare; 8660 8661 if (!hasOwnProperty.call(attrs, attrName)) { 8662 // In the case of user defined a binding with the same name as a method in Object.prototype but didn't set 8663 // the corresponding attribute. We need to make sure subsequent code won't access to the prototype function 8664 attrs[attrName] = undefined; 8665 } 8666 8667 switch (mode) { 8668 8669 case '@': 8670 if (!attrs[attrName] && !optional) { 8671 destination[scopeName] = undefined; 8672 } 8673 8674 attrs.$observe(attrName, function(value) { 8675 destination[scopeName] = value; 8676 }); 8677 attrs.$$observers[attrName].$$scope = scope; 8678 if (attrs[attrName]) { 8679 // If the attribute has been provided then we trigger an interpolation to ensure 8680 // the value is there for use in the link fn 8681 destination[scopeName] = $interpolate(attrs[attrName])(scope); 8682 } 8683 break; 8684 8685 case '=': 8686 if (optional && !attrs[attrName]) { 8687 return; 8688 } 8689 parentGet = $parse(attrs[attrName]); 8690 8691 if (parentGet.literal) { 8692 compare = equals; 8693 } else { 8694 compare = function(a, b) { return a === b || (a !== a && b !== b); }; 8695 } 8696 parentSet = parentGet.assign || function() { 8697 // reset the change, or we will throw this exception on every $digest 8698 lastValue = destination[scopeName] = parentGet(scope); 8699 throw $compileMinErr('nonassign', 8700 "Expression '{0}' used with directive '{1}' is non-assignable!", 8701 attrs[attrName], directive.name); 8702 }; 8703 lastValue = destination[scopeName] = parentGet(scope); 8704 var parentValueWatch = function parentValueWatch(parentValue) { 8705 if (!compare(parentValue, destination[scopeName])) { 8706 // we are out of sync and need to copy 8707 if (!compare(parentValue, lastValue)) { 8708 // parent changed and it has precedence 8709 destination[scopeName] = parentValue; 8710 } else { 8711 // if the parent can be assigned then do so 8712 parentSet(scope, parentValue = destination[scopeName]); 8713 } 8714 } 8715 return lastValue = parentValue; 8716 }; 8717 parentValueWatch.$stateful = true; 8718 var unwatch; 8719 if (definition.collection) { 8720 unwatch = scope.$watchCollection(attrs[attrName], parentValueWatch); 8721 } else { 8722 unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); 8723 } 8724 onNewScopeDestroyed = (onNewScopeDestroyed || []); 8725 onNewScopeDestroyed.push(unwatch); 8726 break; 8727 8728 case '&': 8729 parentGet = $parse(attrs[attrName]); 8730 8731 // Don't assign noop to destination if expression is not val
8731id 8732 if (parentGet === noop && optional) break; 8733 8734 destination[scopeName] = function(locals) { 8735 return parentGet(scope, locals); 8736 }; 8737 break; 8738 } 8739 }); 8740 var destroyBindings = onNewScopeDestroyed ? function destroyBindings() { 8741 for (var i = 0, ii = onNewScopeDestroyed.length; i < ii; ++i) { 8742 onNewScopeDestroyed[i](); 8743 } 8744 } : noop; 8745 if (newScope && destroyBindings !== noop) { 8746 newScope.$on('$destroy', destroyBindings); 8747 return noop; 8748 } 8749 return destroyBindings; 8750 } 8751 }]; 8752} 8753 8754var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i; 8755/** 8756 * Converts all accepted directives format into proper directive name. 8757 * @param name Name to normalize 8758 */ 8759function directiveNormalize(name) { 8760 return camelCase(name.replace(PREFIX_REGEXP, '')); 8761} 8762 8763/** 8764 * @ngdoc type 8765 * @name $compile.directive.Attributes 8766 * 8767 * @description 8768 * A shared object between directive compile / linking functions which contains normalized DOM 8769 * element attributes. The values reflect current binding state `{{ }}`. The normalization is 8770 * needed since all of these are treated as equivalent in Angular: 8771 * 8772 * ``` 8773 * <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a"> 8774 * ``` 8775 */ 8776 8777/** 8778 * @ngdoc property 8779 * @name $compile.directive.Attributes#$attr 8780 * 8781 * @description 8782 * A map of DOM element attribute names to the normalized name. This is 8783 * needed to do reverse lookup from normalized name back to actual name. 8784 */ 8785 8786 8787/** 8788 * @ngdoc method 8789 * @name $compile.directive.Attributes#$set 8790 * @kind function 8791 * 8792 * @description 8793 * Set DOM element attribute value. 8794 * 8795 * 8796 * @param {string} name Normalized element attribute name of the property to modify. The name is 8797 * reverse-translated using the {@link ng.$compile.directive.Attributes#$attr $attr} 8798 * property to the original name. 8799 * @param {string} value Value to set the attribute to. The value can be an interpolated string. 8800 */ 8801 8802 8803 8804/** 8805 * Closure compiler type information 8806 */ 8807 8808function nodesetLinkingFn( 8809 /* angular.Scope */ scope, 8810 /* NodeList */ nodeList, 8811 /* Element */ rootElement, 8812 /* function(Function) */ boundTranscludeFn 8813) {} 8814 8815function directiveLinkingFn( 8816 /* nodesetLinkingFn */ nodesetLinkingFn, 8817 /* angular.Scope */ scope, 8818 /* Node */ node, 8819 /* Element */ rootElement, 8820 /* function(Function) */ boundTranscludeFn 8821) {} 8822 8823function tokenDifference(str1, str2) { 8824 var values = '', 8825 tokens1 = str1.split(/\s+/), 8826 tokens2 = str2.split(/\s+/); 8827 8828 outer: 8829 for (var i = 0; i < tokens1.length; i++) { 8830 var token = tokens1[i]; 8831 for (var j = 0; j < tokens2.length; j++) { 8832 if (token == tokens2[j]) continue outer; 8833 } 8834 values += (values.length > 0 ? ' ' : '') + token; 8835 } 8836 return values; 8837} 8838 8839function removeComments(jqNodes) { 8840 jqNodes = jqLite(jqNodes); 8841 var i = jqNodes.length; 8842 8843 if (i <= 1) { 8844 return jqNodes; 8845 } 8846 8847 while (i--) { 8848 var node = jqNodes[i]; 8849 if (node.nodeType === NODE_TYPE_COMMENT) { 8850 splice.call(jqNodes, i, 1); 8851 } 8852 } 8853 return jqNodes; 8854} 8855 8856var $controllerMinErr = minErr('$controller'); 8857 8858 8859var CNTRL_REG = /^(\S+)(\s+as\s+(\w+))?$/; 8860function identifierForController(controller, ident) { 8861 if (ident && isString(ident)) return ident; 8862 if (isString(controller)) { 8863 var match = CNTRL_REG.exec(controller); 8864 if (match) return match[3]; 8865 } 8866} 8867 8868 8869/** 8870 * @ngdoc provider 8871 * @name $controllerProvider 8872 * @description 8873 * The {@link ng.$controller $controller service} is used by Angular to create new 8874 * controllers. 8875 * 8876 * This provider allows controller registration via the 8877 * {@link ng.$controllerProvider#register register} method. 8878 */ 8879function $ControllerProvider() { 8880 var controllers = {}, 8881 globals = false; 8882 8883 /** 8884 * @ngdoc method 8885 * @name $controllerProvider#register 8886 * @param {string|Object} name Controller name, or an object map of controllers where the keys are 8887 * the names and the values are the constructors. 8888 * @param {Function|Array} constructor Controller constructor fn (optionally decorated with DI 8889 * annotations in the array notation). 8890 */ 8891 this.register = function(name, constructor) { 8892 assertNotHasOwnProperty(name, 'controller'); 8893 if (isObject(name)) { 8894 extend(controllers, name); 8895 } else { 8896 controllers[name] = constructor; 8897 } 8898 }; 8899 8900 /** 8901 * @ngdoc method 8902 * @name $controllerProvider#allowGlobals 8903 * @description If called, allows `$controller` to find controller constructors on `window` 8904 */ 8905 this.allowGlobals = function() { 8906 globals = true; 8907 }; 8908 8909 8910 this.$get = ['$injector', '$window', function($injector, $window) { 8911 8912 /** 8913 * @ngdoc service 8914 * @name $controller 8915 * @requires $injector 8916 * 8917 * @param {Function|string} constructor If called with a function then it's considered to be the 8918 * controller constructor function. Otherwise it's considered to be a string which is used 8919 * to retrieve the controller constructor using the following steps: 8920 * 8921 * * check if a controller with given name is registered via `$controllerProvider` 8922 * * check if evaluating the string on the current scope returns a constructor 8923 * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global 8924 * `window` object (not recommended) 8925 * 8926 * The string can use the `controller as property` syntax, where the controller instance is published 8927 * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this 8928 * to work correctly. 8929 * 8930 * @param {Object} locals Injection locals for Controller. 8931 * @return {Object} Instance of given controller. 8932 *
vendor: 4,024 bytes, lines 8933-9037
8933 * @description 8934 * `$controller` service is responsible for instantiating controllers. 8935 * 8936 * It's just a simple call to {@link auto.$injector $injector}, but extracted into 8937 * a service, so that one can override this service with [BC version](https://gist.github.com/1649788). 8938 */ 8939 return function(expression, locals, later, ident) { 8940 // PRIVATE API: 8941 // param `later` --- indicates that the controller's constructor is invoked at a later time. 8942 // If true, $controller will allocate the object with the correct 8943 // prototype chain, but will not invoke the controller until a returned 8944 // callback is invoked. 8945 // param `ident` --- An optional label which overrides the label parsed from the controller 8946 // expression, if any. 8947 var instance, match, constructor, identifier; 8948 later = later === true; 8949 if (ident && isString(ident)) { 8950 identifier = ident; 8951 } 8952 8953 if (isString(expression)) { 8954 match = expression.match(CNTRL_REG); 8955 if (!match) { 8956 throw $controllerMinErr('ctrlfmt', 8957 "Badly formed controller string '{0}'. " + 8958 "Must match `__name__ as __id__` or `__name__`.", expression); 8959 } 8960 constructor = match[1], 8961 identifier = identifier || match[3]; 8962 expression = controllers.hasOwnProperty(constructor) 8963 ? controllers[constructor] 8964 : getter(locals.$scope, constructor, true) || 8965 (globals ? getter($window, constructor, true) : undefined); 8966 8967 assertArgFn(expression, constructor, true); 8968 } 8969 8970 if (later) { 8971 // Instantiate controller later: 8972 // This machinery is used to create an instance of the object before calling the 8973 // controller's constructor itself. 8974 // 8975 // This allows properties to be added to the controller before the constructor is 8976 // invoked. Primarily, this is used for isolate scope bindings in $compile. 8977 // 8978 // This feature is not intended for use by applications, and is thus not documented 8979 // publicly. 8980 // Object creation: http://jsperf.com/create-constructor/2 8981 var controllerPrototype = (isArray(expression) ? 8982 expression[expression.length - 1] : expression).prototype; 8983 instance = Object.create(controllerPrototype || null); 8984 8985 if (identifier) { 8986 addIdentifier(locals, identifier, instance, constructor || expression.name); 8987 } 8988 8989 var instantiate; 8990 return instantiate = extend(function() { 8991 var result = $injector.invoke(expression, instance, locals, constructor); 8992 if (result !== instance && (isObject(result) || isFunction(result))) { 8993 instance = result; 8994 if (identifier) { 8995 // If result changed, re-assign controllerAs value to scope. 8996 addIdentifier(locals, identifier, instance, constructor || expression.name); 8997 } 8998 } 8999 return instance; 9000 }, { 9001 instance: instance, 9002 identifier: identifier 9003 }); 9004 } 9005 9006 instance = $injector.instantiate(expression, locals, constructor); 9007 9008 if (identifier) { 9009 addIdentifier(locals, identifier, instance, constructor || expression.name); 9010 } 9011 9012 return instance; 9013 }; 9014 9015 function addIdentifier(locals, identifier, instance, name) { 9016 if (!(locals && isObject(locals.$scope))) { 9017 throw minErr('$controller')('noscp', 9018 "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", 9019 name, identifier); 9020 } 9021 9022 locals.$scope[identifier] = instance; 9023 } 9024 }]; 9025} 9026 9027/** 9028 * @ngdoc service 9029 * @name $document 9030 * @requires $window 9031 * 9032 * @description 9033 * A {@link angular.element jQuery or jqLite} wrapper for the browser's `window.document` object. 9034 * 9035 * @example 9036 <example module="documentExample"> 9037 <file name="index.html">
9038 <div ng-controller="ExampleController"> 9039 <p>$document title: <b ng-bind="title"></b></p> 9040 <p>window.document title: <b ng-bind="windowTitle"></b></p> 9041 </div> 9042 </file> 9043 <file name="script.js"> 9044 angular.module('documentExample', []) 9045 .controller('ExampleController', ['$scope', '$document', function($scope, $document) { 9046 $scope.title = $document[0].title; 9047 $scope.windowTitle = angular.element(window.document)[0].title; 9048 }]); 9049 </file> 9050 </example> 9051 */ 9052function $DocumentProvider() { 9053 this.$get = ['$window', function(window) { 9054 return jqLite(window.document); 9055 }]; 9056} 9057 9058/** 9059 * @ngdoc service 9060 * @name $exceptionHandler 9061 * @requires ng.$log 9062 * 9063 * @description 9064 * Any uncaught exception in angular expressions is delegated to this service. 9065 * The default implementation simply delegates to `$log.error` which logs it into 9066 * the browser console. 9067 * 9068 * In unit tests, if `angular-mocks.js` is loaded, this service is overridden by 9069 * {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing. 9070 * 9071 * ## Example: 9072 * 9073 * ```js 9074 * angular.module('exceptionOverride', []).factory('$exceptionHandler', function() { 9075 * return function(exception, cause) { 9076 * exception.message += ' (caused by "' + cause + '")'; 9077 * throw exception; 9078 * }; 9079 * }); 9080 * ``` 9081 * 9082 * This example will override the normal action of `$exceptionHandler`, to make angular 9083 * exceptions fail hard when they happen, instead of just logging to the console. 9084 * 9085 * <hr /> 9086 * Note, that code executed in event-listeners (even those registered using jqLite's `on`/`bind` 9087 * methods) does not delegate exceptions to the {@link ng.$exceptionHandler $exceptionHandler} 9088 * (unless executed during a digest). 9089 * 9090 * If you wish, you can manually delegate exceptions, e.g. 9091 * `try { ... } catch(e) { $exceptionHandler(e); }` 9092 * 9093 * @param {Error} exception Exception associated with the error. 9094 * @param {string=} cause optional information about the context in which 9095 * the error was thrown. 9096 * 9097 */ 9098function $ExceptionHandlerProvider() { 9099 this.$get = ['$log', function($log) { 9100 return function(exception, cause) { 9101 $log.error.apply($log, arguments); 9102 }; 9103 }]; 9104} 9105 9106var APPLICATION_JSON = 'application/json'; 9107var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}; 9108var JSON_START = /^\[|^\{(?!\{)/; 9109var JSON_ENDS = { 9110 '[': /]$/, 9111 '{': /}$/ 9112}; 9113var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; 9114 9115function serializeValue(v) { 9116 if (isObject(v)) { 9117 return isDate(v) ? v.toISOString() : toJson(v); 9118 } 9119 return v; 9120} 9121 9122 9123function $HttpParamSerializerProvider() { 9124 /** 9125 * @ngdoc service 9126 * @name $httpParamSerializer 9127 * @description 9128 * 9129 * Default {@link $http `$http`} params serializer that converts objects to strings 9130 * according to the following rules: 9131 * 9132 * * `{'foo': 'bar'}` results in `foo=bar` 9133 * * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object) 9134 * * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element) 9135 * * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D"` (stringified and encoded representation of an object) 9136 * 9137 * Note that serializer will sort the request parameters alphabetically. 9138 * */ 9139 9140 this.$get = function() { 9141 return function ngParamSerializer(params) { 9142 if (!params) return ''; 9143 var parts = []; 9144 forEachSorted(params, function(value, key) { 9145 if (value === null || isUndefined(value)) return; 9146 if (isArray(value)) { 9147 forEach(value, function(v, k) { 9148 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v))); 9149 }); 9150 } else { 9151 parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value))); 9152 } 9153 }); 9154 9155 return parts.join('&'); 9156 }; 9157 }; 9158} 9159 9160function $HttpParamSerializerJQLikeProvider() { 9161 /** 9162 * @ngdoc service 9163 * @name $httpParamSerializerJQLike 9164 * @description 9165 * 9166 * Alternative {@link $http `$http`} params serializer that follows 9167 * jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
9168 * The serializer will also sort the params alphabetically. 9169 * 9170 * To use it for serializing `$http` request parameters, set it as the `paramSerializer` property: 9171 * 9172 * ```js 9173 * $http({ 9174 * url: myUrl, 9175 * method: 'GET', 9176 * params: myParams, 9177 * paramSerializer: '$httpParamSerializerJQLike' 9178 * }); 9179 * ``` 9180 * 9181 * It is also possible to set it as the default `paramSerializer` in the 9182 * {@link $httpProvider#defaults `$httpProvider`}. 9183 * 9184 * Additionally, you can inject the serializer and use it explicitly, for example to serialize 9185 * form data for submission: 9186 * 9187 * ```js 9188 * .controller(function($http, $httpParamSerializerJQLike) { 9189 * //... 9190 * 9191 * $http({ 9192 * url: myUrl, 9193 * method: 'POST', 9194 * data: $httpParamSerializerJQLike(myData), 9195 * headers: { 9196 * 'Content-Type': 'application/x-www-form-urlencoded' 9197 * } 9198 * }); 9199 * 9200 * }); 9201 * ``` 9202 * 9203 * */ 9204 this.$get = function() { 9205 return function jQueryLikeParamSerializer(params) { 9206 if (!params) return ''; 9207 var parts = []; 9208 serialize(params, '', true); 9209 return parts.join('&'); 9210 9211 function serialize(toSerialize, prefix, topLevel) { 9212 if (toSerialize === null || isUndefined(toSerialize)) return; 9213 if (isArray(toSerialize)) { 9214 forEach(toSerialize, function(value) { 9215 serialize(value, prefix + '[]'); 9216 }); 9217 } else if (isObject(toSerialize) && !isDate(toSerialize)) { 9218 forEachSorted(toSerialize, function(value, key) { 9219 serialize(value, prefix + 9220 (topLevel ? '' : '[') + 9221 key + 9222 (topLevel ? '' : ']')); 9223 }); 9224 } else { 9225 parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize))); 9226 } 9227 } 9228 }; 9229 }; 9230} 9231 9232function defaultHttpResponseTransform(data, headers) { 9233 if (isString(data)) { 9234 // Strip json vulnerability protection prefix and trim whitespace 9235 var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim(); 9236 9237 if (tempData) { 9238 var contentType = headers('Content-Type'); 9239 if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) { 9240 data = fromJson(tempData); 9241 } 9242 } 9243 } 9244 9245 return data; 9246} 9247 9248function isJsonLike(str) { 9249 var jsonStart = str.match(JSON_START); 9250 return jsonStart && JSON_ENDS[jsonStart[0]].test(str); 9251} 9252 9253/** 9254 * Parse headers into key value object 9255 * 9256 * @param {string} headers Raw headers as a string 9257 * @returns {Object} Parsed headers as key value object 9258 */ 9259function parseHeaders(headers) { 9260 var parsed = createMap(), i; 9261 9262 function fillInParsed(key, val) { 9263 if (key) { 9264 parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 9265 } 9266 } 9267 9268 if (isString(headers)) { 9269 forEach(headers.split('\n'), function(line) { 9270 i = line.indexOf(':');
vendor: 4,256 bytes, lines 9271-9408
9271 fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1))); 9272 }); 9273 } else if (isObject(headers)) { 9274 forEach(headers, function(headerVal, headerKey) { 9275 fillInParsed(lowercase(headerKey), trim(headerVal)); 9276 }); 9277 } 9278 9279 return parsed; 9280} 9281 9282 9283/** 9284 * Returns a function that provides access to parsed headers. 9285 * 9286 * Headers are lazy parsed when first requested. 9287 * @see parseHeaders 9288 * 9289 * @param {(string|Object)} headers Headers to provide access to. 9290 * @returns {function(string=)} Returns a getter function which if called with: 9291 * 9292 * - if called with single an argument returns a single header value or null 9293 * - if called with no arguments returns an object containing all headers. 9294 */ 9295function headersGetter(headers) { 9296 var headersObj; 9297 9298 return function(name) { 9299 if (!headersObj) headersObj = parseHeaders(headers); 9300 9301 if (name) { 9302 var value = headersObj[lowercase(name)]; 9303 if (value === void 0) { 9304 value = null; 9305 } 9306 return value; 9307 } 9308 9309 return headersObj; 9310 }; 9311} 9312 9313 9314/** 9315 * Chain all given functions 9316 * 9317 * This function is used for both request and response transforming 9318 * 9319 * @param {*} data Data to transform. 9320 * @param {function(string=)} headers HTTP headers getter fn. 9321 * @param {number} status HTTP status code of the response. 9322 * @param {(Function|Array.<Function>)} fns Function or an array of functions. 9323 * @returns {*} Transformed data. 9324 */ 9325function transformData(data, headers, status, fns) { 9326 if (isFunction(fns)) { 9327 return fns(data, headers, status); 9328 } 9329 9330 forEach(fns, function(fn) { 9331 data = fn(data, headers, status); 9332 }); 9333 9334 return data; 9335} 9336 9337 9338function isSuccess(status) { 9339 return 200 <= status && status < 300; 9340} 9341 9342 9343/** 9344 * @ngdoc provider 9345 * @name $httpProvider 9346 * @description 9347 * Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service. 9348 * */ 9349function $HttpProvider() { 9350 /** 9351 * @ngdoc property 9352 * @name $httpProvider#defaults 9353 * @description 9354 * 9355 * Object containing default values for all {@link ng.$http $http} requests. 9356 * 9357 * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`} 9358 * that will provide the cache for all requests who set their `cache` property to `true`. 9359 * If you set the `default.cache = false` then only requests that specify their own custom 9360 * cache object will be cached. See {@link $http#caching $http Caching} for more information. 9361 * 9362 * - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token. 9363 * Defaults value is `'XSRF-TOKEN'`. 9364 * 9365 * - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the 9366 * XSRF token. Defaults value is `'X-XSRF-TOKEN'`. 9367 * 9368 * - **`defaults.headers`** - {Object} - Default headers for all $http requests. 9369 * Refer to {@link ng.$http#setting-http-headers $http} for documentation on 9370 * setting default headers. 9371 * - **`defaults.headers.common`** 9372 * - **`defaults.headers.post`** 9373 * - **`defaults.headers.put`** 9374 * - **`defaults.headers.patch`** 9375 * 9376 * 9377 * - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function 9378 * used to the prepare string representation of request parameters (specified as an object). 9379 * If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}. 9380 * Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}. 9381 * 9382 **/ 9383 var defaults = this.defaults = { 9384 // transform incoming response data 9385 transformResponse: [defaultHttpResponseTransform], 9386 9387 // transform outgoing request data 9388 transformRequest: [function(d) { 9389 return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d; 9390 }], 9391 9392 // default headers 9393 headers: { 9394 common: { 9395 'Accept': 'application/json, text/plain, */*' 9396 }, 9397 post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 9398 put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON), 9399 patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON) 9400 }, 9401 9402 xsrfCookieName: 'XSRF-TOKEN', 9403 xsrfHeaderName: 'X-XSRF-TOKEN', 9404 9405 paramSerializer: '$httpParamSerializer' 9406 }; 9407 9408 var useApplyAsync = false;
9409 /** 9410 * @ngdoc method 9411 * @name $httpProvider#useApplyAsync 9412 * @description 9413 * 9414 * Configure $http service to combine processing of multiple http responses received at around 9415 * the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in 9416 * significant performance improvement for bigger applications that make many HTTP requests 9417 * concurrently (common during application bootstrap). 9418 * 9419 * Defaults to false. If no value is specified, returns the current configured value. 9420 * 9421 * @param {boolean=} value If true, when requests are loaded, they will schedule a deferred 9422 * "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window 9423 * to load and share the same digest cycle. 9424 * 9425 * @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining. 9426 * otherwise, returns the current configured value. 9427 **/ 9428 this.useApplyAsync = function(value) { 9429 if (isDefined(value)) { 9430 useApplyAsync = !!value; 9431 return this; 9432 } 9433 return useApplyAsync; 9434 }; 9435 9436 /** 9437 * @ngdoc property 9438 * @name $httpProvider#interceptors 9439 * @description 9440 * 9441 * Array containing service factories for all synchronous or asynchronous {@link ng.$http $http} 9442 * pre-processing of request or postprocessing of responses. 9443 * 9444 * These service factories are ordered by request, i.e. they are applied in the same order as the 9445 * array, on request, but reverse order, on response. 9446 * 9447 * {@link ng.$http#interceptors Interceptors detailed info} 9448 **/ 9449 var interceptorFactories = this.interceptors = []; 9450 9451 this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', 9452 function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) { 9453 9454 var defaultCache = $cacheFactory('$http'); 9455 9456 /** 9457 * Make sure that default param serializer is exposed as a function 9458 */ 9459 defaults.paramSerializer = isString(defaults.paramSerializer) ? 9460 $injector.get(defaults.paramSerializer) : defaults.paramSerializer; 9461 9462 /** 9463 * Interceptors stored in reverse order. Inner interceptors before outer interceptors. 9464 * The reversal is needed so that we can build up the interception chain around the 9465 * server request. 9466 */ 9467 var reversedInterceptors = []; 9468 9469 forEach(interceptorFactories, function(interceptorFactory) { 9470 reversedInterceptors.unshift(isString(interceptorFactory) 9471 ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory)); 9472 }); 9473 9474 /** 9475 * @ngdoc service 9476 * @kind function 9477 * @name $http 9478 * @requires ng.$httpBackend 9479 * @requires $cacheFactory 9480 * @requires $rootScope 9481 * @requires $q 9482 * @requires $injector 9483 * 9484 * @description 9485 * The `$http` service is a core Angular service that facilitates communication with the remote 9486 * HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest) 9487 * object or via [JSONP](http://en.wikipedia.org/wiki/JSONP). 9488 * 9489 * For unit testing applications that use `$http` service, see 9490 * {@link ngMock.$httpBackend $httpBackend mock}. 9491 * 9492 * For a higher level of abstraction, please check out the {@link ngResource.$resource 9493 * $resource} service. 9494 * 9495 * The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by 9496 * the $q service. While for simple usage patterns this doesn't matter much, for advanced usage 9497 * it is important to familiarize yourself with these APIs and the guarantees they provide. 9498 * 9499 * 9500 * ## General usage 9501 * The `$http` service is a function which takes a single argument â a configuration object â 9502 * that is used to generate an HTTP request and returns a {@link ng.$q promise} 9503 * with two $http specific methods: `success` and `error`. 9504 * 9505 * ```js 9506 * // Simple GET request example : 9507 * $http.get('/someUrl'). 9508 * success(function(data, status, headers, config) { 9509 * // this callback will be called asynchronously 9510 * // when the response is available 9511 * }). 9512 * error(function(data, status, headers, config) { 9513 * // called asynchronously if an error occurs 9514 * // or server returns response with an error status. 9515 * }); 9516 * ``` 9517 * 9518 * ```js 9519 * // Simple POST request example (passing data) : 9520 * $http.post('/someUrl', {msg:'hello word!'}). 9521 * success(function(data, status, headers, config) { 9522 * // this callback will be called asynchronously 9523 * // when the response is available 9524 * }). 9525 * error(function(data, status, headers, config) { 9526 * // called asynchronously if an error occurs 9527 * // or server returns response with an error status. 9528 * }); 9529 * ``` 9530 * 9531 * 9532 * Since the returned value of calling the $http function is a `promise`, you can also use 9533 * the `then` method to register callbacks, and these callbacks will receive a single argument â 9534 * an object representing the response. See the API signature and type info below for more 9535 * details. 9536 * 9537 * A response status code between 200 and 299 is considered a success status and 9538 * will result in the success callback being called. Note that if the response is a redirect, 9539 * XMLHttpRequest will transparently follow it, meaning that the error callback will not be 9540 * called for such responses. 9541 * 9542 * ## Writing Unit Tests that use $http 9543 * When unit testing (using {@link ngMock ngMock}), it is necessary to call 9544 * {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending 9545 * request using trained responses. 9546 * 9547 * ``` 9548 * $httpBackend.expectGET(...); 9549 * $http.get(...); 9550 * $httpBackend.flush(); 9551 * ``` 9552 * 9553 * ## Shortcut methods 9554 * 9555 * Shortcut methods are also available. All shortcut methods require passing in the URL, and 9556 * request data must be passed in for POST/PUT requests. 9557 * 9558 * ```js 9559 * $http.get('/someUrl').success(successCallback); 9560 * $http.post('/someUrl', data).success(successCallback); 9561 * ``` 9562 * 9563 * Complete list of shortcut methods: 9564 * 9565 * - {@link ng.$http#get $http.get} 9566 * - {@link ng.$http#head $http.head} 9567 * - {@link ng.$http#post $http.post} 9568 * - {@link ng.$http#put $http.put} 9569 * - {@link ng.$http#delete $http.delete} 9570 * - {@link ng.$http#jsonp $http.jsonp} 9571 * - {@link ng.$http#patch $http.patch} 9572 * 9573 * 9574 * ## Setting HTTP Headers 9575 * 9576 * The $http service will automatically add certain HTTP headers to all requests. These defaults 9577 * can be fully configured by accessing the `$httpProvider.defaults.headers` configuration 9578 * object, which currently contains this default configuration: 9579 * 9580 * - `$httpProvider.defaults.headers.common` (headers that are common for all requests): 9581 * - `Accept: application/json, text/plain, * / *` 9582 * - `$httpProvider.defaults.headers.post`: (header defaults for POST requests) 9583 * - `Content-Type: application/json` 9584 * - `$httpProvider.defaults.headers.put` (header defaults for PUT requests) 9585 * - `Content-Type: application/json` 9586 *
9587 * To add or overwrite these defaults, simply add or remove a property from these configuration 9588 * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object 9589 * with the lowercased HTTP method name as the key, e.g. 9590 * `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }`. 9591 * 9592 * The defaults can also be set at runtime via the `$http.defaults` object in the same 9593 * fashion. For example: 9594 * 9595 * ``` 9596 * module.run(function($http) { 9597 * $http.defaults.headers.common.Authorization = 'Basic YmVlcDpib29w' 9598 * }); 9599 * ``` 9600 * 9601 * In addition, you can supply a `headers` property in the config object passed when 9602 * calling `$http(config)`, which overrides the defaults without changing them globally. 9603 * 9604 * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, 9605 * Use the `headers` property, setting the desired header to `undefined`. For example: 9606 * 9607 * ```js 9608 * var req = { 9609 * method: 'POST', 9610 * url: 'http://example.com', 9611 * headers: { 9612 * 'Content-Type': undefined 9613 * }, 9614 * data: { test: 'test' } 9615 * } 9616 * 9617 * $http(req).success(function(){...}).error(function(){...}); 9618 * ``` 9619 * 9620 * ## Transforming Requests and Responses 9621 * 9622 * Both requests and responses can be transformed using transformation functions: `transformRequest` 9623 * and `transformResponse`. These properties can be a single function that returns 9624 * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions, 9625 * which allows you to `push` or `unshift` a new transformation function into the transformation chain. 9626 * 9627 * ### Default Transformations 9628 * 9629 * The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and 9630 * `defaults.transformResponse` properties. If a request does not provide its own transformations 9631 * then these will be applied. 9632 * 9633 * You can augment or replace the default transformations by modifying these properties by adding to or 9634 * replacing the array. 9635 * 9636 * Angular provides the following default transformations: 9637 * 9638 * Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`): 9639 * 9640 * - If the `data` property of the request configuration object contains an object, serialize it 9641 * into JSON format. 9642 * 9643 * Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`): 9644 * 9645 * - If XSRF prefix is detected, strip it (see Security Considerations section below). 9646 * - If JSON response is detected, deserialize it using a JSON parser. 9647 * 9648 * 9649 * ### Overriding the Default Transformations Per Request 9650 * 9651 * If you wish override the request/response transformations only for a single request then provide 9652 * `transformRequest` and/or `transformResponse` properties on the configuration object passed 9653 * into `$http`. 9654 * 9655 * Note that if you provide these properties on the config object the default transformations will be 9656 * overwritten. If you wish to augment the default transformations then you must include them in your 9657 * local transformation array. 9658 * 9659 * The following code demonstrates adding a new response transformation to be run after the default response 9660 * transformations have been run. 9661 * 9662 * ```js 9663 * function appendTransform(defaults, transform) { 9664 * 9665 * // We can't guarantee that the default transformation is an array 9666 * defaults = angular.isArray(defaults) ? defaults : [defaults]; 9667 * 9668 * // Append the new transformation to the defaults 9669 * return defaults.concat(transform); 9670 * } 9671 * 9672 * $http({ 9673 * url: '...', 9674 * method: 'GET', 9675 * transformResponse: appendTransform($http.defaults.transformResponse, function(value) { 9676 * return doTransform(value); 9677 * }) 9678 * }); 9679 * ``` 9680 * 9681 * 9682 * ## Caching 9683 *
9684 * To enable caching, set the request configuration `cache` property to `true` (to use default 9685 * cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}). 9686 * When the cache is enabled, `$http` stores the response from the server in the specified 9687 * cache. The next time the same request is made, the response is served from the cache without 9688 * sending a request to the server. 9689 * 9690 * Note that even if the response is served from cache, delivery of the data is asynchronous in 9691 * the same way that real requests are. 9692 * 9693 * If there are multiple GET requests for the same URL that should be cached using the same 9694 * cache, but the cache is not populated yet, only one request to the server will be made and 9695 * the remaining requests will be fulfilled using the response from the first request. 9696 * 9697 * You can change the default cache to a new object (built with 9698 * {@link ng.$cacheFactory `$cacheFactory`}) by updating the 9699 * {@link ng.$http#defaults `$http.defaults.cache`} property. All requests who set 9700 * their `cache` property to `true` will now use this cache object. 9701 * 9702 * If you set the default cache to `false` then only requests that specify their own custom 9703 * cache object will be cached. 9704 * 9705 * ## Interceptors 9706 * 9707 * Before you start creating interceptors, be sure to understand the 9708 * {@link ng.$q $q and deferred/promise APIs}. 9709 * 9710 * For purposes of global error handling, authentication, or any kind of synchronous or 9711 * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be 9712 * able to intercept requests before they are handed to the server and 9713 * responses before they are handed over to the application code that 9714 * initiated these requests. The interceptors leverage the {@link ng.$q 9715 * promise APIs} to fulfill this need for both synchronous and asynchronous pre-processing. 9716 * 9717 * The interceptors are service factories that are registered with the `$httpProvider` by 9718 * adding them to the `$httpProvider.interceptors` array. The factory is called and 9719 * injected with dependencies (if specified) and returns the interceptor. 9720 * 9721 * There are two kinds of interceptors (and two kinds of rejection interceptors): 9722 * 9723 * * `request`: interceptors get called with a http `config` object. The function is free to 9724 * modify the `config` object or create a new one. The function needs to return the `config` 9725 * object directly, or a promise containing the `config` or a new `config` object. 9726 * * `requestError`: interceptor gets called when a previous interceptor threw an error or 9727 * resolved with a rejection. 9728 * * `response`: interceptors get called with http `response` object. The function is free to 9729 * modify the `response` object or create a new one. The function needs to return the `response` 9730 * object directly, or as a promise containing the `response` or a new `response` object. 9731 * * `responseError`: interceptor gets called when a previous interceptor threw an error or 9732 * resolved with a rejection. 9733 * 9734 * 9735 * ```js 9736 * // register the interceptor as a service 9737 * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) { 9738 * return { 9739 * // optional method 9740 * 'request': function(config) { 9741 * // do something on success 9742 * return config; 9743 * }, 9744 * 9745 * // optional method 9746 * 'requestError': function(rejection) { 9747 * // do something on error 9748 * if (canRecover(rejection)) { 9749 * return responseOrNewPromise 9750 * } 9751 * return $q.reject(rejection); 9752 * }, 9753 * 9754 * 9755 * 9756 * // optional method 9757 * 'response': function(response) { 9758 * // do something on success 9759 * return response; 9760 * }, 9761 * 9762 * // optional method 9763 * 'responseError': function(rejection) {
9764 * // do something on error 9765 * if (canRecover(rejection)) { 9766 * return responseOrNewPromise 9767 * } 9768 * return $q.reject(rejection); 9769 * } 9770 * }; 9771 * }); 9772 * 9773 * $httpProvider.interceptors.push('myHttpInterceptor'); 9774 * 9775 * 9776 * // alternatively, register the interceptor via an anonymous factory 9777 * $httpProvider.interceptors.push(function($q, dependency1, dependency2) { 9778 * return { 9779 * 'request': function(config) { 9780 * // same as above 9781 * }, 9782 * 9783 * 'response': function(response) { 9784 * // same as above 9785 * } 9786 * }; 9787 * }); 9788 * ``` 9789 * 9790 * ## Security Considerations 9791 * 9792 * When designing web applications, consider security threats from: 9793 * 9794 * - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 9795 * - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) 9796 * 9797 * Both server and the client must cooperate in order to eliminate these threats. Angular comes 9798 * pre-configured with strategies that address these issues, but for this to work backend server 9799 * cooperation is required. 9800 * 9801 * ### JSON Vulnerability Protection 9802 * 9803 * A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx) 9804 * allows third party website to turn your JSON resource URL into 9805 * [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To 9806 * counter this your server can prefix all JSON requests with following string `")]}',\n"`. 9807 * Angular will automatically strip the prefix before processing it as JSON. 9808 * 9809 * For example if your server needs to return: 9810 * ```js 9811 * ['one','two'] 9812 * ``` 9813 * 9814 * which is vulnerable to attack, your server can return: 9815 * ```js 9816 * )]}', 9817 * ['one','two'] 9818 * ``` 9819 * 9820 * Angular will strip the prefix, before processing the JSON. 9821 * 9822 * 9823 * ### Cross Site Request Forgery (XSRF) Protection 9824 * 9825 * [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is a technique by which 9826 * an unauthorized site can gain your user's private data. Angular provides a mechanism 9827 * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie 9828 * (by default, `XSRF-TOKEN`) and sets it as an HTTP header (`X-XSRF-TOKEN`). Since only 9829 * JavaScript that runs on your domain could read the cookie, your server can be assured that 9830 * the XHR came from JavaScript running on your domain. The header will not be set for 9831 * cross-domain requests. 9832 * 9833 * To take advantage of this, your server needs to set a token in a JavaScript readable session 9834 * cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR requests the 9835 * server can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure 9836 * that only JavaScript running on your domain could have sent the request. The token must be 9837 * unique for each user and must be verifiable by the server (to prevent the JavaScript from 9838 * making up its own tokens). We recommend that the token is a digest of your site's 9839 * authentication cookie with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) 9840 * for added security. 9841 * 9842 * The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName 9843 * properties of either $httpProvider.defaults at config-time, $http.defaults at run-time, 9844 * or the per-request config object. 9845 * 9846 * In order to prevent collisions in environments where multiple Angular apps share the 9847 * same domain or subdomain, we recommend that each application uses unique cookie name. 9848 * 9849 * 9850 * @param {object} config Object describing the request to be made and how it should be 9851 * processed. The object has following properties: 9852 * 9853 * - **method** â `{string}` â HTTP method (e.g. 'GET', 'POST', etc) 9854 * - **url** â `{string}` â Absolute or relative URL of the resource that is being requested. 9855 * - **params** â `{Object.<string|Object>}` â Map of strings or objects which will be serialized 9856 * with the `paramSerializer` and appended as GET parameters. 9857 * - **data** â `{string|Object}` â Data to be sent as the request message data. 9858 * - **headers** â `{Object}` â Map of strings or functions which return strings representing 9859 * HTTP headers to send to the server. If the return value of a function is null, the 9860 * header will not be sent. Functions accept a config object as an argument. 9861 * - **xsrfHeaderName** â `{string}` â Name of HTTP header to populate with the XSRF token. 9862 * - **xsrfCookieName** â `{string}` â Name of cookie containing the XSRF token. 9863 * - **transformRequest** â 9864 * `{function(data, headersGetter)|Array.<function(data, headersGetter)>
9864}` â 9865 * transform function or an array of such functions. The transform function takes the http 9866 * request body and headers and returns its transformed (typically serialized) version. 9867 * See {@link ng.$http#overriding-the-default-transformations-per-request 9868 * Overriding the Default Transformations} 9869 * - **transformResponse** â 9870 * `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}` â 9871 * transform function or an array of such functions. The transform function takes the http 9872 * response body, headers and status and returns its transformed (typically deserialized) version. 9873 * See {@link ng.$http#overriding-the-default-transformations-per-request 9874 * Overriding the Default TransformationjqLiks} 9875 * - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to 9876 * prepare the string representation of request parameters (specified as an object). 9877 * If specified as string, it is interpreted as function registered with the 9878 * {@link $injector $injector}, which means you can create your own serializer 9879 * by registering it as a {@link auto.$provide#service service}. 9880 * The default serializer is the {@link $httpParamSerializer $httpParamSerializer}; 9881 * alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike} 9882 * - **cache** â `{boolean|Cache}` â If true, a default $http cache will be used to cache the 9883 * GET request, otherwise if a cache instance built with 9884 * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 9885 * caching. 9886 * - **timeout** â `{number|Promise}` â timeout in milliseconds, or {@link ng.$q promise} 9887 * that should abort the request when resolved. 9888 * - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the 9889 * XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials) 9890 * for more information. 9891 * - **responseType** - `{string}` - see 9892 * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). 9893 * 9894 * @returns {HttpPromise} Returns a {@link ng.$q promise} object with the 9895 * standard `then` method and two http specific methods: `success` and `error`. The `then` 9896 * method takes two arguments a success and an error callback which will be called with a 9897 * response object. The `success` and `error` methods take a single argument - a function that 9898 * will be called when the request succeeds or fails respectively. The arguments passed into 9899 * these functions are destructured representation of the response object passed into the 9900 * `then` method. The response object has these properties: 9901 * 9902 * - **data** â `{string|Object}` â The response body transformed with the transform 9903 * functions. 9904 * - **status** â `{number}` â HTTP status code of the response. 9905 * - **headers** â `{function([headerName])}` â Header getter function. 9906 * - **config** â `{Object}` â The configuration object that was used to generate the request. 9907 * - **statusText** â `{string}` â HTTP status text of the response. 9908 * 9909 * @property {Array.<Object>} pendingRequests Array of config objects for currently pending 9910 * requests. This is primarily meant to be used for debugging purposes. 9911 * 9912 * 9913 * @example 9914<example module="httpExample"> 9915<file name="index.html"> 9916 <div ng-controller="FetchController"> 9917 <select ng-model="method" aria-label="Request method"> 9918 <option>GET</option> 9919 <option>JSONP</option> 9920 </select> 9921 <input type="text" ng-model="url" size="80" aria-label="URL" /> 9922 <button id="fetchbtn" ng-click="fetch()">fetch</button><br> 9923 <button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button> 9924 <button id="samplejsonpbtn" 9925 ng-click="updateModel('JSONP', 9926 'https://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')"> 9927 Sample JSONP 9928 </button> 9929 <button id="invalidjsonpbtn" 9930 ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist&callback=JSON_CALLBACK')"> 9931 Invalid JSONP 9932 </button> 9933 <pre>http status code: {{status}}</pre> 9934 <pre>http response data: {{data}}</pre> 9935 </div> 9936</file> 9937<file name="script.js"> 9938 angular.module('httpExample', []) 9939 .controller('FetchController', ['$scope', '$http', '$templateCache', 9940 function($scope, $http, $templateCache) { 9941 $scope.method = 'GET'; 9942 $scope.url = 'http-hello.html'; 9943 9944 $scope.fetch = function() { 9945 $scope.code = null; 9946 $scope.response = null; 9947 9948 $http({method: $scope.method, url: $scope.url, cache: $templateCache}). 9949 success(function(data, status) { 9950 $scope.status = status; 9951 $scope.data = data; 9952 }). 9953 error(function(data, status) { 9954 $scope.data = data || "Request failed"; 9955 $scope.status = status; 9956 }); 9957 }; 9958 9959 $scope.updateModel = function(method, url) { 9960 $scope.method = method; 9961 $scope.url = url; 9962 }; 9963 }]); 9964</file> 9965<file name="http-hello.html"> 9966 Hello, $http! 9967</file> 9968<file name="protractor.js" type="protractor"> 9969 var status = element(by.binding('status')); 9970 var data = element(by.binding('data')); 9971 var fetchBtn = element(by.id('fetchbtn')); 9972 var sampleGetBtn = element(by.id('samplegetbtn')); 9973 var sampleJsonpBtn = element(by.id('samplejsonpbtn')); 9974 var invalidJsonpBtn = element(by.id('invalidjsonpbtn')); 9975 9976 it('should make an xhr GET request', function() { 9977 sampleGetBtn.click(); 9978 fetchBtn.click(); 9979 expect(status.getText()).toMatch('200'); 9980 expect(data.getText()).toMatch(/Hello, \$http!/); 9981 }); 9982 9983// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185 9984// it('should make a JSONP request to angularjs.org', function() { 9985// sampleJsonpBtn.click(); 9986// fetchBtn.click(); 9987// expect(status.getText()).toMatch('200'); 9988// expect(data.getText()).toMatch(/Super Hero!/); 9989// }); 9990
9991 it('should make JSONP request to invalid URL and invoke the error handler', 9992 function() { 9993 invalidJsonpBtn.click(); 9994 fetchBtn.click(); 9995 expect(status.getText()).toMatch('0'); 9996 expect(data.getText()).toMatch('Request failed'); 9997 }); 9998</file> 9999</example> 10000 */ 10001 function $http(requestConfig) { 10002 10003 if (!angular.isObject(requestConfig)) { 10004 throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig); 10005 } 10006 10007 var config = extend({ 10008 method: 'get', 10009 transformRequest: defaults.transformRequest, 10010 transformResponse: defaults.transformResponse, 10011 paramSerializer: defaults.paramSerializer 10012 }, requestConfig); 10013 10014 config.headers = mergeHeaders(requestConfig); 10015 config.method = uppercase(config.method); 10016 config.paramSerializer = isString(config.paramSerializer) ? 10017 $injector.get(config.paramSerializer) : config.paramSerializer; 10018 10019 var serverRequest = function(config) { 10020 var headers = config.headers; 10021 var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest); 10022 10023 // strip content-type if data is undefined 10024 if (isUndefined(reqData)) { 10025 forEach(headers, function(value, header) { 10026 if (lowercase(header) === 'content-type') { 10027 delete headers[header]; 10028 } 10029 }); 10030 } 10031 10032 if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) { 10033 config.withCredentials = defaults.withCredentials; 10034 } 10035 10036 // send request 10037 return sendReq(config, reqData).then(transformResponse, transformResponse); 10038 }; 10039 10040 var chain = [serverRequest, undefined]; 10041 var promise = $q.when(config); 10042 10043 // apply interceptors 10044 forEach(reversedInterceptors, function(interceptor) { 10045 if (interceptor.request || interceptor.requestError) { 10046 chain.unshift(interceptor.request, interceptor.requestError); 10047 } 10048 if (interceptor.response || interceptor.responseError) { 10049 chain.push(interceptor.response, interceptor.responseError); 10050 } 10051 }); 10052 10053 while (chain.length) { 10054 var thenFn = chain.shift(); 10055 var rejectFn = chain.shift(); 10056 10057 promise = promise.then(thenFn, rejectFn); 10058 } 10059 10060 promise.success = function(fn) { 10061 assertArgFn(fn, 'fn'); 10062 10063 promise.then(function(response) { 10064 fn(response.data, response.status, response.headers, config); 10065 }); 10066 return promise; 10067 }; 10068 10069 promise.error = function(fn) { 10070 assertArgFn(fn, 'fn'); 10071 10072 promise.then(null, function(response) { 10073 fn(response.data, response.status, response.headers, config); 10074 }); 10075 return promise; 10076 }; 10077 10078 return promise; 10079 10080 function transformResponse(response) { 10081 // make a copy since the response must be cacheable 10082 var resp = extend({}, response); 10083 if (!response.data) { 10084 resp.data = response.data; 10085 } else { 10086 resp.data = transformData(response.data, response.headers, response.status, config.transformResponse); 10087 } 10088 return (isSuccess(response.status)) 10089 ? resp 10090 : $q.reject(resp); 10091 } 10092 10093 function executeHeaderFns(headers, config) { 10094 var headerContent, processedHeaders = {}; 10095 10096 forEach(headers, function(headerFn, header) { 10097 if (isFunction(headerFn)) { 10098 headerContent = headerFn(config); 10099 if (headerContent != null) { 10100 processedHeaders[header] = headerContent; 10101 } 10102 } else { 10103 processedHeaders[header] = headerFn; 10104 } 10105 }); 10106 10107 return processedHeaders; 10108 } 10109 10110 function mergeHeaders(config) { 10111 var defHeaders = defaults.headers, 10112 reqHeaders = extend({}, config.headers), 10113 defHeaderName, lowercaseDefHeaderName, reqHeaderName; 10114 10115 defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]); 10116
vendor: 4,228 bytes, lines 10117-10256
10117 // using for-in instead of forEach to avoid unecessary iteration after header has been found 10118 defaultHeadersIteration: 10119 for (defHeaderName in defHeaders) { 10120 lowercaseDefHeaderName = lowercase(defHeaderName); 10121 10122 for (reqHeaderName in reqHeaders) { 10123 if (lowercase(reqHeaderName) === lowercaseDefHeaderName) { 10124 continue defaultHeadersIteration; 10125 } 10126 } 10127 10128 reqHeaders[defHeaderName] = defHeaders[defHeaderName]; 10129 } 10130 10131 // execute if header value is a function for merged headers 10132 return executeHeaderFns(reqHeaders, shallowCopy(config)); 10133 } 10134 } 10135 10136 $http.pendingRequests = []; 10137 10138 /** 10139 * @ngdoc method 10140 * @name $http#get 10141 * 10142 * @description 10143 * Shortcut method to perform `GET` request. 10144 * 10145 * @param {string} url Relative or absolute URL specifying the destination of the request 10146 * @param {Object=} config Optional configuration object 10147 * @returns {HttpPromise} Future object 10148 */ 10149 10150 /** 10151 * @ngdoc method 10152 * @name $http#delete 10153 * 10154 * @description 10155 * Shortcut method to perform `DELETE` request. 10156 * 10157 * @param {string} url Relative or absolute URL specifying the destination of the request 10158 * @param {Object=} config Optional configuration object 10159 * @returns {HttpPromise} Future object 10160 */ 10161 10162 /** 10163 * @ngdoc method 10164 * @name $http#head 10165 * 10166 * @description 10167 * Shortcut method to perform `HEAD` request. 10168 * 10169 * @param {string} url Relative or absolute URL specifying the destination of the request 10170 * @param {Object=} config Optional configuration object 10171 * @returns {HttpPromise} Future object 10172 */ 10173 10174 /** 10175 * @ngdoc method 10176 * @name $http#jsonp 10177 * 10178 * @description 10179 * Shortcut method to perform `JSONP` request. 10180 * 10181 * @param {string} url Relative or absolute URL specifying the destination of the request. 10182 * The name of the callback should be the string `JSON_CALLBACK`. 10183 * @param {Object=} config Optional configuration object 10184 * @returns {HttpPromise} Future object 10185 */ 10186 createShortMethods('get', 'delete', 'head', 'jsonp'); 10187 10188 /** 10189 * @ngdoc method 10190 * @name $http#post 10191 * 10192 * @description 10193 * Shortcut method to perform `POST` request. 10194 * 10195 * @param {string} url Relative or absolute URL specifying the destination of the request 10196 * @param {*} data Request content 10197 * @param {Object=} config Optional configuration object 10198 * @returns {HttpPromise} Future object 10199 */ 10200 10201 /** 10202 * @ngdoc method 10203 * @name $http#put 10204 * 10205 * @description 10206 * Shortcut method to perform `PUT` request. 10207 * 10208 * @param {string} url Relative or absolute URL specifying the destination of the request 10209 * @param {*} data Request content 10210 * @param {Object=} config Optional configuration object 10211 * @returns {HttpPromise} Future object 10212 */ 10213 10214 /** 10215 * @ngdoc method 10216 * @name $http#patch 10217 * 10218 * @description 10219 * Shortcut method to perform `PATCH` request. 10220 * 10221 * @param {string} url Relative or absolute URL specifying the destination of the request 10222 * @param {*} data Request content 10223 * @param {Object=} config Optional configuration object 10224 * @returns {HttpPromise} Future object 10225 */ 10226 createShortMethodsWithData('post', 'put', 'patch'); 10227 10228 /** 10229 * @ngdoc property 10230 * @name $http#defaults 10231 * 10232 * @description 10233 * Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of 10234 * default headers, withCredentials as well as request and response transformations. 10235 * 10236 * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. 10237 */ 10238 $http.defaults = defaults; 10239 10240 10241 return $http; 10242 10243 10244 function createShortMethods(names) { 10245 forEach(arguments, function(name) { 10246 $http[name] = function(url, config) { 10247 return $http(extend({}, config || {}, { 10248 method: name, 10249 url: url 10250 })); 10251 }; 10252 }); 10253 } 10254 10255 10256 function createShortMethodsWithData(name) {
10257 forEach(arguments, function(name) { 10258 $http[name] = function(url, data, config) { 10259 return $http(extend({}, config || {}, { 10260 method: name, 10261 url: url, 10262 data: data 10263 })); 10264 }; 10265 }); 10266 } 10267 10268 10269 /** 10270 * Makes the request. 10271 * 10272 * !!! ACCESSES CLOSURE VARS: 10273 * $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests 10274 */ 10275 function sendReq(config, reqData) { 10276 var deferred = $q.defer(), 10277 promise = deferred.promise, 10278 cache, 10279 cachedResp, 10280 reqHeaders = config.headers, 10281 url = buildUrl(config.url, config.paramSerializer(config.params)); 10282 10283 $http.pendingRequests.push(config); 10284 promise.then(removePendingReq, removePendingReq); 10285 10286 10287 if ((config.cache || defaults.cache) && config.cache !== false && 10288 (config.method === 'GET' || config.method === 'JSONP')) { 10289 cache = isObject(config.cache) ? config.cache 10290 : isObject(defaults.cache) ? defaults.cache 10291 : defaultCache; 10292 } 10293 10294 if (cache) { 10295 cachedResp = cache.get(url); 10296 if (isDefined(cachedResp)) { 10297 if (isPromiseLike(cachedResp)) { 10298 // cached request has already been sent, but there is no response yet 10299 cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult); 10300 } else { 10301 // serving from cache 10302 if (isArray(cachedResp)) { 10303 resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]); 10304 } else { 10305 resolvePromise(cachedResp, 200, {}, 'OK'); 10306 } 10307 } 10308 } else { 10309 // put the promise for the non-transformed response into cache as a placeholder 10310 cache.put(url, promise); 10311 } 10312 } 10313 10314 10315 // if we won't have the response in cache, set the xsrf headers and 10316 // send the request to the backend 10317 if (isUndefined(cachedResp)) { 10318 var xsrfValue = urlIsSameOrigin(config.url) 10319 ? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName] 10320 : undefined; 10321 if (xsrfValue) { 10322 reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue; 10323 } 10324 10325 $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, 10326 config.withCredentials, config.responseType); 10327 } 10328 10329 return promise; 10330 10331 10332 /** 10333 * Callback registered to $httpBackend(): 10334 * - caches the response if desired 10335 * - resolves the raw $http promise 10336 * - calls $apply 10337 */ 10338 function done(status, response, headersString, statusText) { 10339 if (cache) { 10340 if (isSuccess(status)) { 10341 cache.put(url, [status, response, parseHeaders(headersString), statusText]); 10342 } else { 10343 // remove promise from the cache 10344 cache.remove(url); 10345 } 10346 } 10347 10348 function resolveHttpPromise() { 10349 resolvePromise(response, status, headersString, statusText); 10350 } 10351 10352 if (useApplyAsync) { 10353 $rootScope.$applyAsync(resolveHttpPromise); 10354 } else { 10355 resolveHttpPromise(); 10356 if (!$rootScope.$$phase) $rootScope.$apply(); 10357 } 10358 } 10359 10360 10361 /** 10362 * Resolves the raw $http promise. 10363 */ 10364 function resolvePromise(response, status, headers, statusText) { 10365 // normalize internal statuses to 0 10366 status = Math.max(status, 0); 10367 10368 (isSuccess(status) ? deferred.resolve : deferred.reject)({ 10369 data: response, 10370 status: status, 10371 headers: headersGetter(headers), 10372 config: config, 10373 statusText: statusText 10374 }); 10375 } 10376 10377 function resolvePromiseWithResult(result) { 10378 resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText); 10379 } 10380 10381 function removePendingReq() { 10382 var idx = $http.pendingRequests.indexOf(config); 10383 if (idx !== -1) $http.pendingRequests.splice(idx, 1); 10384 } 10385 } 10386 10387 10388 function buildUrl(url, serializedParams) { 10389 if (serializedParams.length > 0) { 10390 url += ((url.indexOf('?') == -1) ? '?' : '&') + serializedParams; 10391 } 10392 return url; 10393 } 10394 }]; 10395} 10396 10397function createXhr() { 10398 return new window.XMLHttpRequest(); 10399} 10400 10401/** 10402 * @ngdoc service 10403 * @name $httpBackend 10404 * @requires $window 10405 * @requires $document 10406 * 10407 * @description 10408 * HTTP backend used by the {@link ng.$http service} that delegates to 10409 * XMLHttpRequest object or JSONP and deals with browser incompatibilities. 10410 * 10411 * You should never need to use this service directly, instead use the higher-level abstractions: 10412 * {@link ng.$http $http} or {@link ngResource.$resource $resource}. 10413 * 10414 * During testing this implementation is swapped with {@link ngMock.$httpBackend mock 10415 * $httpBackend} which can be trained with responses. 10416 */ 10417function $HttpBackendProvider() { 10418 this.$get = ['$browser', '$window', '$document', function($browser, $window, $document) { 10419 return createHttpBackend($browser, createXhr, $browser.defer, $window.angular.callbacks, $document[0]); 10420 }]; 10421} 10422 10423function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) { 10424 // TODO(vojta): fix the signature 10425 return function(method, url, post, callback, headers, timeout, withCredentials, responseType) { 10426 $browser.$$incOutstandingRequestCount(); 10427 url = url || $browser.url(); 10428 10429 if (lowercase(method) == 'jsonp') { 10430 var callbackId = '_' + (callbacks.counter++).toString(36); 10431 callbacks[callbackId] = function(data) { 10432 callbacks[callbackId].data = data; 10433 callbacks[callbackId].called = true; 10434 }; 10435 10436 var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId), 10437 callbackId, function(status, text) {
10438 completeRequest(callback, status, callbacks[callbackId].data, "", text); 10439 callbacks[callbackId] = noop; 10440 }); 10441 } else { 10442 10443 var xhr = createXhr(); 10444 10445 xhr.open(method, url, true); 10446 forEach(headers, function(value, key) { 10447 if (isDefined(value)) { 10448 xhr.setRequestHeader(key, value); 10449 } 10450 }); 10451 10452 xhr.onload = function requestLoaded() { 10453 var statusText = xhr.statusText || ''; 10454 10455 // responseText is the old-school way of retrieving response (supported by IE8 & 9) 10456 // response/responseType properties were introduced in XHR Level2 spec (supported by IE10) 10457 var response = ('response' in xhr) ? xhr.response : xhr.responseText; 10458 10459 // normalize IE9 bug (http://bugs.jquery.com/ticket/1450) 10460 var status = xhr.status === 1223 ? 204 : xhr.status; 10461 10462 // fix status code when it is 0 (0 status is undocumented). 10463 // Occurs when accessing file resources or on Android 4.1 stock browser 10464 // while retrieving files from application cache. 10465 if (status === 0) { 10466 status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0; 10467 } 10468 10469 completeRequest(callback, 10470 status, 10471 response, 10472 xhr.getAllResponseHeaders(), 10473 statusText); 10474 }; 10475 10476 var requestError = function() { 10477 // The response is always empty 10478 // See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error 10479 completeRequest(callback, -1, null, null, ''); 10480 }; 10481 10482 xhr.onerror = requestError; 10483 xhr.onabort = requestError; 10484 10485 if (withCredentials) { 10486 xhr.withCredentials = true; 10487 } 10488 10489 if (responseType) { 10490 try { 10491 xhr.responseType = responseType; 10492 } catch (e) { 10493 // WebKit added support for the json responseType value on 09/03/2013 10494 // https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are 10495 // known to throw when setting the value "json" as the response type. Other older 10496 // browsers implementing the responseType 10497 // 10498 // The json response type can be ignored if not supported, because JSON payloads are 10499 // parsed on the client-side regardless. 10500 if (responseType !== 'json') { 10501 throw e; 10502 } 10503 } 10504 } 10505 10506 xhr.send(post); 10507 } 10508 10509 if (timeout > 0) { 10510 var timeoutId = $browserDefer(timeoutRequest, timeout); 10511 } else if (isPromiseLike(timeout)) { 10512 timeout.then(timeoutRequest); 10513 } 10514 10515 10516 function timeoutRequest() { 10517 jsonpDone && jsonpDone(); 10518 xhr && xhr.abort(); 10519 } 10520 10521 function completeRequest(callback, status, response, headersString, statusText) { 10522 // cancel timeout and subsequent timeout promise resolution 10523 if (timeoutId !== undefined) { 10524 $browserDefer.cancel(timeoutId); 10525 } 10526 jsonpDone = xhr = null; 10527 10528 callback(status, response, headersString, statusText); 10529 $browser.$$completeOutstandingRequest(noop); 10530 } 10531 }; 10532 10533 function jsonpReq(url, callbackId, done) { 10534 // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.: 10535 // - fetches local scripts via XHR and evals them 10536 // - adds and immediately removes script elements from the document 10537 var script = rawDocument.createElement('script'), callback = null; 10538 script.type = "text/javascript"; 10539 script.src = url; 10540 script.async = true; 10541 10542 callback = function(event) { 10543 removeEventListenerFn(script, "load", callback); 10544 removeEventListenerFn(script, "error", callback); 10545 rawDocument.body.removeChild(script); 10546 script = null; 10547 var status = -1; 10548 var text = "unknown"; 10549 10550 if (event) { 10551 if (event.type === "load" && !callbacks[callbackId].called) { 10552 event = { type: "error" }; 10553 } 10554 text = event.type; 10555 status = event.type === "error" ? 404 : 200; 10556 } 10557 10558 if (done) { 10559 done(status, text); 10560 } 10561 }; 10562 10563 addEventListenerFn(script, "load", callback); 10564 addEventListenerFn(script, "error", callback); 10565 rawDocument.body.appendChild(script); 10566 return callback; 10567 } 10568} 10569 10570var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate'); 10571$interpolateMinErr.throwNoconcat = function(text) { 10572 throw $interpolateMinErr('noconcat', 10573 "Error while interpolating: {0}\nStrict Contextual Escaping disallows " + 10574 "interpolations that concatenate multiple expressions when a trusted value is " + 10575 "required. See http://docs.angularjs.org/api/ng.$sce", text); 10576}; 10577 10578$interpolateMinErr.interr = function(text, err) { 10579 return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString()); 10580}; 10581 10582/** 10583 * @ngdoc provider 10584 * @name $interpolateProvider 10585 * 10586 * @description 10587 *
10588 * Used for configuring the interpolation markup. Defaults to `{{` and `}}`. 10589 * 10590 * @example 10591<example module="customInterpolationApp"> 10592<file name="index.html"> 10593<script> 10594 var customInterpolationApp = angular.module('customInterpolationApp', []); 10595 10596 customInterpolationApp.config(function($interpolateProvider) { 10597 $interpolateProvider.startSymbol('//'); 10598 $interpolateProvider.endSymbol('//'); 10599 }); 10600 10601 10602 customInterpolationApp.controller('DemoController', function() { 10603 this.label = "This binding is brought you by // interpolation symbols."; 10604 }); 10605</script> 10606<div ng-app="App" ng-controller="DemoController as demo"> 10607 //demo.label// 10608</div> 10609</file> 10610<file name="protractor.js" type="protractor"> 10611 it('should interpolate binding with custom symbols', function() { 10612 expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.'); 10613 }); 10614</file> 10615</example> 10616 */ 10617function $InterpolateProvider() { 10618 var startSymbol = '{{'; 10619 var endSymbol = '}}'; 10620 10621 /** 10622 * @ngdoc method 10623 * @name $interpolateProvider#startSymbol 10624 * @description 10625 * Symbol to denote start of expression in the interpolated string. Defaults to `{{`. 10626 * 10627 * @param {string=} value new value to set the starting symbol to. 10628 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 10629 */ 10630 this.startSymbol = function(value) { 10631 if (value) { 10632 startSymbol = value; 10633 return this; 10634 } else { 10635 return startSymbol; 10636 } 10637 }; 10638 10639 /** 10640 * @ngdoc method 10641 * @name $interpolateProvider#endSymbol 10642 * @description 10643 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 10644 * 10645 * @param {string=} value new value to set the ending symbol to. 10646 * @returns {string|self} Returns the symbol when used as getter and self if used as setter. 10647 */ 10648 this.endSymbol = function(value) { 10649 if (value) { 10650 endSymbol = value; 10651 return this; 10652 } else { 10653 return endSymbol; 10654 } 10655 }; 10656 10657 10658 this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) { 10659 var startSymbolLength = startSymbol.length, 10660 endSymbolLength = endSymbol.length, 10661 escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'), 10662 escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g'); 10663 10664 function escape(ch) { 10665 return '\\\\\\' + ch; 10666 } 10667 10668 function unescapeText(text) { 10669 return text.replace(escapedStartRegexp, startSymbol). 10670 replace(escapedEndRegexp, endSymbol); 10671 } 10672 10673 function stringify(value) { 10674 if (value == null) { // null || undefined 10675 return ''; 10676 } 10677 switch (typeof value) { 10678 case 'string': 10679 break; 10680 case 'number': 10681 value = '' + value; 10682 break; 10683 default: 10684 value = toJson(value); 10685 } 10686 10687 return value; 10688 } 10689 10690 /** 10691 * @ngdoc service 10692 * @name $interpolate 10693 * @kind function 10694 * 10695 * @requires $parse 10696 * @requires $sce 10697 * 10698 * @description 10699 * 10700 * Compiles a string with markup into an interpolation function. This service is used by the 10701 * HTML {@link ng.$compile $compile} service for data binding. See 10702 * {@link ng.$interpolateProvider $interpolateProvider} for configuring the 10703 * interpolation markup. 10704 * 10705 * 10706 * ```js 10707 * var $interpolate = ...; // injected 10708 * var exp = $interpolate('Hello {{name | uppercase}}!'); 10709 * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!'); 10710 * ``` 10711 * 10712 * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is 10713 * `true`, the interpolation function will return `undefined` unless all embedded expressions 10714 * evaluate to a value other than `undefined`. 10715 * 10716 * ```js 10717 * var $interpolate = ...; // injected 10718 * var context = {greeting: 'Hello', name: undefined }; 10719 * 10720 * // default "forgiving" mode 10721 * var exp = $interpolate('{{greeting}} {{name}}!'); 10722 * expect(exp(context)).toEqual('Hello !'); 10723 * 10724 * // "allOrNothing" mode 10725 * exp = $interpolate('{{greeting}} {{name}}!', false, null, true); 10726 * expect(exp(context)).toBeUndefined(); 10727 * context.name = 'Angular'; 10728 * expect(exp(context)).toEqual('Hello Angular!'); 10729 * ``` 10730 * 10731 * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior. 10732 * 10733 * ####Escaped Interpolation 10734 * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers 10735 * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash). 10736 * It will be rendered as a regular start/end marker, and will not be interpreted as an expression 10737 * or binding. 10738 * 10739 * This enables web-servers to prevent script injection attacks and defacing attacks, to some 10740 * degree, while also enabling code examples to work without relying on the 10741 * {@link ng.directive:ngNonBindable ngNonBindable} directive. 10742 * 10743 * **For security purposes, it is strongly encouraged that web servers escape user-supplied data, 10744 * replacing angle brackets (<, >) with &lt; and &gt; respectively, and replacing all 10745 * interpolation start/end markers with their escaped counterparts.** 10746 * 10747 * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered 10748 * output when the $interpolate service processes the text. So, for HTML elements interpolated 10749 * by {@link ng.$compile $compile}
10749, or otherwise interpolated with the `mustHaveExpression` parameter 10750 * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such, 10751 * this is typically useful only when user-data is used in rendering a template from the server, or 10752 * when otherwise untrusted data is used by a directive. 10753 * 10754 * <example> 10755 * <file name="index.html"> 10756 * <div ng-init="username='A user'"> 10757 * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\} 10758 * </p> 10759 * <p><strong>{{username}}</strong> attempts to inject code which will deface the 10760 * application, but fails to accomplish their task, because the server has correctly 10761 * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash) 10762 * characters.</p> 10763 * <p>Instead, the result of the attempted script injection is visible, and can be removed 10764 * from the database by an administrator.</p> 10765 * </div> 10766 * </file> 10767 * </example> 10768 * 10769 * @param {string} text The text with markup to interpolate. 10770 * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have 10771 * embedded expression in order to return an interpolation function. Strings with no 10772 * embedded expression will return null for the interpolation function. 10773 * @param {string=} trustedContext when provided, the returned function passes the interpolated 10774 * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult, 10775 * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that 10776 * provides Strict Contextual Escaping for details. 10777 * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined 10778 * unless all embedded expressions evaluate to a value other than `undefined`. 10779 * @returns {function(context)} an interpolation function which is used to compute the 10780 * interpolated string. The function has these parameters: 10781 * 10782 * - `context`: evaluation context for all expressions embedded in the interpolated text 10783 */ 10784 function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) { 10785 allOrNothing = !!allOrNothing; 10786 var startIndex, 10787 endIndex, 10788 index = 0, 10789 expressions = [], 10790 parseFns = [], 10791 textLength = text.length, 10792 exp, 10793 concat = [], 10794 expressionPositions = []; 10795 10796 while (index < textLength) { 10797 if (((startIndex = text.indexOf(startSymbol, index)) != -1) && 10798 ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) { 10799 if (index !== startIndex) { 10800 concat.push(unescapeText(text.substring(index, startIndex))); 10801 } 10802 exp = text.substring(startIndex + startSymbolLength, endIndex); 10803 expressions.push(exp); 10804 parseFns.push($parse(exp, parseStringifyInterceptor)); 10805 index = endIndex + endSymbolLength; 10806 expressionPositions.push(concat.length); 10807 concat.push(''); 10808 } else { 10809 // we did not find an interpolation, so we have to add the remainder to the separators array 10810 if (index !== textLength) { 10811 concat.push(unescapeText(text.substring(index))); 10812 } 10813 break; 10814 } 10815 } 10816 10817 // Concatenating expressions makes it hard to reason about whether some combination of 10818 // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a 10819 // single expression be used for iframe[src], object[src], etc., we ensure that the value 10820 // that's used is assigned or constructed by some JS code somewhere that is more testable or 10821 // make it obvious that you bound the value to some user controlled value. This helps reduce 10822 // the load when auditing for XSS issues. 10823 if (trustedContext && concat.length > 1) { 10824 $interpolateMinErr.throwNoconcat(text); 10825 } 10826 10827 if (!mustHaveExpression || expressions.length) { 10828 var compute = function(values) { 10829 for (var i = 0, ii = expressions.length; i < ii; i++) { 10830 if (allOrNothing && isUndefined(values[i])) return; 10831 concat[expressionPositions[i]] = values[i]; 10832 } 10833 return concat.join(''); 10834 }; 10835 10836 var getValue = function(value) { 10837 return trustedContext ? 10838 $sce.getTrusted(trustedContext, value) : 10839 $sce.valueOf(value); 10840 }; 10841 10842 return extend(function interpolationFn(context) { 10843 var i = 0; 10844 var ii = expressions.length; 10845 var values = new Array(ii); 10846 10847 try { 10848 for (; i < ii; i++) { 10849 values[i] = parseFns[i](context); 10850 } 10851 10852 return compute(values); 10853 } catch (err) { 10854 $exceptionHandler($interpolateMinErr.interr(text, err)); 10855 } 10856 10857 }, { 10858 // all of these properties are undocumented for now 10859 exp: text, //just for compatibility with regular watchers created via $watch 10860 expressions: expressions, 10861 $$watchDelegate: function(scope, listener) { 10862 var lastValue; 10863 return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) { 10864 var currValue = compute(values); 10865 if (isFunction(listener)) { 10866 listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope); 10867 }
10868 lastValue = currValue; 10869 }); 10870 } 10871 }); 10872 } 10873 10874 function parseStringifyInterceptor(value) { 10875 try { 10876 value = getValue(value); 10877 return allOrNothing && !isDefined(value) ? value : stringify(value); 10878 } catch (err) { 10879 $exceptionHandler($interpolateMinErr.interr(text, err)); 10880 } 10881 } 10882 } 10883 10884 10885 /** 10886 * @ngdoc method 10887 * @name $interpolate#startSymbol 10888 * @description 10889 * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`. 10890 * 10891 * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change 10892 * the symbol. 10893 * 10894 * @returns {string} start symbol. 10895 */ 10896 $interpolate.startSymbol = function() { 10897 return startSymbol; 10898 }; 10899 10900 10901 /** 10902 * @ngdoc method 10903 * @name $interpolate#endSymbol 10904 * @description 10905 * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`. 10906 * 10907 * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change 10908 * the symbol. 10909 * 10910 * @returns {string} end symbol. 10911 */ 10912 $interpolate.endSymbol = function() { 10913 return endSymbol; 10914 }; 10915 10916 return $interpolate; 10917 }]; 10918} 10919 10920function $IntervalProvider() { 10921 this.$get = ['$rootScope', '$window', '$q', '$$q', 10922 function($rootScope, $window, $q, $$q) { 10923 var intervals = {}; 10924 10925 10926 /** 10927 * @ngdoc service 10928 * @name $interval 10929 * 10930 * @description 10931 * Angular's wrapper for `window.setInterval`. The `fn` function is executed every `delay` 10932 * milliseconds. 10933 * 10934 * The return value of registering an interval function is a promise. This promise will be 10935 * notified upon each tick of the interval, and will be resolved after `count` iterations, or 10936 * run indefinitely if `count` is not defined. The value of the notification will be the 10937 * number of iterations that have run. 10938 * To cancel an interval, call `$interval.cancel(promise)`. 10939 * 10940 * In tests you can use {@link ngMock.$interval#flush `$interval.flush(millis)`} to 10941 * move forward by `millis` milliseconds and trigger any functions scheduled to run in that 10942 * time. 10943 * 10944 * <div class="alert alert-warning"> 10945 * **Note**: Intervals created by this service must be explicitly destroyed when you are finished 10946 * with them. In particular they are not automatically destroyed when a controller's scope or a 10947 * directive's element are destroyed. 10948 * You should take this into consideration and make sure to always cancel the interval at the 10949 * appropriate moment. See the example below for more details on how and when to do this. 10950 * </div> 10951 * 10952 * @param {function()} fn A function that should be called repeatedly. 10953 * @param {number} delay Number of milliseconds between each function call. 10954 * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat 10955 * indefinitely. 10956 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 10957 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 10958 * @param {...*=} Pass additional parameters to the executed function. 10959 * @returns {promise} A promise which will be notified on each iteration. 10960 * 10961 * @example 10962 * <example module="intervalExample"> 10963 * <file name="index.html"> 10964 * <script> 10965 * angular.module('intervalExample', []) 10966 * .controller('ExampleController', ['$scope', '$interval', 10967 * function($scope, $interval) { 10968 * $scope.format = 'M/d/yy h:mm:ss a'; 10969 * $scope.blood_1 = 100; 10970 * $scope.blood_2 = 120; 10971 * 10972 * var stop; 10973 * $scope.fight = function() { 10974 * // Don't start a new fight if we are already fighting 10975 * if ( angular.isDefined(stop) ) return; 10976 * 10977 * stop = $interval(function() { 10978 * if ($scope.blood_1 > 0 && $scope.blood_2 > 0) { 10979 * $scope.blood_1 = $scope.blood_1 - 3; 10980 * $scope.blood_2 = $scope.blood_2 - 4; 10981 * } else { 10982 * $scope.stopFight(); 10983 * } 10984 * }, 100); 10985 * }; 10986 * 10987 * $scope.stopFight = function() { 10988 * if (angular.isDefined(stop)) { 10989 * $interval.cancel(stop); 10990 * stop = undefined; 10991 * } 10992 * }; 10993 * 10994 * $scope.resetFight = function() { 10995 * $scope.blood_1 = 100; 10996 * $scope.blood_2 = 120; 10997 * }; 10998 * 10999 * $scope.$on('$destroy', function() { 11000 * // Make sure that the interval is destroyed too 11001 * $scope.stopFight(); 11002 * }); 11003 * }]) 11004 * // Register the 'myCurrentTime' directive factory method. 11005 * // We inject $interval and dateFilter service since the factory method is DI. 11006 * .directive('myCurrentTime', ['$interval', 'dateFilter', 11007 * function($interval, dateFilter) { 11008 * // return the directive link function. (compile function not needed) 11009 * return function(scope, element, attrs) { 11010 * var format, // date format 11011 * stopTime; // so that we can cancel the time updates 11012 * 11013 * // used to update the UI 11014 * function updateTime() { 11015 * element.text(dateFilter(new Date(), format)); 11016 * } 11017 * 11018 * // watch the expression, and update the UI on change. 11019 * scope.$watch(attrs.myCurrentTime, function(value) { 11020 * format = value; 11021 * updateTime(); 11022 * }); 11023 * 11024 * stopTime = $interval(updateTime, 1000); 11025 * 11026 * // listen on DOM destroy (removal) event, and cancel the next UI update 11027 * // to prevent updating time after the DOM element was removed. 11028 * element.on('$destroy', function() { 11029 * $interval.cancel(stopTime); 11030 * }); 11031 * } 11032 * }]); 11033 * </script> 11034 * 11035 * <div>
11036 * <div ng-controller="ExampleController"> 11037 * <label>Date format: <input ng-model="format"></label> <hr/> 11038 * Current time is: <span my-current-time="format"></span> 11039 * <hr/> 11040 * Blood 1 : <font color='red'>{{blood_1}}</font> 11041 * Blood 2 : <font color='red'>{{blood_2}}</font> 11042 * <button type="button" data-ng-click="fight()">Fight</button> 11043 * <button type="button" data-ng-click="stopFight()">StopFight</button> 11044 * <button type="button" data-ng-click="resetFight()">resetFight</button> 11045 * </div> 11046 * </div> 11047 * 11048 * </file> 11049 * </example> 11050 */ 11051 function interval(fn, delay, count, invokeApply) { 11052 var hasParams = arguments.length > 4, 11053 args = hasParams ? sliceArgs(arguments, 4) : [], 11054 setInterval = $window.setInterval, 11055 clearInterval = $window.clearInterval, 11056 iteration = 0, 11057 skipApply = (isDefined(invokeApply) && !invokeApply), 11058 deferred = (skipApply ? $$q : $q).defer(), 11059 promise = deferred.promise; 11060 11061 count = isDefined(count) ? count : 0; 11062 11063 promise.then(null, null, (!hasParams) ? fn : function() { 11064 fn.apply(null, args); 11065 }); 11066 11067 promise.$$intervalId = setInterval(function tick() { 11068 deferred.notify(iteration++); 11069 11070 if (count > 0 && iteration >= count) { 11071 deferred.resolve(iteration); 11072 clearInterval(promise.$$intervalId); 11073 delete intervals[promise.$$intervalId]; 11074 } 11075 11076 if (!skipApply) $rootScope.$apply(); 11077 11078 }, delay); 11079 11080 intervals[promise.$$intervalId] = deferred; 11081 11082 return promise; 11083 } 11084 11085 11086 /** 11087 * @ngdoc method 11088 * @name $interval#cancel 11089 * 11090 * @description 11091 * Cancels a task associated with the `promise`. 11092 * 11093 * @param {promise} promise returned by the `$interval` function. 11094 * @returns {boolean} Returns `true` if the task was successfully canceled. 11095 */ 11096 interval.cancel = function(promise) { 11097 if (promise && promise.$$intervalId in intervals) { 11098 intervals[promise.$$intervalId].reject('canceled'); 11099 $window.clearInterval(promise.$$intervalId); 11100 delete intervals[promise.$$intervalId]; 11101 return true; 11102 } 11103 return false; 11104 }; 11105 11106 return interval; 11107 }]; 11108} 11109 11110/** 11111 * @ngdoc service 11112 * @name $locale 11113 * 11114 * @description 11115 * $locale service provides localization rules for various Angular components. As of right now the 11116 * only public api is: 11117 * 11118 * * `id` â `{string}` â locale id formatted as `languageId-countryId` (e.g. `en-us`) 11119 */ 11120function $LocaleProvider() { 11121 this.$get = function() { 11122 return { 11123 id: 'en-us', 11124 11125 NUMBER_FORMATS: { 11126 DECIMAL_SEP: '.', 11127 GROUP_SEP: ',', 11128 PATTERNS: [ 11129 { // Decimal Pattern 11130 minInt: 1, 11131 minFrac: 0, 11132 maxFrac: 3, 11133 posPre: '', 11134 posSuf: '', 11135 negPre: '-', 11136 negSuf: '', 11137 gSize: 3, 11138 lgSize: 3 11139 },{ //Currency Pattern 11140 minInt: 1, 11141 minFrac: 2, 11142 maxFrac: 2, 11143 posPre: '\u00A4', 11144 posSuf: '', 11145 negPre: '(\u00A4', 11146 negSuf: ')', 11147 gSize: 3, 11148 lgSize: 3 11149 } 11150 ], 11151 CURRENCY_SYM: '$' 11152 }, 11153 11154 DATETIME_FORMATS: { 11155 MONTH: 11156 'January,February,March,April,May,June,July,August,September,October,November,December' 11157 .split(','), 11158 SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','), 11159 DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','), 11160 SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','), 11161 AMPMS: ['AM','PM'], 11162 medium: 'MMM d, y h:mm:ss a', 11163 'short': 'M/d/yy h:mm a', 11164 fullDate: 'EEEE, MMMM d, y', 11165 longDate: 'MMMM d, y', 11166 mediumDate: 'MMM d, y', 11167 shortDate: 'M/d/yy', 11168 mediumTime: 'h:mm:ss a', 11169 shortTime: 'h:mm a', 11170 ERANAMES: [ 11171 "Before Christ", 11172 "Anno Domini" 11173 ], 11174 ERAS: [ 11175 "BC", 11176 "AD" 11177 ] 11178 }, 11179 11180 pluralCat: function(num) { 11181 if (num === 1) { 11182 return 'one'; 11183 } 11184 return 'other'; 11185 } 11186 }; 11187 }; 11188} 11189 11190var PATH_MATCH = /^([^\?#]*)(\?([^#]*))?(#(.*))?$/, 11191 DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp': 21}; 11192var $locationMinErr = minErr('$location'); 11193 11194 11195/** 11196 * Encode path using encodeUriSegment, ignoring forward slashes 11197 * 11198 * @param {string} path Path to encode 11199 * @returns {string} 11200 */ 11201function encodePath(path) { 11202 var segments = path.split('/'), 11203 i = segments.length; 11204 11205 while (i--) { 11206 segments[i] = encodeUriSegment(segments[i]); 11207 } 11208 11209 return segments.join('/'); 11210} 11211 11212function parseAbsoluteUrl(absoluteUrl, locationObj) {
11213 var parsedUrl = urlResolve(absoluteUrl); 11214 11215 locationObj.$$protocol = parsedUrl.protocol; 11216 locationObj.$$host = parsedUrl.hostname; 11217 locationObj.$$port = toInt(parsedUrl.port) || DEFAULT_PORTS[parsedUrl.protocol] || null; 11218} 11219 11220 11221function parseAppUrl(relativeUrl, locationObj) { 11222 var prefixed = (relativeUrl.charAt(0) !== '/'); 11223 if (prefixed) { 11224 relativeUrl = '/' + relativeUrl; 11225 } 11226 var match = urlResolve(relativeUrl); 11227 locationObj.$$path = decodeURIComponent(prefixed && match.pathname.charAt(0) === '/' ? 11228 match.pathname.substring(1) : match.pathname); 11229 locationObj.$$search = parseKeyValue(match.search); 11230 locationObj.$$hash = decodeURIComponent(match.hash); 11231 11232 // make sure path starts with '/'; 11233 if (locationObj.$$path && locationObj.$$path.charAt(0) != '/') { 11234 locationObj.$$path = '/' + locationObj.$$path; 11235 } 11236} 11237 11238 11239/** 11240 * 11241 * @param {string} begin 11242 * @param {string} whole 11243 * @returns {string} returns text from whole after begin or undefined if it does not begin with 11244 * expected string. 11245 */ 11246function beginsWith(begin, whole) { 11247 if (whole.indexOf(begin) === 0) { 11248 return whole.substr(begin.length); 11249 } 11250} 11251 11252 11253function stripHash(url) { 11254 var index = url.indexOf('#'); 11255 return index == -1 ? url : url.substr(0, index); 11256} 11257 11258function trimEmptyHash(url) { 11259 return url.replace(/(#.+)|#$/, '$1'); 11260} 11261 11262 11263function stripFile(url) { 11264 return url.substr(0, stripHash(url).lastIndexOf('/') + 1); 11265} 11266 11267/* return the server only (scheme://host:port) */ 11268function serverBase(url) { 11269 return url.substring(0, url.indexOf('/', url.indexOf('//') + 2)); 11270} 11271 11272 11273/** 11274 * LocationHtml5Url represents an url 11275 * This object is exposed as $location service when HTML5 mode is enabled and supported 11276 * 11277 * @constructor 11278 * @param {string} appBase application base URL 11279 * @param {string} basePrefix url path prefix 11280 */ 11281function LocationHtml5Url(appBase, basePrefix) { 11282 this.$$html5 = true; 11283 basePrefix = basePrefix || ''; 11284 var appBaseNoFile = stripFile(appBase); 11285 parseAbsoluteUrl(appBase, this); 11286 11287 11288 /** 11289 * Parse given html5 (regular) url string into properties 11290 * @param {string} url HTML5 url 11291 * @private 11292 */ 11293 this.$$parse = function(url) { 11294 var pathUrl = beginsWith(appBaseNoFile, url); 11295 if (!isString(pathUrl)) { 11296 throw $locationMinErr('ipthprfx', 'Invalid url "{0}", missing path prefix "{1}".', url, 11297 appBaseNoFile); 11298 } 11299 11300 parseAppUrl(pathUrl, this); 11301 11302 if (!this.$$path) { 11303 this.$$path = '/'; 11304 } 11305 11306 this.$$compose(); 11307 }; 11308 11309 /** 11310 * Compose url and update `absUrl` property 11311 * @private 11312 */ 11313 this.$$compose = function() { 11314 var search = toKeyValue(this.$$search), 11315 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11316 11317 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11318 this.$$absUrl = appBaseNoFile + this.$$url.substr(1); // first char is always '/' 11319 }; 11320 11321 this.$$parseLinkUrl = function(url, relHref) { 11322 if (relHref && relHref[0] === '#') { 11323 // special case for links to hash fragments: 11324 // keep the old url and only replace the hash fragment 11325 this.hash(relHref.slice(1)); 11326 return true; 11327 } 11328 var appUrl, prevAppUrl; 11329 var rewrittenUrl; 11330 11331 if ((appUrl = beginsWith(appBase, url)) !== undefined) { 11332 prevAppUrl = appUrl; 11333 if ((appUrl = beginsWith(basePrefix, appUrl)) !== undefined) { 11334 rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl); 11335 } else { 11336 rewrittenUrl = appBase + prevAppUrl; 11337 } 11338 } else if ((appUrl = beginsWith(appBaseNoFile, url)) !== undefined) { 11339 rewrittenUrl = appBaseNoFile + appUrl; 11340 } else if (appBaseNoFile == url + '/') { 11341 rewrittenUrl = appBaseNoFile; 11342 } 11343 if (rewrittenUrl) { 11344 this.$$parse(rewrittenUrl); 11345 } 11346 return !!rewrittenUrl; 11347 }; 11348} 11349 11350 11351/** 11352 * LocationHashbangUrl represents url 11353 * This object is exposed as $location service when developer doesn't opt into html5 mode. 11354 * It also serves as the base class for html5 mode fallback on legacy browsers. 11355 * 11356 * @constructor 11357 * @param {string} appBase application base URL 11358 * @param {string} hashPrefix hashbang prefix 11359 */ 11360function LocationHashbangUrl(appBase, hashPrefix) { 11361 var appBaseNoFile = stripFile(appBase); 11362 11363 parseAbsoluteUrl(appBase, this); 11364 11365 11366 /** 11367 * Parse given hashbang url into properties 11368 * @param {string} url Hashbang url 11369 * @private 11370 */ 11371 this.$$parse = function(url) { 11372 var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url); 11373 var withoutHashUrl; 11374 11375 if (withoutBaseUrl.charAt(0) === '#') { 11376 11377 // The rest of the url starts with a hash so we have 11378 // got either a hashbang path or a plain hash fragment 11379 withoutHashUrl = beginsWith(hashPrefix, withoutBaseUrl); 11380 if (isUndefined(withoutHashUrl)) { 11381 // There was no hashbang prefix so we just have a hash fragment 11382 withoutHashUrl = withoutBaseUrl; 11383 } 11384 11385 } else { 11386 // There was no hashbang path nor hash fragment: 11387 // If we are in HTML5 mode we use what is left as the path; 11388 // Otherwise we ignore what is left 11389 withoutHashUrl = this.$$html5 ? withoutBaseUrl : ''; 11390 } 11391 11392 parseAppUrl(withoutHashUrl, this); 11393 11394 this.$$path = removeWindowsDriveName(this.$$path, withoutHashUrl, appBase); 11395 11396 this.$$compose(); 11397 11398 /* 11399 * In Windows, on an anchor node on documents loaded from 11400 * the filesystem, the browser will return a pathname
11401 * prefixed with the drive name ('/C:/path') when a 11402 * pathname without a drive is set: 11403 * * a.setAttribute('href', '/foo') 11404 * * a.pathname === '/C:/foo' //true 11405 * 11406 * Inside of Angular, we're always using pathnames that 11407 * do not include drive names for routing. 11408 */ 11409 function removeWindowsDriveName(path, url, base) { 11410 /* 11411 Matches paths for file protocol on windows, 11412 such as /C:/foo/bar, and captures only /foo/bar. 11413 */ 11414 var windowsFilePathExp = /^\/[A-Z]:(\/.*)/; 11415 11416 var firstPathSegmentMatch; 11417 11418 //Get the relative path from the input URL. 11419 if (url.indexOf(base) === 0) { 11420 url = url.replace(base, ''); 11421 } 11422 11423 // The input URL intentionally contains a first path segment that ends with a colon. 11424 if (windowsFilePathExp.exec(url)) { 11425 return path; 11426 } 11427 11428 firstPathSegmentMatch = windowsFilePathExp.exec(path); 11429 return firstPathSegmentMatch ? firstPathSegmentMatch[1] : path; 11430 } 11431 }; 11432 11433 /** 11434 * Compose hashbang url and update `absUrl` property 11435 * @private 11436 */ 11437 this.$$compose = function() { 11438 var search = toKeyValue(this.$$search), 11439 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11440 11441 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11442 this.$$absUrl = appBase + (this.$$url ? hashPrefix + this.$$url : ''); 11443 }; 11444 11445 this.$$parseLinkUrl = function(url, relHref) { 11446 if (stripHash(appBase) == stripHash(url)) { 11447 this.$$parse(url); 11448 return true; 11449 } 11450 return false; 11451 }; 11452} 11453 11454 11455/** 11456 * LocationHashbangUrl represents url 11457 * This object is exposed as $location service when html5 history api is enabled but the browser 11458 * does not support it. 11459 * 11460 * @constructor 11461 * @param {string} appBase application base URL 11462 * @param {string} hashPrefix hashbang prefix 11463 */ 11464function LocationHashbangInHtml5Url(appBase, hashPrefix) { 11465 this.$$html5 = true; 11466 LocationHashbangUrl.apply(this, arguments); 11467 11468 var appBaseNoFile = stripFile(appBase); 11469 11470 this.$$parseLinkUrl = function(url, relHref) { 11471 if (relHref && relHref[0] === '#') { 11472 // special case for links to hash fragments: 11473 // keep the old url and only replace the hash fragment 11474 this.hash(relHref.slice(1)); 11475 return true; 11476 } 11477 11478 var rewrittenUrl; 11479 var appUrl; 11480 11481 if (appBase == stripHash(url)) { 11482 rewrittenUrl = url; 11483 } else if ((appUrl = beginsWith(appBaseNoFile, url))) { 11484 rewrittenUrl = appBase + hashPrefix + appUrl; 11485 } else if (appBaseNoFile === url + '/') { 11486 rewrittenUrl = appBaseNoFile; 11487 } 11488 if (rewrittenUrl) { 11489 this.$$parse(rewrittenUrl); 11490 } 11491 return !!rewrittenUrl; 11492 }; 11493 11494 this.$$compose = function() { 11495 var search = toKeyValue(this.$$search), 11496 hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : ''; 11497 11498 this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash; 11499 // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#' 11500 this.$$absUrl = appBase + hashPrefix + this.$$url; 11501 }; 11502 11503} 11504 11505 11506var locationPrototype = { 11507 11508 /** 11509 * Are we in html5 mode? 11510 * @private 11511 */ 11512 $$html5: false, 11513 11514 /** 11515 * Has any change been replacing? 11516 * @private 11517 */ 11518 $$replace: false, 11519 11520 /** 11521 * @ngdoc method 11522 * @name $location#absUrl 11523 * 11524 * @description 11525 * This method is getter only. 11526 * 11527 * Return full url representation with all segments encoded according to rules specified in 11528 * [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). 11529 * 11530 * 11531 * ```js 11532 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11533 * var absUrl = $location.absUrl(); 11534 * // => "http://example.com/#/some/path?foo=bar&baz=xoxo" 11535 * ``` 11536 * 11537 * @return {string} full url 11538 */ 11539 absUrl: locationGetter('$$absUrl'), 11540 11541 /** 11542 * @ngdoc method 11543 * @name $location#url 11544 * 11545 * @description 11546 * This method is getter / setter. 11547 * 11548 * Return url (e.g. `/path?a=b#hash`) when called without any parameter. 11549 * 11550 * Change path, search and hash, when called with parameter and return `$location`. 11551 * 11552 * 11553 * ```js
11554 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11555 * var url = $location.url(); 11556 * // => "/some/path?foo=bar&baz=xoxo" 11557 * ``` 11558 * 11559 * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`) 11560 * @return {string} url 11561 */ 11562 url: function(url) { 11563 if (isUndefined(url)) { 11564 return this.$$url; 11565 } 11566 11567 var match = PATH_MATCH.exec(url); 11568 if (match[1] || url === '') this.path(decodeURIComponent(match[1])); 11569 if (match[2] || match[1] || url === '') this.search(match[3] || ''); 11570 this.hash(match[5] || ''); 11571 11572 return this; 11573 }, 11574 11575 /** 11576 * @ngdoc method 11577 * @name $location#protocol 11578 * 11579 * @description 11580 * This method is getter only. 11581 * 11582 * Return protocol of current url. 11583 * 11584 * 11585 * ```js 11586 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11587 * var protocol = $location.protocol(); 11588 * // => "http" 11589 * ``` 11590 * 11591 * @return {string} protocol of current url 11592 */ 11593 protocol: locationGetter('$$protocol'), 11594 11595 /** 11596 * @ngdoc method 11597 * @name $location#host 11598 * 11599 * @description 11600 * This method is getter only. 11601 * 11602 * Return host of current url. 11603 * 11604 * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only. 11605 * 11606 * 11607 * ```js 11608 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11609 * var host = $location.host(); 11610 * // => "example.com" 11611 * 11612 * // given url http://user:[email protected]:8080/#/some/path?foo=bar&baz=xoxo 11613 * host = $location.host(); 11614 * // => "example.com" 11615 * host = location.host; 11616 * // => "example.com:8080" 11617 * ``` 11618 * 11619 * @return {string} host of current url. 11620 */ 11621 host: locationGetter('$$host'), 11622 11623 /** 11624 * @ngdoc method 11625 * @name $location#port 11626 * 11627 * @description 11628 * This method is getter only. 11629 * 11630 * Return port of current url. 11631 * 11632 * 11633 * ```js 11634 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11635 * var port = $location.port(); 11636 * // => 80 11637 * ``` 11638 * 11639 * @return {Number} port 11640 */ 11641 port: locationGetter('$$port'), 11642 11643 /** 11644 * @ngdoc method 11645 * @name $location#path 11646 * 11647 * @description 11648 * This method is getter / setter. 11649 * 11650 * Return path of current url when called without any parameter. 11651 * 11652 * Change path when called with parameter and return `$location`. 11653 * 11654 * Note: Path should always begin with forward slash (/), this method will add the forward slash 11655 * if it is missing. 11656 * 11657 * 11658 * ```js 11659 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11660 * var path = $location.path(); 11661 * // => "/some/path" 11662 * ``` 11663 * 11664 * @param {(string|number)=} path New path 11665 * @return {string} path 11666 */ 11667 path: locationGetterSetter('$$path', function(path) { 11668 path = path !== null ? path.toString() : ''; 11669 return path.charAt(0) == '/' ? path : '/' + path; 11670 }), 11671 11672 /** 11673 * @ngdoc method 11674 * @name $location#search 11675 * 11676 * @description 11677 * This method is getter / setter. 11678 * 11679 * Return search part (as object) of current url when called without any parameter. 11680 * 11681 * Change search part when called with parameter and return `$location`. 11682 * 11683 * 11684 * ```js 11685 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo 11686 * var searchObject = $location.search(); 11687 * // => {foo: 'bar', baz: 'xoxo'} 11688 * 11689 * // set foo to 'yipee' 11690 * $location.search('foo', 'yipee'); 11691 * // $location.search() => {foo: 'yipee', baz: 'xoxo'} 11692 * ``` 11693 * 11694 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or 11695 * hash object. 11696 * 11697 * When called with a single argument the method acts as a setter, setting the `search` component 11698 * of `$location` to the specified value. 11699 * 11700 * If the argument is a hash object containing an array of values, these values will be encoded 11701 * as duplicate search parameters in the url. 11702 * 11703 * @param {(string|Number|Array<string>|boolean)=} paramValue If `search` is a string or number, then `paramValue` 11704 * will override only a single search property. 11705 * 11706 * If `paramValue` is an array, it will override the property of the `search` component of 11707 * `$location` specified via the first argument. 11708 * 11709 * If `paramValue` is `null`, the property specified via the first argument will be deleted. 11710 * 11711 * If `paramValue` is `true`, the property specified via the first argument will be added with
vendor: 18,948 bytes, lines 11711-12300
11711no 11712 * value nor trailing equal sign. 11713 * 11714 * @return {Object} If called with no arguments returns the parsed `search` object. If called with 11715 * one or more arguments returns `$location` object itself. 11716 */ 11717 search: function(search, paramValue) { 11718 switch (arguments.length) { 11719 case 0: 11720 return this.$$search; 11721 case 1: 11722 if (isString(search) || isNumber(search)) { 11723 search = search.toString(); 11724 this.$$search = parseKeyValue(search); 11725 } else if (isObject(search)) { 11726 search = copy(search, {}); 11727 // remove object undefined or null properties 11728 forEach(search, function(value, key) { 11729 if (value == null) delete search[key]; 11730 }); 11731 11732 this.$$search = search; 11733 } else { 11734 throw $locationMinErr('isrcharg', 11735 'The first argument of the `$location#search()` call must be a string or an object.'); 11736 } 11737 break; 11738 default: 11739 if (isUndefined(paramValue) || paramValue === null) { 11740 delete this.$$search[search]; 11741 } else { 11742 this.$$search[search] = paramValue; 11743 } 11744 } 11745 11746 this.$$compose(); 11747 return this; 11748 }, 11749 11750 /** 11751 * @ngdoc method 11752 * @name $location#hash 11753 * 11754 * @description 11755 * This method is getter / setter. 11756 * 11757 * Return hash fragment when called without any parameter. 11758 * 11759 * Change hash fragment when called with parameter and return `$location`. 11760 * 11761 * 11762 * ```js 11763 * // given url http://example.com/#/some/path?foo=bar&baz=xoxo#hashValue 11764 * var hash = $location.hash(); 11765 * // => "hashValue" 11766 * ``` 11767 * 11768 * @param {(string|number)=} hash New hash fragment 11769 * @return {string} hash 11770 */ 11771 hash: locationGetterSetter('$$hash', function(hash) { 11772 return hash !== null ? hash.toString() : ''; 11773 }), 11774 11775 /** 11776 * @ngdoc method 11777 * @name $location#replace 11778 * 11779 * @description 11780 * If called, all changes to $location during current `$digest` will be replacing current history 11781 * record, instead of adding new one. 11782 */ 11783 replace: function() { 11784 this.$$replace = true; 11785 return this; 11786 } 11787}; 11788 11789forEach([LocationHashbangInHtml5Url, LocationHashbangUrl, LocationHtml5Url], function(Location) { 11790 Location.prototype = Object.create(locationPrototype); 11791 11792 /** 11793 * @ngdoc method 11794 * @name $location#state 11795 * 11796 * @description 11797 * This method is getter / setter. 11798 * 11799 * Return the history state object when called without any parameter. 11800 * 11801 * Change the history state object when called with one parameter and return `$location`. 11802 * The state object is later passed to `pushState` or `replaceState`. 11803 * 11804 * NOTE: This method is supported only in HTML5 mode and only in browsers supporting 11805 * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support 11806 * older browsers (like IE9 or Android < 4.0), don't use this method. 11807 * 11808 * @param {object=} state State object for pushState or replaceState 11809 * @return {object} state 11810 */ 11811 Location.prototype.state = function(state) { 11812 if (!arguments.length) { 11813 return this.$$state; 11814 } 11815 11816 if (Location !== LocationHtml5Url || !this.$$html5) { 11817 throw $locationMinErr('nostate', 'History API state support is available only ' + 11818 'in HTML5 mode and only in browsers supporting HTML5 History API'); 11819 } 11820 // The user might modify `stateObject` after invoking `$location.state(stateObject)` 11821 // but we're changing the $$state reference to $browser.state() during the $digest 11822 // so the modification window is narrow. 11823 this.$$state = isUndefined(state) ? null : state; 11824 11825 return this; 11826 }; 11827}); 11828 11829 11830function locationGetter(property) { 11831 return function() { 11832 return this[property]; 11833 }; 11834} 11835 11836 11837function locationGetterSetter(property, preprocess) { 11838 return function(value) { 11839 if (isUndefined(value)) { 11840 return this[property]; 11841 } 11842 11843 this[property] = preprocess(value); 11844 this.$$compose(); 11845 11846 return this; 11847 }; 11848} 11849 11850 11851/** 11852 * @ngdoc service 11853 * @name $location 11854 * 11855 * @requires $rootElement 11856 * 11857 * @description 11858 * The $location service parses the URL in the browser address bar (based on the 11859 * [window.location](https://developer.mozilla.org/en/window.location)) and makes the URL 11860 * available to your application. Changes to the URL in the address bar are reflected into 11861 * $location service and changes to $location are reflected into the browser address bar. 11862 * 11863 * **The $location service:** 11864 * 11865 * - Exposes the current URL in the browser address bar, so you can 11866 * - Watch and observe the URL. 11867 * - Change the URL. 11868 * - Synchronizes the URL with the browser when the user 11869 * - Changes the address bar. 11870 * - Clicks the back or forward button (or clicks a History link). 11871 * - Clicks on a link. 11872 * - Represents the URL object as a set of methods (protocol, host, port, path, search, hash). 11873 * 11874 * For more information see {@link guide/$location Developer Guide: Using $location} 11875 */ 11876 11877/** 11878 * @ngdoc provider 11879 * @name $locationProvider 11880 * @description 11881 * Use the `$locationProvider` to configure how the application deep linking paths are stored. 11882 */ 11883function $LocationProvider() { 11884 var hashPrefix = '', 11885 html5Mode = { 11886 enabled: false, 11887 requireBase: true, 11888 rewriteLinks: true 11889 }; 11890 11891 /** 11892 * @ngdoc method 11893 * @name $locationProvider#hashPrefix 11894 * @description 11895 * @param {string=} prefix Prefix for hash part (containing path and search) 11896 * @returns {*} current value if used as getter or itself (chaining) if used as setter 11897 */ 11898 this.hashPrefix = function(prefix) { 11899 if (isDefined(prefix)) { 11900 hashPrefix = prefix; 11901 return this; 11902 } else { 11903 return hashPrefix; 11904 } 11905 }; 11906 11907 /** 11908 * @ngdoc method 11909 * @name $locationProvider#html5Mode 11910 * @description 11911 * @param {(boolean|Object)=} mode If boolean, sets `html5Mode.enabled` to value. 11912 * If object, sets `enabled`, `requireBase` and `rewriteLinks` to respective values. Supported 11913 * properties: 11914 * - **enabled** â `{boolean}` â (default: false) If true, will rely on `history.pushState` to 11915 * change urls where supported. Will fall back to hash-prefixed paths in browsers that do not 11916 * support `pushState`. 11917 * - **requireBase** - `{boolean}` - (default: `true`) When html5Mode is enabled, specifies 11918 * whether or not a <base> tag is required to be present. If `enabled` and `requireBase` are 11919 * true, and a base tag is not present, an error will be thrown when `$location` is injected. 11920 * See the {@link guide/$location $location guide for more information} 11921 * - **rewriteLinks** - `{boolean}` - (default: `true`) When html5Mode is enabled, 11922 * enables/disables url rewriting for relative links. 11923 * 11924 * @returns {Object} html5Mode object if used as getter or itself (chaining) if used as setter 11925 */ 11926 this.html5Mode = function(mode) { 11927 if (isBoolean(mode)) { 11928 html5Mode.enabled = mode; 11929 return this; 11930 } else if (isObject(mode)) { 11931 11932 if (isBoolean(mode.enabled)) { 11933 html5Mode.enabled = mode.enabled; 11934 } 11935 11936 if (isBoolean(mode.requireBase)) { 11937 html5Mode.requireBase = mode.requireBase; 11938 } 11939 11940 if (isBoolean(mode.rewriteLinks)) { 11941 html5Mode.rewriteLinks = mode.rewriteLinks; 11942 } 11943 11944 return this; 11945 } else { 11946 return html5Mode; 11947 } 11948 }; 11949 11950 /** 11951 * @ngdoc event 11952 * @name $location#$locationChangeStart 11953 * @eventType broadcast on root scope 11954 * @description 11955 * Broadcasted before a URL will change. 11956 * 11957 * This change can be prevented by calling 11958 * `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} for more 11959 * details about event object. Upon successful change 11960 * {@link ng.$location#$locationChangeSuccess $locationChangeSuccess} is fired. 11961 * 11962 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 11963 * the browser supports the HTML5 History API. 11964 * 11965 * @param {Object} angularEvent Synthetic event object. 11966 * @param {string} newUrl New URL 11967 * @param {string=} oldUrl URL that was before it was changed. 11968 * @param {string=} newState New history state object 11969 * @param {string=} oldState History state object that was before it was changed. 11970 */ 11971 11972 /** 11973 * @ngdoc event 11974 * @name $location#$locationChangeSuccess 11975 * @eventType broadcast on root scope 11976 * @description 11977 * Broadcasted after a URL was changed. 11978 * 11979 * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when 11980 * the browser supports the HTML5 History API. 11981 * 11982 * @param {Object} angularEvent Synthetic event object. 11983 * @param {string} newUrl New URL 11984 * @param {string=} oldUrl URL that was before it was changed. 11985 * @param {string=} newState New history state object 11986 * @param {string=} oldState History state object that was before it was changed. 11987 */ 11988 11989 this.$get = ['$rootScope', '$browser', '$sniffer', '$rootElement', '$window', 11990 function($rootScope, $browser, $sniffer, $rootElement, $window) { 11991 var $location, 11992 LocationMode, 11993 baseHref = $browser.baseHref(), // if base[href] is undefined, it defaults to '' 11994 initialUrl = $browser.url(), 11995 appBase; 11996 11997 if (html5Mode.enabled) { 11998 if (!baseHref && html5Mode.requireBase) { 11999 throw $locationMinErr('nobase', 12000 "$location in HTML5 mode requires a <base> tag to be present!"); 12001 } 12002 appBase = serverBase(initialUrl) + (baseHref || '/'); 12003 LocationMode = $sniffer.history ? LocationHtml5Url : LocationHashbangInHtml5Url; 12004 } else { 12005 appBase = stripHash(initialUrl); 12006 LocationMode = LocationHashbangUrl; 12007 } 12008 $location = new LocationMode(appBase, '#' + hashPrefix); 12009 $location.$$parseLinkUrl(initialUrl, initialUrl); 12010 12011 $location.$$state = $browser.state(); 12012 12013 var IGNORE_URI_REGEXP = /^\s*(javascript|mailto):/i; 12014 12015 function setBrowserUrlWithFallback(url, replace, state) { 12016 var oldUrl = $location.url(); 12017 var oldState = $location.$$state; 12018 try { 12019 $browser.url(url, replace, state); 12020 12021 // Make sure $location.state() returns referentially identical (not just deeply equal) 12022 // state object; this makes possible quick checking if the state changed in the digest 12023 // loop. Checking deep equality would be too expensive. 12024 $location.$$state = $browser.state(); 12025 } catch (e) { 12026 // Restore old values if pushState fails 12027 $location.url(oldUrl); 12028 $location.$$state = oldState; 12029 12030 throw e; 12031 } 12032 } 12033 12034 $rootElement.on('click', function(event) { 12035 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser) 12036 // currently we open nice url link and redirect then 12037 12038 if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return; 12039 12040 var elm = jqLite(event.target); 12041 12042 // traverse the DOM up to find first A tag 12043 while (nodeName_(elm[0]) !== 'a') { 12044 // ignore rewriting if no A tag (reached root element, or no parent - removed from document) 12045 if (elm[0] === $rootElement[0] || !(elm = elm.parent())[0]) return; 12046 } 12047 12048 var absHref = elm.prop('href'); 12049 // get the actual href attribute - see 12050 // http://msdn.microsoft.com/en-us/library/ie/dd347148(v=vs.85).aspx 12051 var relHref = elm.attr('href') || elm.attr('xlink:href'); 12052 12053 if (isObject(absHref) && absHref.toString() === '[object SVGAnimatedString]') { 12054 // SVGAnimatedString.animVal should be identical to SVGAnimatedString.baseVal, unless during 12055 // an animation. 12056 absHref = urlResolve(absHref.animVal).href; 12057 } 12058 12059 // Ignore when url is started with javascript: or mailto: 12060 if (IGNORE_URI_REGEXP.test(absHref)) return; 12061 12062 if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) { 12063 if ($location.$$parseLinkUrl(absHref, relHref)) { 12064 // We do a preventDefault for all urls that are part of the angular application, 12065 // in html5mode and also without, so that we are able to abort navigation without 12066 // getting double entries in the location history. 12067 event.preventDefault(); 12068 // update location manually 12069 if ($location.absUrl() != $browser.url()) { 12070 $rootScope.$apply(); 12071 // hack to work around FF6 bug 684208 when scenario runner clicks on links 12072 $window.angular['ff-684208-preventDefault'] = true; 12073 } 12074 } 12075 } 12076 }); 12077 12078 12079 // rewrite hashbang url <> html5 url 12080 if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) { 12081 $browser.url($location.absUrl(), true); 12082 } 12083 12084 var initializing = true; 12085 12086 // update $location when $browser url changes 12087 $browser.onUrlChange(function(newUrl, newState) { 12088 $rootScope.$evalAsync(function() { 12089 var oldUrl = $location.absUrl(); 12090 var oldState = $location.$$state; 12091 var defaultPrevented; 12092 12093 $location.$$parse(newUrl); 12094 $location.$$state = newState; 12095 12096 defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 12097 newState, oldState).defaultPrevented; 12098 12099 // if the location was changed by a `$locationChangeStart` handler then stop 12100 // processing this location change 12101 if ($location.absUrl() !== newUrl) return; 12102 12103 if (defaultPrevented) { 12104 $location.$$parse(oldUrl); 12105 $location.$$state = oldState; 12106 setBrowserUrlWithFallback(oldUrl, false, oldState); 12107 } else { 12108 initializing = false; 12109 afterLocationChange(oldUrl, oldState); 12110 } 12111 }); 12112 if (!$rootScope.$$phase) $rootScope.$digest(); 12113 }); 12114 12115 // update browser 12116 $rootScope.$watch(function $locationWatch() { 12117 var oldUrl = trimEmptyHash($browser.url()); 12118 var newUrl = trimEmptyHash($location.absUrl()); 12119 var oldState = $browser.state(); 12120 var currentReplace = $location.$$replace; 12121 var urlOrStateChanged = oldUrl !== newUrl || 12122 ($location.$$html5 && $sniffer.history && oldState !== $location.$$state); 12123 12124 if (initializing || urlOrStateChanged) { 12125 initializing = false; 12126 12127 $rootScope.$evalAsync(function() { 12128 var newUrl = $location.absUrl(); 12129 var defaultPrevented = $rootScope.$broadcast('$locationChangeStart', newUrl, oldUrl, 12130 $location.$$state, oldState).defaultPrevented; 12131 12132 // if the location was changed by a `$locationChangeStart` handler then stop 12133 // processing this location change 12134 if ($location.absUrl() !== newUrl) return; 12135 12136 if (defaultPrevented) { 12137 $location.$$parse(oldUrl); 12138 $location.$$state = oldState; 12139 } else { 12140 if (urlOrStateChanged) { 12141 setBrowserUrlWithFallback(newUrl, currentReplace, 12142 oldState === $location.$$state ? null : $location.$$state); 12143 } 12144 afterLocationChange(oldUrl, oldState); 12145 } 12146 }); 12147 } 12148 12149 $location.$$replace = false; 12150 12151 // we don't need to return anything because $evalAsync will make the digest loop dirty when 12152 // there is a change 12153 }); 12154 12155 return $location; 12156 12157 function afterLocationChange(oldUrl, oldState) { 12158 $rootScope.$broadcast('$locationChangeSuccess', $location.absUrl(), oldUrl, 12159 $location.$$state, oldState); 12160 } 12161}]; 12162} 12163 12164/** 12165 * @ngdoc service 12166 * @name $log 12167 * @requires $window 12168 * 12169 * @description 12170 * Simple service for logging. Default implementation safely writes the message 12171 * into the browser's console (if present). 12172 * 12173 * The main purpose of this service is to simplify debugging and troubleshooting. 12174 * 12175 * The default is to log `debug` messages. You can use 12176 * {@link ng.$logProvider ng.$logProvider#debugEnabled} to change this. 12177 * 12178 * @example 12179 <example module="logExample"> 12180 <file name="script.js"> 12181 angular.module('logExample', []) 12182 .controller('LogController', ['$scope', '$log', function($scope, $log) { 12183 $scope.$log = $log; 12184 $scope.message = 'Hello World!'; 12185 }]); 12186 </file> 12187 <file name="index.html"> 12188 <div ng-controller="LogController"> 12189 <p>Reload this page with open console, enter text and hit the log button...</p> 12190 <label>Message: 12191 <input type="text" ng-model="message" /></label> 12192 <button ng-click="$log.log(message)">log</button> 12193 <button ng-click="$log.warn(message)">warn</button> 12194 <button ng-click="$log.info(message)">info</button> 12195 <button ng-click="$log.error(message)">error</button> 12196 <button ng-click="$log.debug(message)">debug</button> 12197 </div> 12198 </file> 12199 </example> 12200 */ 12201 12202/** 12203 * @ngdoc provider 12204 * @name $logProvider 12205 * @description 12206 * Use the `$logProvider` to configure how the application logs messages 12207 */ 12208function $LogProvider() { 12209 var debug = true, 12210 self = this; 12211 12212 /** 12213 * @ngdoc method 12214 * @name $logProvider#debugEnabled 12215 * @description 12216 * @param {boolean=} flag enable or disable debug level messages 12217 * @returns {*} current value if used as getter or itself (chaining) if used as setter 12218 */ 12219 this.debugEnabled = function(flag) { 12220 if (isDefined(flag)) { 12221 debug = flag; 12222 return this; 12223 } else { 12224 return debug; 12225 } 12226 }; 12227 12228 this.$get = ['$window', function($window) { 12229 return { 12230 /** 12231 * @ngdoc method 12232 * @name $log#log 12233 * 12234 * @description 12235 * Write a log message 12236 */ 12237 log: consoleLog('log'), 12238 12239 /** 12240 * @ngdoc method 12241 * @name $log#info 12242 * 12243 * @description 12244 * Write an information message 12245 */ 12246 info: consoleLog('info'), 12247 12248 /** 12249 * @ngdoc method 12250 * @name $log#warn 12251 * 12252 * @description 12253 * Write a warning message 12254 */ 12255 warn: consoleLog('warn'), 12256 12257 /** 12258 * @ngdoc method 12259 * @name $log#error 12260 * 12261 * @description 12262 * Write an error message 12263 */ 12264 error: consoleLog('error'), 12265 12266 /** 12267 * @ngdoc method 12268 * @name $log#debug 12269 * 12270 * @description 12271 * Write a debug message 12272 */ 12273 debug: (function() { 12274 var fn = consoleLog('debug'); 12275 12276 return function() { 12277 if (debug) { 12278 fn.apply(self, arguments); 12279 } 12280 }; 12281 }()) 12282 }; 12283 12284 function formatError(arg) { 12285 if (arg instanceof Error) { 12286 if (arg.stack) { 12287 arg = (arg.message && arg.stack.indexOf(arg.message) === -1) 12288 ? 'Error: ' + arg.message + '\n' + arg.stack 12289 : arg.stack; 12290 } else if (arg.sourceURL) { 12291 arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line; 12292 } 12293 } 12294 return arg; 12295 } 12296 12297 function consoleLog(type) { 12298 var console = $window.console || {}, 12299 logFn = console[type] || console.log || noop, 12300 hasApply = false;
12301 12302 // Note: reading logFn.apply throws an error in IE11 in IE8 document mode. 12303 // The reason behind this is that console.log has type "object" in IE8... 12304 try { 12305 hasApply = !!logFn.apply; 12306 } catch (e) {} 12307 12308 if (hasApply) { 12309 return function() { 12310 var args = []; 12311 forEach(arguments, function(arg) { 12312 args.push(formatError(arg)); 12313 }); 12314 return logFn.apply(console, args); 12315 }; 12316 } 12317 12318 // we are IE which either doesn't have window.console => this is noop and we do nothing, 12319 // or we are IE where console.log doesn't have apply so we log at least first 2 args 12320 return function(arg1, arg2) { 12321 logFn(arg1, arg2 == null ? '' : arg2); 12322 }; 12323 } 12324 }]; 12325} 12326 12327/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 12328 * Any commits to this file should be reviewed with security in mind. * 12329 * Changes to this file can potentially create security vulnerabilities. * 12330 * An approval from 2 Core members with history of modifying * 12331 * this file is required. * 12332 * * 12333 * Does the change somehow allow for arbitrary javascript to be executed? * 12334 * Or allows for someone to change the prototype of built-in objects? * 12335 * Or gives undesired access to variables likes document or window? * 12336 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 12337 12338var $parseMinErr = minErr('$parse'); 12339 12340// Sandboxing Angular Expressions 12341// ------------------------------ 12342// Angular expressions are generally considered safe because these expressions only have direct 12343// access to `$scope` and locals. However, one can obtain the ability to execute arbitrary JS code by 12344// obtaining a reference to native JS functions such as the Function constructor. 12345// 12346// As an example, consider the following Angular expression: 12347// 12348// {}.toString.constructor('alert("evil JS code")') 12349// 12350// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits 12351// against the expression language, but not to prevent exploits that were enabled by exposing 12352// sensitive JavaScript or browser APIs on Scope. Exposing such objects on a Scope is never a good 12353// practice and therefore we are not even trying to protect against interaction with an object 12354// explicitly exposed in this way. 12355// 12356// In general, it is not possible to access a Window object from an angular expression unless a 12357// window or some DOM object that has a reference to window is published onto a Scope. 12358// Similarly we prevent invocations of function known to be dangerous, as well as assignments to 12359// native objects. 12360// 12361// See https://docs.angularjs.org/guide/security 12362 12363 12364function ensureSafeMemberName(name, fullExpression) { 12365 if (name === "__defineGetter__" || name === "__defineSetter__" 12366 || name === "__lookupGetter__" || name === "__lookupSetter__" 12367 || name === "__proto__") { 12368 throw $parseMinErr('isecfld', 12369 'Attempting to access a disallowed field in Angular expressions! ' 12370 + 'Expression: {0}', fullExpression); 12371 } 12372 return name; 12373} 12374 12375function ensureSafeObject(obj, fullExpression) { 12376 // nifty check if obj is Function that is fast and works across iframes and other contexts 12377 if (obj) { 12378 if (obj.constructor === obj) { 12379 throw $parseMinErr('isecfn', 12380 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 12381 fullExpression); 12382 } else if (// isWindow(obj) 12383 obj.window === obj) { 12384 throw $parseMinErr('isecwindow', 12385 'Referencing the Window in Angular expressions is disallowed! Expression: {0}', 12386 fullExpression); 12387 } else if (// isElement(obj) 12388 obj.children && (obj.nodeName || (obj.prop && obj.attr && obj.find))) { 12389 throw $parseMinErr('isecdom', 12390 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 12391 fullExpression); 12392 } else if (// block Object so that we can't get hold of dangerous Object.* methods 12393 obj === Object) { 12394 throw $parseMinErr('isecobj', 12395 'Referencing Object in Angular expressions is disallowed! Expression: {0}', 12396 fullExpression); 12397 } 12398 } 12399 return obj; 12400} 12401 12402var CALL = Function.prototype.call; 12403var APPLY = Function.prototype.apply; 12404var BIND = Function.prototype.bind; 12405 12406function ensureSafeFunction(obj, fullExpression) { 12407 if (obj) { 12408 if (obj.constructor === obj) { 12409 throw $parseMinErr('isecfn', 12410 'Referencing Function in Angular expressions is disallowed! Expression: {0}', 12411 fullExpression); 12412 } else if (obj === CALL || obj === APPLY || obj === BIND) { 12413 throw $parseMinErr('isecff', 12414 'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}', 12415 fullExpression); 12416 } 12417 } 12418} 12419 12420var OPERATORS = createMap(); 12421forEach('+ - * / % === !== == != < > <= >= && || ! = |'.split(' '), function(operator) { OPERATORS[operator] = true; }); 12422var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; 12423 12424
vendor: 47,196 bytes, lines 12425-13910
12425///////////////////////////////////////// 12426 12427 12428/** 12429 * @constructor 12430 */ 12431var Lexer = function(options) { 12432 this.options = options; 12433}; 12434 12435Lexer.prototype = { 12436 constructor: Lexer, 12437 12438 lex: function(text) { 12439 this.text = text; 12440 this.index = 0; 12441 this.tokens = []; 12442 12443 while (this.index < this.text.length) { 12444 var ch = this.text.charAt(this.index); 12445 if (ch === '"' || ch === "'") { 12446 this.readString(ch); 12447 } else if (this.isNumber(ch) || ch === '.' && this.isNumber(this.peek())) { 12448 this.readNumber(); 12449 } else if (this.isIdent(ch)) { 12450 this.readIdent(); 12451 } else if (this.is(ch, '(){}[].,;:?')) { 12452 this.tokens.push({index: this.index, text: ch}); 12453 this.index++; 12454 } else if (this.isWhitespace(ch)) { 12455 this.index++; 12456 } else { 12457 var ch2 = ch + this.peek(); 12458 var ch3 = ch2 + this.peek(2); 12459 var op1 = OPERATORS[ch]; 12460 var op2 = OPERATORS[ch2]; 12461 var op3 = OPERATORS[ch3]; 12462 if (op1 || op2 || op3) { 12463 var token = op3 ? ch3 : (op2 ? ch2 : ch); 12464 this.tokens.push({index: this.index, text: token, operator: true}); 12465 this.index += token.length; 12466 } else { 12467 this.throwError('Unexpected next character ', this.index, this.index + 1); 12468 } 12469 } 12470 } 12471 return this.tokens; 12472 }, 12473 12474 is: function(ch, chars) { 12475 return chars.indexOf(ch) !== -1; 12476 }, 12477 12478 peek: function(i) { 12479 var num = i || 1; 12480 return (this.index + num < this.text.length) ? this.text.charAt(this.index + num) : false; 12481 }, 12482 12483 isNumber: function(ch) { 12484 return ('0' <= ch && ch <= '9') && typeof ch === "string"; 12485 }, 12486 12487 isWhitespace: function(ch) { 12488 // IE treats non-breaking space as \u00A0 12489 return (ch === ' ' || ch === '\r' || ch === '\t' || 12490 ch === '\n' || ch === '\v' || ch === '\u00A0'); 12491 }, 12492 12493 isIdent: function(ch) { 12494 return ('a' <= ch && ch <= 'z' || 12495 'A' <= ch && ch <= 'Z' || 12496 '_' === ch || ch === '$'); 12497 }, 12498 12499 isExpOperator: function(ch) { 12500 return (ch === '-' || ch === '+' || this.isNumber(ch)); 12501 }, 12502 12503 throwError: function(error, start, end) { 12504 end = end || this.index; 12505 var colStr = (isDefined(start) 12506 ? 's ' + start + '-' + this.index + ' [' + this.text.substring(start, end) + ']' 12507 : ' ' + end); 12508 throw $parseMinErr('lexerr', 'Lexer Error: {0} at column{1} in expression [{2}].', 12509 error, colStr, this.text); 12510 }, 12511 12512 readNumber: function() { 12513 var number = ''; 12514 var start = this.index; 12515 while (this.index < this.text.length) { 12516 var ch = lowercase(this.text.charAt(this.index)); 12517 if (ch == '.' || this.isNumber(ch)) { 12518 number += ch; 12519 } else { 12520 var peekCh = this.peek(); 12521 if (ch == 'e' && this.isExpOperator(peekCh)) { 12522 number += ch; 12523 } else if (this.isExpOperator(ch) && 12524 peekCh && this.isNumber(peekCh) && 12525 number.charAt(number.length - 1) == 'e') { 12526 number += ch; 12527 } else if (this.isExpOperator(ch) && 12528 (!peekCh || !this.isNumber(peekCh)) && 12529 number.charAt(number.length - 1) == 'e') { 12530 this.throwError('Invalid exponent'); 12531 } else { 12532 break; 12533 } 12534 } 12535 this.index++; 12536 } 12537 this.tokens.push({ 12538 index: start, 12539 text: number, 12540 constant: true, 12541 value: Number(number) 12542 }); 12543 }, 12544 12545 readIdent: function() { 12546 var start = this.index; 12547 while (this.index < this.text.length) { 12548 var ch = this.text.charAt(this.index); 12549 if (!(this.isIdent(ch) || this.isNumber(ch))) { 12550 break; 12551 } 12552 this.index++; 12553 } 12554 this.tokens.push({ 12555 index: start, 12556 text: this.text.slice(start, this.index), 12557 identifier: true 12558 }); 12559 }, 12560 12561 readString: function(quote) { 12562 var start = this.index; 12563 this.index++; 12564 var string = ''; 12565 var rawString = quote; 12566 var escape = false; 12567 while (this.index < this.text.length) { 12568 var ch = this.text.charAt(this.index); 12569 rawString += ch; 12570 if (escape) { 12571 if (ch === 'u') { 12572 var hex = this.text.substring(this.index + 1, this.index + 5); 12573 if (!hex.match(/[\da-f]{4}/i)) { 12574 this.throwError('Invalid unicode escape [\\u' + hex + ']'); 12575 } 12576 this.index += 4; 12577 string += String.fromCharCode(parseInt(hex, 16)); 12578 } else { 12579 var rep = ESCAPE[ch]; 12580 string = string + (rep || ch); 12581 } 12582 escape = false; 12583 } else if (ch === '\\') { 12584 escape = true; 12585 } else if (ch === quote) { 12586 this.index++; 12587 this.tokens.push({ 12588 index: start, 12589 text: rawString, 12590 constant: true, 12591 value: string 12592 }); 12593 return; 12594 } else { 12595 string += ch; 12596 } 12597 this.index++; 12598 } 12599 this.throwError('Unterminated quote', start); 12600 } 12601}; 12602 12603var AST = function(lexer, options) { 12604 this.lexer = lexer; 12605 this.options = options; 12606}; 12607 12608AST.Program = 'Program'; 12609AST.ExpressionStatement = 'ExpressionStatement'; 12610AST.AssignmentExpression = 'AssignmentExpression'; 12611AST.ConditionalExpression = 'ConditionalExpression'; 12612AST.LogicalExpression = 'LogicalExpression'; 12613AST.BinaryExpression = 'BinaryExpression'; 12614AST.UnaryExpression = 'UnaryExpression'; 12615AST.CallExpression = 'CallExpression'; 12616AST.MemberExpression = 'MemberExpression'; 12617AST.Identifier = 'Identifier'; 12618AST.Literal = 'Literal'; 12619AST.ArrayExpression = 'ArrayExpression'; 12620AST.Property = 'Property'; 12621AST.ObjectExpression = 'ObjectExpression'; 12622AST.ThisExpression = 'ThisExpression'; 12623 12624// Internal use only 12625AST.NGValueParameter = 'NGValueParameter'; 12626 12627AST.prototype = { 12628 ast: function(text) { 12629 this.text = text; 12630 this.tokens = this.lexer.lex(text); 12631 12632 var value = this.program(); 12633 12634 if (this.tokens.length !== 0) { 12635 this.throwError('is an unexpected token', this.tokens[0]); 12636 } 12637 12638 return value; 12639 }, 12640 12641 program: function() { 12642 var body = []; 12643 while (true) { 12644 if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) 12645 body.push(this.expressionStatement()); 12646 if (!this.expect(';')) { 12647 return { type: AST.Program, body: body}; 12648 } 12649 } 12650 }, 12651 12652 expressionStatement: function() { 12653 return { type: AST.ExpressionStatement, expression: this.filterChain() }; 12654 }, 12655 12656 filterChain: function() { 12657 var left = this.expression(); 12658 var token; 12659 while ((token = this.expect('|'))) { 12660 left = this.filter(left); 12661 } 12662 return left; 12663 }, 12664 12665 expression: function() { 12666 return this.assignment(); 12667 }, 12668 12669 assignment: function() { 12670 var result = this.ternary(); 12671 if (this.expect('=')) { 12672 result = { type: AST.AssignmentExpression, left: result, right: this.assignment(), operator: '='}; 12673 } 12674 return result; 12675 }, 12676 12677 ternary: function() { 12678 var test = this.logicalOR(); 12679 var alternate; 12680 var consequent; 12681 if (this.expect('?')) { 12682 alternate = this.expression(); 12683 if (this.consume(':')) { 12684 consequent = this.expression(); 12685 return { type: AST.ConditionalExpression, test: test, alternate: alternate, consequent: consequent}; 12686 } 12687 } 12688 return test; 12689 }, 12690 12691 logicalOR: function() { 12692 var left = this.logicalAND(); 12693 while (this.expect('||')) { 12694 left = { type: AST.LogicalExpression, operator: '||', left: left, right: this.logicalAND() }; 12695 } 12696 return left; 12697 }, 12698 12699 logicalAND: function() { 12700 var left = this.equality(); 12701 while (this.expect('&&')) { 12702 left = { type: AST.LogicalExpression, operator: '&&', left: left, right: this.equality()}; 12703 } 12704 return left; 12705 }, 12706 12707 equality: function() { 12708 var left = this.relational(); 12709 var token; 12710 while ((token = this.expect('==','!=','===','!=='))) { 12711 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.relational() }; 12712 } 12713 return left; 12714 }, 12715 12716 relational: function() { 12717 var left = this.additive(); 12718 var token; 12719 while ((token = this.expect('<', '>', '<=', '>='))) { 12720 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.additive() }; 12721 } 12722 return left; 12723 }, 12724 12725 additive: function() { 12726 var left = this.multiplicative(); 12727 var token; 12728 while ((token = this.expect('+','-'))) { 12729 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.multiplicative() }; 12730 } 12731 return left; 12732 }, 12733 12734 multiplicative: function() { 12735 var left = this.unary(); 12736 var token; 12737 while ((token = this.expect('*','/','%'))) { 12738 left = { type: AST.BinaryExpression, operator: token.text, left: left, right: this.unary() }; 12739 } 12740 return left; 12741 }, 12742 12743 unary: function() { 12744 var token; 12745 if ((token = this.expect('+', '-', '!'))) { 12746 return { type: AST.UnaryExpression, operator: token.text, prefix: true, argument: this.unary() }; 12747 } else { 12748 return this.primary(); 12749 } 12750 }, 12751 12752 primary: function() { 12753 var primary; 12754 if (this.expect('(')) { 12755 primary = this.filterChain(); 12756 this.consume(')'); 12757 } else if (this.expect('[')) { 12758 primary = this.arrayDeclaration(); 12759 } else if (this.expect('{')) { 12760 primary = this.object(); 12761 } else if (this.constants.hasOwnProperty(this.peek().text)) { 12762 primary = copy(this.constants[this.consume().text]); 12763 } else if (this.peek().identifier) { 12764 primary = this.identifier(); 12765 } else if (this.peek().constant) { 12766 primary = this.constant(); 12767 } else { 12768 this.throwError('not a primary expression', this.peek()); 12769 } 12770 12771 var next; 12772 while ((next = this.expect('(', '[', '.'))) { 12773 if (next.text === '(') { 12774 primary = {type: AST.CallExpression, callee: primary, arguments: this.parseArguments() }; 12775 this.consume(')'); 12776 } else if (next.text === '[') { 12777 primary = { type: AST.MemberExpression, object: primary, property: this.expression(), computed: true }; 12778 this.consume(']'); 12779 } else if (next.text === '.') { 12780 primary = { type: AST.MemberExpression, object: primary, property: this.identifier(), computed: false }; 12781 } else { 12782 this.throwError('IMPOSSIBLE'); 12783 } 12784 } 12785 return primary; 12786 }, 12787 12788 filter: function(baseExpression) { 12789 var args = [baseExpression]; 12790 var result = {type: AST.CallExpression, callee: this.identifier(), arguments: args, filter: true}; 12791 12792 while (this.expect(':')) { 12793 args.push(this.expression()); 12794 } 12795 12796 return result; 12797 }, 12798 12799 parseArguments: function() { 12800 var args = []; 12801 if (this.peekToken().text !== ')') { 12802 do { 12803 args.push(this.expression()); 12804 } while (this.expect(',')); 12805 } 12806 return args; 12807 }, 12808 12809 identifier: function() { 12810 var token = this.consume(); 12811 if (!token.identifier) { 12812 this.throwError('is not a valid identifier', token); 12813 } 12814 return { type: AST.Identifier, name: token.text }; 12815 }, 12816 12817 constant: function() { 12818 // TODO check that it is a constant 12819 return { type: AST.Literal, value: this.consume().value }; 12820 }, 12821 12822 arrayDeclaration: function() { 12823 var elements = []; 12824 if (this.peekToken().text !== ']') { 12825 do { 12826 if (this.peek(']')) { 12827 // Support trailing commas per ES5.1. 12828 break; 12829 } 12830 elements.push(this.expression()); 12831 } while (this.expect(',')); 12832 } 12833 this.consume(']'); 12834 12835 return { type: AST.ArrayExpression, elements: elements }; 12836 }, 12837 12838 object: function() { 12839 var properties = [], property; 12840 if (this.peekToken().text !== '}') { 12841 do { 12842 if (this.peek('}')) { 12843 // Support trailing commas per ES5.1. 12844 break; 12845 } 12846 property = {type: AST.Property, kind: 'init'}; 12847 if (this.peek().constant) { 12848 property.key = this.constant(); 12849 } else if (this.peek().identifier) { 12850 property.key = this.identifier(); 12851 } else { 12852 this.throwError("invalid key", this.peek()); 12853 } 12854 this.consume(':'); 12855 property.value = this.expression(); 12856 properties.push(property); 12857 } while (this.expect(',')); 12858 } 12859 this.consume('}'); 12860 12861 return {type: AST.ObjectExpression, properties: properties }; 12862 }, 12863 12864 throwError: function(msg, token) { 12865 throw $parseMinErr('syntax', 12866 'Syntax Error: Token \'{0}\' {1} at column {2} of the expression [{3}] starting at [{4}].', 12867 token.text, msg, (token.index + 1), this.text, this.text.substring(token.index)); 12868 }, 12869 12870 consume: function(e1) { 12871 if (this.tokens.length === 0) { 12872 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 12873 } 12874 12875 var token = this.expect(e1); 12876 if (!token) { 12877 this.throwError('is unexpected, expecting [' + e1 + ']', this.peek()); 12878 } 12879 return token; 12880 }, 12881 12882 peekToken: function() { 12883 if (this.tokens.length === 0) { 12884 throw $parseMinErr('ueoe', 'Unexpected end of expression: {0}', this.text); 12885 } 12886 return this.tokens[0]; 12887 }, 12888 12889 peek: function(e1, e2, e3, e4) { 12890 return this.peekAhead(0, e1, e2, e3, e4); 12891 }, 12892 12893 peekAhead: function(i, e1, e2, e3, e4) { 12894 if (this.tokens.length > i) { 12895 var token = this.tokens[i]; 12896 var t = token.text; 12897 if (t === e1 || t === e2 || t === e3 || t === e4 || 12898 (!e1 && !e2 && !e3 && !e4)) { 12899 return token; 12900 } 12901 } 12902 return false; 12903 }, 12904 12905 expect: function(e1, e2, e3, e4) { 12906 var token = this.peek(e1, e2, e3, e4); 12907 if (token) { 12908 this.tokens.shift(); 12909 return token; 12910 } 12911 return false; 12912 }, 12913 12914 12915 /* `undefined` is not a constant, it is an identifier, 12916 * but using it as an identifier is not supported 12917 */ 12918 constants: { 12919 'true': { type: AST.Literal, value: true }, 12920 'false': { type: AST.Literal, value: false }, 12921 'null': { type: AST.Literal, value: null }, 12922 'undefined': {type: AST.Literal, value: undefined }, 12923 'this': {type: AST.ThisExpression } 12924 } 12925}; 12926 12927function ifDefined(v, d) { 12928 return typeof v !== 'undefined' ? v : d; 12929} 12930 12931function plusFn(l, r) { 12932 if (typeof l === 'undefined') return r; 12933 if (typeof r === 'undefined') return l; 12934 return l + r; 12935} 12936 12937function isStateless($filter, filterName) { 12938 var fn = $filter(filterName); 12939 return !fn.$stateful; 12940} 12941 12942function findConstantAndWatchExpressions(ast, $filter) { 12943 var allConstants; 12944 var argsToWatch; 12945 switch (ast.type) { 12946 case AST.Program: 12947 allConstants = true; 12948 forEach(ast.body, function(expr) { 12949 findConstantAndWatchExpressions(expr.expression, $filter); 12950 allConstants = allConstants && expr.expression.constant; 12951 }); 12952 ast.constant = allConstants; 12953 break; 12954 case AST.Literal: 12955 ast.constant = true; 12956 ast.toWatch = []; 12957 break; 12958 case AST.UnaryExpression: 12959 findConstantAndWatchExpressions(ast.argument, $filter); 12960 ast.constant = ast.argument.constant; 12961 ast.toWatch = ast.argument.toWatch; 12962 break; 12963 case AST.BinaryExpression: 12964 findConstantAndWatchExpressions(ast.left, $filter); 12965 findConstantAndWatchExpressions(ast.right, $filter); 12966 ast.constant = ast.left.constant && ast.right.constant; 12967 ast.toWatch = ast.left.toWatch.concat(ast.right.toWatch); 12968 break; 12969 case AST.LogicalExpression: 12970 findConstantAndWatchExpressions(ast.left, $filter); 12971 findConstantAndWatchExpressions(ast.right, $filter); 12972 ast.constant = ast.left.constant && ast.right.constant; 12973 ast.toWatch = ast.constant ? [] : [ast]; 12974 break; 12975 case AST.ConditionalExpression: 12976 findConstantAndWatchExpressions(ast.test, $filter); 12977 findConstantAndWatchExpressions(ast.alternate, $filter); 12978 findConstantAndWatchExpressions(ast.consequent, $filter); 12979 ast.constant = ast.test.constant && ast.alternate.constant && ast.consequent.constant; 12980 ast.toWatch = ast.constant ? [] : [ast]; 12981 break; 12982 case AST.Identifier: 12983 ast.constant = false; 12984 ast.toWatch = [ast]; 12985 break; 12986 case AST.MemberExpression: 12987 findConstantAndWatchExpressions(ast.object, $filter); 12988 if (ast.computed) { 12989 findConstantAndWatchExpressions(ast.property, $filter); 12990 } 12991 ast.constant = ast.object.constant && (!ast.computed || ast.property.constant); 12992 ast.toWatch = [ast]; 12993 break; 12994 case AST.CallExpression: 12995 allConstants = ast.filter ? isStateless($filter, ast.callee.name) : false; 12996 argsToWatch = []; 12997 forEach(ast.arguments, function(expr) { 12998 findConstantAndWatchExpressions(expr, $filter); 12999 allConstants = allConstants && expr.constant; 13000 if (!expr.constant) { 13001 argsToWatch.push.apply(argsToWatch, expr.toWatch); 13002 } 13003 }); 13004 ast.constant = allConstants; 13005 ast.toWatch = ast.filter && isStateless($filter, ast.callee.name) ? argsToWatch : [ast]; 13006 break; 13007 case AST.AssignmentExpression: 13008 findConstantAndWatchExpressions(ast.left, $filter); 13009 findConstantAndWatchExpressions(ast.right, $filter); 13010 ast.constant = ast.left.constant && ast.right.constant; 13011 ast.toWatch = [ast]; 13012 break; 13013 case AST.ArrayExpression: 13014 allConstants = true; 13015 argsToWatch = []; 13016 forEach(ast.elements, function(expr) { 13017 findConstantAndWatchExpressions(expr, $filter); 13018 allConstants = allConstants && expr.constant; 13019 if (!expr.constant) { 13020 argsToWatch.push.apply(argsToWatch, expr.toWatch); 13021 } 13022 }); 13023 ast.constant = allConstants; 13024 ast.toWatch = argsToWatch; 13025 break; 13026 case AST.ObjectExpression: 13027 allConstants = true; 13028 argsToWatch = []; 13029 forEach(ast.properties, function(property) { 13030 findConstantAndWatchExpressions(property.value, $filter); 13031 allConstants = allConstants && property.value.constant; 13032 if (!property.value.constant) { 13033 argsToWatch.push.apply(argsToWatch, property.value.toWatch); 13034 } 13035 }); 13036 ast.constant = allConstants; 13037 ast.toWatch = argsToWatch; 13038 break; 13039 case AST.ThisExpression: 13040 ast.constant = false; 13041 ast.toWatch = []; 13042 break; 13043 } 13044} 13045 13046function getInputs(body) { 13047 if (body.length != 1) return; 13048 var lastExpression = body[0].expression; 13049 var candidate = lastExpression.toWatch; 13050 if (candidate.length !== 1) return candidate; 13051 return candidate[0] !== lastExpression ? candidate : undefined; 13052} 13053 13054function isAssignable(ast) { 13055 return ast.type === AST.Identifier || ast.type === AST.MemberExpression; 13056} 13057 13058function assignableAST(ast) { 13059 if (ast.body.length === 1 && isAssignable(ast.body[0].expression)) { 13060 return {type: AST.AssignmentExpression, left: ast.body[0].expression, right: {type: AST.NGValueParameter}, operator: '='}; 13061 } 13062} 13063 13064function isLiteral(ast) { 13065 return ast.body.length === 0 || 13066 ast.body.length === 1 && ( 13067 ast.body[0].expression.type === AST.Literal || 13068 ast.body[0].expression.type === AST.ArrayExpression || 13069 ast.body[0].expression.type === AST.ObjectExpression); 13070} 13071 13072function isConstant(ast) { 13073 return ast.constant; 13074} 13075 13076function ASTCompiler(astBuilder, $filter) { 13077 this.astBuilder = astBuilder; 13078 this.$filter = $filter; 13079} 13080 13081ASTCompiler.prototype = { 13082 compile: function(expression, expensiveChecks) { 13083 var self = this; 13084 var ast = this.astBuilder.ast(expression); 13085 this.state = { 13086 nextId: 0, 13087 filters: {}, 13088 expensiveChecks: expensiveChecks, 13089 fn: {vars: [], body: [], own: {}}, 13090 assign: {vars: [], body: [], own: {}}, 13091 inputs: [] 13092 }; 13093 findConstantAndWatchExpressions(ast, self.$filter); 13094 var extra = ''; 13095 var assignable; 13096 this.stage = 'assign'; 13097 if ((assignable = assignableAST(ast))) { 13098 this.state.computing = 'assign'; 13099 var result = this.nextId(); 13100 this.recurse(assignable, result); 13101 extra = 'fn.assign=' + this.generateFunction('assign', 's,v,l'); 13102 } 13103 var toWatch = getInputs(ast.body); 13104 self.stage = 'inputs'; 13105 forEach(toWatch, function(watch, key) { 13106 var fnKey = 'fn' + key; 13107 self.state[fnKey] = {vars: [], body: [], own: {}}; 13108 self.state.computing = fnKey; 13109 var intoId = self.nextId(); 13110 self.recurse(watch, intoId); 13111 self.return_(intoId); 13112 self.state.inputs.push(fnKey); 13113 watch.watchId = key; 13114 }); 13115 this.state.computing = 'fn'; 13116 this.stage = 'main'; 13117 this.recurse(ast); 13118 var fnString = 13119 // The build and minification steps remove the string "use strict" from the code, but this is done using a regex. 13120 // This is a workaround for this until we do a better job at only removing the prefix only when we should. 13121 '"' + this.USE + ' ' + this.STRICT + '";\n' + 13122 this.filterPrefix() + 13123 'var fn=' + this.generateFunction('fn', 's,l,a,i') + 13124 extra + 13125 this.watchFns() + 13126 'return fn;'; 13127 13128 /* jshint -W054 */ 13129 var fn = (new Function('$filter', 13130 'ensureSafeMemberName', 13131 'ensureSafeObject', 13132 'ensureSafeFunction', 13133 'ifDefined', 13134 'plus', 13135 'text', 13136 fnString))( 13137 this.$filter, 13138 ensureSafeMemberName, 13139 ensureSafeObject, 13140 ensureSafeFunction, 13141 ifDefined, 13142 plusFn, 13143 expression); 13144 /* jshint +W054 */ 13145 this.state = this.stage = undefined; 13146 fn.literal = isLiteral(ast); 13147 fn.constant = isConstant(ast); 13148 return fn; 13149 }, 13150 13151 USE: 'use', 13152 13153 STRICT: 'strict', 13154 13155 watchFns: function() { 13156 var result = []; 13157 var fns = this.state.inputs; 13158 var self = this; 13159 forEach(fns, function(name) { 13160 result.push('var ' + name + '=' + self.generateFunction(name, 's')); 13161 }); 13162 if (fns.length) { 13163 result.push('fn.inputs=[' + fns.join(',') + '];'); 13164 } 13165 return result.join(''); 13166 }, 13167 13168 generateFunction: function(name, params) { 13169 return 'function(' + params + '){' + 13170 this.varsPrefix(name) + 13171 this.body(name) + 13172 '};'; 13173 }, 13174 13175 filterPrefix: function() { 13176 var parts = []; 13177 var self = this; 13178 forEach(this.state.filters, function(id, filter) { 13179 parts.push(id + '=$filter(' + self.escape(filter) + ')'); 13180 }); 13181 if (parts.length) return 'var ' + parts.join(',') + ';'; 13182 return ''; 13183 }, 13184 13185 varsPrefix: function(section) { 13186 return this.state[section].vars.length ? 'var ' + this.state[section].vars.join(',') + ';' : ''; 13187 }, 13188 13189 body: function(section) { 13190 return this.state[section].body.join(''); 13191 }, 13192 13193 recurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 13194 var left, right, self = this, args, expression; 13195 recursionFn = recursionFn || noop; 13196 if (!skipWatchIdCheck && isDefined(ast.watchId)) { 13197 intoId = intoId || this.nextId(); 13198 this.if_('i', 13199 this.lazyAssign(intoId, this.computedMember('i', ast.watchId)), 13200 this.lazyRecurse(ast, intoId, nameId, recursionFn, create, true) 13201 ); 13202 return; 13203 } 13204 switch (ast.type) { 13205 case AST.Program: 13206 forEach(ast.body, function(expression, pos) { 13207 self.recurse(expression.expression, undefined, undefined, function(expr) { right = expr; }); 13208 if (pos !== ast.body.length - 1) { 13209 self.current().body.push(right, ';'); 13210 } else { 13211 self.return_(right); 13212 } 13213 }); 13214 break; 13215 case AST.Literal: 13216 expression = this.escape(ast.value); 13217 this.assign(intoId, expression); 13218 recursionFn(expression); 13219 break; 13220 case AST.UnaryExpression: 13221 this.recurse(ast.argument, undefined, undefined, function(expr) { right = expr; }); 13222 expression = ast.operator + '(' + this.ifDefined(right, 0) + ')'; 13223 this.assign(intoId, expression); 13224 recursionFn(expression); 13225 break; 13226 case AST.BinaryExpression: 13227 this.recurse(ast.left, undefined, undefined, function(expr) { left = expr; }); 13228 this.recurse(ast.right, undefined, undefined, function(expr) { right = expr; }); 13229 if (ast.operator === '+') { 13230 expression = this.plus(left, right); 13231 } else if (ast.operator === '-') { 13232 expression = this.ifDefined(left, 0) + ast.operator + this.ifDefined(right, 0); 13233 } else { 13234 expression = '(' + left + ')' + ast.operator + '(' + right + ')'; 13235 } 13236 this.assign(intoId, expression); 13237 recursionFn(expression); 13238 break; 13239 case AST.LogicalExpression: 13240 intoId = intoId || this.nextId(); 13241 self.recurse(ast.left, intoId); 13242 self.if_(ast.operator === '&&' ? intoId : self.not(intoId), self.lazyRecurse(ast.right, intoId)); 13243 recursionFn(intoId); 13244 break; 13245 case AST.ConditionalExpression: 13246 intoId = intoId || this.nextId(); 13247 self.recurse(ast.test, intoId); 13248 self.if_(intoId, self.lazyRecurse(ast.alternate, intoId), self.lazyRecurse(ast.consequent, intoId)); 13249 recursionFn(intoId); 13250 break; 13251 case AST.Identifier: 13252 intoId = intoId || this.nextId(); 13253 if (nameId) { 13254 nameId.context = self.stage === 'inputs' ? 's' : this.assign(this.nextId(), this.getHasOwnProperty('l', ast.name) + '?l:s'); 13255 nameId.computed = false; 13256 nameId.name = ast.name; 13257 } 13258 ensureSafeMemberName(ast.name); 13259 self.if_(self.stage === 'inputs' || self.not(self.getHasOwnProperty('l', ast.name)), 13260 function() { 13261 self.if_(self.stage === 'inputs' || 's', function() { 13262 if (create && create !== 1) { 13263 self.if_( 13264 self.not(self.nonComputedMember('s', ast.name)), 13265 self.lazyAssign(self.nonComputedMember('s', ast.name), '{}')); 13266 } 13267 self.assign(intoId, self.nonComputedMember('s', ast.name)); 13268 }); 13269 }, intoId && self.lazyAssign(intoId, self.nonComputedMember('l', ast.name)) 13270 ); 13271 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.name)) { 13272 self.addEnsureSafeObject(intoId); 13273 } 13274 recursionFn(intoId); 13275 break; 13276 case AST.MemberExpression: 13277 left = nameId && (nameId.context = this.nextId()) || this.nextId(); 13278 intoId = intoId || this.nextId(); 13279 self.recurse(ast.object, left, undefined, function() { 13280 self.if_(self.notNull(left), function() { 13281 if (ast.computed) { 13282 right = self.nextId(); 13283 self.recurse(ast.property, right); 13284 self.addEnsureSafeMemberName(right); 13285 if (create && create !== 1) { 13286 self.if_(self.not(self.computedMember(left, right)), self.lazyAssign(self.computedMember(left, right), '{}')); 13287 } 13288 expression = self.ensureSafeObject(self.computedMember(left, right)); 13289 self.assign(intoId, expression); 13290 if (nameId) { 13291 nameId.computed = true; 13292 nameId.name = right; 13293 } 13294 } else { 13295 ensureSafeMemberName(ast.property.name); 13296 if (create && create !== 1) { 13297 self.if_(self.not(self.nonComputedMember(left, ast.property.name)), self.lazyAssign(self.nonComputedMember(left, ast.property.name), '{}')); 13298 } 13299 expression = self.nonComputedMember(left, ast.property.name); 13300 if (self.state.expensiveChecks || isPossiblyDangerousMemberName(ast.property.name)) { 13301 expression = self.ensureSafeObject(expression); 13302 } 13303 self.assign(intoId, expression); 13304 if (nameId) { 13305 nameId.computed = false; 13306 nameId.name = ast.property.name; 13307 } 13308 } 13309 }, function() { 13310 self.assign(intoId, 'undefined'); 13311 }); 13312 recursionFn(intoId); 13313 }, !!create); 13314 break; 13315 case AST.CallExpression: 13316 intoId = intoId || this.nextId(); 13317 if (ast.filter) { 13318 right = self.filter(ast.callee.name); 13319 args = []; 13320 forEach(ast.arguments, function(expr) { 13321 var argument = self.nextId(); 13322 self.recurse(expr, argument); 13323 args.push(argument); 13324 }); 13325 expression = right + '(' + args.join(',') + ')'; 13326 self.assign(intoId, expression); 13327 recursionFn(intoId); 13328 } else { 13329 right = self.nextId(); 13330 left = {}; 13331 args = []; 13332 self.recurse(ast.callee, right, left, function() { 13333 self.if_(self.notNull(right), function() { 13334 self.addEnsureSafeFunction(right); 13335 forEach(ast.arguments, function(expr) { 13336 self.recurse(expr, self.nextId(), undefined, function(argument) { 13337 args.push(self.ensureSafeObject(argument)); 13338 }); 13339 }); 13340 if (left.name) { 13341 if (!self.state.expensiveChecks) { 13342 self.addEnsureSafeObject(left.context); 13343 } 13344 expression = self.member(left.context, left.name, left.computed) + '(' + args.join(',') + ')'; 13345 } else { 13346 expression = right + '(' + args.join(',') + ')'; 13347 } 13348 expression = self.ensureSafeObject(expression); 13349 self.assign(intoId, expression); 13350 }, function() { 13351 self.assign(intoId, 'undefined'); 13352 }); 13353 recursionFn(intoId); 13354 }); 13355 } 13356 break; 13357 case AST.AssignmentExpression: 13358 right = this.nextId(); 13359 left = {}; 13360 if (!isAssignable(ast.left)) { 13361 throw $parseMinErr('lval', 'Trying to assing a value to a non l-value'); 13362 } 13363 this.recurse(ast.left, undefined, left, function() { 13364 self.if_(self.notNull(left.context), function() { 13365 self.recurse(ast.right, right); 13366 self.addEnsureSafeObject(self.member(left.context, left.name, left.computed)); 13367 expression = self.member(left.context, left.name, left.computed) + ast.operator + right; 13368 self.assign(intoId, expression); 13369 recursionFn(intoId || expression); 13370 }); 13371 }, 1); 13372 break; 13373 case AST.ArrayExpression: 13374 args = []; 13375 forEach(ast.elements, function(expr) { 13376 self.recurse(expr, self.nextId(), undefined, function(argument) { 13377 args.push(argument); 13378 }); 13379 }); 13380 expression = '[' + args.join(',') + ']'; 13381 this.assign(intoId, expression); 13382 recursionFn(expression); 13383 break; 13384 case AST.ObjectExpression: 13385 args = []; 13386 forEach(ast.properties, function(property) { 13387 self.recurse(property.value, self.nextId(), undefined, function(expr) { 13388 args.push(self.escape( 13389 property.key.type === AST.Identifier ? property.key.name : 13390 ('' + property.key.value)) + 13391 ':' + expr); 13392 }); 13393 }); 13394 expression = '{' + args.join(',') + '}'; 13395 this.assign(intoId, expression); 13396 recursionFn(expression); 13397 break; 13398 case AST.ThisExpression: 13399 this.assign(intoId, 's'); 13400 recursionFn('s'); 13401 break; 13402 case AST.NGValueParameter: 13403 this.assign(intoId, 'v'); 13404 recursionFn('v'); 13405 break; 13406 } 13407 }, 13408 13409 getHasOwnProperty: function(element, property) { 13410 var key = element + '.' + property; 13411 var own = this.current().own; 13412 if (!own.hasOwnProperty(key)) { 13413 own[key] = this.nextId(false, element + '&&(' + this.escape(property) + ' in ' + element + ')'); 13414 } 13415 return own[key]; 13416 }, 13417 13418 assign: function(id, value) { 13419 if (!id) return; 13420 this.current().body.push(id, '=', value, ';'); 13421 return id; 13422 }, 13423 13424 filter: function(filterName) { 13425 if (!this.state.filters.hasOwnProperty(filterName)) { 13426 this.state.filters[filterName] = this.nextId(true); 13427 } 13428 return this.state.filters[filterName]; 13429 }, 13430 13431 ifDefined: function(id, defaultValue) { 13432 return 'ifDefined(' + id + ',' + this.escape(defaultValue) + ')'; 13433 }, 13434 13435 plus: function(left, right) { 13436 return 'plus(' + left + ',' + right + ')'; 13437 }, 13438 13439 return_: function(id) { 13440 this.current().body.push('return ', id, ';'); 13441 }, 13442 13443 if_: function(test, alternate, consequent) { 13444 if (test === true) { 13445 alternate(); 13446 } else { 13447 var body = this.current().body; 13448 body.push('if(', test, '){'); 13449 alternate(); 13450 body.push('}'); 13451 if (consequent) { 13452 body.push('else{'); 13453 consequent(); 13454 body.push('}'); 13455 } 13456 } 13457 }, 13458 13459 not: function(expression) { 13460 return '!(' + expression + ')'; 13461 }, 13462 13463 notNull: function(expression) { 13464 return expression + '!=null'; 13465 }, 13466 13467 nonComputedMember: function(left, right) { 13468 return left + '.' + right; 13469 }, 13470 13471 computedMember: function(left, right) { 13472 return left + '[' + right + ']'; 13473 }, 13474 13475 member: function(left, right, computed) { 13476 if (computed) return this.computedMember(left, right); 13477 return this.nonComputedMember(left, right); 13478 }, 13479 13480 addEnsureSafeObject: function(item) { 13481 this.current().body.push(this.ensureSafeObject(item), ';'); 13482 }, 13483 13484 addEnsureSafeMemberName: function(item) { 13485 this.current().body.push(this.ensureSafeMemberName(item), ';'); 13486 }, 13487 13488 addEnsureSafeFunction: function(item) { 13489 this.current().body.push(this.ensureSafeFunction(item), ';'); 13490 }, 13491 13492 ensureSafeObject: function(item) { 13493 return 'ensureSafeObject(' + item + ',text)'; 13494 }, 13495 13496 ensureSafeMemberName: function(item) { 13497 return 'ensureSafeMemberName(' + item + ',text)'; 13498 }, 13499 13500 ensureSafeFunction: function(item) { 13501 return 'ensureSafeFunction(' + item + ',text)'; 13502 }, 13503 13504 lazyRecurse: function(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck) { 13505 var self = this; 13506 return function() { 13507 self.recurse(ast, intoId, nameId, recursionFn, create, skipWatchIdCheck); 13508 }; 13509 }, 13510 13511 lazyAssign: function(id, value) { 13512 var self = this; 13513 return function() { 13514 self.assign(id, value); 13515 }; 13516 }, 13517 13518 stringEscapeRegex: /[^ a-zA-Z0-9]/g, 13519 13520 stringEscapeFn: function(c) { 13521 return '\\u' + ('0000' + c.charCodeAt(0).toString(16)).slice(-4); 13522 }, 13523 13524 escape: function(value) { 13525 if (isString(value)) return "'" + value.replace(this.stringEscapeRegex, this.stringEscapeFn) + "'"; 13526 if (isNumber(value)) return value.toString(); 13527 if (value === true) return 'true'; 13528 if (value === false) return 'false'; 13529 if (value === null) return 'null'; 13530 if (typeof value === 'undefined') return 'undefined'; 13531 13532 throw $parseMinErr('esc', 'IMPOSSIBLE'); 13533 }, 13534 13535 nextId: function(skip, init) { 13536 var id = 'v' + (this.state.nextId++); 13537 if (!skip) { 13538 this.current().vars.push(id + (init ? '=' + init : '')); 13539 } 13540 return id; 13541 }, 13542 13543 current: function() { 13544 return this.state[this.state.computing]; 13545 } 13546}; 13547 13548 13549function ASTInterpreter(astBuilder, $filter) { 13550 this.astBuilder = astBuilder; 13551 this.$filter = $filter; 13552} 13553 13554ASTInterpreter.prototype = { 13555 compile: function(expression, expensiveChecks) { 13556 var self = this; 13557 var ast = this.astBuilder.ast(expression); 13558 this.expression = expression; 13559 this.expensiveChecks = expensiveChecks; 13560 findConstantAndWatchExpressions(ast, self.$filter); 13561 var assignable; 13562 var assign; 13563 if ((assignable = assignableAST(ast))) { 13564 assign = this.recurse(assignable); 13565 } 13566 var toWatch = getInputs(ast.body); 13567 var inputs; 13568 if (toWatch) { 13569 inputs = []; 13570 forEach(toWatch, function(watch, key) { 13571 var input = self.recurse(watch); 13572 watch.input = input; 13573 inputs.push(input); 13574 watch.watchId = key; 13575 }); 13576 } 13577 var expressions = []; 13578 forEach(ast.body, function(expression) { 13579 expressions.push(self.recurse(expression.expression)); 13580 }); 13581 var fn = ast.body.length === 0 ? function() {} : 13582 ast.body.length === 1 ? expressions[0] : 13583 function(scope, locals) { 13584 var lastValue; 13585 forEach(expressions, function(exp) { 13586 lastValue = exp(scope, locals); 13587 }); 13588 return lastValue; 13589 }; 13590 if (assign) { 13591 fn.assign = function(scope, value, locals) { 13592 return assign(scope, locals, value); 13593 }; 13594 } 13595 if (inputs) { 13596 fn.inputs = inputs; 13597 } 13598 fn.literal = isLiteral(ast); 13599 fn.constant = isConstant(ast); 13600 return fn; 13601 }, 13602 13603 recurse: function(ast, context, create) { 13604 var left, right, self = this, args, expression; 13605 if (ast.input) { 13606 return this.inputs(ast.input, ast.watchId); 13607 } 13608 switch (ast.type) { 13609 case AST.Literal: 13610 return this.value(ast.value, context); 13611 case AST.UnaryExpression: 13612 right = this.recurse(ast.argument); 13613 return this['unary' + ast.operator](right, context); 13614 case AST.BinaryExpression: 13615 left = this.recurse(ast.left); 13616 right = this.recurse(ast.right); 13617 return this['binary' + ast.operator](left, right, context); 13618 case AST.LogicalExpression: 13619 left = this.recurse(ast.left); 13620 right = this.recurse(ast.right); 13621 return this['binary' + ast.operator](left, right, context); 13622 case AST.ConditionalExpression: 13623 return this['ternary?:']( 13624 this.recurse(ast.test), 13625 this.recurse(ast.alternate), 13626 this.recurse(ast.consequent), 13627 context 13628 ); 13629 case AST.Identifier: 13630 ensureSafeMemberName(ast.name, self.expression); 13631 return self.identifier(ast.name, 13632 self.expensiveChecks || isPossiblyDangerousMemberName(ast.name), 13633 context, create, self.expression); 13634 case AST.MemberExpression: 13635 left = this.recurse(ast.object, false, !!create); 13636 if (!ast.computed) { 13637 ensureSafeMemberName(ast.property.name, self.expression); 13638 right = ast.property.name; 13639 } 13640 if (ast.computed) right = this.recurse(ast.property); 13641 return ast.computed ? 13642 this.computedMember(left, right, context, create, self.expression) : 13643 this.nonComputedMember(left, right, self.expensiveChecks, context, create, self.expression); 13644 case AST.CallExpression: 13645 args = []; 13646 forEach(ast.arguments, function(expr) { 13647 args.push(self.recurse(expr)); 13648 }); 13649 if (ast.filter) right = this.$filter(ast.callee.name); 13650 if (!ast.filter) right = this.recurse(ast.callee, true); 13651 return ast.filter ? 13652 function(scope, locals, assign, inputs) { 13653 var values = []; 13654 for (var i = 0; i < args.length; ++i) { 13655 values.push(args[i](scope, locals, assign, inputs)); 13656 } 13657 var value = right.apply(undefined, values, inputs); 13658 return context ? {context: undefined, name: undefined, value: value} : value; 13659 } : 13660 function(scope, locals, assign, inputs) { 13661 var rhs = right(scope, locals, assign, inputs); 13662 var value; 13663 if (rhs.value != null) { 13664 ensureSafeObject(rhs.context, self.expression); 13665 ensureSafeFunction(rhs.value, self.expression); 13666 var values = []; 13667 for (var i = 0; i < args.length; ++i) { 13668 values.push(ensureSafeObject(args[i](scope, locals, assign, inputs), self.expression)); 13669 } 13670 value = ensureSafeObject(rhs.value.apply(rhs.context, values), self.expression); 13671 } 13672 return context ? {value: value} : value; 13673 }; 13674 case AST.AssignmentExpression: 13675 left = this.recurse(ast.left, true, 1); 13676 right = this.recurse(ast.right); 13677 return function(scope, locals, assign, inputs) { 13678 var lhs = left(scope, locals, assign, inputs); 13679 var rhs = right(scope, locals, assign, inputs); 13680 ensureSafeObject(lhs.value, self.expression); 13681 lhs.context[lhs.name] = rhs; 13682 return context ? {value: rhs} : rhs; 13683 }; 13684 case AST.ArrayExpression: 13685 args = []; 13686 forEach(ast.elements, function(expr) { 13687 args.push(self.recurse(expr)); 13688 }); 13689 return function(scope, locals, assign, inputs) { 13690 var value = []; 13691 for (var i = 0; i < args.length; ++i) { 13692 value.push(args[i](scope, locals, assign, inputs)); 13693 } 13694 return context ? {value: value} : value; 13695 }; 13696 case AST.ObjectExpression: 13697 args = []; 13698 forEach(ast.properties, function(property) { 13699 args.push({key: property.key.type === AST.Identifier ? 13700 property.key.name : 13701 ('' + property.key.value), 13702 value: self.recurse(property.value) 13703 }); 13704 }); 13705 return function(scope, locals, assign, inputs) { 13706 var value = {}; 13707 for (var i = 0; i < args.length; ++i) { 13708 value[args[i].key] = args[i].value(scope, locals, assign, inputs); 13709 } 13710 return context ? {value: value} : value; 13711 }; 13712 case AST.ThisExpression: 13713 return function(scope) { 13714 return context ? {value: scope} : scope; 13715 }; 13716 case AST.NGValueParameter: 13717 return function(scope, locals, assign, inputs) { 13718 return context ? {value: assign} : assign; 13719 }; 13720 } 13721 }, 13722 13723 'unary+': function(argument, context) { 13724 return function(scope, locals, assign, inputs) { 13725 var arg = argument(scope, locals, assign, inputs); 13726 if (isDefined(arg)) { 13727 arg = +arg; 13728 } else { 13729 arg = 0; 13730 } 13731 return context ? {value: arg} : arg; 13732 }; 13733 }, 13734 'unary-': function(argument, context) { 13735 return function(scope, locals, assign, inputs) { 13736 var arg = argument(scope, locals, assign, inputs); 13737 if (isDefined(arg)) { 13738 arg = -arg; 13739 } else { 13740 arg = 0; 13741 } 13742 return context ? {value: arg} : arg; 13743 }; 13744 }, 13745 'unary!': function(argument, context) { 13746 return function(scope, locals, assign, inputs) { 13747 var arg = !argument(scope, locals, assign, inputs); 13748 return context ? {value: arg} : arg; 13749 }; 13750 }, 13751 'binary+': function(left, right, context) { 13752 return function(scope, locals, assign, inputs) { 13753 var lhs = left(scope, locals, assign, inputs); 13754 var rhs = right(scope, locals, assign, inputs); 13755 var arg = plusFn(lhs, rhs); 13756 return context ? {value: arg} : arg; 13757 }; 13758 }, 13759 'binary-': function(left, right, context) { 13760 return function(scope, locals, assign, inputs) { 13761 var lhs = left(scope, locals, assign, inputs); 13762 var rhs = right(scope, locals, assign, inputs); 13763 var arg = (isDefined(lhs) ? lhs : 0) - (isDefined(rhs) ? rhs : 0); 13764 return context ? {value: arg} : arg; 13765 }; 13766 }, 13767 'binary*': function(left, right, context) { 13768 return function(scope, locals, assign, inputs) { 13769 var arg = left(scope, locals, assign, inputs) * right(scope, locals, assign, inputs); 13770 return context ? {value: arg} : arg; 13771 }; 13772 }, 13773 'binary/': function(left, right, context) { 13774 return function(scope, locals, assign, inputs) { 13775 var arg = left(scope, locals, assign, inputs) / right(scope, locals, assign, inputs); 13776 return context ? {value: arg} : arg; 13777 }; 13778 }, 13779 'binary%': function(left, right, context) { 13780 return function(scope, locals, assign, inputs) { 13781 var arg = left(scope, locals, assign, inputs) % right(scope, locals, assign, inputs); 13782 return context ? {value: arg} : arg; 13783 }; 13784 }, 13785 'binary===': function(left, right, context) { 13786 return function(scope, locals, assign, inputs) { 13787 var arg = left(scope, locals, assign, inputs) === right(scope, locals, assign, inputs); 13788 return context ? {value: arg} : arg; 13789 }; 13790 }, 13791 'binary!==': function(left, right, context) { 13792 return function(scope, locals, assign, inputs) { 13793 var arg = left(scope, locals, assign, inputs) !== right(scope, locals, assign, inputs); 13794 return context ? {value: arg} : arg; 13795 }; 13796 }, 13797 'binary==': function(left, right, context) { 13798 return function(scope, locals, assign, inputs) { 13799 var arg = left(scope, locals, assign, inputs) == right(scope, locals, assign, inputs); 13800 return context ? {value: arg} : arg; 13801 }; 13802 }, 13803 'binary!=': function(left, right, context) { 13804 return function(scope, locals, assign, inputs) { 13805 var arg = left(scope, locals, assign, inputs) != right(scope, locals, assign, inputs); 13806 return context ? {value: arg} : arg; 13807 }; 13808 }, 13809 'binary<': function(left, right, context) { 13810 return function(scope, locals, assign, inputs) { 13811 var arg = left(scope, locals, assign, inputs) < right(scope, locals, assign, inputs); 13812 return context ? {value: arg} : arg; 13813 }; 13814 }, 13815 'binary>': function(left, right, context) { 13816 return function(scope, locals, assign, inputs) { 13817 var arg = left(scope, locals, assign, inputs) > right(scope, locals, assign, inputs); 13818 return context ? {value: arg} : arg; 13819 }; 13820 }, 13821 'binary<=': function(left, right, context) { 13822 return function(scope, locals, assign, inputs) { 13823 var arg = left(scope, locals, assign, inputs) <= right(scope, locals, assign, inputs); 13824 return context ? {value: arg} : arg; 13825 }; 13826 }, 13827 'binary>=': function(left, right, context) { 13828 return function(scope, locals, assign, inputs) { 13829 var arg = left(scope, locals, assign, inputs) >= right(scope, locals, assign, inputs); 13830 return context ? {value: arg} : arg; 13831 }; 13832 }, 13833 'binary&&': function(left, right, context) { 13834 return function(scope, locals, assign, inputs) { 13835 var arg = left(scope, locals, assign, inputs) && right(scope, locals, assign, inputs); 13836 return context ? {value: arg} : arg; 13837 }; 13838 }, 13839 'binary||': function(left, right, context) { 13840 return function(scope, locals, assign, inputs) { 13841 var arg = left(scope, locals, assign, inputs) || right(scope, locals, assign, inputs); 13842 return context ? {value: arg} : arg; 13843 }; 13844 }, 13845 'ternary?:': function(test, alternate, consequent, context) { 13846 return function(scope, locals, assign, inputs) { 13847 var arg = test(scope, locals, assign, inputs) ? alternate(scope, locals, assign, inputs) : consequent(scope, locals, assign, inputs); 13848 return context ? {value: arg} : arg; 13849 }; 13850 }, 13851 value: function(value, context) { 13852 return function() { return context ? {context: undefined, name: undefined, value: value} : value; }; 13853 }, 13854 identifier: function(name, expensiveChecks, context, create, expression) { 13855 return function(scope, locals, assign, inputs) { 13856 var base = locals && (name in locals) ? locals : scope; 13857 if (create && create !== 1 && base && !(base[name])) { 13858 base[name] = {}; 13859 } 13860 var value = base ? base[name] : undefined; 13861 if (expensiveChecks) { 13862 ensureSafeObject(value, expression); 13863 } 13864 if (context) { 13865 return {context: base, name: name, value: value}; 13866 } else { 13867 return value; 13868 } 13869 }; 13870 }, 13871 computedMember: function(left, right, context, create, expression) { 13872 return function(scope, locals, assign, inputs) { 13873 var lhs = left(scope, locals, assign, inputs); 13874 var rhs; 13875 var value; 13876 if (lhs != null) { 13877 rhs = right(scope, locals, assign, inputs); 13878 ensureSafeMemberName(rhs, expression); 13879 if (create && create !== 1 && lhs && !(lhs[rhs])) { 13880 lhs[rhs] = {}; 13881 } 13882 value = lhs[rhs]; 13883 ensureSafeObject(value, expression); 13884 } 13885 if (context) { 13886 return {context: lhs, name: rhs, value: value}; 13887 } else { 13888 return value; 13889 } 13890 }; 13891 }, 13892 nonComputedMember: function(left, right, expensiveChecks, context, create, expression) { 13893 return function(scope, locals, assign, inputs) { 13894 var lhs = left(scope, locals, assign, inputs); 13895 if (create && create !== 1 && lhs && !(lhs[right])) { 13896 lhs[right] = {}; 13897 } 13898 var value = lhs != null ? lhs[right] : undefined; 13899 if (expensiveChecks || isPossiblyDangerousMemberName(right)) { 13900 ensureSafeObject(value, expression); 13901 } 13902 if (context) { 13903 return {context: lhs, name: right, value: value}; 13904 } else { 13905 return value; 13906 } 13907 }; 13908 }, 13909 inputs: function(input, watchId) { 13910 return function(scope, value, locals, inputs) {
13911 if (inputs) return inputs[watchId]; 13912 return input(scope, value, locals); 13913 }; 13914 } 13915}; 13916 13917/** 13918 * @constructor 13919 */ 13920var Parser = function(lexer, $filter, options) { 13921 this.lexer = lexer; 13922 this.$filter = $filter; 13923 this.options = options; 13924 this.ast = new AST(this.lexer); 13925 this.astCompiler = options.csp ? new ASTInterpreter(this.ast, $filter) : 13926 new ASTCompiler(this.ast, $filter); 13927}; 13928 13929Parser.prototype = { 13930 constructor: Parser, 13931 13932 parse: function(text) { 13933 return this.astCompiler.compile(text, this.options.expensiveChecks); 13934 } 13935}; 13936 13937////////////////////////////////////////////////// 13938// Parser helper functions 13939////////////////////////////////////////////////// 13940 13941function setter(obj, path, setValue, fullExp) { 13942 ensureSafeObject(obj, fullExp); 13943 13944 var element = path.split('.'), key; 13945 for (var i = 0; element.length > 1; i++) { 13946 key = ensureSafeMemberName(element.shift(), fullExp); 13947 var propertyObj = ensureSafeObject(obj[key], fullExp); 13948 if (!propertyObj) { 13949 propertyObj = {}; 13950 obj[key] = propertyObj; 13951 } 13952 obj = propertyObj; 13953 } 13954 key = ensureSafeMemberName(element.shift(), fullExp); 13955 ensureSafeObject(obj[key], fullExp); 13956 obj[key] = setValue; 13957 return setValue; 13958} 13959 13960var getterFnCacheDefault = createMap(); 13961var getterFnCacheExpensive = createMap(); 13962 13963function isPossiblyDangerousMemberName(name) { 13964 return name == 'constructor'; 13965} 13966 13967var objectValueOf = Object.prototype.valueOf; 13968 13969function getValueOf(value) { 13970 return isFunction(value.valueOf) ? value.valueOf() : objectValueOf.call(value); 13971} 13972 13973/////////////////////////////////// 13974 13975/** 13976 * @ngdoc service 13977 * @name $parse 13978 * @kind function 13979 * 13980 * @description 13981 * 13982 * Converts Angular {@link guide/expression expression} into a function. 13983 * 13984 * ```js 13985 * var getter = $parse('user.name'); 13986 * var setter = getter.assign; 13987 * var context = {user:{name:'angular'}}; 13988 * var locals = {user:{name:'local'}}; 13989 * 13990 * expect(getter(context)).toEqual('angular'); 13991 * setter(context, 'newValue'); 13992 * expect(context.user.name).toEqual('newValue'); 13993 * expect(getter(context, locals)).toEqual('local'); 13994 * ``` 13995 * 13996 * 13997 * @param {string} expression String expression to compile. 13998 * @returns {function(context, locals)} a function which represents the compiled expression: 13999 * 14000 * * `context` â `{object}` â an object against which any expressions embedded in the strings 14001 * are evaluated against (typically a scope object). 14002 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 14003 * `context`. 14004 * 14005 * The returned function also has the following properties: 14006 * * `literal` â `{boolean}` â whether the expression's top-level node is a JavaScript 14007 * literal. 14008 * * `constant` â `{boolean}` â whether the expression is made entirely of JavaScript 14009 * constant literals. 14010 * * `assign` â `{?function(context, value)}` â if the expression is assignable, this will be 14011 * set to a function to change its value on the given context. 14012 * 14013 */ 14014 14015 14016/** 14017 * @ngdoc provider 14018 * @name $parseProvider 14019 * 14020 * @description 14021 * `$parseProvider` can be used for configuring the default behavior of the {@link ng.$parse $parse} 14022 * service. 14023 */ 14024function $ParseProvider() { 14025 var cacheDefault = createMap(); 14026 var cacheExpensive = createMap(); 14027 14028 this.$get = ['$filter', '$sniffer', function($filter, $sniffer) { 14029 var $parseOptions = { 14030 csp: $sniffer.csp, 14031 expensiveChecks: false 14032 }, 14033 $parseOptionsExpensive = { 14034 csp: $sniffer.csp, 14035 expensiveChecks: true 14036 }; 14037 14038 return function $parse(exp, interceptorFn, expensiveChecks) { 14039 var parsedExpression, oneTime, cacheKey; 14040 14041 switch (typeof exp) { 14042 case 'string': 14043 exp = exp.trim(); 14044 cacheKey = exp; 14045 14046 var cache = (expensiveChecks ? cacheExpensive : cacheDefault); 14047 parsedExpression = cache[cacheKey]; 14048 14049 if (!parsedExpression) { 14050 if (exp.charAt(0) === ':' && exp.charAt(1) === ':') { 14051 oneTime = true; 14052 exp = exp.substring(2); 14053 } 14054 var parseOptions = expensiveChecks ? $parseOptionsExpensive : $parseOptions; 14055 var lexer = new Lexer(parseOptions); 14056 var parser = new Parser(lexer, $filter, parseOptions); 14057 parsedExpression = parser.parse(exp); 14058 if (parsedExpression.constant) { 14059 parsedExpression.$$watchDelegate = constantWatchDelegate; 14060 } else if (oneTime) { 14061 parsedExpression.$$watchDelegate = parsedExpression.literal ? 14062 oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; 14063 } else if (parsedExpression.inputs) { 14064 parsedExpression.$$watchDelegate = inputsWatchDelegate; 14065 } 14066 cache[cacheKey] = parsedExpression; 14067 } 14068 return addInterceptor(parsedExpression, interceptorFn); 14069 14070 case 'function': 14071 return addInterceptor(exp, interceptorFn); 14072 14073 default: 14074 return noop; 14075 } 14076 }; 14077 14078 function expressionInputDirtyCheck(newValue, oldValueOfValue) { 14079 14080 if (newValue == null || oldValueOfValue == null) { // null/undefined 14081 return newValue === oldValueOfValue; 14082 } 14083 14084 if (typeof newValue === 'object') { 14085 14086 // attempt to convert the value to a primitive type 14087 // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can 14088 // be cheaply dirty-checked 14089 newValue = getValueOf(newValue); 14090 14091 if (typeof newValue === 'object') { 14092 // objects/arrays are not supported - deep-watching them would be too expensive 14093 return false;
14094 } 14095 14096 // fall-through to the primitive equality check 14097 } 14098 14099 //Primitive or NaN 14100 return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); 14101 } 14102 14103 function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression, prettyPrintExpression) { 14104 var inputExpressions = parsedExpression.inputs; 14105 var lastResult; 14106 14107 if (inputExpressions.length === 1) { 14108 var oldInputValueOf = expressionInputDirtyCheck; // init to something unique so that equals check fails 14109 inputExpressions = inputExpressions[0]; 14110 return scope.$watch(function expressionInputWatch(scope) { 14111 var newInputValue = inputExpressions(scope); 14112 if (!expressionInputDirtyCheck(newInputValue, oldInputValueOf)) { 14113 lastResult = parsedExpression(scope, undefined, undefined, [newInputValue]); 14114 oldInputValueOf = newInputValue && getValueOf(newInputValue); 14115 } 14116 return lastResult; 14117 }, listener, objectEquality, prettyPrintExpression); 14118 } 14119 14120 var oldInputValueOfValues = []; 14121 var oldInputValues = []; 14122 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 14123 oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails 14124 oldInputValues[i] = null; 14125 } 14126 14127 return scope.$watch(function expressionInputsWatch(scope) { 14128 var changed = false; 14129 14130 for (var i = 0, ii = inputExpressions.length; i < ii; i++) { 14131 var newInputValue = inputExpressions[i](scope); 14132 if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { 14133 oldInputValues[i] = newInputValue; 14134 oldInputValueOfValues[i] = newInputValue && getValueOf(newInputValue); 14135 } 14136 } 14137 14138 if (changed) { 14139 lastResult = parsedExpression(scope, undefined, undefined, oldInputValues); 14140 } 14141 14142 return lastResult; 14143 }, listener, objectEquality, prettyPrintExpression); 14144 } 14145 14146 function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14147 var unwatch, lastValue; 14148 return unwatch = scope.$watch(function oneTimeWatch(scope) { 14149 return parsedExpression(scope); 14150 }, function oneTimeListener(value, old, scope) { 14151 lastValue = value; 14152 if (isFunction(listener)) { 14153 listener.apply(this, arguments); 14154 } 14155 if (isDefined(value)) { 14156 scope.$$postDigest(function() { 14157 if (isDefined(lastValue)) { 14158 unwatch(); 14159 } 14160 }); 14161 } 14162 }, objectEquality); 14163 } 14164 14165 function oneTimeLiteralWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14166 var unwatch, lastValue; 14167 return unwatch = scope.$watch(function oneTimeWatch(scope) { 14168 return parsedExpression(scope); 14169 }, function oneTimeListener(value, old, scope) { 14170 lastValue = value; 14171 if (isFunction(listener)) { 14172 listener.call(this, value, old, scope); 14173 } 14174 if (isAllDefined(value)) { 14175 scope.$$postDigest(function() { 14176 if (isAllDefined(lastValue)) unwatch(); 14177 }); 14178 } 14179 }, objectEquality); 14180 14181 function isAllDefined(value) { 14182 var allDefined = true; 14183 forEach(value, function(val) { 14184 if (!isDefined(val)) allDefined = false; 14185 }); 14186 return allDefined; 14187 } 14188 } 14189 14190 function constantWatchDelegate(scope, listener, objectEquality, parsedExpression) { 14191 var unwatch; 14192 return unwatch = scope.$watch(function constantWatch(scope) { 14193 return parsedExpression(scope); 14194 }, function constantListener(value, old, scope) { 14195 if (isFunction(listener)) { 14196 listener.apply(this, arguments); 14197 } 14198 unwatch(); 14199 }, objectEquality); 14200 } 14201 14202 function addInterceptor(parsedExpression, interceptorFn) { 14203 if (!interceptorFn) return parsedExpression; 14204 var watchDelegate = parsedExpression.$$watchDelegate; 14205 14206 var regularWatch = 14207 watchDelegate !== oneTimeLiteralWatchDelegate && 14208 watchDelegate !== oneTimeWatchDelegate; 14209 14210 var fn = regularWatch ? function regularInterceptedExpression(scope, locals, assign, inputs) { 14211 var value = parsedExpression(scope, locals, assign, inputs); 14212 return interceptorFn(value, scope, locals); 14213 } : function oneTimeInterceptedExpression(scope, locals, assign, inputs) { 14214 var value = parsedExpression(scope, locals, assign, inputs); 14215 var result = interceptorFn(value, scope, locals); 14216 // we only return the interceptor's result if the 14217 // initial value is defined (for bind-once) 14218 return isDefined(value) ? result : value; 14219 }; 14220
14221 // Propagate $$watchDelegates other then inputsWatchDelegate 14222 if (parsedExpression.$$watchDelegate && 14223 parsedExpression.$$watchDelegate !== inputsWatchDelegate) { 14224 fn.$$watchDelegate = parsedExpression.$$watchDelegate; 14225 } else if (!interceptorFn.$stateful) { 14226 // If there is an interceptor, but no watchDelegate then treat the interceptor like 14227 // we treat filters - it is assumed to be a pure function unless flagged with $stateful 14228 fn.$$watchDelegate = inputsWatchDelegate; 14229 fn.inputs = parsedExpression.inputs ? parsedExpression.inputs : [parsedExpression]; 14230 } 14231 14232 return fn; 14233 } 14234 }]; 14235} 14236 14237/** 14238 * @ngdoc service 14239 * @name $q 14240 * @requires $rootScope 14241 * 14242 * @description 14243 * A service that helps you run functions asynchronously, and use their return values (or exceptions) 14244 * when they are done processing. 14245 * 14246 * This is an implementation of promises/deferred objects inspired by 14247 * [Kris Kowal's Q](https://github.com/kriskowal/q). 14248 * 14249 * $q can be used in two fashions --- one which is more similar to Kris Kowal's Q or jQuery's Deferred 14250 * implementations, and the other which resembles ES6 promises to some degree. 14251 * 14252 * # $q constructor 14253 * 14254 * The streamlined ES6 style promise is essentially just using $q as a constructor which takes a `resolver` 14255 * function as the first argument. This is similar to the native Promise implementation from ES6 Harmony, 14256 * see [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise). 14257 * 14258 * While the constructor-style use is supported, not all of the supporting methods from ES6 Harmony promises are 14259 * available yet. 14260 * 14261 * It can be used like so: 14262 * 14263 * ```js 14264 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 14265 * // are available in the current lexical scope (they could have been injected or passed in). 14266 * 14267 * function asyncGreet(name) { 14268 * // perform some asynchronous operation, resolve or reject the promise when appropriate. 14269 * return $q(function(resolve, reject) { 14270 * setTimeout(function() { 14271 * if (okToGreet(name)) { 14272 * resolve('Hello, ' + name + '!'); 14273 * } else { 14274 * reject('Greeting ' + name + ' is not allowed.'); 14275 * } 14276 * }, 1000); 14277 * }); 14278 * } 14279 * 14280 * var promise = asyncGreet('Robin Hood'); 14281 * promise.then(function(greeting) { 14282 * alert('Success: ' + greeting); 14283 * }, function(reason) { 14284 * alert('Failed: ' + reason); 14285 * }); 14286 * ``` 14287 * 14288 * Note: progress/notify callbacks are not currently supported via the ES6-style interface. 14289 * 14290 * However, the more traditional CommonJS-style usage is still available, and documented below. 14291 * 14292 * [The CommonJS Promise proposal](http://wiki.commonjs.org/wiki/Promises) describes a promise as an 14293 * interface for interacting with an object that represents the result of an action that is 14294 * performed asynchronously, and may or may not be finished at any given point in time. 14295 * 14296 * From the perspective of dealing with error handling, deferred and promise APIs are to 14297 * asynchronous programming what `try`, `catch` and `throw` keywords are to synchronous programming. 14298 * 14299 * ```js 14300 * // for the purpose of this example let's assume that variables `$q` and `okToGreet` 14301 * // are available in the current lexical scope (they could have been injected or passed in). 14302 * 14303 * function asyncGreet(name) { 14304 * var deferred = $q.defer(); 14305 * 14306 * setTimeout(function() { 14307 * deferred.notify('About to greet ' + name + '.'); 14308 * 14309 * if (okToGreet(name)) { 14310 * deferred.resolve('Hello, ' + name + '!'); 14311 * } else { 14312 * deferred.reject('Greeting ' + name + ' is not allowed.'); 14313 * } 14314 * }, 1000); 14315 * 14316 * return deferred.promise; 14317 * } 14318 * 14319 * var promise = asyncGreet('Robin Hood'); 14320 * promise.then(function(greeting) { 14321 * alert('Success: ' + greeting); 14322 * }, function(reason) { 14323 * alert('Failed: ' + reason); 14324 * }, function(update) { 14325 * alert('Got notification: ' + update); 14326 * }); 14327 * ``` 14328 * 14329 * At first it might not be obvious why this extra complexity is worth the trouble. The payoff 14330 * comes in the way of guarantees that promise and deferred APIs make, see 14331 * https://github.com/kriskowal/uncommonjs/blob/master/promises/specification.md. 14332 *
14333 * Additionally the promise api allows for composition that is very hard to do with the 14334 * traditional callback ([CPS](http://en.wikipedia.org/wiki/Continuation-passing_style)) approach. 14335 * For more on this please see the [Q documentation](https://github.com/kriskowal/q) especially the 14336 * section on serial or parallel joining of promises. 14337 * 14338 * # The Deferred API 14339 * 14340 * A new instance of deferred is constructed by calling `$q.defer()`. 14341 * 14342 * The purpose of the deferred object is to expose the associated Promise instance as well as APIs 14343 * that can be used for signaling the successful or unsuccessful completion, as well as the status 14344 * of the task. 14345 * 14346 * **Methods** 14347 * 14348 * - `resolve(value)` â resolves the derived promise with the `value`. If the value is a rejection 14349 * constructed via `$q.reject`, the promise will be rejected instead. 14350 * - `reject(reason)` â rejects the derived promise with the `reason`. This is equivalent to 14351 * resolving it with a rejection constructed via `$q.reject`. 14352 * - `notify(value)` - provides updates on the status of the promise's execution. This may be called 14353 * multiple times before the promise is either resolved or rejected. 14354 * 14355 * **Properties** 14356 * 14357 * - promise â `{Promise}` â promise object associated with this deferred. 14358 * 14359 * 14360 * # The Promise API 14361 * 14362 * A new promise instance is created when a deferred instance is created and can be retrieved by 14363 * calling `deferred.promise`. 14364 * 14365 * The purpose of the promise object is to allow for interested parties to get access to the result 14366 * of the deferred task when it completes. 14367 * 14368 * **Methods** 14369 * 14370 * - `then(successCallback, errorCallback, notifyCallback)` â regardless of when the promise was or 14371 * will be resolved or rejected, `then` calls one of the success or error callbacks asynchronously 14372 * as soon as the result is available. The callbacks are called with a single argument: the result 14373 * or rejection reason. Additionally, the notify callback may be called zero or more times to 14374 * provide a progress indication, before the promise is resolved or rejected. 14375 * 14376 * This method *returns a new promise* which is resolved or rejected via the return value of the 14377 * `successCallback`, `errorCallback` (unless that value is a promise, in which case it is resolved
14378 * with the value which is resolved in that promise using 14379 * [promise chaining](http://www.html5rocks.com/en/tutorials/es6/promises/#toc-promises-queues)). 14380 * It also notifies via the return value of the `notifyCallback` method. The promise cannot be 14381 * resolved or rejected from the notifyCallback method. 14382 * 14383 * - `catch(errorCallback)` â shorthand for `promise.then(null, errorCallback)` 14384 * 14385 * - `finally(callback, notifyCallback)` â allows you to observe either the fulfillment or rejection of a promise, 14386 * but to do so without modifying the final value. This is useful to release resources or do some 14387 * clean-up that needs to be done whether the promise was rejected or resolved. See the [full 14388 * specification](https://github.com/kriskowal/q/wiki/API-Reference#promisefinallycallback) for 14389 * more information. 14390 * 14391 * # Chaining promises 14392 * 14393 * Because calling the `then` method of a promise returns a new derived promise, it is easily 14394 * possible to create a chain of promises: 14395 * 14396 * ```js 14397 * promiseB = promiseA.then(function(result) { 14398 * return result + 1; 14399 * }); 14400 * 14401 * // promiseB will be resolved immediately after promiseA is resolved and its value 14402 * // will be the result of promiseA incremented by 1 14403 * ``` 14404 * 14405 * It is possible to create chains of any length and since a promise can be resolved with another 14406 * promise (which will defer its resolution further), it is possible to pause/defer resolution of 14407 * the promises at any point in the chain. This makes it possible to implement powerful APIs like 14408 * $http's response interceptors. 14409 * 14410 * 14411 * # Differences between Kris Kowal's Q and $q 14412 * 14413 * There are two main differences: 14414 * 14415 * - $q is integrated with the {@link ng.$rootScope.Scope} Scope model observation 14416 * mechanism in angular, which means faster propagation of resolution or rejection into your 14417 * models and avoiding unnecessary browser repaints, which would result in flickering UI. 14418 * - Q has many more features than $q, but that comes at a cost of bytes. $q is tiny, but contains
14419 * all the important functionality needed for common async tasks. 14420 * 14421 * # Testing 14422 * 14423 * ```js 14424 * it('should simulate promise', inject(function($q, $rootScope) { 14425 * var deferred = $q.defer(); 14426 * var promise = deferred.promise; 14427 * var resolvedValue; 14428 * 14429 * promise.then(function(value) { resolvedValue = value; }); 14430 * expect(resolvedValue).toBeUndefined(); 14431 * 14432 * // Simulate resolving of promise 14433 * deferred.resolve(123); 14434 * // Note that the 'then' function does not get called synchronously. 14435 * // This is because we want the promise API to always be async, whether or not 14436 * // it got called synchronously or asynchronously. 14437 * expect(resolvedValue).toBeUndefined(); 14438 * 14439 * // Propagate promise resolution to 'then' functions using $apply(). 14440 * $rootScope.$apply(); 14441 * expect(resolvedValue).toEqual(123); 14442 * })); 14443 * ``` 14444 * 14445 * @param {function(function, function)} resolver Function which is responsible for resolving or 14446 * rejecting the newly created promise. The first parameter is a function which resolves the 14447 * promise, the second parameter is a function which rejects the promise. 14448 * 14449 * @returns {Promise} The newly created promise. 14450 */ 14451function $QProvider() { 14452 14453 this.$get = ['$rootScope', '$exceptionHandler', function($rootScope, $exceptionHandler) { 14454 return qFactory(function(callback) { 14455 $rootScope.$evalAsync(callback); 14456 }, $exceptionHandler); 14457 }]; 14458} 14459 14460function $$QProvider() { 14461 this.$get = ['$browser', '$exceptionHandler', function($browser, $exceptionHandler) { 14462 return qFactory(function(callback) { 14463 $browser.defer(callback); 14464 }, $exceptionHandler); 14465 }]; 14466} 14467 14468/** 14469 * Constructs a promise manager. 14470 * 14471 * @param {function(function)} nextTick Function for executing functions in the next turn. 14472 * @param {function(...*)} exceptionHandler Function into which unexpected exceptions are passed for 14473 * debugging purposes. 14474 * @returns {object} Promise manager. 14475 */ 14476function qFactory(nextTick, exceptionHandler) { 14477 var $qMinErr = minErr('$q', TypeError); 14478 function callOnce(self, resolveFn, rejectFn) { 14479 var called = false; 14480 function wrap(fn) { 14481 return function(value) { 14482 if (called) return; 14483 called = true; 14484 fn.call(self, value); 14485 }; 14486 } 14487 14488 return [wrap(resolveFn), wrap(rejectFn)]; 14489 } 14490 14491 /** 14492 * @ngdoc method 14493 * @name ng.$q#defer 14494 * @kind function 14495 * 14496 * @description 14497 * Creates a `Deferred` object which represents a task which will finish in the future. 14498 * 14499 * @returns {Deferred} Returns a new instance of deferred. 14500 */ 14501 var defer = function() { 14502 return new Deferred(); 14503 }; 14504 14505 function Promise() { 14506 this.$$state = { status: 0 }; 14507 } 14508 14509 Promise.prototype = { 14510 then: function(onFulfilled, onRejected, progressBack) { 14511 var result = new Deferred(); 14512 14513 this.$$state.pending = this.$$state.pending || []; 14514 this.$$state.pending.push([result, onFulfilled, onRejected, progressBack]); 14515 if (this.$$state.status > 0) scheduleProcessQueue(this.$$state); 14516 14517 return result.promise; 14518 }, 14519 14520 "catch": function(callback) { 14521 return this.then(null, callback); 14522 }, 14523 14524 "finally": function(callback, progressBack) { 14525 return this.then(function(value) { 14526 return handleCallback(value, true, callback); 14527 }, function(error) { 14528 return handleCallback(error, false, callback); 14529 }, progressBack); 14530 } 14531 }; 14532 14533 //Faster, more basic than angular.bind http://jsperf.com/angular-bind-vs-custom-vs-native 14534 function simpleBind(context, fn) { 14535 return function(value) { 14536 fn.call(context, value); 14537 }; 14538 } 14539 14540 function processQueue(state) { 14541 var fn, deferred, pending; 14542 14543 pending = state.pending; 14544 state.processScheduled = false; 14545 state.pending = undefined; 14546 for (var i = 0, ii = pending.length; i < ii; ++i) { 14547 deferred = pending[i][0]; 14548 fn = pending[i][state.status]; 14549 try { 14550 if (isFunction(fn)) { 14551 deferred.resolve(fn(state.value)); 14552 } else if (state.status === 1) { 14553 deferred.resolve(state.value); 14554 } else { 14555 deferred.reject(state.value); 14556 } 14557 } catch (e) { 14558 deferred.reject(e); 14559 exceptionHandler(e); 14560 } 14561 } 14562 } 14563 14564 function scheduleProcessQueue(state) { 14565 if (state.processScheduled || !state.pending) return; 14566 state.processScheduled = true; 14567 nextTick(function() { processQueue(state); }); 14568 } 14569 14570 function Deferred() { 14571 this.promise = new Promise(); 14572 //Necessary to support unbound execution :/ 14573 this.resolve = simpleBind(this, this.resolve); 14574 this.reject = simpleBind(this, this.reject); 14575 this.notify = simpleBind(this, this.notify); 14576 } 14577 14578 Deferred.prototype = { 14579 resolve: function(val) { 14580 if (this.promise.$$state.status) return; 14581 if (val === this.promise) { 14582 this.$$reject($qMinErr( 14583 'qcycle', 14584 "Expected promise to be resolved with value other than
14584itself '{0}'", 14585 val)); 14586 } else { 14587 this.$$resolve(val); 14588 } 14589 14590 }, 14591 14592 $$resolve: function(val) { 14593 var then, fns; 14594 14595 fns = callOnce(this, this.$$resolve, this.$$reject); 14596 try { 14597 if ((isObject(val) || isFunction(val))) then = val && val.then; 14598 if (isFunction(then)) { 14599 this.promise.$$state.status = -1; 14600 then.call(val, fns[0], fns[1], this.notify); 14601 } else { 14602 this.promise.$$state.value = val; 14603 this.promise.$$state.status = 1; 14604 scheduleProcessQueue(this.promise.$$state); 14605 } 14606 } catch (e) { 14607 fns[1](e); 14608 exceptionHandler(e); 14609 } 14610 }, 14611 14612 reject: function(reason) { 14613 if (this.promise.$$state.status) return; 14614 this.$$reject(reason); 14615 }, 14616 14617 $$reject: function(reason) { 14618 this.promise.$$state.value = reason; 14619 this.promise.$$state.status = 2; 14620 scheduleProcessQueue(this.promise.$$state); 14621 }, 14622 14623 notify: function(progress) { 14624 var callbacks = this.promise.$$state.pending; 14625 14626 if ((this.promise.$$state.status <= 0) && callbacks && callbacks.length) { 14627 nextTick(function() { 14628 var callback, result; 14629 for (var i = 0, ii = callbacks.length; i < ii; i++) { 14630 result = callbacks[i][0]; 14631 callback = callbacks[i][3]; 14632 try { 14633 result.notify(isFunction(callback) ? callback(progress) : progress); 14634 } catch (e) { 14635 exceptionHandler(e); 14636 } 14637 } 14638 }); 14639 } 14640 } 14641 }; 14642 14643 /** 14644 * @ngdoc method 14645 * @name $q#reject 14646 * @kind function 14647 * 14648 * @description 14649 * Creates a promise that is resolved as rejected with the specified `reason`. This api should be 14650 * used to forward rejection in a chain of promises. If you are dealing with the last promise in 14651 * a promise chain, you don't need to worry about it. 14652 * 14653 * When comparing deferreds/promises to the familiar behavior of try/catch/throw, think of 14654 * `reject` as the `throw` keyword in JavaScript. This also means that if you "catch" an error via 14655 * a promise error callback and you want to forward the error to the promise derived from the 14656 * current promise, you have to "rethrow" the error by returning a rejection constructed via 14657 * `reject`. 14658 * 14659 * ```js 14660 * promiseB = promiseA.then(function(result) { 14661 * // success: do something and resolve promiseB 14662 * // with the old or a new result 14663 * return result; 14664 * }, function(reason) { 14665 * // error: handle the error if possible and 14666 * // resolve promiseB with newPromiseOrValue, 14667 * // otherwise forward the rejection to promiseB 14668 * if (canHandle(reason)) { 14669 * // handle the error and recover 14670 * return newPromiseOrValue; 14671 * } 14672 * return $q.reject(reason); 14673 * }); 14674 * ``` 14675 * 14676 * @param {*} reason Constant, message, exception or an object representing the rejection reason. 14677 * @returns {Promise} Returns a promise that was already resolved as rejected with the `reason`. 14678 */ 14679 var reject = function(reason) { 14680 var result = new Deferred(); 14681 result.reject(reason); 14682 return result.promise; 14683 }; 14684 14685 var makePromise = function makePromise(value, resolved) { 14686 var result = new Deferred(); 14687 if (resolved) { 14688 result.resolve(value); 14689 } else { 14690 result.reject(value); 14691 } 14692 return result.promise; 14693 }; 14694 14695 var handleCallback = function handleCallback(value, isResolved, callback) { 14696 var callbackOutput = null; 14697 try { 14698 if (isFunction(callback)) callbackOutput = callback(); 14699 } catch (e) { 14700 return makePromise(e, false); 14701 } 14702 if (isPromiseLike(callbackOutput)) { 14703 return callbackOutput.then(function() { 14704 return makePromise(value, isResolved); 14705 }, function(error) { 14706 return makePromise(error, false); 14707 }); 14708 } else { 14709 return makePromise(value, isResolved); 14710 } 14711 }; 14712 14713 /** 14714 * @ngdoc method 14715 * @name $q#when 14716 * @kind function 14717 * 14718 * @description 14719 * Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. 14720 * This is useful when you are dealing with an object that might or might not be a promise, or if 14721 * the promise comes from a source that can't be trusted. 14722 * 14723 * @param {*} value Value or a promise 14724 * @returns {Promise} Returns a promise of the passed value or promise 14725 */ 14726 14727 14728 var when = function(value, callback, errback, progressBack) { 14729 var result = new Deferred(); 14730 result.resolve(value); 14731 return result.promise.then(callback, errback, progressBack); 14732 }; 14733 14734 /** 14735 * @ngdoc method 14736 * @name $q#resolve 14737 * @kind function 14738 * 14739 * @description 14740 * Alias of {@link ng.$q#when when} to maintain naming consistency with ES6. 14741 * 14742 * @param {*} value Value or a promise 14743 * @returns {Promise} Returns a promise of the passed value or promise 14744 */ 14745 var resolve = when; 14746 14747 /** 14748 * @ngdoc method 14749 * @name $q#all 14750 * @kind function 14751 * 14752 * @description 14753 * Combines multiple promises into a single promise that is resolved when all of the input 14754 * promises are resolved. 14755 * 14756 * @param {Array.<Promise>|Object.<Promise>} promises An array or hash of promises. 14757 * @returns {Promise} Returns a single promise that will be resolved with an array/hash of values, 14758 * each value corresponding to the promise at the same index/key in the `promises` array/hash. 14759 * If any of the promises is resolved with a rejection, this resulting promise will be rejected 14760 * with the same rejection value. 14761 */ 14762 14763 function all(promises) { 14764 var deferred = new Deferred(), 14765 counter = 0, 14766 results = isArray(promises) ? [] : {}; 14767
14768 forEach(promises, function(promise, key) { 14769 counter++; 14770 when(promise).then(function(value) { 14771 if (results.hasOwnProperty(key)) return; 14772 results[key] = value; 14773 if (!(--counter)) deferred.resolve(results); 14774 }, function(reason) { 14775 if (results.hasOwnProperty(key)) return; 14776 deferred.reject(reason); 14777 }); 14778 }); 14779 14780 if (counter === 0) { 14781 deferred.resolve(results); 14782 } 14783 14784 return deferred.promise; 14785 } 14786 14787 var $Q = function Q(resolver) { 14788 if (!isFunction(resolver)) { 14789 throw $qMinErr('norslvr', "Expected resolverFn, got '{0}'", resolver); 14790 } 14791 14792 if (!(this instanceof Q)) { 14793 // More useful when $Q is the Promise itself. 14794 return new Q(resolver); 14795 } 14796 14797 var deferred = new Deferred(); 14798 14799 function resolveFn(value) { 14800 deferred.resolve(value); 14801 } 14802 14803 function rejectFn(reason) { 14804 deferred.reject(reason); 14805 } 14806 14807 resolver(resolveFn, rejectFn); 14808 14809 return deferred.promise; 14810 }; 14811 14812 $Q.defer = defer; 14813 $Q.reject = reject; 14814 $Q.when = when; 14815 $Q.resolve = resolve; 14816 $Q.all = all; 14817 14818 return $Q; 14819} 14820 14821function $$RAFProvider() { //rAF 14822 this.$get = ['$window', '$timeout', function($window, $timeout) { 14823 var requestAnimationFrame = $window.requestAnimationFrame || 14824 $window.webkitRequestAnimationFrame; 14825 14826 var cancelAnimationFrame = $window.cancelAnimationFrame || 14827 $window.webkitCancelAnimationFrame || 14828 $window.webkitCancelRequestAnimationFrame; 14829 14830 var rafSupported = !!requestAnimationFrame; 14831 var rafFn = rafSupported 14832 ? function(fn) { 14833 var id = requestAnimationFrame(fn); 14834 return function() { 14835 cancelAnimationFrame(id); 14836 }; 14837 } 14838 : function(fn) { 14839 var timer = $timeout(fn, 16.66, false); // 1000 / 60 = 16.666 14840 return function() { 14841 $timeout.cancel(timer); 14842 }; 14843 }; 14844 14845 queueFn.supported = rafSupported; 14846 14847 var cancelLastRAF; 14848 var taskCount = 0; 14849 var taskQueue = []; 14850 return queueFn; 14851 14852 function flush() { 14853 for (var i = 0; i < taskQueue.length; i++) { 14854 var task = taskQueue[i]; 14855 if (task) { 14856 taskQueue[i] = null; 14857 task(); 14858 } 14859 } 14860 taskCount = taskQueue.length = 0; 14861 } 14862 14863 function queueFn(asyncFn) { 14864 var index = taskQueue.length; 14865 14866 taskCount++; 14867 taskQueue.push(asyncFn); 14868 14869 if (index === 0) { 14870 cancelLastRAF = rafFn(flush); 14871 } 14872 14873 return function cancelQueueFn() { 14874 if (index >= 0) { 14875 taskQueue[index] = null; 14876 index = null; 14877 14878 if (--taskCount === 0 && cancelLastRAF) { 14879 cancelLastRAF(); 14880 cancelLastRAF = null; 14881 taskQueue.length = 0; 14882 } 14883 } 14884 }; 14885 } 14886 }]; 14887} 14888 14889/** 14890 * DESIGN NOTES 14891 * 14892 * The design decisions behind the scope are heavily favored for speed and memory consumption. 14893 * 14894 * The typical use of scope is to watch the expressions, which most of the time return the same 14895 * value as last time so we optimize the operation. 14896 * 14897 * Closures construction is expensive in terms of speed as well as memory: 14898 * - No closures, instead use prototypical inheritance for API 14899 * - Internal state needs to be stored on scope directly, which means that private state is 14900 * exposed as $$____ properties 14901 * 14902 * Loop operations are optimized by using while(count--) { ... } 14903 * - this means that in order to keep the same order of execution as addition we have to add 14904 * items to the array at the beginning (unshift) instead of at the end (push) 14905 * 14906 * Child scopes are created and removed often 14907 * - Using an array would be slow since inserts in middle are expensive so we use linked list 14908 * 14909 * There are few watches then a lot of observers. This is why you don't want the observer to be 14910 * implemented in the same way as watch. Watch requires return of initialization function which 14911 * are expensive to construct. 14912 */ 14913 14914 14915/** 14916 * @ngdoc provider 14917 * @name $rootScopeProvider 14918 * @description 14919 * 14920 * Provider for the $rootScope service. 14921 */ 14922 14923/** 14924 * @ngdoc method 14925 * @name $rootScopeProvider#digestTtl 14926 * @description 14927 * 14928 * Sets the number of `$digest` iterations the scope should attempt to execute before giving up and 14929 * assuming that the model is unstable. 14930 * 14931 * The current default is 10 iterations. 14932 * 14933 * In complex applications it's possible that the dependencies between `$watch`s will result in 14934 * several digest iterations. However if an application needs more than the default 10 digest 14935 * iterations for its model to stabilize then you should investigate what is causing the model to 14936 * continuously change during the digest. 14937 * 14938 * Increasing the TTL could have performance implications, so you should not change it without 14939 * proper justification. 14940 * 14941 * @param {number} limit The number of digest iterations. 14942 */ 14943 14944 14945/** 14946 * @ngdoc service 14947 * @name $rootScope 14948 * @description 14949 * 14950 * Every application has a single root {@link ng.$rootScope.Scope scope}. 14951 * All other scopes are descendant scopes of the root scope. Scopes provide separation 14952 * between the model and the view, via a mechanism for watching the model for changes. 14953 * They also provide an event emission/broadcast and subscription facility. See the 14954 * {@link guide/scope developer guide on scopes}. 14955 */ 14956function $RootScopeProvider() { 14957 var TTL = 10; 14958 var $rootScopeMinErr = minErr('$rootScope'); 14959 var lastDirtyWatch = null; 14960 var applyAsyncId = null; 14961 14962 this.digestTtl = function(value) { 14963 if (arguments.length) { 14964 TTL = value; 14965 } 14966 return TTL; 14967 }; 14968 14969 function createChildScopeClass(parent) { 14970 function ChildScope() {
vendor: 4,153 bytes, lines 14971-15087
14971 this.$$watchers = this.$$nextSibling = 14972 this.$$childHead = this.$$childTail = null; 14973 this.$$listeners = {}; 14974 this.$$listenerCount = {}; 14975 this.$$watchersCount = 0; 14976 this.$id = nextUid(); 14977 this.$$ChildScope = null; 14978 } 14979 ChildScope.prototype = parent; 14980 return ChildScope; 14981 } 14982 14983 this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', 14984 function($injector, $exceptionHandler, $parse, $browser) { 14985 14986 function destroyChildScope($event) { 14987 $event.currentScope.$$destroyed = true; 14988 } 14989 14990 /** 14991 * @ngdoc type 14992 * @name $rootScope.Scope 14993 * 14994 * @description 14995 * A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the 14996 * {@link auto.$injector $injector}. Child scopes are created using the 14997 * {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when 14998 * compiled HTML template is executed.) 14999 * 15000 * Here is a simple scope snippet to show how you can interact with the scope. 15001 * ```html 15002 * <file src="./test/ng/rootScopeSpec.js" tag="docs1" /> 15003 * ``` 15004 * 15005 * # Inheritance 15006 * A scope can inherit from a parent scope, as in this example: 15007 * ```js 15008 var parent = $rootScope; 15009 var child = parent.$new(); 15010 15011 parent.salutation = "Hello"; 15012 expect(child.salutation).toEqual('Hello'); 15013 15014 child.salutation = "Welcome"; 15015 expect(child.salutation).toEqual('Welcome'); 15016 expect(parent.salutation).toEqual('Hello'); 15017 * ``` 15018 * 15019 * When interacting with `Scope` in tests, additional helper methods are available on the 15020 * instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional 15021 * details. 15022 * 15023 * 15024 * @param {Object.<string, function()>=} providers Map of service factory which need to be 15025 * provided for the current scope. Defaults to {@link ng}. 15026 * @param {Object.<string, *>=} instanceCache Provides pre-instantiated services which should 15027 * append/override services provided by `providers`. This is handy 15028 * when unit-testing and having the need to override a default 15029 * service. 15030 * @returns {Object} Newly created scope. 15031 * 15032 */ 15033 function Scope() { 15034 this.$id = nextUid(); 15035 this.$$phase = this.$parent = this.$$watchers = 15036 this.$$nextSibling = this.$$prevSibling = 15037 this.$$childHead = this.$$childTail = null; 15038 this.$root = this; 15039 this.$$destroyed = false; 15040 this.$$listeners = {}; 15041 this.$$listenerCount = {}; 15042 this.$$watchersCount = 0; 15043 this.$$isolateBindings = null; 15044 } 15045 15046 /** 15047 * @ngdoc property 15048 * @name $rootScope.Scope#$id 15049 * 15050 * @description 15051 * Unique scope ID (monotonically increasing) useful for debugging. 15052 */ 15053 15054 /** 15055 * @ngdoc property 15056 * @name $rootScope.Scope#$parent 15057 * 15058 * @description 15059 * Reference to the parent scope. 15060 */ 15061 15062 /** 15063 * @ngdoc property 15064 * @name $rootScope.Scope#$root 15065 * 15066 * @description 15067 * Reference to the root scope. 15068 */ 15069 15070 Scope.prototype = { 15071 constructor: Scope, 15072 /** 15073 * @ngdoc method 15074 * @name $rootScope.Scope#$new 15075 * @kind function 15076 * 15077 * @description 15078 * Creates a new child {@link ng.$rootScope.Scope scope}. 15079 * 15080 * The parent scope will propagate the {@link ng.$rootScope.Scope#$digest $digest()} event. 15081 * The scope can be removed from the scope hierarchy using {@link ng.$rootScope.Scope#$destroy $destroy()}. 15082 * 15083 * {@link ng.$rootScope.Scope#$destroy $destroy()} must be called on a scope when it is 15084 * desired for the scope and its child scopes to be permanently detached from the parent and 15085 * thus stop participating in model change detection and listener notification by invoking. 15086 * 15087 * @param {boolean} isolate If true, then the scope does not prototypically inherit from the
15088 * parent scope. The scope is isolated, as it can not see parent scope properties. 15089 * When creating widgets, it is useful for the widget to not accidentally read parent 15090 * state. 15091 * 15092 * @param {Scope} [parent=this] The {@link ng.$rootScope.Scope `Scope`} that will be the `$parent` 15093 * of the newly created scope. Defaults to `this` scope if not provided. 15094 * This is used when creating a transclude scope to correctly place it 15095 * in the scope hierarchy while maintaining the correct prototypical 15096 * inheritance. 15097 * 15098 * @returns {Object} The newly created child scope. 15099 * 15100 */ 15101 $new: function(isolate, parent) { 15102 var child; 15103 15104 parent = parent || this; 15105 15106 if (isolate) { 15107 child = new Scope(); 15108 child.$root = this.$root; 15109 } else { 15110 // Only create a child scope class if somebody asks for one, 15111 // but cache it to allow the VM to optimize lookups. 15112 if (!this.$$ChildScope) { 15113 this.$$ChildScope = createChildScopeClass(this); 15114 } 15115 child = new this.$$ChildScope(); 15116 } 15117 child.$parent = parent; 15118 child.$$prevSibling = parent.$$childTail; 15119 if (parent.$$childHead) { 15120 parent.$$childTail.$$nextSibling = child; 15121 parent.$$childTail = child; 15122 } else { 15123 parent.$$childHead = parent.$$childTail = child; 15124 } 15125 15126 // When the new scope is not isolated or we inherit from `this`, and 15127 // the parent scope is destroyed, the property `$$destroyed` is inherited 15128 // prototypically. In all other cases, this property needs to be set 15129 // when the parent scope is destroyed. 15130 // The listener needs to be added after the parent is set 15131 if (isolate || parent != this) child.$on('$destroy', destroyChildScope); 15132 15133 return child; 15134 }, 15135 15136 /** 15137 * @ngdoc method 15138 * @name $rootScope.Scope#$watch 15139 * @kind function 15140 * 15141 * @description 15142 * Registers a `listener` callback to be executed whenever the `watchExpression` changes. 15143 * 15144 * - The `watchExpression` is called on every call to {@link ng.$rootScope.Scope#$digest 15145 * $digest()} and should return the value that will be watched. (Since 15146 * {@link ng.$rootScope.Scope#$digest $digest()} reruns when it detects changes the 15147 * `watchExpression` can execute multiple times per 15148 * {@link ng.$rootScope.Scope#$digest $digest()} and should be idempotent.) 15149 * - The `listener` is called only when the value from the current `watchExpression` and the 15150 * previous call to `watchExpression` are not equal (with the exception of the initial run, 15151 * see below). Inequality is determined according to reference inequality, 15152 * [strict comparison](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Comparison_Operators) 15153 * via the `!==` Javascript operator, unless `objectEquality == true` 15154 * (see next point) 15155 * - When `objectEquality == true`, inequality of the `watchExpression` is determined 15156 * according to the {@link angular.equals} function. To save the value of the object for 15157 * later comparison, the {@link angular.copy} function is used. This therefore means that 15158 * watching complex objects will have adverse memory and performance implications. 15159 * - The watch `listener` may change the model, which may trigger other `listener`s to fire. 15160 * This is achieved by rerunning the watchers until no changes are detected. The rerun 15161 * iteration limit is 10 to prevent an infinite loop deadlock. 15162 * 15163 * 15164 * If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called, 15165 * you can register a `watchExpression` function with no `listener`. (Since `watchExpression` 15166 * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a 15167 * change is detected, be prepared for multiple calls to your listener.) 15168 * 15169 * After a watcher is registered with the scope, the `listener` fn is called asynchronously 15170 * (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the 15171 * watcher. In rare cases, this is undesirable because the listener is called when the result 15172 * of `watchExpression` didn't change. To detect this scenario within the `listener` fn, you 15173 * can compare the `newVal` and `oldVal`. If these two values are identical (`===`) then the 15174 * listener was called due to initialization. 15175 * 15176 * 15177 * 15178 * # Example 15179 * ```js 15180 // let's assume that scope was dependency injected as the $rootScope 15181 var scope = $rootScope; 15182 scope.name = 'misko'; 15183 scope.counter = 0; 15184 15185 expect(scope.counter).toEqual(0); 15186 scope.$watch('name', function(newValue, oldValue) { 15187 scope.counter = scope.counter + 1; 15188 }); 15189 expect(scope.counter).toEqual(0); 15190 15191 scope.$digest(); 15192 // the listener is always called during the first $digest loop after it was registered 15193 expect(scope.counter).toEqual(1); 15194 15195 scope.$digest(); 15196 // but now it will not be called unless the value changes 15197 expect(scope.counter).toEqual(1); 15198 15199 scope.name = 'adam'; 15200 scope.$digest(); 15201 expect(scope.counter).toEqual(2); 15202 15203 15204
15205 // Using a function as a watchExpression 15206 var food; 15207 scope.foodCounter = 0; 15208 expect(scope.foodCounter).toEqual(0); 15209 scope.$watch( 15210 // This function returns the value being watched. It is called for each turn of the $digest loop 15211 function() { return food; }, 15212 // This is the change listener, called when the value returned from the above function changes 15213 function(newValue, oldValue) { 15214 if ( newValue !== oldValue ) { 15215 // Only increment the counter if the value changed 15216 scope.foodCounter = scope.foodCounter + 1; 15217 } 15218 } 15219 ); 15220 // No digest has been run so the counter will be zero 15221 expect(scope.foodCounter).toEqual(0); 15222 15223 // Run the digest but since food has not changed count will still be zero 15224 scope.$digest(); 15225 expect(scope.foodCounter).toEqual(0); 15226 15227 // Update food and run digest. Now the counter will increment 15228 food = 'cheeseburger'; 15229 scope.$digest(); 15230 expect(scope.foodCounter).toEqual(1); 15231 15232 * ``` 15233 * 15234 * 15235 * 15236 * @param {(function()|string)} watchExpression Expression that is evaluated on each 15237 * {@link ng.$rootScope.Scope#$digest $digest} cycle. A change in the return value triggers 15238 * a call to the `listener`. 15239 * 15240 * - `string`: Evaluated as {@link guide/expression expression} 15241 * - `function(scope)`: called with current `scope` as a parameter. 15242 * @param {function(newVal, oldVal, scope)} listener Callback called whenever the value 15243 * of `watchExpression` changes. 15244 * 15245 * - `newVal` contains the current value of the `watchExpression` 15246 * - `oldVal` contains the previous value of the `watchExpression` 15247 * - `scope` refers to the current scope 15248 * @param {boolean=} objectEquality Compare for object equality using {@link angular.equals} instead of 15249 * comparing for reference equality. 15250 * @returns {function()} Returns a deregistration function for this listener. 15251 */ 15252 $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { 15253 var get = $parse(watchExp); 15254 15255 if (get.$$watchDelegate) { 15256 return get.$$watchDelegate(this, listener, objectEquality, get, watchExp); 15257 } 15258 var scope = this, 15259 array = scope.$$watchers, 15260 watcher = { 15261 fn: listener, 15262 last: initWatchVal, 15263 get: get, 15264 exp: prettyPrintExpression || watchExp, 15265 eq: !!objectEquality 15266 }; 15267 15268 lastDirtyWatch = null; 15269 15270 if (!isFunction(listener)) { 15271 watcher.fn = noop; 15272 } 15273 15274 if (!array) { 15275 array = scope.$$watchers = []; 15276 } 15277 // we use unshift since we use a while loop in $digest for speed. 15278 // the while loop reads in reverse order. 15279 array.unshift(watcher); 15280 incrementWatchersCount(this, 1); 15281 15282 return function deregisterWatch() { 15283 if (arrayRemove(array, watcher) >= 0) { 15284 incrementWatchersCount(scope, -1); 15285 } 15286 lastDirtyWatch = null; 15287 }; 15288 }, 15289 15290 /** 15291 * @ngdoc method 15292 * @name $rootScope.Scope#$watchGroup 15293 * @kind function 15294 * 15295 * @description 15296 * A variant of {@link ng.$rootScope.Scope#$watch $watch()} where it watches an array of `watchExpressions`. 15297 * If any one expression in the collection changes the `listener` is executed. 15298 * 15299 * - The items in the `watchExpressions` array are observed via standard $watch operation and are examined on every 15300 * call to $digest() to see if any items changes. 15301 * - The `listener` is called whenever any expression in the `watchExpressions` array changes. 15302 * 15303 * @param {Array.<string|Function(scope)>} watchExpressions Array of expressions that will be individually 15304 * watched using {@link ng.$rootScope.Scope#$watch $watch()} 15305 * 15306 * @param {function(newValues, oldValues, scope)} listener Callback called whenever the return value of any 15307 * expression in `watchExpressions` changes 15308 * The `newValues` array contains the current values of the `watchExpressions`, with the indexes matching 15309 * those of `watchExpression` 15310 * and the `oldValues` array contains the previous values of the `watchExpressions`, with the indexes matching 15311 * those of `watchExpression` 15312 * The `scope` refers to the current scope. 15313 * @returns {function()} Returns a de-registration function for all listeners. 15314 */ 15315 $watchGroup: function(watchExpressions, listener) { 15316 var oldValues = new Array(watchExpressions.length); 15317 var newValues = new Array(watchExpressions.length); 15318 var deregisterFns = []; 15319 var self = this; 15320 var changeReactionScheduled = false;
vendor: 10,180 bytes, lines 15321-15583
15321 var firstRun = true; 15322 15323 if (!watchExpressions.length) { 15324 // No expressions means we call the listener ASAP 15325 var shouldCall = true; 15326 self.$evalAsync(function() { 15327 if (shouldCall) listener(newValues, newValues, self); 15328 }); 15329 return function deregisterWatchGroup() { 15330 shouldCall = false; 15331 }; 15332 } 15333 15334 if (watchExpressions.length === 1) { 15335 // Special case size of one 15336 return this.$watch(watchExpressions[0], function watchGroupAction(value, oldValue, scope) { 15337 newValues[0] = value; 15338 oldValues[0] = oldValue; 15339 listener(newValues, (value === oldValue) ? newValues : oldValues, scope); 15340 }); 15341 } 15342 15343 forEach(watchExpressions, function(expr, i) { 15344 var unwatchFn = self.$watch(expr, function watchGroupSubAction(value, oldValue) { 15345 newValues[i] = value; 15346 oldValues[i] = oldValue; 15347 if (!changeReactionScheduled) { 15348 changeReactionScheduled = true; 15349 self.$evalAsync(watchGroupAction); 15350 } 15351 }); 15352 deregisterFns.push(unwatchFn); 15353 }); 15354 15355 function watchGroupAction() { 15356 changeReactionScheduled = false; 15357 15358 if (firstRun) { 15359 firstRun = false; 15360 listener(newValues, newValues, self); 15361 } else { 15362 listener(newValues, oldValues, self); 15363 } 15364 } 15365 15366 return function deregisterWatchGroup() { 15367 while (deregisterFns.length) { 15368 deregisterFns.shift()(); 15369 } 15370 }; 15371 }, 15372 15373 15374 /** 15375 * @ngdoc method 15376 * @name $rootScope.Scope#$watchCollection 15377 * @kind function 15378 * 15379 * @description 15380 * Shallow watches the properties of an object and fires whenever any of the properties change 15381 * (for arrays, this implies watching the array items; for object maps, this implies watching 15382 * the properties). If a change is detected, the `listener` callback is fired. 15383 * 15384 * - The `obj` collection is observed via standard $watch operation and is examined on every 15385 * call to $digest() to see if any items have been added, removed, or moved. 15386 * - The `listener` is called whenever anything within the `obj` has changed. Examples include 15387 * adding, removing, and moving items belonging to an object or array. 15388 * 15389 * 15390 * # Example 15391 * ```js 15392 $scope.names = ['igor', 'matias', 'misko', 'james']; 15393 $scope.dataCount = 4; 15394 15395 $scope.$watchCollection('names', function(newNames, oldNames) { 15396 $scope.dataCount = newNames.length; 15397 }); 15398 15399 expect($scope.dataCount).toEqual(4); 15400 $scope.$digest(); 15401 15402 //still at 4 ... no changes 15403 expect($scope.dataCount).toEqual(4); 15404 15405 $scope.names.pop(); 15406 $scope.$digest(); 15407 15408 //now there's been a change 15409 expect($scope.dataCount).toEqual(3); 15410 * ``` 15411 * 15412 * 15413 * @param {string|function(scope)} obj Evaluated as {@link guide/expression expression}. The 15414 * expression value should evaluate to an object or an array which is observed on each 15415 * {@link ng.$rootScope.Scope#$digest $digest} cycle. Any shallow change within the 15416 * collection will trigger a call to the `listener`. 15417 * 15418 * @param {function(newCollection, oldCollection, scope)} listener a callback function called 15419 * when a change is detected. 15420 * - The `newCollection` object is the newly modified data obtained from the `obj` expression 15421 * - The `oldCollection` object is a copy of the former collection data. 15422 * Due to performance considerations, the`oldCollection` value is computed only if the 15423 * `listener` function declares two or more arguments. 15424 * - The `scope` argument refers to the current scope. 15425 * 15426 * @returns {function()} Returns a de-registration function for this listener. When the 15427 * de-registration function is executed, the internal watch operation is terminated. 15428 */ 15429 $watchCollection: function(obj, listener) { 15430 $watchCollectionInterceptor.$stateful = true; 15431 15432 var self = this; 15433 // the current value, updated on each dirty-check run 15434 var newValue; 15435 // a shallow copy of the newValue from the last dirty-check run, 15436 // updated to match newValue during dirty-check run 15437 var oldValue; 15438 // a shallow copy of the newValue from when the last change happened 15439 var veryOldValue; 15440 // only track veryOldValue if the listener is asking for it 15441 var trackVeryOldValue = (listener.length > 1); 15442 var changeDetected = 0; 15443 var changeDetector = $parse(obj, $watchCollectionInterceptor); 15444 var internalArray = []; 15445 var internalObject = {}; 15446 var initRun = true; 15447 var oldLength = 0; 15448 15449 function $watchCollectionInterceptor(_value) { 15450 newValue = _value; 15451 var newLength, key, bothNaN, newItem, oldItem; 15452 15453 // If the new value is undefined, then return undefined as the watch may be a one-time watch 15454 if (isUndefined(newValue)) return; 15455 15456 if (!isObject(newValue)) { // if primitive 15457 if (oldValue !== newValue) { 15458 oldValue = newValue; 15459 changeDetected++; 15460 } 15461 } else if (isArrayLike(newValue)) { 15462 if (oldValue !== internalArray) { 15463 // we are transitioning from something which was not an array into array. 15464 oldValue = internalArray; 15465 oldLength = oldValue.length = 0; 15466 changeDetected++; 15467 } 15468 15469 newLength = newValue.length; 15470 15471 if (oldLength !== newLength) { 15472 // if lengths do not match we need to trigger change notification 15473 changeDetected++; 15474 oldValue.length = oldLength = newLength; 15475 } 15476 // copy the items to oldValue and look for changes. 15477 for (var i = 0; i < newLength; i++) { 15478 oldItem = oldValue[i]; 15479 newItem = newValue[i]; 15480 15481 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 15482 if (!bothNaN && (oldItem !== newItem)) { 15483 changeDetected++; 15484 oldValue[i] = newItem; 15485 } 15486 } 15487 } else { 15488 if (oldValue !== internalObject) { 15489 // we are transitioning from something which was not an object into object. 15490 oldValue = internalObject = {}; 15491 oldLength = 0; 15492 changeDetected++; 15493 } 15494 // copy the items to oldValue and look for changes. 15495 newLength = 0; 15496 for (key in newValue) { 15497 if (newValue.hasOwnProperty(key)) { 15498 newLength++; 15499 newItem = newValue[key]; 15500 oldItem = oldValue[key]; 15501 15502 if (key in oldValue) { 15503 bothNaN = (oldItem !== oldItem) && (newItem !== newItem); 15504 if (!bothNaN && (oldItem !== newItem)) { 15505 changeDetected++; 15506 oldValue[key] = newItem; 15507 } 15508 } else { 15509 oldLength++; 15510 oldValue[key] = newItem; 15511 changeDetected++; 15512 } 15513 } 15514 } 15515 if (oldLength > newLength) { 15516 // we used to have more keys, need to find them and destroy them. 15517 changeDetected++; 15518 for (key in oldValue) { 15519 if (!newValue.hasOwnProperty(key)) { 15520 oldLength--; 15521 delete oldValue[key]; 15522 } 15523 } 15524 } 15525 } 15526 return changeDetected; 15527 } 15528 15529 function $watchCollectionAction() { 15530 if (initRun) { 15531 initRun = false; 15532 listener(newValue, newValue, self); 15533 } else { 15534 listener(newValue, veryOldValue, self); 15535 } 15536 15537 // make a copy for the next time a collection is changed 15538 if (trackVeryOldValue) { 15539 if (!isObject(newValue)) { 15540 //primitive 15541 veryOldValue = newValue; 15542 } else if (isArrayLike(newValue)) { 15543 veryOldValue = new Array(newValue.length); 15544 for (var i = 0; i < newValue.length; i++) { 15545 veryOldValue[i] = newValue[i]; 15546 } 15547 } else { // if object 15548 veryOldValue = {}; 15549 for (var key in newValue) { 15550 if (hasOwnProperty.call(newValue, key)) { 15551 veryOldValue[key] = newValue[key]; 15552 } 15553 } 15554 } 15555 } 15556 } 15557 15558 return this.$watch(changeDetector, $watchCollectionAction); 15559 }, 15560 15561 /** 15562 * @ngdoc method 15563 * @name $rootScope.Scope#$digest 15564 * @kind function 15565 * 15566 * @description 15567 * Processes all of the {@link ng.$rootScope.Scope#$watch watchers} of the current scope and 15568 * its children. Because a {@link ng.$rootScope.Scope#$watch watcher}'s listener can change 15569 * the model, the `$digest()` keeps calling the {@link ng.$rootScope.Scope#$watch watchers} 15570 * until no more listeners are firing. This means that it is possible to get into an infinite 15571 * loop. This function will throw `'Maximum iteration limit exceeded.'` if the number of 15572 * iterations exceeds 10. 15573 * 15574 * Usually, you don't call `$digest()` directly in 15575 * {@link ng.directive:ngController controllers} or in 15576 * {@link ng.$compileProvider#directive directives}. 15577 * Instead, you should call {@link ng.$rootScope.Scope#$apply $apply()} (typically from within 15578 * a {@link ng.$compileProvider#directive directive}), which will force a `$digest()`. 15579 * 15580 * If you want to be notified whenever `$digest()` is called, 15581 * you can register a `watchExpression` function with 15582 * {@link ng.$rootScope.Scope#$watch $watch()} with no `listener`. 15583 *
15584 * In unit tests, you may need to call `$digest()` to simulate the scope life cycle. 15585 * 15586 * # Example 15587 * ```js 15588 var scope = ...; 15589 scope.name = 'misko'; 15590 scope.counter = 0; 15591 15592 expect(scope.counter).toEqual(0); 15593 scope.$watch('name', function(newValue, oldValue) { 15594 scope.counter = scope.counter + 1; 15595 }); 15596 expect(scope.counter).toEqual(0); 15597 15598 scope.$digest(); 15599 // the listener is always called during the first $digest loop after it was registered 15600 expect(scope.counter).toEqual(1); 15601 15602 scope.$digest(); 15603 // but now it will not be called unless the value changes 15604 expect(scope.counter).toEqual(1); 15605 15606 scope.name = 'adam'; 15607 scope.$digest(); 15608 expect(scope.counter).toEqual(2); 15609 * ``` 15610 * 15611 */ 15612 $digest: function() { 15613 var watch, value, last, 15614 watchers, 15615 length, 15616 dirty, ttl = TTL, 15617 next, current, target = this, 15618 watchLog = [], 15619 logIdx, logMsg, asyncTask; 15620 15621 beginPhase('$digest'); 15622 // Check for changes to browser url that happened in sync before the call to $digest 15623 $browser.$$checkUrlChange(); 15624 15625 if (this === $rootScope && applyAsyncId !== null) { 15626 // If this is the root scope, and $applyAsync has scheduled a deferred $apply(), then 15627 // cancel the scheduled $apply and flush the queue of expressions to be evaluated. 15628 $browser.defer.cancel(applyAsyncId); 15629 flushApplyAsync(); 15630 } 15631 15632 lastDirtyWatch = null; 15633 15634 do { // "while dirty" loop 15635 dirty = false; 15636 current = target; 15637 15638 while (asyncQueue.length) { 15639 try { 15640 asyncTask = asyncQueue.shift(); 15641 asyncTask.scope.$eval(asyncTask.expression, asyncTask.locals); 15642 } catch (e) { 15643 $exceptionHandler(e); 15644 } 15645 lastDirtyWatch = null; 15646 } 15647 15648 traverseScopesLoop: 15649 do { // "traverse the scopes" loop 15650 if ((watchers = current.$$watchers)) { 15651 // process our watches 15652 length = watchers.length; 15653 while (length--) { 15654 try { 15655 watch = watchers[length]; 15656 // Most common watches are on primitives, in which case we can short 15657 // circuit it with === operator, only when === fails do we use .equals 15658 if (watch) { 15659 if ((value = watch.get(current)) !== (last = watch.last) && 15660 !(watch.eq 15661 ? equals(value, last) 15662 : (typeof value === 'number' && typeof last === 'number' 15663 && isNaN(value) && isNaN(last)))) { 15664 dirty = true; 15665 lastDirtyWatch = watch; 15666 watch.last = watch.eq ? copy(value, null) : value; 15667 watch.fn(value, ((last === initWatchVal) ? value : last), current); 15668 if (ttl < 5) { 15669 logIdx = 4 - ttl; 15670 if (!watchLog[logIdx]) watchLog[logIdx] = []; 15671 watchLog[logIdx].push({ 15672 msg: isFunction(watch.exp) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp, 15673 newVal: value, 15674 oldVal: last 15675 }); 15676 } 15677 } else if (watch === lastDirtyWatch) { 15678 // If the most recently dirty watcher is now clean, short circuit since the remaining watchers 15679 // have already been tested. 15680 dirty = false; 15681 break traverseScopesLoop; 15682 } 15683 } 15684 } catch (e) { 15685 $exceptionHandler(e); 15686 } 15687 } 15688 } 15689 15690 // Insanity Warning: scope depth-first traversal 15691 // yes, this code is a bit crazy, but it works and we have tests to prove it! 15692 // this piece should be kept in sync with the traversal in $broadcast 15693 if (!(next = ((current.$$watchersCount && current.$$childHead) || 15694 (current !== target && current.$$nextSibling)))) { 15695 while (current !== target && !(next = current.$$nextSibling)) { 15696 current = current.$parent; 15697 } 15698 } 15699 } while ((current = next)); 15700 15701 // `break traverseScopesLoop;` takes us to here 15702 15703 if ((dirty || asyncQueue.length) && !(ttl--)) { 15704 clearPhase(); 15705 throw $rootScopeMinErr('infdig', 15706 '{0} $digest() iterations reached. Aborting!\n' + 15707 'Watchers fired in the last 5 iterations: {1}', 15708 TTL, watchLog); 15709 } 15710 15711 }
vendor: 5,032 bytes, lines 15711-15837
15711 while (dirty || asyncQueue.length); 15712 15713 clearPhase(); 15714 15715 while (postDigestQueue.length) { 15716 try { 15717 postDigestQueue.shift()(); 15718 } catch (e) { 15719 $exceptionHandler(e); 15720 } 15721 } 15722 }, 15723 15724 15725 /** 15726 * @ngdoc event 15727 * @name $rootScope.Scope#$destroy 15728 * @eventType broadcast on scope being destroyed 15729 * 15730 * @description 15731 * Broadcasted when a scope and its children are being destroyed. 15732 * 15733 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 15734 * clean up DOM bindings before an element is removed from the DOM. 15735 */ 15736 15737 /** 15738 * @ngdoc method 15739 * @name $rootScope.Scope#$destroy 15740 * @kind function 15741 * 15742 * @description 15743 * Removes the current scope (and all of its children) from the parent scope. Removal implies 15744 * that calls to {@link ng.$rootScope.Scope#$digest $digest()} will no longer 15745 * propagate to the current scope and its children. Removal also implies that the current 15746 * scope is eligible for garbage collection. 15747 * 15748 * The `$destroy()` is usually used by directives such as 15749 * {@link ng.directive:ngRepeat ngRepeat} for managing the 15750 * unrolling of the loop. 15751 * 15752 * Just before a scope is destroyed, a `$destroy` event is broadcasted on this scope. 15753 * Application code can register a `$destroy` event handler that will give it a chance to 15754 * perform any necessary cleanup. 15755 * 15756 * Note that, in AngularJS, there is also a `$destroy` jQuery event, which can be used to 15757 * clean up DOM bindings before an element is removed from the DOM. 15758 */ 15759 $destroy: function() { 15760 // We can't destroy a scope that has been already destroyed. 15761 if (this.$$destroyed) return; 15762 var parent = this.$parent; 15763 15764 this.$broadcast('$destroy'); 15765 this.$$destroyed = true; 15766 15767 if (this === $rootScope) { 15768 //Remove handlers attached to window when $rootScope is removed 15769 $browser.$$applicationDestroyed(); 15770 } 15771 15772 incrementWatchersCount(this, -this.$$watchersCount); 15773 for (var eventName in this.$$listenerCount) { 15774 decrementListenerCount(this, this.$$listenerCount[eventName], eventName); 15775 } 15776 15777 // sever all the references to parent scopes (after this cleanup, the current scope should 15778 // not be retained by any of our references and should be eligible for garbage collection) 15779 if (parent && parent.$$childHead == this) parent.$$childHead = this.$$nextSibling; 15780 if (parent && parent.$$childTail == this) parent.$$childTail = this.$$prevSibling; 15781 if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling; 15782 if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling; 15783 15784 // Disable listeners, watchers and apply/digest methods 15785 this.$destroy = this.$digest = this.$apply = this.$evalAsync = this.$applyAsync = noop; 15786 this.$on = this.$watch = this.$watchGroup = function() { return noop; }; 15787 this.$$listeners = {}; 15788 15789 // All of the code below is bogus code that works around V8's memory leak via optimized code 15790 // and inline caches. 15791 // 15792 // see: 15793 // - https://code.google.com/p/v8/issues/detail?id=2073#c26 15794 // - https://github.com/angular/angular.js/issues/6794#issuecomment-38648909 15795 // - https://github.com/angular/angular.js/issues/1313#issuecomment-10378451 15796 15797 this.$parent = this.$$nextSibling = this.$$prevSibling = this.$$childHead = 15798 this.$$childTail = this.$root = this.$$watchers = null; 15799 }, 15800 15801 /** 15802 * @ngdoc method 15803 * @name $rootScope.Scope#$eval 15804 * @kind function 15805 * 15806 * @description 15807 * Executes the `expression` on the current scope and returns the result. Any exceptions in 15808 * the expression are propagated (uncaught). This is useful when evaluating Angular 15809 * expressions. 15810 * 15811 * # Example 15812 * ```js 15813 var scope = ng.$rootScope.Scope(); 15814 scope.a = 1; 15815 scope.b = 2; 15816 15817 expect(scope.$eval('a+b')).toEqual(3); 15818 expect(scope.$eval(function(scope){ return scope.a + scope.b; })).toEqual(3); 15819 * ``` 15820 * 15821 * @param {(string|function())=} expression An angular expression to be executed. 15822 * 15823 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 15824 * - `function(scope)`: execute the function with the current `scope` parameter. 15825 * 15826 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 15827 * @returns {*} The result of evaluating the expression. 15828 */ 15829 $eval: function(expr, locals) { 15830 return $parse(expr)(this, locals); 15831 }, 15832 15833 /** 15834 * @ngdoc method 15835 * @name $rootScope.Scope#$evalAsync 15836 * @kind function 15837 *
15838 * @description 15839 * Executes the expression on the current scope at a later point in time. 15840 * 15841 * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only 15842 * that: 15843 * 15844 * - it will execute after the function that scheduled the evaluation (preferably before DOM 15845 * rendering). 15846 * - at least one {@link ng.$rootScope.Scope#$digest $digest cycle} will be performed after 15847 * `expression` execution. 15848 * 15849 * Any exceptions from the execution of the expression are forwarded to the 15850 * {@link ng.$exceptionHandler $exceptionHandler} service. 15851 * 15852 * __Note:__ if this function is called outside of a `$digest` cycle, a new `$digest` cycle 15853 * will be scheduled. However, it is encouraged to always call code that changes the model 15854 * from within an `$apply` call. That includes code evaluated via `$evalAsync`. 15855 * 15856 * @param {(string|function())=} expression An angular expression to be executed. 15857 * 15858 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 15859 * - `function(scope)`: execute the function with the current `scope` parameter. 15860 * 15861 * @param {(object)=} locals Local variables object, useful for overriding values in scope. 15862 */ 15863 $evalAsync: function(expr, locals) { 15864 // if we are outside of an $digest loop and this is the first time we are scheduling async 15865 // task also schedule async auto-flush 15866 if (!$rootScope.$$phase && !asyncQueue.length) { 15867 $browser.defer(function() { 15868 if (asyncQueue.length) { 15869 $rootScope.$digest(); 15870 } 15871 }); 15872 } 15873 15874 asyncQueue.push({scope: this, expression: expr, locals: locals}); 15875 }, 15876 15877 $$postDigest: function(fn) { 15878 postDigestQueue.push(fn); 15879 }, 15880 15881 /** 15882 * @ngdoc method 15883 * @name $rootScope.Scope#$apply 15884 * @kind function 15885 * 15886 * @description 15887 * `$apply()` is used to execute an expression in angular from outside of the angular 15888 * framework. (For example from browser DOM events, setTimeout, XHR or third party libraries). 15889 * Because we are calling into the angular framework we need to perform proper scope life 15890 * cycle of {@link ng.$exceptionHandler exception handling}, 15891 * {@link ng.$rootScope.Scope#$digest executing watches}. 15892 * 15893 * ## Life cycle 15894 * 15895 * # Pseudo-Code of `$apply()` 15896 * ```js 15897 function $apply(expr) { 15898 try { 15899 return $eval(expr); 15900 } catch (e) { 15901 $exceptionHandler(e); 15902 } finally { 15903 $root.$digest(); 15904 } 15905 } 15906 * ``` 15907 * 15908 * 15909 * Scope's `$apply()` method transitions through the following stages: 15910 * 15911 * 1. The {@link guide/expression expression} is executed using the 15912 * {@link ng.$rootScope.Scope#$eval $eval()} method. 15913 * 2. Any exceptions from the execution of the expression are forwarded to the 15914 * {@link ng.$exceptionHandler $exceptionHandler} service. 15915 * 3. The {@link ng.$rootScope.Scope#$watch watch} listeners are fired immediately after the 15916 * expression was executed using the {@link ng.$rootScope.Scope#$digest $digest()} method. 15917 * 15918 * 15919 * @param {(string|function())=} exp An angular expression to be executed. 15920 * 15921 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 15922 * - `function(scope)`: execute the function with current `scope` parameter. 15923 * 15924 * @returns {*} The result of evaluating the expression. 15925 */ 15926 $apply: function(expr) { 15927 try { 15928 beginPhase('$apply'); 15929 return this.$eval(expr); 15930 } catch (e) { 15931 $exceptionHandler(e); 15932 } finally { 15933 clearPhase(); 15934 try { 15935 $rootScope.$digest(); 15936 } catch (e) { 15937 $exceptionHandler(e); 15938 throw e; 15939 } 15940 } 15941 }, 15942 15943 /** 15944 * @ngdoc method 15945 * @name $rootScope.Scope#$applyAsync 15946 * @kind function 15947 *
15948 * @description 15949 * Schedule the invocation of $apply to occur at a later time. The actual time difference 15950 * varies across browsers, but is typically around ~10 milliseconds. 15951 * 15952 * This can be used to queue up multiple expressions which need to be evaluated in the same 15953 * digest. 15954 * 15955 * @param {(string|function())=} exp An angular expression to be executed. 15956 * 15957 * - `string`: execute using the rules as defined in {@link guide/expression expression}. 15958 * - `function(scope)`: execute the function with current `scope` parameter. 15959 */ 15960 $applyAsync: function(expr) { 15961 var scope = this; 15962 expr && applyAsyncQueue.push($applyAsyncExpression); 15963 scheduleApplyAsync(); 15964 15965 function $applyAsyncExpression() { 15966 scope.$eval(expr); 15967 } 15968 }, 15969 15970 /** 15971 * @ngdoc method 15972 * @name $rootScope.Scope#$on 15973 * @kind function 15974 * 15975 * @description 15976 * Listens on events of a given type. See {@link ng.$rootScope.Scope#$emit $emit} for 15977 * discussion of event life cycle. 15978 * 15979 * The event listener function format is: `function(event, args...)`. The `event` object 15980 * passed into the listener has the following attributes: 15981 * 15982 * - `targetScope` - `{Scope}`: the scope on which the event was `$emit`-ed or 15983 * `$broadcast`-ed. 15984 * - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the 15985 * event propagates through the scope hierarchy, this property is set to null. 15986 * - `name` - `{string}`: name of the event. 15987 * - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel 15988 * further event propagation (available only for events that were `$emit`-ed). 15989 * - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag 15990 * to true. 15991 * - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called. 15992 * 15993 * @param {string} name Event name to listen on. 15994 * @param {function(event, ...args)} listener Function to call when the event is emitted. 15995 * @returns {function()} Returns a deregistration function for this listener. 15996 */ 15997 $on: function(name, listener) { 15998 var namedListeners = this.$$listeners[name]; 15999 if (!namedListeners) { 16000 this.$$listeners[name] = namedListeners = []; 16001 } 16002 namedListeners.push(listener); 16003 16004 var current = this; 16005 do { 16006 if (!current.$$listenerCount[name]) { 16007 current.$$listenerCount[name] = 0; 16008 } 16009 current.$$listenerCount[name]++; 16010 } while ((current = current.$parent)); 16011 16012 var self = this; 16013 return function() { 16014 var indexOfListener = namedListeners.indexOf(listener); 16015 if (indexOfListener !== -1) { 16016 namedListeners[indexOfListener] = null; 16017 decrementListenerCount(self, 1, name); 16018 } 16019 }; 16020 }, 16021 16022 16023 /** 16024 * @ngdoc method 16025 * @name $rootScope.Scope#$emit 16026 * @kind function 16027 * 16028 * @description 16029 * Dispatches an event `name` upwards through the scope hierarchy notifying the 16030 * registered {@link ng.$rootScope.Scope#$on} listeners. 16031 * 16032 * The event life cycle starts at the scope on which `$emit` was called. All 16033 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 16034 * notified. Afterwards, the event traverses upwards toward the root scope and calls all 16035 * registered listeners along the way. The event will stop propagating if one of the listeners 16036 * cancels it. 16037 * 16038 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 16039 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 16040 * 16041 * @param {string} name Event name to emit. 16042 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 16043 * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). 16044 */ 16045 $emit: function(name, args) { 16046 var empty = [], 16047 namedListeners, 16048 scope = this, 16049 stopPropagation = false, 16050 event = { 16051 name: name, 16052 targetScope: scope, 16053 stopPropagation: function() {stopPropagation = true;}, 16054 preventDefault: function() { 16055 event.defaultPrevented = true; 16056 }, 16057 defaultPrevented: false 16058 }, 16059 listenerArgs = concat([event], arguments, 1), 16060 i, length; 16061 16062 do { 16063 namedListeners = scope.$$listeners[name] || empty; 16064 event.currentScope = scope; 16065 for (i = 0, length = namedListeners.length; i < length; i++) { 16066 16067 // if listeners were deregistered, defragment the array 16068 if (!namedListeners[i]) { 16069 namedListeners.splice(i, 1); 16070 i--; 16071 length--; 16072 continue; 16073 } 16074 try { 16075 //allow all listeners attached to the current scope to run 16076 namedListeners[i].apply(null, listenerArgs); 16077 } catch (e) { 16078 $exceptionHandler(e); 16079 } 16080 } 16081 //if any listener on the current scope stops propagation, prevent bubbling 16082 if (stopPropagation) { 16083 event.currentScope = null; 16084 return event; 16085 } 16086 //traverse upwards 16087 scope = scope.$parent; 16088 } while (scope); 16089 16090 event.currentScope = null; 16091 16092 return event; 16093 }, 16094 16095 16096 /** 16097 * @ngdoc method 16098 * @name $rootScope.Scope#$broadcast 16099 * @kind function 16100 *
16101 * @description 16102 * Dispatches an event `name` downwards to all child scopes (and their children) notifying the 16103 * registered {@link ng.$rootScope.Scope#$on} listeners. 16104 * 16105 * The event life cycle starts at the scope on which `$broadcast` was called. All 16106 * {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get 16107 * notified. Afterwards, the event propagates to all direct and indirect scopes of the current 16108 * scope and calls all registered listeners along the way. The event cannot be canceled. 16109 * 16110 * Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed 16111 * onto the {@link ng.$exceptionHandler $exceptionHandler} service. 16112 * 16113 * @param {string} name Event name to broadcast. 16114 * @param {...*} args Optional one or more arguments which will be passed onto the event listeners. 16115 * @return {Object} Event object, see {@link ng.$rootScope.Scope#$on} 16116 */ 16117 $broadcast: function(name, args) { 16118 var target = this, 16119 current = target, 16120 next = target, 16121 event = { 16122 name: name, 16123 targetScope: target, 16124 preventDefault: function() { 16125 event.defaultPrevented = true; 16126 }, 16127 defaultPrevented: false 16128 }; 16129 16130 if (!target.$$listenerCount[name]) return event; 16131 16132 var listenerArgs = concat([event], arguments, 1), 16133 listeners, i, length; 16134 16135 //down while you can, then up and next sibling or up and next sibling until back at root 16136 while ((current = next)) { 16137 event.currentScope = current; 16138 listeners = current.$$listeners[name] || []; 16139 for (i = 0, length = listeners.length; i < length; i++) { 16140 // if listeners were deregistered, defragment the array 16141 if (!listeners[i]) { 16142 listeners.splice(i, 1); 16143 i--; 16144 length--; 16145 continue; 16146 } 16147 16148 try { 16149 listeners[i].apply(null, listenerArgs); 16150 } catch (e) { 16151 $exceptionHandler(e); 16152 } 16153 } 16154 16155 // Insanity Warning: scope depth-first traversal 16156 // yes, this code is a bit crazy, but it works and we have tests to prove it! 16157 // this piece should be kept in sync with the traversal in $digest 16158 // (though it differs due to having the extra check for $$listenerCount) 16159 if (!(next = ((current.$$listenerCount[name] && current.$$childHead) || 16160 (current !== target && current.$$nextSibling)))) { 16161 while (current !== target && !(next = current.$$nextSibling)) { 16162 current = current.$parent; 16163 } 16164 } 16165 } 16166 16167 event.currentScope = null; 16168 return event; 16169 } 16170 }; 16171 16172 var $rootScope = new Scope(); 16173 16174 //The internal queues. Expose them on the $rootScope for debugging/testing purposes. 16175 var asyncQueue = $rootScope.$$asyncQueue = []; 16176 var postDigestQueue = $rootScope.$$postDigestQueue = []; 16177 var applyAsyncQueue = $rootScope.$$applyAsyncQueue = []; 16178 16179 return $rootScope; 16180 16181 16182 function beginPhase(phase) { 16183 if ($rootScope.$$phase) { 16184 throw $rootScopeMinErr('inprog', '{0} already in progress', $rootScope.$$phase); 16185 } 16186 16187 $rootScope.$$phase = phase; 16188 } 16189 16190 function clearPhase() { 16191 $rootScope.$$phase = null; 16192 } 16193 16194 function incrementWatchersCount(current, count) { 16195 do { 16196 current.$$watchersCount += count; 16197 } while ((current = current.$parent)); 16198 } 16199 16200 function decrementListenerCount(current, count, name) { 16201 do { 16202 current.$$listenerCount[name] -= count; 16203 16204 if (current.$$listenerCount[name] === 0) { 16205 delete current.$$listenerCount[name]; 16206 } 16207 } while ((current = current.$parent)); 16208 } 16209 16210 /** 16211 * function used as an initial value for watchers. 16212 * because it's unique we can easily tell it apart from other values 16213 */ 16214 function initWatchVal() {} 16215 16216 function flushApplyAsync() {
vendor: 4,838 bytes, lines 16217-16344
16217 while (applyAsyncQueue.length) { 16218 try { 16219 applyAsyncQueue.shift()(); 16220 } catch (e) { 16221 $exceptionHandler(e); 16222 } 16223 } 16224 applyAsyncId = null; 16225 } 16226 16227 function scheduleApplyAsync() { 16228 if (applyAsyncId === null) { 16229 applyAsyncId = $browser.defer(function() { 16230 $rootScope.$apply(flushApplyAsync); 16231 }); 16232 } 16233 } 16234 }]; 16235} 16236 16237/** 16238 * @description 16239 * Private service to sanitize uris for links and images. Used by $compile and $sanitize. 16240 */ 16241function $$SanitizeUriProvider() { 16242 var aHrefSanitizationWhitelist = /^\s*(https?|ftp|mailto|tel|file):/, 16243 imgSrcSanitizationWhitelist = /^\s*((https?|ftp|file|blob):|data:image\/)/; 16244 16245 /** 16246 * @description 16247 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 16248 * urls during a[href] sanitization. 16249 * 16250 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 16251 * 16252 * Any url about to be assigned to a[href] via data-binding is first normalized and turned into 16253 * an absolute url. Afterwards, the url is matched against the `aHrefSanitizationWhitelist` 16254 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 16255 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 16256 * 16257 * @param {RegExp=} regexp New regexp to whitelist urls with. 16258 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 16259 * chaining otherwise. 16260 */ 16261 this.aHrefSanitizationWhitelist = function(regexp) { 16262 if (isDefined(regexp)) { 16263 aHrefSanitizationWhitelist = regexp; 16264 return this; 16265 } 16266 return aHrefSanitizationWhitelist; 16267 }; 16268 16269 16270 /** 16271 * @description 16272 * Retrieves or overrides the default regular expression that is used for whitelisting of safe 16273 * urls during img[src] sanitization. 16274 * 16275 * The sanitization is a security measure aimed at prevent XSS attacks via html links. 16276 * 16277 * Any url about to be assigned to img[src] via data-binding is first normalized and turned into 16278 * an absolute url. Afterwards, the url is matched against the `imgSrcSanitizationWhitelist` 16279 * regular expression. If a match is found, the original url is written into the dom. Otherwise, 16280 * the absolute url is prefixed with `'unsafe:'` string and only then is it written into the DOM. 16281 * 16282 * @param {RegExp=} regexp New regexp to whitelist urls with. 16283 * @returns {RegExp|ng.$compileProvider} Current RegExp if called without value or self for 16284 * chaining otherwise. 16285 */ 16286 this.imgSrcSanitizationWhitelist = function(regexp) { 16287 if (isDefined(regexp)) { 16288 imgSrcSanitizationWhitelist = regexp; 16289 return this; 16290 } 16291 return imgSrcSanitizationWhitelist; 16292 }; 16293 16294 this.$get = function() { 16295 return function sanitizeUri(uri, isImage) { 16296 var regex = isImage ? imgSrcSanitizationWhitelist : aHrefSanitizationWhitelist; 16297 var normalizedVal; 16298 normalizedVal = urlResolve(uri).href; 16299 if (normalizedVal !== '' && !normalizedVal.match(regex)) { 16300 return 'unsafe:' + normalizedVal; 16301 } 16302 return uri; 16303 }; 16304 }; 16305} 16306 16307/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 16308 * Any commits to this file should be reviewed with security in mind. * 16309 * Changes to this file can potentially create security vulnerabilities. * 16310 * An approval from 2 Core members with history of modifying * 16311 * this file is required. * 16312 * * 16313 * Does the change somehow allow for arbitrary javascript to be executed? * 16314 * Or allows for someone to change the prototype of built-in objects? * 16315 * Or gives undesired access to variables likes document or window? * 16316 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ 16317 16318var $sceMinErr = minErr('$sce'); 16319 16320var SCE_CONTEXTS = { 16321 HTML: 'html', 16322 CSS: 'css', 16323 URL: 'url', 16324 // RESOURCE_URL is a subtype of URL used in contexts where a privileged resource is sourced from a 16325 // url. (e.g. ng-include, script src, templateUrl) 16326 RESOURCE_URL: 'resourceUrl', 16327 JS: 'js' 16328}; 16329 16330// Helper functions follow. 16331 16332function adjustMatcher(matcher) { 16333 if (matcher === 'self') { 16334 return matcher; 16335 } else if (isString(matcher)) { 16336 // Strings match exactly except for 2 wildcards - '*' and '**'. 16337 // '*' matches any character except those from the set ':/.?&'. 16338 // '**' matches any character (like .* in a RegExp). 16339 // More than 2 *'s raises an error as it's ill defined. 16340 if (matcher.indexOf('***') > -1) { 16341 throw $sceMinErr('iwcard', 16342 'Illegal sequence *** in string matcher. String: {0}', matcher); 16343 } 16344 matcher = escapeForRegexp(matcher).
16345 replace('\\*\\*', '.*'). 16346 replace('\\*', '[^:/.?&;]*'); 16347 return new RegExp('^' + matcher + '$'); 16348 } else if (isRegExp(matcher)) { 16349 // The only other type of matcher allowed is a Regexp. 16350 // Match entire URL / disallow partial matches. 16351 // Flags are reset (i.e. no global, ignoreCase or multiline) 16352 return new RegExp('^' + matcher.source + '$'); 16353 } else { 16354 throw $sceMinErr('imatcher', 16355 'Matchers may only be "self", string patterns or RegExp objects'); 16356 } 16357} 16358 16359 16360function adjustMatchers(matchers) { 16361 var adjustedMatchers = []; 16362 if (isDefined(matchers)) { 16363 forEach(matchers, function(matcher) { 16364 adjustedMatchers.push(adjustMatcher(matcher)); 16365 }); 16366 } 16367 return adjustedMatchers; 16368} 16369 16370 16371/** 16372 * @ngdoc service 16373 * @name $sceDelegate 16374 * @kind function 16375 * 16376 * @description 16377 * 16378 * `$sceDelegate` is a service that is used by the `$sce` service to provide {@link ng.$sce Strict 16379 * Contextual Escaping (SCE)} services to AngularJS. 16380 * 16381 * Typically, you would configure or override the {@link ng.$sceDelegate $sceDelegate} instead of 16382 * the `$sce` service to customize the way Strict Contextual Escaping works in AngularJS. This is 16383 * because, while the `$sce` provides numerous shorthand methods, etc., you really only need to 16384 * override 3 core functions (`trustAs`, `getTrusted` and `valueOf`) to replace the way things 16385 * work because `$sce` delegates to `$sceDelegate` for these operations. 16386 * 16387 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} to configure this service. 16388 * 16389 * The default instance of `$sceDelegate` should work out of the box with little pain. While you 16390 * can override it completely to change the behavior of `$sce`, the common case would 16391 * involve configuring the {@link ng.$sceDelegateProvider $sceDelegateProvider} instead by setting 16392 * your own whitelists and blacklists for trusting URLs used for loading AngularJS resources such as 16393 * templates. Refer {@link ng.$sceDelegateProvider#resourceUrlWhitelist 16394 * $sceDelegateProvider.resourceUrlWhitelist} and {@link 16395 * ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 16396 */ 16397 16398/** 16399 * @ngdoc provider 16400 * @name $sceDelegateProvider 16401 * @description 16402 * 16403 * The `$sceDelegateProvider` provider allows developers to configure the {@link ng.$sceDelegate 16404 * $sceDelegate} service. This allows one to get/set the whitelists and blacklists used to ensure 16405 * that the URLs used for sourcing Angular templates are safe. Refer {@link 16406 * ng.$sceDelegateProvider#resourceUrlWhitelist $sceDelegateProvider.resourceUrlWhitelist} and 16407 * {@link ng.$sceDelegateProvider#resourceUrlBlacklist $sceDelegateProvider.resourceUrlBlacklist} 16408 * 16409 * For the general details about this service in Angular, read the main page for {@link ng.$sce 16410 * Strict Contextual Escaping (SCE)}. 16411 * 16412 * **Example**: Consider the following case. <a name="example"></a> 16413 * 16414 * - your app is hosted at url `http://myapp.example.com/` 16415 * - but some of your templates are hosted on other domains you control such as 16416 * `http://srv01.assets.example.com/`, `http://srv02.assets.example.com/`, etc. 16417 * - and you have an open redirect at `http://myapp.example.com/clickThru?...`. 16418 * 16419 * Here is what a secure configuration for this scenario might look like: 16420 * 16421 * ``` 16422 * angular.module('myApp', []).config(function($sceDelegateProvider) { 16423 * $sceDelegateProvider.resourceUrlWhitelist([ 16424 * // Allow same origin resource loads. 16425 * 'self', 16426 * // Allow loading from our assets domain. Notice the difference between * and **. 16427 * 'http://srv*.assets.example.com/**' 16428 * ]); 16429 * 16430 * // The blacklist overrides the whitelist so the open redirect here is blocked. 16431 * $sceDelegateProvider.resourceUrlBlacklist([ 16432 * 'http://myapp.example.com/clickThru**' 16433 * ]); 16434 * }); 16435 * ``` 16436 */ 16437 16438function $SceDelegateProvider() { 16439 this.SCE_CONTEXTS = SCE_CONTEXTS; 16440 16441 // Resource URLs can also be trusted by policy. 16442 var resourceUrlWhitelist = ['self'], 16443 resourceUrlBlacklist = []; 16444 16445 /** 16446 * @ngdoc method 16447 * @name $sceDelegateProvider#resourceUrlWhitelist 16448 * @kind function 16449 * 16450 * @param {Array=} whitelist When provided, replaces the resourceUrlWhitelist with the value 16451 * provided. This must be an array or null. A snapshot of this array is used so further 16452 * changes to the array are ignored. 16453 * 16454 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 16455 * allowed in this array.
vendor: 4,148 bytes, lines 16456-16580
16456 * 16457 * Note: **an empty whitelist array will block all URLs**! 16458 * 16459 * @return {Array} the currently set whitelist array. 16460 * 16461 * The **default value** when no whitelist has been explicitly set is `['self']` allowing only 16462 * same origin resource requests. 16463 * 16464 * @description 16465 * Sets/Gets the whitelist of trusted resource URLs. 16466 */ 16467 this.resourceUrlWhitelist = function(value) { 16468 if (arguments.length) { 16469 resourceUrlWhitelist = adjustMatchers(value); 16470 } 16471 return resourceUrlWhitelist; 16472 }; 16473 16474 /** 16475 * @ngdoc method 16476 * @name $sceDelegateProvider#resourceUrlBlacklist 16477 * @kind function 16478 * 16479 * @param {Array=} blacklist When provided, replaces the resourceUrlBlacklist with the value 16480 * provided. This must be an array or null. A snapshot of this array is used so further 16481 * changes to the array are ignored. 16482 * 16483 * Follow {@link ng.$sce#resourceUrlPatternItem this link} for a description of the items 16484 * allowed in this array. 16485 * 16486 * The typical usage for the blacklist is to **block 16487 * [open redirects](http://cwe.mitre.org/data/definitions/601.html)** served by your domain as 16488 * these would otherwise be trusted but actually return content from the redirected domain. 16489 * 16490 * Finally, **the blacklist overrides the whitelist** and has the final say. 16491 * 16492 * @return {Array} the currently set blacklist array. 16493 * 16494 * The **default value** when no whitelist has been explicitly set is the empty array (i.e. there 16495 * is no blacklist.) 16496 * 16497 * @description 16498 * Sets/Gets the blacklist of trusted resource URLs. 16499 */ 16500 16501 this.resourceUrlBlacklist = function(value) { 16502 if (arguments.length) { 16503 resourceUrlBlacklist = adjustMatchers(value); 16504 } 16505 return resourceUrlBlacklist; 16506 }; 16507 16508 this.$get = ['$injector', function($injector) { 16509 16510 var htmlSanitizer = function htmlSanitizer(html) { 16511 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 16512 }; 16513 16514 if ($injector.has('$sanitize')) { 16515 htmlSanitizer = $injector.get('$sanitize'); 16516 } 16517 16518 16519 function matchUrl(matcher, parsedUrl) { 16520 if (matcher === 'self') { 16521 return urlIsSameOrigin(parsedUrl); 16522 } else { 16523 // definitely a regex. See adjustMatchers() 16524 return !!matcher.exec(parsedUrl.href); 16525 } 16526 } 16527 16528 function isResourceUrlAllowedByPolicy(url) { 16529 var parsedUrl = urlResolve(url.toString()); 16530 var i, n, allowed = false; 16531 // Ensure that at least one item from the whitelist allows this url. 16532 for (i = 0, n = resourceUrlWhitelist.length; i < n; i++) { 16533 if (matchUrl(resourceUrlWhitelist[i], parsedUrl)) { 16534 allowed = true; 16535 break; 16536 } 16537 } 16538 if (allowed) { 16539 // Ensure that no item from the blacklist blocked this url. 16540 for (i = 0, n = resourceUrlBlacklist.length; i < n; i++) { 16541 if (matchUrl(resourceUrlBlacklist[i], parsedUrl)) { 16542 allowed = false; 16543 break; 16544 } 16545 } 16546 } 16547 return allowed; 16548 } 16549 16550 function generateHolderType(Base) { 16551 var holderType = function TrustedValueHolderType(trustedValue) { 16552 this.$$unwrapTrustedValue = function() { 16553 return trustedValue; 16554 }; 16555 }; 16556 if (Base) { 16557 holderType.prototype = new Base(); 16558 } 16559 holderType.prototype.valueOf = function sceValueOf() { 16560 return this.$$unwrapTrustedValue(); 16561 }; 16562 holderType.prototype.toString = function sceToString() { 16563 return this.$$unwrapTrustedValue().toString(); 16564 }; 16565 return holderType; 16566 } 16567 16568 var trustedValueHolderBase = generateHolderType(), 16569 byType = {}; 16570 16571 byType[SCE_CONTEXTS.HTML] = generateHolderType(trustedValueHolderBase); 16572 byType[SCE_CONTEXTS.CSS] = generateHolderType(trustedValueHolderBase); 16573 byType[SCE_CONTEXTS.URL] = generateHolderType(trustedValueHolderBase); 16574 byType[SCE_CONTEXTS.JS] = generateHolderType(trustedValueHolderBase); 16575 byType[SCE_CONTEXTS.RESOURCE_URL] = generateHolderType(byType[SCE_CONTEXTS.URL]); 16576 16577 /** 16578 * @ngdoc method 16579 * @name $sceDelegate#trustAs 16580 *
16581 * @description 16582 * Returns an object that is trusted by angular for use in specified strict 16583 * contextual escaping contexts (such as ng-bind-html, ng-include, any src 16584 * attribute interpolation, any dom event binding attribute interpolation 16585 * such as for onclick, etc.) that uses the provided value. 16586 * See {@link ng.$sce $sce} for enabling strict contextual escaping. 16587 * 16588 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 16589 * resourceUrl, html, js and css. 16590 * @param {*} value The value that that should be considered trusted/safe. 16591 * @returns {*} A value that can be used to stand in for the provided `value` in places 16592 * where Angular expects a $sce.trustAs() return value. 16593 */ 16594 function trustAs(type, trustedValue) { 16595 var Constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 16596 if (!Constructor) { 16597 throw $sceMinErr('icontext', 16598 'Attempted to trust a value in invalid context. Context: {0}; Value: {1}', 16599 type, trustedValue); 16600 } 16601 if (trustedValue === null || trustedValue === undefined || trustedValue === '') { 16602 return trustedValue; 16603 } 16604 // All the current contexts in SCE_CONTEXTS happen to be strings. In order to avoid trusting 16605 // mutable objects, we ensure here that the value passed in is actually a string. 16606 if (typeof trustedValue !== 'string') { 16607 throw $sceMinErr('itype', 16608 'Attempted to trust a non-string value in a content requiring a string: Context: {0}', 16609 type); 16610 } 16611 return new Constructor(trustedValue); 16612 } 16613 16614 /** 16615 * @ngdoc method 16616 * @name $sceDelegate#valueOf 16617 * 16618 * @description 16619 * If the passed parameter had been returned by a prior call to {@link ng.$sceDelegate#trustAs 16620 * `$sceDelegate.trustAs`}, returns the value that had been passed to {@link 16621 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. 16622 * 16623 * If the passed parameter is not a value that had been returned by {@link 16624 * ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}, returns it as-is. 16625 * 16626 * @param {*} value The result of a prior {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} 16627 * call or anything else. 16628 * @returns {*} The `value` that was originally provided to {@link ng.$sceDelegate#trustAs 16629 * `$sceDelegate.trustAs`} if `value` is the result of such a call. Otherwise, returns 16630 * `value` unchanged. 16631 */ 16632 function valueOf(maybeTrusted) { 16633 if (maybeTrusted instanceof trustedValueHolderBase) { 16634 return maybeTrusted.$$unwrapTrustedValue(); 16635 } else { 16636 return maybeTrusted; 16637 } 16638 } 16639 16640 /** 16641 * @ngdoc method 16642 * @name $sceDelegate#getTrusted 16643 * 16644 * @description 16645 * Takes the result of a {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`} call and 16646 * returns the originally supplied value if the queried context type is a supertype of the 16647 * created type. If this condition isn't satisfied, throws an exception. 16648 * 16649 * @param {string} type The kind of context in which this value is to be used. 16650 * @param {*} maybeTrusted The result of a prior {@link ng.$sceDelegate#trustAs 16651 * `$sceDelegate.trustAs`} call. 16652 * @returns {*} The value the was originally provided to {@link ng.$sceDelegate#trustAs 16653 * `$sceDelegate.trustAs`} if valid in this context. Otherwise, throws an exception. 16654 */ 16655 function getTrusted(type, maybeTrusted) { 16656 if (maybeTrusted === null || maybeTrusted === undefined || maybeTrusted === '') { 16657 return maybeTrusted; 16658 } 16659 var constructor = (byType.hasOwnProperty(type) ? byType[type] : null); 16660 if (constructor && maybeTrusted instanceof constructor) { 16661 return maybeTrusted.$$unwrapTrustedValue(); 16662 } 16663 // If we get here, then we may only take one of two actions. 16664 // 1. sanitize the value for the requested type, or 16665 // 2. throw an exception. 16666 if (type === SCE_CONTEXTS.RESOURCE_URL) { 16667 if (isResourceUrlAllowedByPolicy(maybeTrusted)) { 16668 return maybeTrusted; 16669 } else { 16670 throw $sceMinErr('insecurl', 16671 'Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}', 16672 maybeTrusted.toString()); 16673 } 16674 } else if (type === SCE_CONTEXTS.HTML) { 16675 return htmlSanitizer(maybeTrusted); 16676 } 16677 throw $sceMinErr('unsafe', 'Attempting to use an unsafe value in a safe context.'); 16678 } 16679 16680 return { trustAs: trustAs, 16681 getTrusted: getTrusted,
16682 valueOf: valueOf }; 16683 }]; 16684} 16685 16686 16687/** 16688 * @ngdoc provider 16689 * @name $sceProvider 16690 * @description 16691 * 16692 * The $sceProvider provider allows developers to configure the {@link ng.$sce $sce} service. 16693 * - enable/disable Strict Contextual Escaping (SCE) in a module 16694 * - override the default implementation with a custom delegate 16695 * 16696 * Read more about {@link ng.$sce Strict Contextual Escaping (SCE)}. 16697 */ 16698 16699/* jshint maxlen: false*/ 16700 16701/** 16702 * @ngdoc service 16703 * @name $sce 16704 * @kind function 16705 * 16706 * @description 16707 * 16708 * `$sce` is a service that provides Strict Contextual Escaping services to AngularJS. 16709 * 16710 * # Strict Contextual Escaping 16711 * 16712 * Strict Contextual Escaping (SCE) is a mode in which AngularJS requires bindings in certain 16713 * contexts to result in a value that is marked as safe to use for that context. One example of 16714 * such a context is binding arbitrary html controlled by the user via `ng-bind-html`. We refer 16715 * to these contexts as privileged or SCE contexts. 16716 * 16717 * As of version 1.2, Angular ships with SCE enabled by default. 16718 * 16719 * Note: When enabled (the default), IE<11 in quirks mode is not supported. In this mode, IE<11 allow 16720 * one to execute arbitrary javascript by the use of the expression() syntax. Refer 16721 * <http://blogs.msdn.com/b/ie/archive/2008/10/16/ending-expressions.aspx> to learn more about them.
16722 * You can ensure your document is in standards mode and not quirks mode by adding `<!doctype html>` 16723 * to the top of your HTML document. 16724 * 16725 * SCE assists in writing code in way that (a) is secure by default and (b) makes auditing for 16726 * security vulnerabilities such as XSS, clickjacking, etc. a lot easier. 16727 * 16728 * Here's an example of a binding in a privileged context: 16729 * 16730 * ``` 16731 * <input ng-model="userHtml" aria-label="User input"> 16732 * <div ng-bind-html="userHtml"></div> 16733 * ``` 16734 * 16735 * Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE 16736 * disabled, this application allows the user to render arbitrary HTML into the DIV. 16737 * In a more realistic example, one may be rendering user comments, blog articles, etc. via 16738 * bindings. (HTML is just one example of a context where rendering user controlled input creates 16739 * security vulnerabilities.) 16740 * 16741 * For the case of HTML, you might use a library, either on the client side, or on the server side, 16742 * to sanitize unsafe HTML before binding to the value and rendering it in the document. 16743 * 16744 * How would you ensure that every place that used these types of bindings was bound to a value that 16745 * was sanitized by your library (or returned as safe for rendering by your server?) How can you 16746 * ensure that you didn't accidentally delete the line that sanitized the value, or renamed some 16747 * properties/fields and forgot to update the binding to the sanitized value? 16748 * 16749 * To be secure by default, you want to ensure that any such bindings are disallowed unless you can 16750 * determine that something explicitly says it's safe to use a value for binding in that
16751 * context. You can then audit your code (a simple grep would do) to ensure that this is only done 16752 * for those values that you can easily tell are safe - because they were received from your server, 16753 * sanitized by your library, etc. You can organize your codebase to help with this - perhaps 16754 * allowing only the files in a specific directory to do this. Ensuring that the internal API 16755 * exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task. 16756 * 16757 * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs} 16758 * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to 16759 * obtain values that will be accepted by SCE / privileged contexts. 16760 * 16761 * 16762 * ## How does it work? 16763 * 16764 * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted 16765 * $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link 16766 * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the 16767 * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals. 16768 * 16769 * As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link 16770 * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly 16771 * simplified): 16772 * 16773 * ``` 16774 * var ngBindHtmlDirective = ['$sce', function($sce) { 16775 * return function(scope, element, attr) { 16776 * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) { 16777 * element.html(value || ''); 16778 * }); 16779 * }; 16780 * }]; 16781 * ``` 16782 * 16783 * ## Impact on loading templates 16784 * 16785 * This applies both to the {@link ng.directive:ngInclude `ng-include`} directive as well as 16786 * `templateUrl`'s specified by {@link guide/directive directives}. 16787 * 16788 * By default, Angular only loads templates from the same domain and protocol as the application 16789 * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl 16790 * $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or 16791 * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist 16792 * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value. 16793 * 16794 * *Please note*: 16795 * The browser's 16796 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 16797 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 16798 * policy apply in addition to this and may further restrict whether the template is successfully 16799 * loaded. This means that without the right CORS policy, loading templates from a different domain 16800 * won't work on all browsers. Also, loading templates from `file://` URL does not work on some 16801 * browsers. 16802 * 16803 * ## This feels like too much overhead 16804 * 16805 * It's important to remember that SCE only applies to interpolation expressions. 16806 * 16807 * If your expressions are constant literals, they're automatically trusted and you don't need to 16808 * call `$sce.trustAs` on them (remember to include the `ngSanitize` module) (e.g. 16809 * `<div ng-bind-html="'<b>implicitly trusted</b>'"></div>`) just works. 16810 * 16811 * Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them 16812 * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here. 16813 * 16814 * The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load 16815 * templates in `ng-include` from your application's domain without having to even know about SCE. 16816 * It blocks loading templates from other domains or loading templates over http from an https 16817 * served document. You can change these by setting your own custom {@link 16818 * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link 16819 * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs. 16820 * 16821 * This significantly reduces the overhead. It is far easier to pay the small overhead and have an 16822 * application that's secure and can be audited to verify that with much more ease than bolting 16823 * security onto an application later. 16824 * 16825 * <a name="contexts"></a> 16826 * ## What trusted context types are supported? 16827 * 16828 * | Context | Notes | 16829 * |---------------------|----------------| 16830 * | `$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. | 16831 * | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. | 16832 * | `$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. | 16833 * | `$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. | 16834 * | `$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. | 16835 * 16836 * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist} <a name="resourceUrlPatternItem"></a> 16837 *
16838 * Each element in these arrays must be one of the following: 16839 * 16840 * - **'self'** 16841 * - The special **string**, `'self'`, can be used to match against all URLs of the **same 16842 * domain** as the application document using the **same protocol**. 16843 * - **String** (except the special value `'self'`) 16844 * - The string is matched against the full *normalized / absolute URL* of the resource 16845 * being tested (substring matches are not good enough.) 16846 * - There are exactly **two wildcard sequences** - `*` and `**`. All other characters 16847 * match themselves. 16848 * - `*`: matches zero or more occurrences of any character other than one of the following 6 16849 * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use 16850 * in a whitelist. 16851 * - `**`: matches zero or more occurrences of *any* character. As such, it's not 16852 * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g. 16853 * http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might 16854 * not have been the intention.) Its usage at the very end of the path is ok. (e.g. 16855 * http://foo.example.com/templates/**). 16856 * - **RegExp** (*see caveat below*) 16857 * - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax 16858 * (and all the inevitable escaping) makes them *harder to maintain*. It's easy to 16859 * accidentally introduce a bug when one updates a complex expression (imho, all regexes should 16860 * have good test coverage.). For instance, the use of `.` in the regex is correct only in a 16861 * small number of cases. A `.` character in the regex used when matching the scheme or a 16862 * subdomain could be matched against a `:` or literal `.` that was likely not intended. It 16863 * is highly recommended to use the string patterns and only fall back to regular expressions 16864 * if they as a last resort. 16865 * - The regular expression must be an instance of RegExp (i.e. not a string.) It is 16866 * matched against the **entire** *normalized / absolute URL* of the resource being tested 16867 * (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags 16868 * present on the RegExp (such as multiline, global, ignoreCase) are ignored. 16869 * - If you are generating your JavaScript from some other templating engine (not 16870 * recommended, e.g. in issue [#4006](https://github.com/angular/angular.js/issues/4006)), 16871 * remember to escape your regular expression (and be aware that you might need more than 16872 * one level of escaping depending on your templating engine and the way you interpolated 16873 * the value.) Do make use of your platform's escaping mechanism as it might be good 16874 * enough before coding your own. e.g. Ruby has 16875 * [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape) 16876 * and Python has [re.escape](http://docs.python.org/library/re.html#re.escape). 16877 * Javascript lacks a similar built in function for escaping. Take a look at Google 16878 * Closure library's [goog.string.regExpEscape(s)]( 16879 * http://docs.closure-library.googlecode.com/git/closure_goog_string_string.js.source.html#line962). 16880 * 16881 * Refer {@link ng.$sceDelegateProvider $sceDelegateProvider} for an example. 16882 * 16883 * ## Show me an example using SCE. 16884 * 16885 * <example module="mySceApp" deps="angular-sanitize.js"> 16886 * <file name="index.html"> 16887 * <div ng-controller="AppController as myCtrl"> 16888 * <i ng-bind-html="myCtrl.explicitlyTrustedHtml" id="explicitlyTrustedHtml"></i><br><br> 16889 * <b>User comments</b><br> 16890 * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when 16891 * $sanitize is available. If $sanitize isn't available, this results in an error instead of an 16892 * exploit. 16893 * <div class="well"> 16894 * <div ng-repeat="userComment in myCtrl.userComments"> 16895 * <b>{{userComment.name}}</b>: 16896 * <span ng-bind-html="userComment.htmlComment" class="htmlComment"></span> 16897 * <br> 16898 * </div>
16899 * </div> 16900 * </div> 16901 * </file> 16902 * 16903 * <file name="script.js"> 16904 * angular.module('mySceApp', ['ngSanitize']) 16905 * .controller('AppController', ['$http', '$templateCache', '$sce', 16906 * function($http, $templateCache, $sce) { 16907 * var self = this; 16908 * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) { 16909 * self.userComments = userComments; 16910 * }); 16911 * self.explicitlyTrustedHtml = $sce.trustAsHtml( 16912 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 16913 * 'sanitization."">Hover over this text.</span>'); 16914 * }]); 16915 * </file> 16916 * 16917 * <file name="test_data.json"> 16918 * [ 16919 * { "name": "Alice", 16920 * "htmlComment": 16921 * "<span onmouseover='this.textContent=\"PWN3D!\"'>Is <i>anyone</i> reading this?</span>" 16922 * }, 16923 * { "name": "Bob", 16924 * "htmlComment": "<i>Yes!</i> Am I the only other one?" 16925 * } 16926 * ] 16927 * </file> 16928 * 16929 * <file name="protractor.js" type="protractor"> 16930 * describe('SCE doc demo', function() { 16931 * it('should sanitize untrusted values', function() { 16932 * expect(element.all(by.css('.htmlComment')).first().getInnerHtml()) 16933 * .toBe('<span>Is <i>anyone</i> reading this?</span>'); 16934 * }); 16935 * 16936 * it('should NOT sanitize explicitly trusted values', function() { 16937 * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe( 16938 * '<span onmouseover="this.textContent="Explicitly trusted HTML bypasses ' + 16939 * 'sanitization."">Hover over this text.</span>'); 16940 * }); 16941 * }); 16942 * </file> 16943 * </example> 16944 * 16945 * 16946 * 16947 * ## Can I disable SCE completely? 16948 * 16949 * Yes, you can. However, this is strongly discouraged. SCE gives you a lot of security benefits 16950 * for little coding overhead. It will be much harder to take an SCE disabled application and 16951 * either secure it on your own or enable SCE at a later stage. It might make sense to disable SCE 16952 * for cases where you have a lot of existing code that was written before SCE was introduced and 16953 * you're migrating them a module at a time. 16954 * 16955 * That said, here's how you can completely disable SCE: 16956 * 16957 * ``` 16958 * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) { 16959 * // Completely disable SCE. For demonstration purposes only! 16960 * // Do not use in new projects. 16961 * $sceProvider.enabled(false); 16962 * }); 16963 * ``` 16964 * 16965 */ 16966/* jshint maxlen: 100 */ 16967 16968function $SceProvider() { 16969 var enabled = true; 16970 16971 /** 16972 * @ngdoc method 16973 * @name $sceProvider#enabled 16974 * @kind function 16975 * 16976 * @param {boolean=} value If provided, then enables/disables SCE. 16977 * @return {boolean} true if SCE is enabled, false otherwise. 16978 * 16979 * @description 16980 * Enables/disables SCE and returns the current value. 16981 */ 16982 this.enabled = function(value) { 16983 if (arguments.length) { 16984 enabled = !!value; 16985 } 16986 return enabled; 16987 }; 16988 16989 16990 /* Design notes on the default implementation for SCE. 16991 * 16992 * The API contract for the SCE delegate 16993 * ------------------------------------- 16994 * The SCE delegate object must provide the following 3 methods: 16995 * 16996 * - trustAs(contextEnum, value) 16997 * This method is used to tell the SCE service that the provided value is OK to use in the 16998 * contexts specified by contextEnum. It must return an object that will be accepted by 16999 * getTrusted() for a compatible contextEnum and return this value. 17000 * 17001 * - valueOf(value) 17002 * For values that were not produced by trustAs(), return them as is. For values that were 17003 * produced by trustAs(), return the corresponding input value to trustAs. Basically, if 17004 * trustAs is wrapping the given values into some type, this operation unwraps it when given 17005 * such a value. 17006 * 17007 * - getTrusted(contextEnum, value) 17008 * This function should return the a value that is safe to use in the context specified by 17009 * contextEnum or throw and exception otherwise. 17010 * 17011 * NOTE: This contract deliberately does NOT state that values returned by trustAs() must be 17012 * opaque or wrapped in some holder object. That happens to be an implementation detail. For 17013 * instance, an implementation could maintain a registry of all trusted objects by context. In 17014 * such a case, trustAs() would return the same object that was passed in. getTrusted() would 17015 * return the same object passed in if it was found in the registry under a compatible context or 17016 * throw an exception otherwise. An implementation might only wrap values some of the time based 17017 * on some criteria. getTrusted() might return a value and not throw an exception for special 17018 * constants or objects even if not wrapped. All such implementations fulfill this contract. 17019 * 17020 * 17021 * A note on the inheritance model for SCE contexts 17022 * ------------------------------------------------ 17023 * I've used inheritance and made RESOURCE_URL wrapped types a subtype of URL wrapped types. This 17024 * is purely an implementation details. 17025 * 17026 * The contract is simply this: 17027 * 17028 * getTrusted($sce.RESOURCE_URL, value) succeeding implies that getTrusted($sce.URL, value) 17029 * will also succeed. 17030 * 17031 * Inheritance happens to capture this in a natural way. In some future, we
17032 * may not use inheritance anymore. That is OK because no code outside of 17033 * sce.js and sceSpecs.js would need to be aware of this detail. 17034 */ 17035 17036 this.$get = ['$parse', '$sceDelegate', function( 17037 $parse, $sceDelegate) { 17038 // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow 17039 // the "expression(javascript expression)" syntax which is insecure. 17040 if (enabled && msie < 8) { 17041 throw $sceMinErr('iequirks', 17042 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' + 17043 'mode. You can fix this by adding the text <!doctype html> to the top of your HTML ' + 17044 'document. See http://docs.angularjs.org/api/ng.$sce for more information.'); 17045 } 17046 17047 var sce = shallowCopy(SCE_CONTEXTS); 17048 17049 /** 17050 * @ngdoc method 17051 * @name $sce#isEnabled 17052 * @kind function 17053 * 17054 * @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you 17055 * have to do it at module config time on {@link ng.$sceProvider $sceProvider}. 17056 * 17057 * @description 17058 * Returns a boolean indicating if SCE is enabled. 17059 */ 17060 sce.isEnabled = function() { 17061 return enabled; 17062 }; 17063 sce.trustAs = $sceDelegate.trustAs; 17064 sce.getTrusted = $sceDelegate.getTrusted; 17065 sce.valueOf = $sceDelegate.valueOf; 17066 17067 if (!enabled) { 17068 sce.trustAs = sce.getTrusted = function(type, value) { return value; }; 17069 sce.valueOf = identity; 17070 } 17071 17072 /** 17073 * @ngdoc method 17074 * @name $sce#parseAs 17075 * 17076 * @description 17077 * Converts Angular {@link guide/expression expression} into a function. This is like {@link 17078 * ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it 17079 * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*, 17080 * *result*)} 17081 * 17082 * @param {string} type The kind of SCE context in which this result will be used. 17083 * @param {string} expression String expression to compile. 17084 * @returns {function(context, locals)} a function which represents the compiled expression: 17085 * 17086 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17087 * are evaluated against (typically a scope object). 17088 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17089 * `context`. 17090 */ 17091 sce.parseAs = function sceParseAs(type, expr) { 17092 var parsed = $parse(expr); 17093 if (parsed.literal && parsed.constant) { 17094 return parsed; 17095 } else { 17096 return $parse(expr, function(value) { 17097 return sce.getTrusted(type, value); 17098 }); 17099 } 17100 }; 17101 17102 /** 17103 * @ngdoc method 17104 * @name $sce#trustAs 17105 * 17106 * @description 17107 * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such, 17108 * returns an object that is trusted by angular for use in specified strict contextual 17109 * escaping contexts (such as ng-bind-html, ng-include, any src attribute 17110 * interpolation, any dom event binding attribute interpolation such as for onclick, etc.) 17111 * that uses the provided value. See * {@link ng.$sce $sce} for enabling strict contextual 17112 * escaping. 17113 * 17114 * @param {string} type The kind of context in which this value is safe for use. e.g. url, 17115 * resourceUrl, html, js and css. 17116 * @param {*} value The value that that should be considered trusted/safe. 17117 * @returns {*} A value that can be used to stand in for the provided `value` in places 17118 * where Angular expects a $sce.trustAs() return value. 17119 */ 17120 17121 /** 17122 * @ngdoc method 17123 * @name $sce#trustAsHtml 17124 * 17125 * @description 17126 * Shorthand method. `$sce.trustAsHtml(value)` â 17127 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`} 17128 * 17129 * @param {*} value The value to trustAs. 17130 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml 17131 * $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives 17132 * only accept expressions that are either literal constants or are the 17133 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17134 */ 17135 17136 /** 17137 * @ngdoc method 17138 * @name $sce#trustAsUrl 17139 *
17140 * @description 17141 * Shorthand method. `$sce.trustAsUrl(value)` â 17142 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`} 17143 * 17144 * @param {*} value The value to trustAs. 17145 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl 17146 * $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives 17147 * only accept expressions that are either literal constants or are the 17148 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17149 */ 17150 17151 /** 17152 * @ngdoc method 17153 * @name $sce#trustAsResourceUrl 17154 * 17155 * @description 17156 * Shorthand method. `$sce.trustAsResourceUrl(value)` â 17157 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`} 17158 * 17159 * @param {*} value The value to trustAs. 17160 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl 17161 * $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives 17162 * only accept expressions that are either literal constants or are the return 17163 * value of {@link ng.$sce#trustAs $sce.trustAs}.) 17164 */ 17165 17166 /** 17167 * @ngdoc method 17168 * @name $sce#trustAsJs 17169 * 17170 * @description 17171 * Shorthand method. `$sce.trustAsJs(value)` â 17172 * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`} 17173 * 17174 * @param {*} value The value to trustAs. 17175 * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs 17176 * $sce.getTrustedJs(value)} to obtain the original value. (privileged directives 17177 * only accept expressions that are either literal constants or are the 17178 * return value of {@link ng.$sce#trustAs $sce.trustAs}.) 17179 */ 17180 17181 /** 17182 * @ngdoc method 17183 * @name $sce#getTrusted 17184 * 17185 * @description 17186 * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such, 17187 * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the 17188 * originally supplied value if the queried context type is a supertype of the created type. 17189 * If this condition isn't satisfied, throws an exception. 17190 * 17191 * @param {string} type The kind of context in which this value is to be used. 17192 * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`} 17193 * call. 17194 * @returns {*} The value the was originally provided to 17195 * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context. 17196 * Otherwise, throws an exception. 17197 */ 17198 17199 /** 17200 * @ngdoc method 17201 * @name $sce#getTrustedHtml 17202 * 17203 * @description 17204 * Shorthand method. `$sce.getTrustedHtml(value)` â 17205 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`} 17206 * 17207 * @param {*} value The value to pass to `$sce.getTrusted`. 17208 * @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)` 17209 */ 17210 17211 /** 17212 * @ngdoc method 17213 * @name $sce#getTrustedCss 17214 * 17215 * @description 17216 * Shorthand method. `$sce.getTrustedCss(value)` â 17217 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`} 17218 * 17219 * @param {*} value The value to pass to `$sce.getTrusted`. 17220 * @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)` 17221 */ 17222 17223 /** 17224 * @ngdoc method 17225 * @name $sce#getTrustedUrl 17226 * 17227 * @description 17228 * Shorthand method. `$sce.getTrustedUrl(value)` â 17229 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`} 17230 * 17231 * @param {*} value The value to pass to `$sce.getTrusted`. 17232 * @returns {*} The return value of `$sce.getTrusted($sce.URL, value)` 17233 */ 17234 17235 /** 17236 * @ngdoc method 17237 * @name $sce#getTrustedResourceUrl 17238 * 17239 * @description 17240 * Shorthand method. `$sce.getTrustedResourceUrl(value)` â 17241 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`} 17242 * 17243 * @param {*} value The value to pass to `$sceDelegate.getTrusted`. 17244 * @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)` 17245 */ 17246 17247 /** 17248 * @ngdoc method 17249 * @name $sce#getTrustedJs 17250 *
17251 * @description 17252 * Shorthand method. `$sce.getTrustedJs(value)` â 17253 * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`} 17254 * 17255 * @param {*} value The value to pass to `$sce.getTrusted`. 17256 * @returns {*} The return value of `$sce.getTrusted($sce.JS, value)` 17257 */ 17258 17259 /** 17260 * @ngdoc method 17261 * @name $sce#parseAsHtml 17262 * 17263 * @description 17264 * Shorthand method. `$sce.parseAsHtml(expression string)` â 17265 * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`} 17266 * 17267 * @param {string} expression String expression to compile. 17268 * @returns {function(context, locals)} a function which represents the compiled expression: 17269 * 17270 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17271 * are evaluated against (typically a scope object). 17272 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17273 * `context`. 17274 */ 17275 17276 /** 17277 * @ngdoc method 17278 * @name $sce#parseAsCss 17279 * 17280 * @description 17281 * Shorthand method. `$sce.parseAsCss(value)` â 17282 * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`} 17283 * 17284 * @param {string} expression String expression to compile. 17285 * @returns {function(context, locals)} a function which represents the compiled expression: 17286 * 17287 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17288 * are evaluated against (typically a scope object). 17289 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17290 * `context`. 17291 */ 17292 17293 /** 17294 * @ngdoc method 17295 * @name $sce#parseAsUrl 17296 * 17297 * @description 17298 * Shorthand method. `$sce.parseAsUrl(value)` â 17299 * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`} 17300 * 17301 * @param {string} expression String expression to compile. 17302 * @returns {function(context, locals)} a function which represents the compiled expression: 17303 * 17304 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17305 * are evaluated against (typically a scope object). 17306 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17307 * `context`. 17308 */ 17309 17310 /** 17311 * @ngdoc method 17312 * @name $sce#parseAsResourceUrl 17313 * 17314 * @description 17315 * Shorthand method. `$sce.parseAsResourceUrl(value)` â 17316 * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`} 17317 * 17318 * @param {string} expression String expression to compile. 17319 * @returns {function(context, locals)} a function which represents the compiled expression: 17320 * 17321 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17322 * are evaluated against (typically a scope object). 17323 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17324 * `context`. 17325 */ 17326 17327 /** 17328 * @ngdoc method 17329 * @name $sce#parseAsJs 17330 * 17331 * @description 17332 * Shorthand method. `$sce.parseAsJs(value)` â 17333 * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`} 17334 * 17335 * @param {string} expression String expression to compile. 17336 * @returns {function(context, locals)} a function which represents the compiled expression: 17337 * 17338 * * `context` â `{object}` â an object against which any expressions embedded in the strings 17339 * are evaluated against (typically a scope object). 17340 * * `locals` â `{object=}` â local variables context object, useful for overriding values in 17341 * `context`. 17342 */ 17343 17344 // Shorthand delegations. 17345 var parse = sce.parseAs, 17346 getTrusted = sce.getTrusted, 17347 trustAs = sce.trustAs; 17348 17349 forEach(SCE_CONTEXTS, function(enumValue, name) { 17350 var lName = lowercase(name); 17351 sce[camelCase("parse_as_" + lName)] = function(expr) { 17352 return parse(enumValue, expr); 17353 }; 17354 sce[camelCase("get_trusted_" + lName)] = function(value) { 17355 return getTrusted(enumValue, value); 17356 }; 17357 sce[camelCase("trust_as_" + lName)] = function(value) { 17358 return trustAs(enumValue, value); 17359 }; 17360 }); 17361 17362 return sce; 17363 }]; 17364} 17365 17366/** 17367 * !!! This is an undocumented "private" service !!! 17368 * 17369 * @name $sniffer 17370 * @requires $window 17371 * @requires $document 17372 * 17373 * @property {boolean} history Does the browser support html5 history api ? 17374 * @property {boolean} transitions Does the browser support CSS transition events ? 17375 * @property {boolean} animations Does the browser support CSS animation events ? 17376 * 17377 * @description 17378 * This is very simple implementation of testing browser's features. 17379 */ 17380function $SnifferProvider() { 17381 this.$get = ['$window', '$document', function($window, $document) { 17382 var eventSupport = {}, 17383 android = 17384 toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]), 17385 boxee = /Boxee/i.test(($window.navigator || {}).userAgent), 17386 document = $document[0] || {}, 17387 vendorPrefix, 17388 vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/, 17389 bodyStyle = document.body && document.body.style, 17390 transitions = false, 17391 animations = false, 17392 match; 17393 17394 if (bodyStyle) { 17395 for (var prop in bodyStyle) {
17396 if (match = vendorRegex.exec(prop)) { 17397 vendorPrefix = match[0]; 17398 vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1); 17399 break; 17400 } 17401 } 17402 17403 if (!vendorPrefix) { 17404 vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit'; 17405 } 17406 17407 transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle)); 17408 animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle)); 17409 17410 if (android && (!transitions || !animations)) { 17411 transitions = isString(bodyStyle.webkitTransition); 17412 animations = isString(bodyStyle.webkitAnimation); 17413 } 17414 } 17415 17416 17417 return { 17418 // Android has history.pushState, but it does not update location correctly 17419 // so let's not use the history API at all. 17420 // http://code.google.com/p/android/issues/detail?id=17471 17421 // https://github.com/angular/angular.js/issues/904 17422 17423 // older webkit browser (533.9) on Boxee box has exactly the same problem as Android has 17424 // so let's not use the history API also 17425 // We are purposefully using `!(android < 4)` to cover the case when `android` is undefined 17426 // jshint -W018 17427 history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee), 17428 // jshint +W018 17429 hasEvent: function(event) { 17430 // IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have 17431 // it. In particular the event is not fired when backspace or delete key are pressed or 17432 // when cut operation is performed. 17433 // IE10+ implements 'input' event but it erroneously fires under various situations, 17434 // e.g. when placeholder changes, or a form is focused. 17435 if (event === 'input' && msie <= 11) return false; 17436 17437 if (isUndefined(eventSupport[event])) { 17438 var divElm = document.createElement('div'); 17439 eventSupport[event] = 'on' + event in divElm; 17440 } 17441 17442 return eventSupport[event]; 17443 }, 17444 csp: csp(), 17445 vendorPrefix: vendorPrefix, 17446 transitions: transitions, 17447 animations: animations, 17448 android: android 17449 }; 17450 }]; 17451} 17452 17453var $compileMinErr = minErr('$compile'); 17454 17455/** 17456 * @ngdoc service 17457 * @name $templateRequest 17458 * 17459 * @description 17460 * The `$templateRequest` service downloads the provided template using `$http` and, upon success, 17461 * stores the contents inside of `$templateCache`. If the HTTP request fails or the response data 17462 * of the HTTP request is empty, a `$compile` error will be thrown (the exception can be thwarted 17463 * by setting the 2nd parameter of the function to true). 17464 * 17465 * @param {string} tpl The HTTP request template URL 17466 * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty 17467 * 17468 * @return {Promise} a promise for the HTTP response data of the given URL. 17469 * 17470 * @property {number} totalPendingRequests total amount of pending template requests being downloaded. 17471 */ 17472function $TemplateRequestProvider() { 17473 this.$get = ['$templateCache', '$http', '$q', function($templateCache, $http, $q) { 17474 function handleRequestFn(tpl, ignoreRequestError) { 17475 handleRequestFn.totalPendingRequests++; 17476 17477 var transformResponse = $http.defaults && $http.defaults.transformResponse; 17478 17479 if (isArray(transformResponse)) { 17480 transformResponse = transformResponse.filter(function(transformer) { 17481 return transformer !== defaultHttpResponseTransform; 17482 }); 17483 } else if (transformResponse === defaultHttpResponseTransform) { 17484 transformResponse = null; 17485 } 17486 17487 var httpOptions = { 17488 cache: $templateCache, 17489 transformResponse: transformResponse 17490 }; 17491 17492 return $http.get(tpl, httpOptions) 17493 ['finally'](function() { 17494 handleRequestFn.totalPendingRequests--; 17495 }) 17496 .then(function(response) { 17497 $templateCache.put(tpl, response.data); 17498 return response.data; 17499 }, handleError); 17500 17501 function handleError(resp) { 17502 if (!ignoreRequestError) { 17503 throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})', 17504 tpl, resp.status, resp.statusText); 17505 } 17506 return $q.reject(resp); 17507 } 17508 } 17509 17510 handleRequestFn.totalPendingRequests = 0; 17511 17512 return handleRequestFn; 17513 }]; 17514} 17515
vendor: 5,440 bytes, lines 17516-17669
17516function $$TestabilityProvider() { 17517 this.$get = ['$rootScope', '$browser', '$location', 17518 function($rootScope, $browser, $location) { 17519 17520 /** 17521 * @name $testability 17522 * 17523 * @description 17524 * The private $$testability service provides a collection of methods for use when debugging 17525 * or by automated test and debugging tools. 17526 */ 17527 var testability = {}; 17528 17529 /** 17530 * @name $$testability#findBindings 17531 * 17532 * @description 17533 * Returns an array of elements that are bound (via ng-bind or {{}}) 17534 * to expressions matching the input. 17535 * 17536 * @param {Element} element The element root to search from. 17537 * @param {string} expression The binding expression to match. 17538 * @param {boolean} opt_exactMatch If true, only returns exact matches 17539 * for the expression. Filters and whitespace are ignored. 17540 */ 17541 testability.findBindings = function(element, expression, opt_exactMatch) { 17542 var bindings = element.getElementsByClassName('ng-binding'); 17543 var matches = []; 17544 forEach(bindings, function(binding) { 17545 var dataBinding = angular.element(binding).data('$binding'); 17546 if (dataBinding) { 17547 forEach(dataBinding, function(bindingName) { 17548 if (opt_exactMatch) { 17549 var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)'); 17550 if (matcher.test(bindingName)) { 17551 matches.push(binding); 17552 } 17553 } else { 17554 if (bindingName.indexOf(expression) != -1) { 17555 matches.push(binding); 17556 } 17557 } 17558 }); 17559 } 17560 }); 17561 return matches; 17562 }; 17563 17564 /** 17565 * @name $$testability#findModels 17566 * 17567 * @description 17568 * Returns an array of elements that are two-way found via ng-model to 17569 * expressions matching the input. 17570 * 17571 * @param {Element} element The element root to search from. 17572 * @param {string} expression The model expression to match. 17573 * @param {boolean} opt_exactMatch If true, only returns exact matches 17574 * for the expression. 17575 */ 17576 testability.findModels = function(element, expression, opt_exactMatch) { 17577 var prefixes = ['ng-', 'data-ng-', 'ng\\:']; 17578 for (var p = 0; p < prefixes.length; ++p) { 17579 var attributeEquals = opt_exactMatch ? '=' : '*='; 17580 var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]'; 17581 var elements = element.querySelectorAll(selector); 17582 if (elements.length) { 17583 return elements; 17584 } 17585 } 17586 }; 17587 17588 /** 17589 * @name $$testability#getLocation 17590 * 17591 * @description 17592 * Shortcut for getting the location in a browser agnostic way. Returns 17593 * the path, search, and hash. (e.g. /path?a=b#hash) 17594 */ 17595 testability.getLocation = function() { 17596 return $location.url(); 17597 }; 17598 17599 /** 17600 * @name $$testability#setLocation 17601 * 17602 * @description 17603 * Shortcut for navigating to a location without doing a full page reload. 17604 * 17605 * @param {string} url The location url (path, search and hash, 17606 * e.g. /path?a=b#hash) to go to. 17607 */ 17608 testability.setLocation = function(url) { 17609 if (url !== $location.url()) { 17610 $location.url(url); 17611 $rootScope.$digest(); 17612 } 17613 }; 17614 17615 /** 17616 * @name $$testability#whenStable 17617 * 17618 * @description 17619 * Calls the callback when $timeout and $http requests are completed. 17620 * 17621 * @param {function} callback 17622 */ 17623 testability.whenStable = function(callback) { 17624 $browser.notifyWhenNoOutstandingRequests(callback); 17625 }; 17626 17627 return testability; 17628 }]; 17629} 17630 17631function $TimeoutProvider() { 17632 this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler', 17633 function($rootScope, $browser, $q, $$q, $exceptionHandler) { 17634 17635 var deferreds = {}; 17636 17637 17638 /** 17639 * @ngdoc service 17640 * @name $timeout 17641 * 17642 * @description 17643 * Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch 17644 * block and delegates any exceptions to 17645 * {@link ng.$exceptionHandler $exceptionHandler} service. 17646 * 17647 * The return value of calling `$timeout` is a promise, which will be resolved when 17648 * the delay has passed and the timeout function, if provided, is executed. 17649 * 17650 * To cancel a timeout request, call `$timeout.cancel(promise)`. 17651 * 17652 * In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to 17653 * synchronously flush the queue of deferred functions. 17654 * 17655 * If you only want a promise that will be resolved after some specified delay 17656 * then you can call `$timeout` without the `fn` function. 17657 * 17658 * @param {function()=} fn A function, whose execution should be delayed. 17659 * @param {number=} [delay=0] Delay in milliseconds. 17660 * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise 17661 * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. 17662 * @param {...*=} Pass additional parameters to the executed function. 17663 * @returns {Promise} Promise that will be resolved when the timeout is reached. The value this 17664 * promise will be resolved with is the return value of the `fn` function. 17665 * 17666 */ 17667 function timeout(fn, delay, invokeApply) { 17668 if (!isFunction(fn)) { 17669 invokeApply = delay;
17670 delay = fn; 17671 fn = noop; 17672 } 17673 17674 var args = sliceArgs(arguments, 3), 17675 skipApply = (isDefined(invokeApply) && !invokeApply), 17676 deferred = (skipApply ? $$q : $q).defer(), 17677 promise = deferred.promise, 17678 timeoutId; 17679 17680 timeoutId = $browser.defer(function() { 17681 try { 17682 deferred.resolve(fn.apply(null, args)); 17683 } catch (e) { 17684 deferred.reject(e); 17685 $exceptionHandler(e); 17686 } 17687 finally { 17688 delete deferreds[promise.$$timeoutId]; 17689 } 17690 17691 if (!skipApply) $rootScope.$apply(); 17692 }, delay); 17693 17694 promise.$$timeoutId = timeoutId; 17695 deferreds[timeoutId] = deferred; 17696 17697 return promise; 17698 } 17699 17700 17701 /** 17702 * @ngdoc method 17703 * @name $timeout#cancel 17704 * 17705 * @description 17706 * Cancels a task associated with the `promise`. As a result of this, the promise will be 17707 * resolved with a rejection. 17708 * 17709 * @param {Promise=} promise Promise returned by the `$timeout` function. 17710 * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfully 17711 * canceled. 17712 */ 17713 timeout.cancel = function(promise) { 17714 if (promise && promise.$$timeoutId in deferreds) { 17715 deferreds[promise.$$timeoutId].reject('canceled'); 17716 delete deferreds[promise.$$timeoutId]; 17717 return $browser.defer.cancel(promise.$$timeoutId); 17718 } 17719 return false; 17720 }; 17721 17722 return timeout; 17723 }]; 17724} 17725 17726// NOTE: The usage of window and document instead of $window and $document here is 17727// deliberate. This service depends on the specific behavior of anchor nodes created by the 17728// browser (resolving and parsing URLs) that is unlikely to be provided by mock objects and 17729// cause us to break tests. In addition, when the browser resolves a URL for XHR, it 17730// doesn't know about mocked locations and resolves URLs to the real document - which is 17731// exactly the behavior needed here. There is little value is mocking these out for this 17732// service. 17733var urlParsingNode = document.createElement("a"); 17734var originUrl = urlResolve(window.location.href); 17735 17736 17737/** 17738 * 17739 * Implementation Notes for non-IE browsers 17740 * ---------------------------------------- 17741 * Assigning a URL to the href property of an anchor DOM node, even one attached to the DOM, 17742 * results both in the normalizing and parsing of the URL. Normalizing means that a relative 17743 * URL will be resolved into an absolute URL in the context of the application document. 17744 * Parsing means that the anchor node's host, hostname, protocol, port, pathname and related 17745 * properties are all populated to reflect the normalized URL. This approach has wide 17746 * compatibility - Safari 1+, Mozilla 1+, Opera 7+,e etc. See 17747 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 17748 * 17749 * Implementation Notes for IE 17750 * --------------------------- 17751 * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other 17752 * browsers. However, the parsed components will not be set if the URL assigned did not specify 17753 * them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We 17754 * work around that by performing the parsing in a 2nd step by taking a previously normalized 17755 * URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the 17756 * properties such as protocol, hostname, port, etc. 17757 * 17758 * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one 17759 * uses the inner HTML approach to assign the URL as part of an HTML snippet - 17760 * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL. 17761 * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception. 17762 * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that 17763 * method and IE < 8 is unsupported. 17764 * 17765 * References: 17766 * http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement 17767 * http://www.aptana.com/reference/html/api/HTMLAnchorElement.html 17768 * http://url.spec.whatwg.org/#urlutils 17769 * https://github.com/angular/angular.js/pull/2902 17770 * http://james.padolsey.com/javascript/parsing-urls-with-the-dom/ 17771 * 17772 * @kind function 17773 * @param {string} url The URL to be parsed. 17774 * @description Normalizes and parses a URL. 17775 * @returns {object} Returns the normalized URL as a dictionary. 17776 * 17777 * | member name | Description | 17778 * |---------------|----------------| 17779 * | href | A normalized version of the provided URL if it was not an absolute URL | 17780 * | protocol | The protocol including the trailing colon | 17781 * | host | The host and port (if the port is non-default) of the normalizedUrl | 17782 * | search | The search params, minus the question mark | 17783 * | hash | The hash string, minus the hash symbol 17784 * | hostname | The hostname 17785 * | port | The port, without ":" 17786 * | pathname | The pathname, beginning with "/" 17787 * 17788 */ 17789function urlResolve(url) { 17790 var href = url; 17791 17792 if (msie) { 17793 // Normalize before parse. Refer Implementation Notes on why this is 17794 // done in two steps on IE. 17795 urlParsingNode.setAttribute("href", href); 17796 href = urlParsingNode.href; 17797 } 17798 17799 urlParsingNode.setAttribute('href', href); 17800 17801 // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 17802 return { 17803 href: urlParsingNode.href, 17804 protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 17805 host: urlParsingNode.host, 17806 search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 17807 hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 17808 hostname: urlParsingNode.hostname, 17809 port: urlParsingNode.port, 17810 pathname: (urlParsingNode.pathname.charAt(0) === '/') 17811 ? urlParsingNode.pathname 17812 : '/' + urlParsingNode.pathname 17813 }; 17814} 17815 17816/** 17817 * Parse a request URL and determine whether this is a same-origin request as the application document. 17818 * 17819 * @param {string|object} requestUrl The url of the request as a string that will be resolved 17820 * or a parsed URL object. 17821 * @returns {boolean} Whether the request is for the same origin as the application document. 17822 */ 17823function urlIsSameOrigin(requestUrl) { 17824 var parsed = (isString(requestUrl)) ? urlResolve(requestUrl) : requestUrl; 17825 return (parsed.protocol === originUrl.protocol && 17826 parsed.host === originUrl.host); 17827} 17828 17829/** 17830 * @ngdoc service 17831 * @name $window 17832 * 17833 * @description 17834 * A reference to the browser's `window` object. While `window` 17835 * is globally available in JavaScript, it causes testability problems, because 17836 * it is a global variable. In angular we always refer to it through the 17837 * `$window` service, so it may be overridden, removed or mocked for testing. 17838 * 17839 * Expressions, like the one defined for the `ngClick` directive in the example 17840 * below, are evaluated with respect to the current scope. Therefore, there is 17841 * no risk of inadvertently coding in a dependency on a global value in such an 17842 * expression. 17843 * 17844 * @example 17845 <example module="windowExample"> 17846 <file name="index.html"> 17847 <script> 17848 angular.module('windowExample', [])
17849 .controller('ExampleController', ['$scope', '$window', function($scope, $window) { 17850 $scope.greeting = 'Hello, World!'; 17851 $scope.doGreeting = function(greeting) { 17852 $window.alert(greeting); 17853 }; 17854 }]); 17855 </script> 17856 <div ng-controller="ExampleController"> 17857 <input type="text" ng-model="greeting" aria-label="greeting" /> 17858 <button ng-click="doGreeting(greeting)">ALERT</button> 17859 </div> 17860 </file> 17861 <file name="protractor.js" type="protractor"> 17862 it('should display the greeting in the input box', function() { 17863 element(by.model('greeting')).sendKeys('Hello, E2E Tests'); 17864 // If we click the button it will block the test runner 17865 // element(':button').click(); 17866 }); 17867 </file> 17868 </example> 17869 */ 17870function $WindowProvider() { 17871 this.$get = valueFn(window); 17872} 17873 17874/** 17875 * @name $$cookieReader 17876 * @requires $document 17877 * 17878 * @description 17879 * This is a private service for reading cookies used by $http and ngCookies 17880 * 17881 * @return {Object} a key/value map of the current cookies 17882 */ 17883function $$CookieReader($document) { 17884 var rawDocument = $document[0] || {}; 17885 var lastCookies = {}; 17886 var lastCookieString = ''; 17887 17888 function safeDecodeURIComponent(str) { 17889 try { 17890 return decodeURIComponent(str); 17891 } catch (e) { 17892 return str; 17893 } 17894 } 17895 17896 return function() { 17897 var cookieArray, cookie, i, index, name; 17898 var currentCookieString = rawDocument.cookie || ''; 17899 17900 if (currentCookieString !== lastCookieString) { 17901 lastCookieString = currentCookieString; 17902 cookieArray = lastCookieString.split('; '); 17903 lastCookies = {}; 17904 17905 for (i = 0; i < cookieArray.length; i++) { 17906 cookie = cookieArray[i]; 17907 index = cookie.indexOf('='); 17908 if (index > 0) { //ignore nameless cookies 17909 name = safeDecodeURIComponent(cookie.substring(0, index)); 17910 // the first value that is seen for a cookie is the most 17911 // specific one. values for the same cookie name that 17912 // follow are for less specific paths. 17913 if (lastCookies[name] === undefined) { 17914 lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1)); 17915 } 17916 } 17917 } 17918 } 17919 return lastCookies; 17920 }; 17921} 17922 17923$$CookieReader.$inject = ['$document']; 17924 17925function $$CookieReaderProvider() { 17926 this.$get = $$CookieReader; 17927} 17928 17929/* global currencyFilter: true, 17930 dateFilter: true, 17931 filterFilter: true, 17932 jsonFilter: true, 17933 limitToFilter: true, 17934 lowercaseFilter: true, 17935 numberFilter: true, 17936 orderByFilter: true, 17937 uppercaseFilter: true, 17938 */ 17939 17940/** 17941 * @ngdoc provider 17942 * @name $filterProvider 17943 * @description 17944 * 17945 * Filters are just functions which transform input to an output. However filters need to be 17946 * Dependency Injected. To achieve this a filter definition consists of a factory function which is 17947 * annotated with dependencies and is responsible for creating a filter function. 17948 * 17949 * <div class="alert alert-warning"> 17950 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 17951 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 17952 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 17953 * (`myapp_subsection_filterx`). 17954 * </div> 17955 * 17956 * ```js 17957 * // Filter registration 17958 * function MyModule($provide, $filterProvider) { 17959 * // create a service to demonstrate injection (not always needed) 17960 * $provide.value('greet', function(name){ 17961 * return 'Hello ' + name + '!'; 17962 * }); 17963 * 17964 * // register a filter factory which uses the 17965 * // greet service to demonstrate DI. 17966 * $filterProvider.register('greet', function(greet){ 17967 * // return the filter function which uses the greet service 17968 * // to generate salutation 17969 * return function(text) { 17970 * // filters need to be forgiving so check input validity 17971 * return text && greet(text) || text; 17972 * }; 17973 * }); 17974 * } 17975 * ``` 17976 * 17977 * The filter function is registered with the `$injector` under the filter name suffix with 17978 * `Filter`. 17979 * 17980 * ```js 17981 * it('should be the same instance', inject( 17982 * function($filterProvider) { 17983 * $filterProvider.register('reverse', function(){ 17984 * return ...; 17985 * }); 17986 * }, 17987 * function($filter, reverseFilter) { 17988 * expect($filter('reverse')).toBe(reverseFilter); 17989 * }); 17990 * ``` 17991 * 17992 * 17993 * For more information about how angular filters work, and how to create your own filters, see 17994 * {@link guide/filter Filters} in the Angular Developer Guide. 17995 */ 17996 17997/** 17998 * @ngdoc service 17999 * @name $filter 18000 * @kind function 18001 * @description 18002 * Filters are used for formatting data displayed to the user. 18003 * 18004 * The general syntax in templates is as follows: 18005 * 18006 * {{ expression [| filter_name[:parameter_value] ... ] }} 18007 * 18008 * @param {String} name Name of the filter function to retrieve 18009 * @return {Function} the filter function 18010 * @example 18011 <example name="$filter" module="filterExample"> 18012 <file name="index.html"> 18013 <div ng-controller="MainCtrl"> 18014 <h3>{{ originalText }}</h3> 18015 <h3>{{ filteredText }}</h3> 18016 </div> 18017 </file> 18018 18019 <file name="script.js"> 18020 angular.module('filterExample', []) 18021 .controller('MainCtrl', function($scope, $filter) { 18022 $scope.originalText = 'hello'; 18023 $scope.filteredText = $filter('uppercase')($scope.originalText); 18024 }); 18025 </file> 18026 </example> 18027 */ 18028$FilterProvider.$inject = ['$provide']; 18029function $FilterProvider($provide) { 18030 var suffix = 'Filter'; 18031 18032 /** 18033 * @ngdoc method 18034 * @name $filterProvider#register 18035 * @param {string|Object} name Name of the filter function, or an object map of filters where 18036 * the keys are the filter names and the values are the filter factories. 18037 * 18038 * <div class="alert alert-warning"> 18039 * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`. 18040 * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace 18041 * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores 18042 * (`myapp_subsection_filterx`). 18043 * </div> 18044 * @returns {Object} Registered filter instance, or if a map of filters was provided then a map 18045 * of the registered filter instances. 18046 */ 18047 function register(name, factory) { 18048 if (isObject(name)) { 18049 var filters = {};
18050 forEach(name, function(filter, key) { 18051 filters[key] = register(key, filter); 18052 }); 18053 return filters; 18054 } else { 18055 return $provide.factory(name + suffix, factory); 18056 } 18057 } 18058 this.register = register; 18059 18060 this.$get = ['$injector', function($injector) { 18061 return function(name) { 18062 return $injector.get(name + suffix); 18063 }; 18064 }]; 18065 18066 //////////////////////////////////////// 18067 18068 /* global 18069 currencyFilter: false, 18070 dateFilter: false, 18071 filterFilter: false, 18072 jsonFilter: false, 18073 limitToFilter: false, 18074 lowercaseFilter: false, 18075 numberFilter: false, 18076 orderByFilter: false, 18077 uppercaseFilter: false, 18078 */ 18079 18080 register('currency', currencyFilter); 18081 register('date', dateFilter); 18082 register('filter', filterFilter); 18083 register('json', jsonFilter); 18084 register('limitTo', limitToFilter); 18085 register('lowercase', lowercaseFilter); 18086 register('number', numberFilter); 18087 register('orderBy', orderByFilter); 18088 register('uppercase', uppercaseFilter); 18089} 18090 18091/** 18092 * @ngdoc filter 18093 * @name filter 18094 * @kind function 18095 * 18096 * @description 18097 * Selects a subset of items from `array` and returns it as a new array. 18098 * 18099 * @param {Array} array The source array. 18100 * @param {string|Object|function()} expression The predicate to be used for selecting items from 18101 * `array`. 18102 * 18103 * Can be one of: 18104 * 18105 * - `string`: The string is used for matching against the contents of the `array`. All strings or 18106 * objects with string properties in `array` that match this string will be returned. This also 18107 * applies to nested object properties. 18108 * The predicate can be negated by prefixing the string with `!`. 18109 * 18110 * - `Object`: A pattern object can be used to filter specific properties on objects contained 18111 * by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items 18112 * which have property `name` containing "M" and property `phone` containing "1". A special 18113 * property name `$` can be used (as in `{$:"text"}`) to accept a match against any 18114 * property of the object or its nested object properties. That's equivalent to the simple 18115 * substring match with a `string` as described above. The predicate can be negated by prefixing 18116 * the string with `!`. 18117 * For example `{name: "!M"}` predicate will return an array of items which have property `name` 18118 * not containing "M". 18119 * 18120 * Note that a named property will match properties on the same level only, while the special 18121 * `$` property will match properties on the same level or deeper. E.g. an array item like 18122 * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but 18123 * **will** be matched by `{$: 'John'}`. 18124 * 18125 * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters. 18126 * The function is called for each element of the array, with the element, its index, and 18127 * the entire array itself as arguments. 18128 * 18129 * The final result is an array of those elements that the predicate returned true for. 18130 * 18131 * @param {function(actual, expected)|true|undefined} comparator Comparator which is used in 18132 * determining if the expected value (from the filter expression) and actual value (from 18133 * the object in the array) should be considered a match. 18134 * 18135 * Can be one of: 18136 * 18137 * - `function(actual, expected)`: 18138 * The function will be given the object value and the predicate value to compare and 18139 * should return true if both values should be considered equal. 18140 * 18141 * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`. 18142 * This is essentially strict comparison of expected and actual. 18143 * 18144 * - `false|undefined`: A short hand for a function which will look for a substring match in case 18145 * insensitive way. 18146 * 18147 * Primitive values are converted to strings. Objects are not compared against primitives, 18148 * unless they have a custom `toString` method (e.g. `Date` objects). 18149 * 18150 * @example 18151 <example> 18152 <file name="index.html"> 18153 <div ng-init="friends = [{name:'John', phone:'555-1276'}, 18154 {name:'Mary', phone:'800-BIG-MARY'}, 18155 {name:'Mike', phone:'555-4321'}, 18156 {name:'Adam', phone:'555-5678'}, 18157 {name:'Julie', phone:'555-8765'}, 18158 {name:'Juliette', phone:'555-5678'}]"></div> 18159 18160 <label>Search: <input ng-model="searchText"></label> 18161 <table id="searchTextResults"> 18162 <tr><th>
18162Name</th><th>Phone</th></tr> 18163 <tr ng-repeat="friend in friends | filter:searchText"> 18164 <td>{{friend.name}}</td> 18165 <td>{{friend.phone}}</td> 18166 </tr> 18167 </table> 18168 <hr> 18169 <label>Any: <input ng-model="search.$"></label> <br> 18170 <label>Name only <input ng-model="search.name"></label><br> 18171 <label>Phone only <input ng-model="search.phone"></label><br> 18172 <label>Equality <input type="checkbox" ng-model="strict"></label><br> 18173 <table id="searchObjResults"> 18174 <tr><th>Name</th><th>Phone</th></tr> 18175 <tr ng-repeat="friendObj in friends | filter:search:strict"> 18176 <td>{{friendObj.name}}</td> 18177 <td>{{friendObj.phone}}</td> 18178 </tr> 18179 </table> 18180 </file> 18181 <file name="protractor.js" type="protractor"> 18182 var expectFriendNames = function(expectedNames, key) { 18183 element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) { 18184 arr.forEach(function(wd, i) { 18185 expect(wd.getText()).toMatch(expectedNames[i]); 18186 }); 18187 }); 18188 }; 18189 18190 it('should search across all fields when filtering with a string', function() { 18191 var searchText = element(by.model('searchText')); 18192 searchText.clear(); 18193 searchText.sendKeys('m'); 18194 expectFriendNames(['Mary', 'Mike', 'Adam'], 'friend'); 18195 18196 searchText.clear(); 18197 searchText.sendKeys('76'); 18198 expectFriendNames(['John', 'Julie'], 'friend'); 18199 }); 18200 18201 it('should search in specific fields when filtering with a predicate object', function() { 18202 var searchAny = element(by.model('search.$')); 18203 searchAny.clear(); 18204 searchAny.sendKeys('i'); 18205 expectFriendNames(['Mary', 'Mike', 'Julie', 'Juliette'], 'friendObj'); 18206 }); 18207 it('should use a equal comparison when comparator is true', function() { 18208 var searchName = element(by.model('search.name')); 18209 var strict = element(by.model('strict')); 18210 searchName.clear(); 18211 searchName.sendKeys('Julie'); 18212 strict.click(); 18213 expectFriendNames(['Julie'], 'friendObj'); 18214 }); 18215 </file> 18216 </example> 18217 */ 18218function filterFilter() { 18219 return function(array, expression, comparator) { 18220 if (!isArrayLike(array)) { 18221 if (array == null) { 18222 return array; 18223 } else { 18224 throw minErr('filter')('notarray', 'Expected array but received: {0}', array); 18225 } 18226 } 18227 18228 var expressionType = getTypeForFilter(expression); 18229 var predicateFn; 18230 var matchAgainstAnyProp; 18231 18232 switch (expressionType) { 18233 case 'function': 18234 predicateFn = expression; 18235 break; 18236 case 'boolean': 18237 case 'null': 18238 case 'number': 18239 case 'string': 18240 matchAgainstAnyProp = true; 18241 //jshint -W086 18242 case 'object': 18243 //jshint +W086 18244 predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp); 18245 break; 18246 default: 18247 return array; 18248 } 18249 18250 return Array.prototype.filter.call(array, predicateFn); 18251 }; 18252} 18253 18254function hasCustomToString(obj) { 18255 return isFunction(obj.toString) && obj.toString !== Object.prototype.toString; 18256} 18257 18258// Helper functions for `filterFilter` 18259function createPredicateFn(expression, comparator, matchAgainstAnyProp) { 18260 var shouldMatchPrimitives = isObject(expression) && ('$' in expression); 18261 var predicateFn; 18262 18263 if (comparator === true) { 18264 comparator = equals; 18265 } else if (!isFunction(comparator)) { 18266 comparator = function(actual, expected) { 18267 if (isUndefined(actual)) { 18268 // No substring matching against `undefined` 18269 return false; 18270 } 18271 if ((actual === null) || (expected === null)) { 18272 // No substring matching against `null`; only match against `null` 18273 return actual === expected; 18274 } 18275 if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) { 18276 // Should not compare primitives against objects, unless they have custom `toString` method 18277 return false; 18278 } 18279 18280 actual = lowercase('' + actual); 18281 expected = lowercase('' + expected); 18282 return actual.indexOf(expected) !== -1; 18283 }; 18284 } 18285 18286 predicateFn = function(item) { 18287 if (shouldMatchPrimitives && !isObject(item)) { 18288 return deepCompare(item, expression.$, comparator, false); 18289 } 18290 return deepCompare(item, expression, comparator, matchAgainstAnyProp); 18291 }; 18292 18293 return predicateFn; 18294} 18295 18296function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { 18297 var actualType = getTypeForFilter(actual); 18298 var expectedType = getTypeForFilter(expected); 18299 18300 if ((expectedType === 'string') && (expected.charAt(0) === '!')) { 18301 return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); 18302 } else if (isArray(actual)) { 18303 // In case `actual` is an array, consider it a match 18304 // if ANY of it's items matches `expected` 18305 return actual.some(function(item) { 18306 return deepCompare(item, expected, comparator, matchAgainstAnyProp); 18307 }); 18308 } 18309 18310 switch (actualType) { 18311 case 'object': 18312 var key; 18313 if (matchAgainstAnyProp) { 18314 for (key in actual) { 18315 if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) { 18316 return true; 18317 } 18318 } 18319 return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false); 18320 } else if (expectedType === 'object') { 18321 for (key in expected) { 18322 var expectedVal = expected[key]; 18323 if (isFunction(expectedVal) || isUndefined(expectedVal)) { 18324 continue; 18325 } 18326 18327 var matchAnyProperty = key === '$'; 18328 var actualVal = matchAnyProperty ? actual : actual[key]; 18329 if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) { 18330 return false;
18331 } 18332 } 18333 return true; 18334 } else { 18335 return comparator(actual, expected); 18336 } 18337 break; 18338 case 'function': 18339 return false; 18340 default: 18341 return comparator(actual, expected); 18342 } 18343} 18344 18345// Used for easily differentiating between `null` and actual `object` 18346function getTypeForFilter(val) { 18347 return (val === null) ? 'null' : typeof val; 18348} 18349 18350/** 18351 * @ngdoc filter 18352 * @name currency 18353 * @kind function 18354 * 18355 * @description 18356 * Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default 18357 * symbol for current locale is used. 18358 * 18359 * @param {number} amount Input to filter. 18360 * @param {string=} symbol Currency symbol or identifier to be displayed. 18361 * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale 18362 * @returns {string} Formatted number. 18363 * 18364 * 18365 * @example 18366 <example module="currencyExample"> 18367 <file name="index.html"> 18368 <script> 18369 angular.module('currencyExample', []) 18370 .controller('ExampleController', ['$scope', function($scope) { 18371 $scope.amount = 1234.56; 18372 }]); 18373 </script>
18374 <div ng-controller="ExampleController"> 18375 <input type="number" ng-model="amount" aria-label="amount"> <br> 18376 default currency symbol ($): <span id="currency-default">{{amount | currency}}</span><br> 18377 custom currency identifier (USD$): <span id="currency-custom">{{amount | currency:"USD$"}}</span> 18378 no fractions (0): <span id="currency-no-fractions">{{amount | currency:"USD$":0}}</span> 18379 </div> 18380 </file> 18381 <file name="protractor.js" type="protractor"> 18382 it('should init with 1234.56', function() { 18383 expect(element(by.id('currency-default')).getText()).toBe('$1,234.56'); 18384 expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56'); 18385 expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235'); 18386 }); 18387 it('should update', function() { 18388 if (browser.params.browser == 'safari') { 18389 // Safari does not understand the minus key. See 18390 // https://github.com/angular/protractor/issues/481 18391 return; 18392 } 18393 element(by.model('amount')).clear(); 18394 element(by.model('amount')).sendKeys('-1234'); 18395 expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)'); 18396 expect(element(by.id('currency-custom')).getText()).toBe('(USD$1,234.00)'); 18397 expect(element(by.id('currency-no-fractions')).getText()).toBe('(USD$1,234)'); 18398 }); 18399 </file> 18400 </example> 18401 */ 18402currencyFilter.$inject = ['$locale']; 18403function currencyFilter($locale) { 18404 var formats = $locale.NUMBER_FORMATS; 18405 return function(amount, currencySymbol, fractionSize) { 18406 if (isUndefined(currencySymbol)) { 18407 currencySymbol = formats.CURRENCY_SYM; 18408 } 18409 18410 if (isUndefined(fractionSize)) { 18411 fractionSize = formats.PATTERNS[1].maxFrac; 18412 } 18413 18414 // if null or undefined pass it through 18415 return (amount == null) 18416 ? amount 18417 : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize). 18418 replace(/\u00A4/g, currencySymbol); 18419 }; 18420} 18421 18422/** 18423 * @ngdoc filter 18424 * @name number 18425 * @kind function 18426 * 18427 * @description 18428 * Formats a number as text. 18429 * 18430 * If the input is null or undefined, it will just be returned. 18431 * If the input is infinite (Infinity/-Infinity) the Infinity symbol 'â' is returned. 18432 * If the input is not a number an empty string is returned. 18433 * 18434 * 18435 * @param {number|string} number Number to format. 18436 * @param {(number|string)=} fractionSize Number of decimal places to round the number to. 18437 * If this is not provided then the fraction size is computed from the current locale's number 18438 * formatting pattern. In the case of the default locale, it will be 3. 18439 * @returns {string} Number rounded to decimalPlaces and places a â,â after each third digit. 18440 * 18441 * @example 18442 <example module="numberFilterExample"> 18443 <file name="index.html"> 18444 <script> 18445 angular.module('numberFilterExample', []) 18446 .controller('ExampleController', ['$scope', function($scope) { 18447 $scope.val = 1234.56789; 18448 }]); 18449 </script>
18450 <div ng-controller="ExampleController"> 18451 <label>Enter number: <input ng-model='val'></label><br> 18452 Default formatting: <span id='number-default'>{{val | number}}</span><br> 18453 No fractions: <span>{{val | number:0}}</span><br> 18454 Negative number: <span>{{-val | number:4}}</span> 18455 </div> 18456 </file> 18457 <file name="protractor.js" type="protractor"> 18458 it('should format numbers', function() { 18459 expect(element(by.id('number-default')).getText()).toBe('1,234.568'); 18460 expect(element(by.binding('val | number:0')).getText()).toBe('1,235'); 18461 expect(element(by.binding('-val | number:4')).getText()).toBe('-1,234.5679'); 18462 }); 18463 18464 it('should update', function() { 18465 element(by.model('val')).clear(); 18466 element(by.model('val')).sendKeys('3374.333'); 18467 expect(element(by.id('number-default')).getText()).toBe('3,374.333'); 18468 expect(element(by.binding('val | number:0')).getText()).toBe('3,374'); 18469 expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330'); 18470 }); 18471 </file> 18472 </example> 18473 */ 18474 18475 18476numberFilter.$inject = ['$locale']; 18477function numberFilter($locale) { 18478 var formats = $locale.NUMBER_FORMATS; 18479 return function(number, fractionSize) { 18480 18481 // if null or undefined pass it through 18482 return (number == null) 18483 ? number 18484 : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP, 18485 fractionSize); 18486 }; 18487} 18488 18489var DECIMAL_SEP = '.'; 18490function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { 18491 if (isObject(number)) return ''; 18492 18493 var isNegative = number < 0; 18494 number = Math.abs(number); 18495 18496 var isInfinity = number === Infinity; 18497 if (!isInfinity && !isFinite(number)) return ''; 18498 18499 var numStr = number + '', 18500 formatedText = '', 18501 hasExponent = false, 18502 parts = []; 18503 18504 if (isInfinity) formatedText = '\u221e'; 18505 18506 if (!isInfinity && numStr.indexOf('e') !== -1) { 18507 var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); 18508 if (match && match[2] == '-' && match[3] > fractionSize + 1) { 18509 number = 0; 18510 } else { 18511 formatedText = numStr; 18512 hasExponent = true; 18513 } 18514 } 18515 18516 if (!isInfinity && !hasExponent) { 18517 var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length; 18518 18519 // determine fractionSize if it is not specified 18520 if (isUndefined(fractionSize)) { 18521 fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); 18522 } 18523 18524 // safely round numbers in JS without hitting imprecisions of floating-point arithmetics 18525 // inspired by: 18526 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round 18527 number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize); 18528 18529 var fraction = ('' + number).split(DECIMAL_SEP); 18530 var whole = fraction[0]; 18531 fraction = fraction[1] || ''; 18532 18533 var i, pos = 0, 18534 lgroup = pattern.lgSize, 18535 group = pattern.gSize; 18536 18537 if (whole.length >= (lgroup + group)) { 18538 pos = whole.length - lgroup; 18539 for (i = 0; i < pos; i++) { 18540 if ((pos - i) % group === 0 && i !== 0) { 18541 formatedText += groupSep; 18542 } 18543 formatedText += whole.charAt(i); 18544 } 18545 } 18546 18547 for (i = pos; i < whole.length; i++) { 18548 if ((whole.length - i) % lgroup === 0 && i !== 0) { 18549 formatedText += groupSep; 18550 } 18551 formatedText += whole.charAt(i); 18552 } 18553 18554 // format fraction part. 18555 while (fraction.length < fractionSize) { 18556 fraction += '0'; 18557 } 18558 18559 if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize); 18560 } else { 18561 if (fractionSize > 0 && number < 1) { 18562 formatedText = number.toFixed(fractionSize); 18563 number = parseFloat(formatedText); 18564 } 18565 } 18566 18567 if (number === 0) { 18568 isNegative = false; 18569 } 18570 18571 parts.push(isNegative ? pattern.negPre : pattern.posPre, 18572 formatedText, 18573 isNegative ? pattern.negSuf : pattern.posSuf); 18574 return parts.join(''); 18575} 18576 18577function padNumber(num, digits, trim) { 18578 var neg = ''; 18579 if (num < 0) { 18580 neg = '-'; 18581 num = -num; 18582 } 18583 num = '' + num; 18584 while (num.length < digits) num = '0' + num; 18585 if (trim) { 18586 num = num.substr(num.length - digits); 18587 } 18588 return neg + num; 18589} 18590 18591 18592function dateGetter(name, size, offset, trim) { 18593 offset = offset || 0; 18594 return function(date) { 18595 var value = date['get' + name](); 18596 if (offset > 0 || value > -offset) { 18597 value += offset; 18598 } 18599 if (value === 0 && offset == -12) value = 12; 18600 return padNumber(value, size, trim); 18601 }; 18602} 18603 18604function dateStrGetter(name, shortForm) { 18605 return function(date, formats) { 18606 var value = date['get' + name](); 18607 var get = uppercase(shortForm ? ('SHORT' + name) : name); 18608 18609 return formats[get][value]; 18610 }; 18611} 18612 18613function timeZoneGetter(date, formats, offset) { 18614 var zone = -1 * offset; 18615 var paddedZone = (zone >= 0) ? "+" : ""; 18616 18617 paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) + 18618 padNumber(Math.abs(zone % 60), 2); 18619 18620 return paddedZone; 18621} 18622 18623function getFirstThursdayOfYear(year) { 18624 // 0 = index of January 18625 var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay(); 18626 // 4 = index of Thursday (+1 to account for 1st = 5) 18627 // 11 = index of *next* Thursday (+1 account for 1st = 12) 18628 return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst); 18629} 18630 18631function getThursdayThisWeek(datetime) { 18632 return new Date(datetime.getFullYear(), datetime.getMonth(), 18633 // 4 = index of Thursday 18634 datetime.getDate() + (4 - datetime.getDay())); 18635} 18636 18637function weekGetter(size) { 18638 return function(date) { 18639 var firstThurs = getFirstThursdayOfYear(date.getFullYear()), 18640 thisThurs = getThursdayThisWeek(date); 18641 18642 var diff = +thisThurs - +firstThurs, 18643 result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week 18644 18645 return padNumber(result, size); 18646 }; 18647} 18648 18649function ampmGetter(date, formats) { 18650 return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; 18651} 18652 18653function eraGetter(date, formats) {
vendor: 5,157 bytes, lines 18654-18766
18654 return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; 18655} 18656 18657function longEraGetter(date, formats) { 18658 return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; 18659} 18660 18661var DATE_FORMATS = { 18662 yyyy: dateGetter('FullYear', 4), 18663 yy: dateGetter('FullYear', 2, 0, true), 18664 y: dateGetter('FullYear', 1), 18665 MMMM: dateStrGetter('Month'), 18666 MMM: dateStrGetter('Month', true), 18667 MM: dateGetter('Month', 2, 1), 18668 M: dateGetter('Month', 1, 1), 18669 dd: dateGetter('Date', 2), 18670 d: dateGetter('Date', 1), 18671 HH: dateGetter('Hours', 2), 18672 H: dateGetter('Hours', 1), 18673 hh: dateGetter('Hours', 2, -12), 18674 h: dateGetter('Hours', 1, -12), 18675 mm: dateGetter('Minutes', 2), 18676 m: dateGetter('Minutes', 1), 18677 ss: dateGetter('Seconds', 2), 18678 s: dateGetter('Seconds', 1), 18679 // while ISO 8601 requires fractions to be prefixed with `.` or `,` 18680 // we can be just safely rely on using `sss` since we currently don't support single or two digit fractions 18681 sss: dateGetter('Milliseconds', 3), 18682 EEEE: dateStrGetter('Day'), 18683 EEE: dateStrGetter('Day', true), 18684 a: ampmGetter, 18685 Z: timeZoneGetter, 18686 ww: weekGetter(2), 18687 w: weekGetter(1), 18688 G: eraGetter, 18689 GG: eraGetter, 18690 GGG: eraGetter, 18691 GGGG: longEraGetter 18692}; 18693 18694var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, 18695 NUMBER_STRING = /^\-?\d+$/; 18696 18697/** 18698 * @ngdoc filter 18699 * @name date 18700 * @kind function 18701 * 18702 * @description 18703 * Formats `date` to a string based on the requested `format`. 18704 * 18705 * `format` string can be composed of the following elements: 18706 * 18707 * * `'yyyy'`: 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 18708 * * `'yy'`: 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 18709 * * `'y'`: 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 18710 * * `'MMMM'`: Month in year (January-December) 18711 * * `'MMM'`: Month in year (Jan-Dec) 18712 * * `'MM'`: Month in year, padded (01-12) 18713 * * `'M'`: Month in year (1-12) 18714 * * `'dd'`: Day in month, padded (01-31) 18715 * * `'d'`: Day in month (1-31) 18716 * * `'EEEE'`: Day in Week,(Sunday-Saturday) 18717 * * `'EEE'`: Day in Week, (Sun-Sat) 18718 * * `'HH'`: Hour in day, padded (00-23) 18719 * * `'H'`: Hour in day (0-23) 18720 * * `'hh'`: Hour in AM/PM, padded (01-12) 18721 * * `'h'`: Hour in AM/PM, (1-12) 18722 * * `'mm'`: Minute in hour, padded (00-59) 18723 * * `'m'`: Minute in hour (0-59) 18724 * * `'ss'`: Second in minute, padded (00-59) 18725 * * `'s'`: Second in minute (0-59) 18726 * * `'sss'`: Millisecond in second, padded (000-999) 18727 * * `'a'`: AM/PM marker 18728 * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) 18729 * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year 18730 * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year 18731 * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') 18732 * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') 18733 * 18734 * `format` string can also be one of the following predefined 18735 * {@link guide/i18n localizable formats}: 18736 * 18737 * * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale 18738 * (e.g. Sep 3, 2010 12:05:08 PM) 18739 * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM) 18740 * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale 18741 * (e.g. Friday, September 3, 2010) 18742 * * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010) 18743 * * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010) 18744 * * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10) 18745 * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM) 18746 * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM) 18747 * 18748 * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g. 18749 * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence 18750 * (e.g. `"h 'o''clock'"`). 18751 * 18752 * @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or 18753 * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its 18754 * shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is 18755 * specified in the string input, the time is considered to be in the local timezone. 18756 * @param {string=} format Formatting rules (see Description). If not specified, 18757 * `mediumDate` is used. 18758 * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the 18759 * continental US time zone abbreviations, but for general use, use a time zone offset, for 18760 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 18761 * If not specified, the timezone of the browser will be used. 18762 * @returns {string} Formatted string or the input if input is not recognized as date/millis. 18763 * 18764 * @example 18765 <example> 18766 <file name="index.html">
vendor: 4,078 bytes, lines 18767-18879
18767 <span ng-non-bindable>{{1288323623006 | date:'medium'}}</span>: 18768 <span>{{1288323623006 | date:'medium'}}</span><br> 18769 <span ng-non-bindable>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span>: 18770 <span>{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}</span><br> 18771 <span ng-non-bindable>{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}</span>: 18772 <span>{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}</span><br> 18773 <span ng-non-bindable>{{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}</span>: 18774 <span>{{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}</span><br> 18775 </file> 18776 <file name="protractor.js" type="protractor"> 18777 it('should format date', function() { 18778 expect(element(by.binding("1288323623006 | date:'medium'")).getText()). 18779 toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/); 18780 expect(element(by.binding("1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'")).getText()). 18781 toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/); 18782 expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()). 18783 toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/); 18784 expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()). 18785 toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/); 18786 }); 18787 </file> 18788 </example> 18789 */ 18790dateFilter.$inject = ['$locale']; 18791function dateFilter($locale) { 18792 18793 18794 var R_ISO8601_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 18795 // 1 2 3 4 5 6 7 8 9 10 11 18796 function jsonStringToDate(string) { 18797 var match; 18798 if (match = string.match(R_ISO8601_STR)) { 18799 var date = new Date(0), 18800 tzHour = 0, 18801 tzMin = 0, 18802 dateSetter = match[8] ? date.setUTCFullYear : date.setFullYear, 18803 timeSetter = match[8] ? date.setUTCHours : date.setHours; 18804 18805 if (match[9]) { 18806 tzHour = toInt(match[9] + match[10]); 18807 tzMin = toInt(match[9] + match[11]); 18808 } 18809 dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); 18810 var h = toInt(match[4] || 0) - tzHour; 18811 var m = toInt(match[5] || 0) - tzMin; 18812 var s = toInt(match[6] || 0); 18813 var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000); 18814 timeSetter.call(date, h, m, s, ms); 18815 return date; 18816 } 18817 return string; 18818 } 18819 18820 18821 return function(date, format, timezone) { 18822 var text = '', 18823 parts = [], 18824 fn, match; 18825 18826 format = format || 'mediumDate'; 18827 format = $locale.DATETIME_FORMATS[format] || format; 18828 if (isString(date)) { 18829 date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date); 18830 } 18831 18832 if (isNumber(date)) { 18833 date = new Date(date); 18834 } 18835 18836 if (!isDate(date) || !isFinite(date.getTime())) { 18837 return date; 18838 } 18839 18840 while (format) { 18841 match = DATE_FORMATS_SPLIT.exec(format); 18842 if (match) { 18843 parts = concat(parts, match, 1); 18844 format = parts.pop(); 18845 } else { 18846 parts.push(format); 18847 format = null; 18848 } 18849 } 18850 18851 var dateTimezoneOffset = date.getTimezoneOffset(); 18852 if (timezone) { 18853 dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset()); 18854 date = convertTimezoneToLocal(date, timezone, true); 18855 } 18856 forEach(parts, function(value) { 18857 fn = DATE_FORMATS[value]; 18858 text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset) 18859 : value.replace(/(^'|'$)/g, '').replace(/''/g, "'"); 18860 }); 18861 18862 return text; 18863 }; 18864} 18865 18866 18867/** 18868 * @ngdoc filter 18869 * @name json 18870 * @kind function 18871 * 18872 * @description 18873 * Allows you to convert a JavaScript object into JSON string. 18874 * 18875 * This filter is mostly useful for debugging. When using the double curly {{value}} notation 18876 * the binding is automatically converted to JSON. 18877 * 18878 * @param {*} object Any JavaScript object (including arrays and primitive types) to filter. 18879 * @param {number=}
18879 spacing The number of spaces to use per indentation, defaults to 2. 18880 * @returns {string} JSON string. 18881 * 18882 * 18883 * @example 18884 <example> 18885 <file name="index.html"> 18886 <pre id="default-spacing">{{ {'name':'value'} | json }}</pre> 18887 <pre id="custom-spacing">{{ {'name':'value'} | json:4 }}</pre> 18888 </file> 18889 <file name="protractor.js" type="protractor"> 18890 it('should jsonify filtered objects', function() { 18891 expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 18892 expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/); 18893 }); 18894 </file> 18895 </example> 18896 * 18897 */ 18898function jsonFilter() { 18899 return function(object, spacing) { 18900 if (isUndefined(spacing)) { 18901 spacing = 2; 18902 } 18903 return toJson(object, spacing); 18904 }; 18905} 18906 18907 18908/** 18909 * @ngdoc filter 18910 * @name lowercase 18911 * @kind function 18912 * @description 18913 * Converts string to lowercase. 18914 * @see angular.lowercase 18915 */ 18916var lowercaseFilter = valueFn(lowercase); 18917 18918 18919/** 18920 * @ngdoc filter 18921 * @name uppercase 18922 * @kind function 18923 * @description 18924 * Converts string to uppercase. 18925 * @see angular.uppercase 18926 */ 18927var uppercaseFilter = valueFn(uppercase); 18928 18929/** 18930 * @ngdoc filter 18931 * @name limitTo 18932 * @kind function 18933 * 18934 * @description 18935 * Creates a new array or string containing only a specified number of elements. The elements 18936 * are taken from either the beginning or the end of the source array, string or number, as specified by 18937 * the value and sign (positive or negative) of `limit`. If a number is used as input, it is 18938 * converted to a string. 18939 * 18940 * @param {Array|string|number} input Source array, string or number to be limited. 18941 * @param {string|number} limit The length of the returned array or string. If the `limit` number 18942 * is positive, `limit` number of items from the beginning of the source array/string are copied. 18943 * If the number is negative, `limit` number of items from the end of the source array/string 18944 * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined, 18945 * the input will be returned unchanged. 18946 * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin` 18947 * indicates an offset from the end of `input`. Defaults to `0`. 18948 * @returns {Array|string} A new sub-array or substring of length `limit` or less if input array 18949 * had less than `limit` elements. 18950 * 18951 * @example 18952 <example module="limitToExample"> 18953 <file name="index.html"> 18954 <script> 18955 angular.module('limitToExample', []) 18956 .controller('ExampleController', ['$scope', function($scope) { 18957 $scope.numbers = [1,2,3,4,5,6,7,8,9]; 18958 $scope.letters = "abcdefghi"; 18959 $scope.longNumber = 2345432342; 18960 $scope.numLimit = 3; 18961 $scope.letterLimit = 3; 18962 $scope.longNumberLimit = 3; 18963 }]); 18964 </script>
18965 <div ng-controller="ExampleController"> 18966 <label> 18967 Limit {{numbers}} to: 18968 <input type="number" step="1" ng-model="numLimit"> 18969 </label> 18970 <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> 18971 <label> 18972 Limit {{letters}} to: 18973 <input type="number" step="1" ng-model="letterLimit"> 18974 </label> 18975 <p>Output letters: {{ letters | limitTo:letterLimit }}</p> 18976 <label> 18977 Limit {{longNumber}} to: 18978 <input type="number" step="1" ng-model="longNumberLimit"> 18979 </label> 18980 <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> 18981 </div> 18982 </file> 18983 <file name="protractor.js" type="protractor"> 18984 var numLimitInput = element(by.model('numLimit')); 18985 var letterLimitInput = element(by.model('letterLimit')); 18986 var longNumberLimitInput = element(by.model('longNumberLimit')); 18987 var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); 18988 var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); 18989 var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); 18990 18991 it('should limit the number array to first three items', function() { 18992 expect(numLimitInput.getAttribute('value')).toBe('3'); 18993 expect(letterLimitInput.getAttribute('value')).toBe('3'); 18994 expect(longNumberLimitInput.getAttribute('value')).toBe('3'); 18995 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); 18996 expect(limitedLetters.getText()).toEqual('Output letters: abc'); 18997 expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); 18998 }); 18999 19000 // There is a bug in safari and protractor that doesn't like the minus key 19001 // it('should update the output when -3 is entered', function() { 19002 // numLimitInput.clear(); 19003 // numLimitInput.sendKeys('-3'); 19004 // letterLimitInput.clear(); 19005 // letterLimitInput.sendKeys('-3'); 19006 // longNumberLimitInput.clear(); 19007 // longNumberLimitInput.sendKeys('-3'); 19008 // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); 19009 // expect(limitedLetters.getText()).toEqual('Output letters: ghi'); 19010 // expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); 19011 // }); 19012 19013 it('should not exceed the maximum size of input array', function() { 19014 numLimitInput.clear(); 19015 numLimitInput.sendKeys('100'); 19016 letterLimitInput.clear(); 19017 letterLimitInput.sendKeys('100'); 19018 longNumberLimitInput.clear(); 19019 longNumberLimitInput.sendKeys('100'); 19020 expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); 19021 expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); 19022 expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); 19023 }); 19024 </file> 19025 </example> 19026*/ 19027function limitToFilter() { 19028 return function(input, limit, begin) { 19029 if (Math.abs(Number(limit)) === Infinity) { 19030 limit = Number(limit); 19031 } else { 19032 limit = toInt(limit); 19033 } 19034 if (isNaN(limit)) return input; 19035 19036 if (isNumber(input)) input = input.toString(); 19037 if (!isArray(input) && !isString(input)) return input; 19038 19039 begin = (!begin || isNaN(begin)) ? 0 : toInt(begin); 19040 begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin; 19041 19042 if (limit >= 0) { 19043 return input.slice(begin, begin + limit); 19044 } else { 19045 if (begin === 0) { 19046 return input.slice(limit, input.length); 19047 } else { 19048 return input.slice(Math.max(0, begin + limit), begin); 19049 } 19050 } 19051 }; 19052} 19053 19054/** 19055 * @ngdoc filter 19056 * @name orderBy 19057 * @kind function 19058 * 19059 * @description 19060 * Orders a specified `array` by the `expression` predicate. It is ordered alphabetically 19061 * for strings and numerically for numbers. Note: if you notice numbers are not being sorted 19062 * as expected, make sure they are actually being saved as numbers and not strings. 19063 * 19064 * @param {Array} array The array to sort. 19065 * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be 19066 * used by the comparator to determine the order of elements. 19067 * 19068 * Can be one of: 19069 * 19070 * - `function`: Getter function. The result of this function will be sorted using the 19071 * `<`, `===`, `>` operator. 19072 * - `string`: An Angular expression. The result of this expression is used to compare elements 19073 * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by 19074 * 3 first characters of a property called `name`). The result of a constant expression 19075 * is interpreted as a property name to be used in comparisons (for example `"special name"` 19076 * to sort object by the value of their `special name` property). An expression can be 19077 * optionally prefixed with `+` or `-` to control ascending or descending sort order 19078 * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array 19079 * element itself is used to compare where sorting. 19080 * - `Array`: An array of function or string predicates. The first predicate in the array 19081 * is used for sorting, but when two items are equivalent, the next predicate is used. 19082 * 19083 * If the predicate is missing or empty then it defaults to `'+'`. 19084 * 19085 * @param {boolean=} reverse Reverse the order of the array. 19086 * @returns {Array} Sorted copy of the source array. 19087 * 19088 * 19089 * @example 19090 * The example below demonstrates a simple ngRepeat, where the data is sorted 19091 * by age in descending order (predicate is set to `'-age'`). 19092 * `reverse` is not set, which means it defaults to `false`. 19093 <example module="orderByExample"> 19094 <file name="index.html"> 19095 <script> 19096 angular.module('orderByExample', [])
19097 .controller('ExampleController', ['$scope', function($scope) { 19098 $scope.friends = 19099 [{name:'John', phone:'555-1212', age:10}, 19100 {name:'Mary', phone:'555-9876', age:19}, 19101 {name:'Mike', phone:'555-4321', age:21}, 19102 {name:'Adam', phone:'555-5678', age:35}, 19103 {name:'Julie', phone:'555-8765', age:29}]; 19104 }]); 19105 </script> 19106 <div ng-controller="ExampleController"> 19107 <table class="friend"> 19108 <tr> 19109 <th>Name</th> 19110 <th>Phone Number</th> 19111 <th>Age</th> 19112 </tr> 19113 <tr ng-repeat="friend in friends | orderBy:'-age'"> 19114 <td>{{friend.name}}</td> 19115 <td>{{friend.phone}}</td> 19116 <td>{{friend.age}}</td> 19117 </tr> 19118 </table> 19119 </div> 19120 </file> 19121 </example> 19122 * 19123 * The predicate and reverse parameters can be controlled dynamically through scope properties, 19124 * as shown in the next example. 19125 * @example 19126 <example module="orderByExample"> 19127 <file name="index.html"> 19128 <script> 19129 angular.module('orderByExample', []) 19130 .controller('ExampleController', ['$scope', function($scope) { 19131 $scope.friends = 19132 [{name:'John', phone:'555-1212', age:10}, 19133 {name:'Mary', phone:'555-9876', age:19}, 19134 {name:'Mike', phone:'555-4321', age:21}, 19135 {name:'Adam', phone:'555-5678', age:35}, 19136 {name:'Julie', phone:'555-8765', age:29}]; 19137 $scope.predicate = 'age'; 19138 $scope.reverse = true; 19139 $scope.order = function(predicate) { 19140 $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false; 19141 $scope.predicate = predicate; 19142 }; 19143 }]); 19144 </script> 19145 <style type="text/css"> 19146 .sortorder:after { 19147 content: '\25b2'; 19148 } 19149 .sortorder.reverse:after { 19150 content: '\25bc'; 19151 } 19152 </style>
19153 <div ng-controller="ExampleController"> 19154 <pre>Sorting predicate = {{predicate}}; reverse = {{reverse}}</pre> 19155 <hr/> 19156 [ <a href="" ng-click="predicate=''">unsorted</a> ] 19157 <table class="friend"> 19158 <tr> 19159 <th> 19160 <a href="" ng-click="order('name')">Name</a> 19161 <span class="sortorder" ng-show="predicate === 'name'" ng-class="{reverse:reverse}"></span> 19162 </th> 19163 <th> 19164 <a href="" ng-click="order('phone')">Phone Number</a> 19165 <span class="sortorder" ng-show="predicate === 'phone'" ng-class="{reverse:reverse}"></span> 19166 </th> 19167 <th> 19168 <a href="" ng-click="order('age')">Age</a> 19169 <span class="sortorder" ng-show="predicate === 'age'" ng-class="{reverse:reverse}"></span> 19170 </th> 19171 </tr> 19172 <tr ng-repeat="friend in friends | orderBy:predicate:reverse"> 19173 <td>{{friend.name}}</td> 19174 <td>{{friend.phone}}</td> 19175 <td>{{friend.age}}</td> 19176 </tr> 19177 </table> 19178 </div> 19179 </file> 19180 </example> 19181 * 19182 * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the 19183 * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the 19184 * desired parameters. 19185 * 19186 * Example: 19187 * 19188 * @example 19189 <example module="orderByExample"> 19190 <file name="index.html">
19191 <div ng-controller="ExampleController"> 19192 <table class="friend"> 19193 <tr> 19194 <th><a href="" ng-click="reverse=false;order('name', false)">Name</a> 19195 (<a href="" ng-click="order('-name',false)">^</a>)</th> 19196 <th><a href="" ng-click="reverse=!reverse;order('phone', reverse)">Phone Number</a></th> 19197 <th><a href="" ng-click="reverse=!reverse;order('age',reverse)">Age</a></th> 19198 </tr> 19199 <tr ng-repeat="friend in friends"> 19200 <td>{{friend.name}}</td> 19201 <td>{{friend.phone}}</td> 19202 <td>{{friend.age}}</td> 19203 </tr> 19204 </table> 19205 </div> 19206 </file> 19207 19208 <file name="script.js"> 19209 angular.module('orderByExample', []) 19210 .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) { 19211 var orderBy = $filter('orderBy'); 19212 $scope.friends = [ 19213 { name: 'John', phone: '555-1212', age: 10 }, 19214 { name: 'Mary', phone: '555-9876', age: 19 }, 19215 { name: 'Mike', phone: '555-4321', age: 21 }, 19216 { name: 'Adam', phone: '555-5678', age: 35 }, 19217 { name: 'Julie', phone: '555-8765', age: 29 } 19218 ]; 19219 $scope.order = function(predicate, reverse) { 19220 $scope.friends = orderBy($scope.friends, predicate, reverse); 19221 }; 19222 $scope.order('-age',false); 19223 }]); 19224 </file> 19225</example> 19226 */ 19227orderByFilter.$inject = ['$parse']; 19228function orderByFilter($parse) { 19229 return function(array, sortPredicate, reverseOrder) { 19230 if (!(isArrayLike(array))) return array; 19231 sortPredicate = isArray(sortPredicate) ? sortPredicate : [sortPredicate]; 19232 if (sortPredicate.length === 0) { sortPredicate = ['+']; } 19233 sortPredicate = sortPredicate.map(function(predicate) { 19234 var descending = false, get = predicate || identity; 19235 if (isString(predicate)) { 19236 if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) { 19237 descending = predicate.charAt(0) == '-'; 19238 predicate = predicate.substring(1); 19239 } 19240 if (predicate === '') { 19241 // Effectively no predicate was passed so we compare identity 19242 return reverseComparator(compare, descending); 19243 } 19244 get = $parse(predicate); 19245 if (get.constant) { 19246 var key = get(); 19247 return reverseComparator(function(a, b) { 19248 return compare(a[key], b[key]); 19249 }, descending); 19250 } 19251 } 19252 return reverseComparator(function(a, b) { 19253 return compare(get(a),get(b)); 19254 }, descending); 19255 }); 19256 return slice.call(array).sort(reverseComparator(comparator, reverseOrder)); 19257 19258 function comparator(o1, o2) { 19259 for (var i = 0; i < sortPredicate.length; i++) { 19260 var comp = sortPredicate[i](o1, o2); 19261 if (comp !== 0) return comp; 19262 } 19263 return 0; 19264 } 19265 function reverseComparator(comp, descending) { 19266 return descending 19267 ? function(a, b) {return comp(b,a);} 19268 : comp; 19269 } 19270 19271 function isPrimitive(value) { 19272 switch (typeof value) { 19273 case 'number': /* falls through */ 19274 case 'boolean': /* falls through */ 19275 case 'string': 19276 return true; 19277 default: 19278 return false; 19279 } 19280 } 19281 19282 function objectToString(value) { 19283 if (value === null) return 'null'; 19284 if (typeof value.valueOf === 'function') { 19285 value = value.valueOf(); 19286 if (isPrimitive(value)) return value; 19287 } 19288 if (typeof value.toString === 'function') { 19289 value = value.toString(); 19290 if (isPrimitive(value)) return value; 19291 } 19292 return ''; 19293 } 19294 19295 function compare(v1, v2) { 19296 var t1 = typeof v1; 19297 var t2 = typeof v2; 19298 if (t1 === t2 && t1 === "object") { 19299 v1 = objectToString(v1); 19300 v2 = objectToString(v2); 19301 } 19302 if (t1 === t2) { 19303 if (t1 === "string") { 19304 v1 = v1.toLowerCase(); 19305 v2 = v2.toLowerCase(); 19306 } 19307 if (v1 === v2) return 0; 19308 return v1 < v2 ? -1 : 1; 19309 } else { 19310 return t1 < t2 ? -1 : 1; 19311 } 19312 } 19313 }; 19314} 19315 19316function ngDirective(directive) { 19317 if (isFunction(directive)) { 19318 directive = { 19319 link: directive 19320 }; 19321 } 19322 directive.restrict = directive.restrict || 'AC'; 19323 return valueFn(directive); 19324} 19325 19326/** 19327 * @ngdoc directive 19328 * @name a 19329 * @restrict E 19330 * 19331 * @description 19332 * Modifies the default behavior of the html A tag so that the default action is prevented when 19333 * the href attribute is empty. 19334 * 19335 * This change permits the easy creation of action links with the `ngClick` directive 19336 * without changing the location or causing page reloads, e.g.: 19337 * `<a href="" ng-click="list.addItem()">Add Item</a>` 19338 */ 19339var htmlAnchorDirective = valueFn({ 19340 restrict: 'E', 19341 compile: function(element, attr) { 19342 if (!attr.href && !attr.xlinkHref) { 19343 return function(scope, element) { 19344 // If the linked element is not an anchor tag anymore, do nothing 19345 if (element[0].nodeName.toLowerCase() !== 'a') return; 19346 19347 // SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute. 19348 var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ? 19349 'xlink:href' : 'href'; 19350 element.on('click', function(event) { 19351 // if we have no href url, then don't navigate anywhere. 19352 if (!element.attr(href)) { 19353 event.preventDefault(); 19354 } 19355 }); 19356 }; 19357 } 19358 } 19359}); 19360 19361/** 19362 * @ngdoc directive 19363 * @name ngHref 19364 * @restrict A 19365 * @priority 99 19366 * 19367 * @description 19368 * Using Angular markup like `{{hash}}` in an href attribute will 19369 * make the link go to the wrong URL if the user clicks it before 19370 * Angular has a chance to replace the `{{hash}}
19370` markup with its 19371 * value. Until Angular replaces the markup the link will be broken 19372 * and will most likely return a 404 error. The `ngHref` directive 19373 * solves this problem. 19374 * 19375 * The wrong way to write it: 19376 * ```html 19377 * <a href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 19378 * ``` 19379 * 19380 * The correct way to write it: 19381 * ```html 19382 * <a ng-href="http://www.gravatar.com/avatar/{{hash}}">link1</a> 19383 * ``` 19384 * 19385 * @element A 19386 * @param {template} ngHref any string which can contain `{{}}` markup. 19387 * 19388 * @example 19389 * This example shows various combinations of `href`, `ng-href` and `ng-click` attributes 19390 * in links and their different behaviors: 19391 <example> 19392 <file name="index.html"> 19393 <input ng-model="value" /><br /> 19394 <a id="link-1" href ng-click="value = 1">link 1</a> (link, don't reload)<br /> 19395 <a id="link-2" href="" ng-click="value = 2">link 2</a> (link, don't reload)<br /> 19396 <a id="link-3" ng-href="/{{'123'}}">link 3</a> (link, reload!)<br /> 19397 <a id="link-4" href="" name="xx" ng-click="value = 4">anchor</a> (link, don't reload)<br /> 19398 <a id="link-5" name="xxx" ng-click="value = 5">anchor</a> (no link)<br /> 19399 <a id="link-6" ng-href="{{value}}">link</a> (link, change location) 19400 </file> 19401 <file name="protractor.js" type="protractor"> 19402 it('should execute ng-click but not reload when href without value', function() { 19403 element(by.id('link-1')).click(); 19404 expect(element(by.model('value')).getAttribute('value')).toEqual('1'); 19405 expect(element(by.id('link-1')).getAttribute('href')).toBe(''); 19406 }); 19407 19408 it('should execute ng-click but not reload when href empty string', function() { 19409 element(by.id('link-2')).click(); 19410 expect(element(by.model('value')).getAttribute('value')).toEqual('2'); 19411 expect(element(by.id('link-2')).getAttribute('href')).toBe(''); 19412 }); 19413 19414 it('should execute ng-click and change url when ng-href specified', function() { 19415 expect(element(by.id('link-3')).getAttribute('href')).toMatch(/\/123$/); 19416 19417 element(by.id('link-3')).click(); 19418 19419 // At this point, we navigate away from an Angular page, so we need 19420 // to use browser.driver to get the base webdriver. 19421 19422 browser.wait(function() { 19423 return browser.driver.getCurrentUrl().then(function(url) { 19424 return url.match(/\/123$/); 19425 }); 19426 }, 5000, 'page should navigate to /123'); 19427 }); 19428 19429 it('should execute ng-click but not reload when href empty string and name specified', function() { 19430 element(by.id('link-4')).click(); 19431 expect(element(by.model('value')).getAttribute('value')).toEqual('4'); 19432 expect(element(by.id('link-4')).getAttribute('href')).toBe(''); 19433 }); 19434 19435 it('should execute ng-click but not reload when no href but name specified', function() { 19436 element(by.id('link-5')).click(); 19437 expect(element(by.model('value')).getAttribute('value')).toEqual('5'); 19438 expect(element(by.id('link-5')).getAttribute('href')).toBe(null); 19439 }); 19440 19441 it('should only change url when only ng-href', function() { 19442 element(by.model('value')).clear(); 19443 element(by.model('value')).sendKeys('6'); 19444 expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/); 19445 19446 element(by.id('link-6')).click(); 19447 19448 // At this point, we navigate away from an Angular page, so we need 19449 // to use browser.driver to get the base webdriver. 19450 browser.wait(function() { 19451 return browser.driver.getCurrentUrl().then(function(url) { 19452 return url.match(/\/6$/); 19453 }); 19454 }, 5000, 'page should navigate to /6'); 19455 }); 19456 </file> 19457 </example> 19458 */ 19459 19460/** 19461 * @ngdoc directive 19462 * @name ngSrc 19463 * @restrict A 19464 * @priority 99 19465 * 19466 * @description 19467 * Using Angular markup like `{{hash}}` in a `src` attribute doesn't 19468 * work right: The browser will fetch from the URL with the literal 19469 * text `{{hash}}` until Angular replaces the expression inside 19470 * `{{hash}}`. The `ngSrc` directive solves this problem. 19471 * 19472 * The buggy way to write it: 19473 * ```html 19474 * <img src="http://www.gravatar.com/avatar/{{hash}}" alt="Description"/> 19475 * ``` 19476 * 19477 * The correct way to write it: 19478 * ```html 19479 * <img ng-src="http://www.gravatar.com/avatar/{{hash}}" alt="Description" /> 19480 * ``` 19481 * 19482 * @element IMG 19483 * @param {template} ngSrc any string which can contain `{{}}` markup. 19484 */ 19485 19486/** 19487 * @ngdoc directive 19488 * @name ngSrcset 19489 * @restrict A 19490 * @priority 99 19491 * 19492 * @description 19493 * Using Angular markup like `{{hash}}` in a `srcset` attribute doesn't 19494 * work right: The browser will fetch from the URL with the literal 19495 * text `{{hash}}` until Angular replaces the expression inside 19496 * `{{hash}}`. The `ngSrcset` directive solves this problem. 19497 * 19498 * The buggy way to write it: 19499 * ```html 19500 * <img srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description"/> 19501 * ``` 19502 * 19503 * The correct way to write it: 19504 * ```html 19505 * <img ng-srcset="http://www.gravatar.com/avatar/{{hash}} 2x" alt="Description" /> 19506 * ``` 19507 * 19508 * @element IMG 19509 * @param {template} ngSrcset any string which can contain `{{}}` markup. 19510 */ 19511 19512/** 19513 * @ngdoc directive 19514 * @name ngDisabled 19515 * @restrict A 19516 * @priority 100 19517 * 19518 * @description 19519 * 19520 * This directive sets the `disabled` attribute on the element if the 19521 * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. 19522 * 19523 * A special directive is necessary because we cannot use interpolation inside the `disabled` 19524 * attribute. The following example would make the button enabled on Chrome/Firefox 19525 * but not on older IEs: 19526 * 19527 * ```html 19528 * <!-- See below for an example of ng-disabled being used correctly --> 19529 * <div ng-init="isDisabled = false"> 19530 * <button disabled="{{isDisabled}}">Disabled</button> 19531 * </div> 19532 * ``` 19533 * 19534 * This is because the HTML specification does not require browsers to preserve the values of 19535 * boolean attributes such as `disabled` (Their presence means true and their absence means false.) 19536 * If we put an Angular interpolation expression into such an attribute then the 19537 * binding information would be lost when the browser removes the attribute. 19538 * 19539 * @example 19540 <example> 19541 <file name="index.html"> 19542 <label>Click me to toggle: <input type="checkbox" ng-model="checked"></label><br/> 19543 <button ng-model="button" ng-disabled="checked">Button</button> 19544 </file> 19545 <file name="protractor.js" type="protractor"> 19546 it('should toggle button', function() { 19547 expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
19548 element(by.model('checked')).click(); 19549 expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy(); 19550 }); 19551 </file> 19552 </example> 19553 * 19554 * @element INPUT 19555 * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, 19556 * then the `disabled` attribute will be set on the element 19557 */ 19558 19559 19560/** 19561 * @ngdoc directive 19562 * @name ngChecked 19563 * @restrict A 19564 * @priority 100 19565 * 19566 * @description 19567 * The HTML specification does not require browsers to preserve the values of boolean attributes 19568 * such as checked. (Their presence means true and their absence means false.) 19569 * If we put an Angular interpolation expression into such an attribute then the 19570 * binding information would be lost when the browser removes the attribute. 19571 * The `ngChecked` directive solves this problem for the `checked` attribute. 19572 * This complementary directive is not removed by the browser and so provides 19573 * a permanent reliable place to store the binding information. 19574 * @example 19575 <example> 19576 <file name="index.html"> 19577 <label>Check me to check both: <input type="checkbox" ng-model="master"></label><br/> 19578 <input id="checkSlave" type="checkbox" ng-checked="master" aria-label="Slave input"> 19579 </file> 19580 <file name="protractor.js" type="protractor"> 19581 it('should check both checkBoxes', function() { 19582 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy(); 19583 element(by.model('master')).click(); 19584 expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy(); 19585 }); 19586 </file> 19587 </example> 19588 * 19589 * @element INPUT 19590 * @param {expression} ngChecked If the {@link guide/expression expression} is truthy, 19591 * then special attribute "checked" will be set on the element 19592 */ 19593 19594 19595/** 19596 * @ngdoc directive 19597 * @name ngReadonly 19598 * @restrict A 19599 * @priority 100 19600 * 19601 * @description 19602 * The HTML specification does not require browsers to preserve the values of boolean attributes 19603 * such as readonly. (Their presence means true and their absence means false.) 19604 * If we put an Angular interpolation expression into such an attribute then the 19605 * binding information would be lost when the browser removes the attribute. 19606 * The `ngReadonly` directive solves this problem for the `readonly` attribute. 19607 * This complementary directive is not removed by the browser and so provides 19608 * a permanent reliable place to store the binding information. 19609 * @example 19610 <example> 19611 <file name="index.html"> 19612 <label>Check me to make text readonly: <input type="checkbox" ng-model="checked"></label><br/> 19613 <input type="text" ng-readonly="checked" value="I'm Angular" aria-label="Readonly field" /> 19614 </file> 19615 <file name="protractor.js" type="protractor"> 19616 it('should toggle readonly attr', function() { 19617 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy(); 19618 element(by.model('checked')).click(); 19619 expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy(); 19620 }); 19621 </file> 19622 </example> 19623 * 19624 * @element INPUT 19625 * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy, 19626 * then special attribute "readonly" will be set on the element 19627 */ 19628 19629 19630/** 19631 * @ngdoc directive 19632 * @name ngSelected 19633 * @restrict A 19634 * @priority 100 19635 * 19636 * @description 19637 * The HTML specification does not require browsers to preserve the values of boolean attributes 19638 * such as selected. (Their presence means true and their absence means false.) 19639 * If we put an Angular interpolation expression into such an attribute then the 19640 * binding information would be lost when the browser removes the attribute. 19641 * The `ngSelected` directive solves this problem for the `selected` attribute. 19642 * This complementary directive is not removed by the browser and so provides 19643 * a permanent reliable place to store the binding information. 19644 * 19645 * @example 19646 <example> 19647 <file name="index.html"> 19648 <label>Check me to select: <input type="checkbox" ng-model="selected"></label><br/> 19649 <select aria-label="ngSelected demo"> 19650 <option>Hello!</option> 19651 <option id="greet" ng-selected="selected">Greetings!</option> 19652 </select> 19653 </file> 19654 <file name="protractor.js" type="protractor"> 19655 it('should select Greetings!', function() { 19656 expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
19657 element(by.model('selected')).click(); 19658 expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy(); 19659 }); 19660 </file> 19661 </example> 19662 * 19663 * @element OPTION 19664 * @param {expression} ngSelected If the {@link guide/expression expression} is truthy, 19665 * then special attribute "selected" will be set on the element 19666 */ 19667 19668/** 19669 * @ngdoc directive 19670 * @name ngOpen 19671 * @restrict A 19672 * @priority 100 19673 * 19674 * @description 19675 * The HTML specification does not require browsers to preserve the values of boolean attributes 19676 * such as open. (Their presence means true and their absence means false.) 19677 * If we put an Angular interpolation expression into such an attribute then the 19678 * binding information would be lost when the browser removes the attribute. 19679 * The `ngOpen` directive solves this problem for the `open` attribute. 19680 * This complementary directive is not removed by the browser and so provides 19681 * a permanent reliable place to store the binding information. 19682 * @example 19683 <example> 19684 <file name="index.html"> 19685 <label>Check me check multiple: <input type="checkbox" ng-model="open"></label><br/> 19686 <details id="details" ng-open="open"> 19687 <summary>Show/Hide me</summary> 19688 </details> 19689 </file> 19690 <file name="protractor.js" type="protractor"> 19691 it('should toggle open', function() { 19692 expect(element(by.id('details')).getAttribute('open')).toBeFalsy(); 19693 element(by.model('open')).click(); 19694 expect(element(by.id('details')).getAttribute('open')).toBeTruthy(); 19695 }); 19696 </file> 19697 </example> 19698 * 19699 * @element DETAILS 19700 * @param {expression} ngOpen If the {@link guide/expression expression} is truthy, 19701 * then special attribute "open" will be set on the element 19702 */ 19703 19704var ngAttributeAliasDirectives = {}; 19705 19706// boolean attrs are evaluated 19707forEach(BOOLEAN_ATTR, function(propName, attrName) { 19708 // binding to multiple is not supported 19709 if (propName == "multiple") return; 19710 19711 function defaultLinkFn(scope, element, attr) { 19712 scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) { 19713 attr.$set(attrName, !!value); 19714 }); 19715 } 19716 19717 var normalized = directiveNormalize('ng-' + attrName); 19718 var linkFn = defaultLinkFn; 19719 19720 if (propName === 'checked') { 19721 linkFn = function(scope, element, attr) { 19722 // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input 19723 if (attr.ngModel !== attr[normalized]) { 19724 defaultLinkFn(scope, element, attr); 19725 } 19726 }; 19727 } 19728 19729 ngAttributeAliasDirectives[normalized] = function() { 19730 return { 19731 restrict: 'A', 19732 priority: 100, 19733 link: linkFn 19734 }; 19735 }; 19736}); 19737 19738// aliased input attrs are evaluated 19739forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) { 19740 ngAttributeAliasDirectives[ngAttr] = function() { 19741 return { 19742 priority: 100, 19743 link: function(scope, element, attr) { 19744 //special case ngPattern when a literal regular expression value 19745 //is used as the expression (this way we don't have to watch anything). 19746 if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") { 19747 var match = attr.ngPattern.match(REGEX_STRING_REGEXP); 19748 if (match) { 19749 attr.$set("ngPattern", new RegExp(match[1], match[2])); 19750 return; 19751 } 19752 } 19753 19754 scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) { 19755 attr.$set(ngAttr, value); 19756 }); 19757 } 19758 }; 19759 }; 19760}); 19761 19762// ng-src, ng-srcset, ng-href are interpolated 19763forEach(['src', 'srcset', 'href'], function(attrName) { 19764 var normalized = directiveNormalize('ng-' + attrName); 19765 ngAttributeAliasDirectives[normalized] = function() { 19766 return { 19767 priority: 99, // it needs to run after the attributes are interpolated 19768 link: function(scope, element, attr) { 19769 var propName = attrName, 19770 name = attrName; 19771 19772 if (attrName === 'href' && 19773 toString.call(element.prop('href')) === '[object SVGAnimatedString]') { 19774 name = 'xlinkHref'; 19775 attr.$attr[name] = 'xlink:href'; 19776 propName = null; 19777 } 19778 19779 attr.$observe(normalized, function(value) { 19780 if (!value) { 19781 if (attrName === 'href') { 19782 attr.$set(name, null); 19783 } 19784 return; 19785 } 19786 19787 attr.$set(name, value); 19788 19789 // on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist 19790 // then calling element.setAttribute('src', 'foo') doesn't do anything, so we need 19791 // to set the property as well to achieve the desired effect. 19792 // we use attr[attrName] value since $set can sanitize the url. 19793 if (msie && propName) element.prop(propName, attr[name]); 19794 }); 19795 } 19796 }; 19797 }; 19798}); 19799 19800/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true 19801 */ 19802var nullFormCtrl = { 19803 $addControl: noop, 19804 $$renameControl: nullFormRenameControl, 19805 $removeControl: noop, 19806 $setValidity: noop, 19807 $setDirty: noop, 19808 $setPristine: noop, 19809 $setSubmitted: noop 19810}, 19811SUBMITTED_CLASS = 'ng-submitted'; 19812 19813function nullFormRenameControl(control, name) { 19814 control.$name = name; 19815} 19816
19817/** 19818 * @ngdoc type 19819 * @name form.FormController 19820 * 19821 * @property {boolean} $pristine True if user has not interacted with the form yet. 19822 * @property {boolean} $dirty True if user has already interacted with the form. 19823 * @property {boolean} $valid True if all of the containing forms and controls are valid. 19824 * @property {boolean} $invalid True if at least one containing control or form is invalid. 19825 * @property {boolean} $submitted True if user has submitted the form even if its invalid. 19826 * 19827 * @property {Object} $error Is an object hash, containing references to controls or 19828 * forms with failing validators, where: 19829 * 19830 * - keys are validation tokens (error names), 19831 * - values are arrays of controls or forms that have a failing validator for given error name. 19832 * 19833 * Built-in validation tokens: 19834 * 19835 * - `email` 19836 * - `max` 19837 * - `maxlength` 19838 * - `min` 19839 * - `minlength` 19840 * - `number` 19841 * - `pattern` 19842 * - `required` 19843 * - `url` 19844 * - `date` 19845 * - `datetimelocal` 19846 * - `time` 19847 * - `week` 19848 * - `month` 19849 * 19850 * @description 19851 * `FormController` keeps track of all its controls and nested forms as well as the state of them, 19852 * such as being valid/invalid or dirty/pristine. 19853 * 19854 * Each {@link ng.directive:form form} directive creates an instance 19855 * of `FormController`. 19856 * 19857 */ 19858//asks for $scope to fool the BC controller module 19859FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate']; 19860function FormController(element, attrs, $scope, $animate, $interpolate) { 19861 var form = this, 19862 controls = []; 19863 19864 var parentForm = form.$$parentForm = element.parent().controller('form') || nullFormCtrl; 19865 19866 // init state 19867 form.$error = {}; 19868 form.$$success = {}; 19869 form.$pending = undefined; 19870 form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope); 19871 form.$dirty = false; 19872 form.$pristine = true; 19873 form.$valid = true; 19874 form.$invalid = false; 19875 form.$submitted = false; 19876 19877 parentForm.$addControl(form); 19878 19879 /** 19880 * @ngdoc method 19881 * @name form.FormController#$rollbackViewValue 19882 * 19883 * @description 19884 * Rollback all form controls pending updates to the `$modelValue`. 19885 * 19886 * Updates may be pending by a debounced event or because the input is waiting for a some future 19887 * event defined in `ng-model-options`. This method is typically needed by the reset button of 19888 * a form that uses `ng-model-options` to pend updates. 19889 */ 19890 form.$rollbackViewValue = function() { 19891 forEach(controls, function(control) { 19892 control.$rollbackViewValue(); 19893 }); 19894 }; 19895 19896 /** 19897 * @ngdoc method 19898 * @name form.FormController#$commitViewValue 19899 * 19900 * @description 19901 * Commit all form controls pending updates to the `$modelValue`. 19902 * 19903 * Updates may be pending by a debounced event or because the input is waiting for a some future 19904 * event defined in `ng-model-options`. This method is rarely needed as `NgModelController` 19905 * usually handles calling this in response to input events. 19906 */ 19907 form.$commitViewValue = function() { 19908 forEach(controls, function(control) { 19909 control.$commitViewValue(); 19910 }); 19911 }; 19912 19913 /** 19914 * @ngdoc method 19915 * @name form.FormController#$addControl 19916 * 19917 * @description 19918 * Register a control with the form. 19919 * 19920 * Input elements using ngModelController do this automatically when they are linked. 19921 */ 19922 form.$addControl = function(control) { 19923 // Breaking change - before, inputs whose name was "hasOwnProperty" were quietly ignored 19924 // and not added to the scope. Now we throw an error. 19925 assertNotHasOwnProperty(control.$name, 'input'); 19926 controls.push(control); 19927 19928 if (control.$name) { 19929 form[control.$name] = control; 19930 } 19931 }; 19932 19933 // Private API: rename a form control 19934 form.$$renameControl = function(control, newName) { 19935 var oldName = control.$name; 19936 19937 if (form[oldName] === control) { 19938 delete form[oldName]; 19939 } 19940 form[newName] = control; 19941 control.$name = newName; 19942 }; 19943 19944 /** 19945 * @ngdoc method 19946 * @name form.FormController#$removeControl 19947 * 19948 * @description 19949 * Deregister a control from the form. 19950 * 19951 * Input elements using ngModelController do this automatically when they are destroyed. 19952 */ 19953 form.$removeControl = function(control) { 19954 if (control.$name && form[control.$name] === control) { 19955 delete form[control.$name]; 19956 }
19957 forEach(form.$pending, function(value, name) { 19958 form.$setValidity(name, null, control); 19959 }); 19960 forEach(form.$error, function(value, name) { 19961 form.$setValidity(name, null, control); 19962 }); 19963 forEach(form.$$success, function(value, name) { 19964 form.$setValidity(name, null, control); 19965 }); 19966 19967 arrayRemove(controls, control); 19968 }; 19969 19970 19971 /** 19972 * @ngdoc method 19973 * @name form.FormController#$setValidity 19974 * 19975 * @description 19976 * Sets the validity of a form control. 19977 * 19978 * This method will also propagate to parent forms. 19979 */ 19980 addSetValidityMethod({ 19981 ctrl: this, 19982 $element: element, 19983 set: function(object, property, controller) { 19984 var list = object[property]; 19985 if (!list) { 19986 object[property] = [controller]; 19987 } else { 19988 var index = list.indexOf(controller); 19989 if (index === -1) { 19990 list.push(controller); 19991 } 19992 } 19993 }, 19994 unset: function(object, property, controller) { 19995 var list = object[property]; 19996 if (!list) { 19997 return; 19998 } 19999 arrayRemove(list, controller); 20000 if (list.length === 0) { 20001 delete object[property]; 20002 } 20003 }, 20004 parentForm: parentForm, 20005 $animate: $animate 20006 }); 20007 20008 /** 20009 * @ngdoc method 20010 * @name form.FormController#$setDirty 20011 * 20012 * @description 20013 * Sets the form to a dirty state. 20014 * 20015 * This method can be called to add the 'ng-dirty' class and set the form to a dirty 20016 * state (ng-dirty class). This method will also propagate to parent forms. 20017 */ 20018 form.$setDirty = function() { 20019 $animate.removeClass(element, PRISTINE_CLASS); 20020 $animate.addClass(element, DIRTY_CLASS); 20021 form.$dirty = true; 20022 form.$pristine = false; 20023 parentForm.$setDirty(); 20024 }; 20025 20026 /** 20027 * @ngdoc method 20028 * @name form.FormController#$setPristine 20029 * 20030 * @description 20031 * Sets the form to its pristine state. 20032 * 20033 * This method can be called to remove the 'ng-dirty' class and set the form to its pristine 20034 * state (ng-pristine class). This method will also propagate to all the controls contained 20035 * in this form. 20036 * 20037 * Setting a form back to a pristine state is often useful when we want to 'reuse' a form after 20038 * saving or resetting it. 20039 */ 20040 form.$setPristine = function() { 20041 $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS); 20042 form.$dirty = false; 20043 form.$pristine = true; 20044 form.$submitted = false; 20045 forEach(controls, function(control) { 20046 control.$setPristine(); 20047 }); 20048 }; 20049 20050 /** 20051 * @ngdoc method 20052 * @name form.FormController#$setUntouched 20053 * 20054 * @description 20055 * Sets the form to its untouched state. 20056 * 20057 * This method can be called to remove the 'ng-touched' class and set the form controls to their 20058 * untouched state (ng-untouched class). 20059 * 20060 * Setting a form controls back to their untouched state is often useful when setting the form 20061 * back to its pristine state. 20062 */ 20063 form.$setUntouched = function() { 20064 forEach(controls, function(control) { 20065 control.$setUntouched(); 20066 }); 20067 }; 20068 20069 /** 20070 * @ngdoc method 20071 * @name form.FormController#$setSubmitted 20072 * 20073 * @description 20074 * Sets the form to its submitted state. 20075 */ 20076 form.$setSubmitted = function() { 20077 $animate.addClass(element, SUBMITTED_CLASS); 20078 form.$submitted = true; 20079 parentForm.$setSubmitted(); 20080 }; 20081} 20082 20083/** 20084 * @ngdoc directive 20085 * @name ngForm 20086 * @restrict EAC 20087 * 20088 * @description 20089 * Nestable alias of {@link ng.directive:form `form`} directive. HTML 20090 * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a 20091 * sub-group of controls needs to be determined. 20092 *
20093 * Note: the purpose of `ngForm` is to group controls, 20094 * but not to be a replacement for the `<form>` tag with all of its capabilities 20095 * (e.g. posting to the server, ...). 20096 * 20097 * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into 20098 * related scope, under this name. 20099 * 20100 */ 20101 20102 /** 20103 * @ngdoc directive 20104 * @name form 20105 * @restrict E 20106 * 20107 * @description 20108 * Directive that instantiates 20109 * {@link form.FormController FormController}. 20110 * 20111 * If the `name` attribute is specified, the form controller is published onto the current scope under 20112 * this name. 20113 * 20114 * # Alias: {@link ng.directive:ngForm `ngForm`} 20115 * 20116 * In Angular, forms can be nested. This means that the outer form is valid when all of the child 20117 * forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so 20118 * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to 20119 * `<form>` but can be nested. This allows you to have nested forms, which is very useful when 20120 * using Angular validation directives in forms that are dynamically generated using the 20121 * {@link ng.directive:ngRepeat `ngRepeat`} directive. Since you cannot dynamically generate the `name` 20122 * attribute of input elements using interpolation, you have to wrap each set of repeated inputs in an 20123 * `ngForm` directive and nest these in an outer `form` element. 20124 * 20125 * 20126 * # CSS classes 20127 * - `ng-valid` is set if the form is valid. 20128 * - `ng-invalid` is set if the form is invalid. 20129 * - `ng-pristine` is set if the form is pristine. 20130 * - `ng-dirty` is set if the form is dirty. 20131 * - `ng-submitted` is set if the form was submitted. 20132 * 20133 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 20134 * 20135 * 20136 * # Submitting a form and preventing the default action 20137 * 20138 * Since the role of forms in client-side Angular applications is different than in classical 20139 * roundtrip apps, it is desirable for the browser not to translate the form submission into a full 20140 * page reload that sends the data to the server. Instead some javascript logic should be triggered 20141 * to handle the form submission in an application-specific way. 20142 * 20143 * For this reason, Angular prevents the default action (form submission to the server) unless the 20144 * `<form>` element has an `action` attribute specified. 20145 * 20146 * You can use one of the following two ways to specify what javascript method should be called when 20147 * a form is submitted: 20148 * 20149 * - {@link ng.directive:ngSubmit ngSubmit} directive on the form element 20150 * - {@link ng.directive:ngClick ngClick} directive on the first 20151 * button or input field of type submit (input[type=submit]) 20152 * 20153 * To prevent double execution of the handler, use only one of the {@link ng.directive:ngSubmit ngSubmit} 20154 * or {@link ng.directive:ngClick ngClick} directives. 20155 * This is because of the following form submission rules in the HTML specification: 20156 * 20157 * - If a form has only one input field then hitting enter in this field triggers form submit 20158 * (`ngSubmit`) 20159 * - if a form has 2+ input fields and no buttons or input[type=submit] then hitting enter 20160 * doesn't trigger submit 20161 * - if a form has one or more input fields and one or more buttons or input[type=submit] then 20162 * hitting enter in any of the input fields will trigger the click handler on the *first* button or 20163 * input[type=submit] (`ngClick`) *and* a submit handler on the enclosing form (`ngSubmit`) 20164 * 20165 * Any pending `ngModelOptions` changes will take place immediately when an enclosing form is 20166 * submitted. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 20167 * to have access to the updated model. 20168 * 20169 * ## Animation Hooks 20170 * 20171 * Animations in ngForm are triggered when any of the associated CSS classes are added and removed. 20172 * These classes are: `.ng-pristine`, `.ng-dirty`, `.ng-invalid` and `.ng-valid` as well as any 20173 * other validations that are performed within the form. Animations in ngForm are similar to how 20174 * they work in ngClass and animations can be hooked into using CSS transitions, keyframes as well 20175 * as JS animations. 20176 * 20177 * The following example shows a simple way to utilize CSS transitions to style a form element
20178 * that has been rendered as invalid after it has been validated: 20179 * 20180 * <pre> 20181 * //be sure to include ngAnimate as a module to hook into more 20182 * //advanced animations 20183 * .my-form { 20184 * transition:0.5s linear all; 20185 * background: white; 20186 * } 20187 * .my-form.ng-invalid { 20188 * background: red; 20189 * color:white; 20190 * } 20191 * </pre> 20192 * 20193 * @example 20194 <example deps="angular-animate.js" animations="true" fixBase="true" module="formExample"> 20195 <file name="index.html"> 20196 <script> 20197 angular.module('formExample', []) 20198 .controller('FormController', ['$scope', function($scope) { 20199 $scope.userType = 'guest'; 20200 }]); 20201 </script> 20202 <style> 20203 .my-form { 20204 -webkit-transition:all linear 0.5s; 20205 transition:all linear 0.5s; 20206 background: transparent; 20207 } 20208 .my-form.ng-invalid { 20209 background: red; 20210 } 20211 </style> 20212 <form name="myForm" ng-controller="FormController" class="my-form"> 20213 userType: <input name="input" ng-model="userType" required> 20214 <span class="error" ng-show="myForm.input.$error.required">Required!</span><br> 20215 <code>userType = {{userType}}</code><br> 20216 <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br> 20217 <code>myForm.input.$error = {{myForm.input.$error}}</code><br> 20218 <code>myForm.$valid = {{myForm.$valid}}</code><br> 20219 <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br> 20220 </form> 20221 </file> 20222 <file name="protractor.js" type="protractor"> 20223 it('should initialize to model', function() { 20224 var userType = element(by.binding('userType')); 20225 var valid = element(by.binding('myForm.input.$valid')); 20226 20227 expect(userType.getText()).toContain('guest'); 20228 expect(valid.getText()).toContain('true'); 20229 }); 20230 20231 it('should be invalid if empty', function() { 20232 var userType = element(by.binding('userType')); 20233 var valid = element(by.binding('myForm.input.$valid')); 20234 var userInput = element(by.model('userType')); 20235 20236 userInput.clear(); 20237 userInput.sendKeys(''); 20238 20239 expect(userType.getText()).toEqual('userType ='); 20240 expect(valid.getText()).toContain('false'); 20241 }); 20242 </file> 20243 </example> 20244 * 20245 * @param {string=} name Name of the form. If specified, the form controller will be published into 20246 * related scope, under this name. 20247 */ 20248var formDirectiveFactory = function(isNgForm) { 20249 return ['$timeout', function($timeout) { 20250 var formDirective = { 20251 name: 'form', 20252 restrict: isNgForm ? 'EAC' : 'E', 20253 controller: FormController, 20254 compile: function ngFormCompile(formElement, attr) { 20255 // Setup initial state of the control 20256 formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); 20257 20258 var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); 20259 20260 return { 20261 pre: function ngFormPreLink(scope, formElement, attr, controller) { 20262 // if `action` attr is not present on the form, prevent the default action (submission) 20263 if (!('action' in attr)) { 20264 // we can't use jq events because if a form is destroyed during submission the default 20265 // action is not prevented. see #1238 20266 // 20267 // IE 9 is not affected because it doesn't fire a submit event and try to do a full 20268 // page reload if the form was destroyed by submission of the form via a click handler 20269 // on a button in the form. Looks like an IE9 specific bug. 20270 var handleFormSubmission = function(event) { 20271 scope.$apply(function() { 20272 controller.$commitViewValue(); 20273 controller.$setSubmitted(); 20274 }); 20275 20276 event.preventDefault(); 20277 }; 20278 20279 addEventListenerFn(formElement[0], 'submit', handleFormSubmission); 20280 20281 // unregister the preventDefault listener so that we don't not leak memory but in a 20282 // way that will achieve the prevention of the default action. 20283 formElement.on('$destroy', function() { 20284 $timeout(function() { 20285 removeEventListenerFn(formElement[0], 'submit', handleFormSubmission); 20286 }, 0, false); 20287 }); 20288 } 20289 20290 var parentFormCtrl = controller.$$parentForm; 20291 20292 if (nameAttr) { 20293 setter(scope, controller.$name, controller, controller.$name); 20294 attr.$observe(nameAttr, function(newValue) { 20295 if (controller.$name === newValue) return;
20296 setter(scope, controller.$name, undefined, controller.$name); 20297 parentFormCtrl.$$renameControl(controller, newValue); 20298 setter(scope, controller.$name, controller, controller.$name); 20299 }); 20300 } 20301 formElement.on('$destroy', function() { 20302 parentFormCtrl.$removeControl(controller); 20303 if (nameAttr) { 20304 setter(scope, attr[nameAttr], undefined, controller.$name); 20305 } 20306 extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards 20307 }); 20308 } 20309 }; 20310 } 20311 }; 20312 20313 return formDirective; 20314 }]; 20315}; 20316 20317var formDirective = formDirectiveFactory(); 20318var ngFormDirective = formDirectiveFactory(true); 20319 20320/* global VALID_CLASS: false, 20321 INVALID_CLASS: false, 20322 PRISTINE_CLASS: false, 20323 DIRTY_CLASS: false, 20324 UNTOUCHED_CLASS: false, 20325 TOUCHED_CLASS: false, 20326 $ngModelMinErr: false, 20327*/ 20328 20329// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231 20330var 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)/; 20331var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; 20332var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i; 20333var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/; 20334var DATE_REGEXP = /^(\d{4})-(\d{2})-(\d{2})$/; 20335var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 20336var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/; 20337var MONTH_REGEXP = /^(\d{4})-(\d\d)$/; 20338var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/; 20339 20340var inputType = { 20341 20342 /** 20343 * @ngdoc input 20344 * @name input[text] 20345 * 20346 * @description 20347 * Standard HTML text input with angular data binding, inherited by most of the `input` elements. 20348 * 20349 * 20350 * @param {string} ngModel Assignable angular expression to data-bind to. 20351 * @param {string=} name Property name of the form under which the control is published. 20352 * @param {string=} required Adds `required` validation error key if the value is not entered. 20353 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20354 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20355 * `required` when you want to data-bind to the `required` attribute. 20356 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 20357 * minlength. 20358 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 20359 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 20360 * any length. 20361 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 20362 * that contains the regular expression body that will be converted to a regular expression 20363 * as in the ngPattern directive. 20364 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 20365 * a RegExp found by evaluating the Angular expression given in the attribute value. 20366 * If the expression evaluates to a RegExp object, then this is used directly. 20367 * If the expression evaluates to a string, then it will be converted to a RegExp 20368 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 20369 * `new RegExp('^abc$')`.<br /> 20370 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 20371 * start at the index of the last search's match, thus not taking the whole input value into 20372 * account. 20373 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20374 * interaction with the input element. 20375 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 20376 * This parameter is ignored for input[type=password] controls, which will never trim the 20377 * input. 20378 * 20379 * @example 20380 <example name="text-input-directive" module="textInputExample"> 20381 <file name="index.html"> 20382 <script> 20383 angular.module('textInputExample', [])
20384 .controller('ExampleController', ['$scope', function($scope) { 20385 $scope.example = { 20386 text: 'guest', 20387 word: /^\s*\w*\s*$/ 20388 }; 20389 }]); 20390 </script> 20391 <form name="myForm" ng-controller="ExampleController"> 20392 <label>Single word: 20393 <input type="text" name="input" ng-model="example.text" 20394 ng-pattern="example.word" required ng-trim="false"> 20395 </label> 20396 <div role="alert"> 20397 <span class="error" ng-show="myForm.input.$error.required"> 20398 Required!</span> 20399 <span class="error" ng-show="myForm.input.$error.pattern"> 20400 Single word only!</span> 20401 </div> 20402 <tt>text = {{example.text}}</tt><br/> 20403 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20404 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20405 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20406 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20407 </form> 20408 </file> 20409 <file name="protractor.js" type="protractor"> 20410 var text = element(by.binding('example.text')); 20411 var valid = element(by.binding('myForm.input.$valid')); 20412 var input = element(by.model('example.text')); 20413 20414 it('should initialize to model', function() { 20415 expect(text.getText()).toContain('guest'); 20416 expect(valid.getText()).toContain('true'); 20417 }); 20418 20419 it('should be invalid if empty', function() { 20420 input.clear(); 20421 input.sendKeys(''); 20422 20423 expect(text.getText()).toEqual('text ='); 20424 expect(valid.getText()).toContain('false'); 20425 }); 20426 20427 it('should be invalid if multi word', function() { 20428 input.clear(); 20429 input.sendKeys('hello world'); 20430 20431 expect(valid.getText()).toContain('false'); 20432 }); 20433 </file> 20434 </example> 20435 */ 20436 'text': textInputType, 20437 20438 /** 20439 * @ngdoc input 20440 * @name input[date] 20441 * 20442 * @description 20443 * Input with date validation and transformation. In browsers that do not yet support 20444 * the HTML5 date input, a text element will be used. In that case, text must be entered in a valid ISO-8601 20445 * date format (yyyy-MM-dd), for example: `2009-01-06`. Since many 20446 * modern browsers do not yet support this input type, it is important to provide cues to users on the 20447 * expected input format via a placeholder or label. 20448 * 20449 * The model must always be a Date object, otherwise Angular will throw an error. 20450 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20451 * 20452 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20453 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20454 * 20455 * @param {string} ngModel Assignable angular expression to data-bind to. 20456 * @param {string=} name Property name of the form under which the control is published. 20457 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 20458 * valid ISO date string (yyyy-MM-dd). 20459 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 20460 * a valid ISO date string (yyyy-MM-dd). 20461 * @param {string=} required Sets `required` validation error key if the value is not entered. 20462 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20463 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20464 * `required` when you want to data-bind to the `required` attribute. 20465 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20466 * interaction with the input element. 20467 * 20468 * @example 20469 <example name="date-input-directive" module="dateInputExample"> 20470 <file name="index.html"> 20471 <script> 20472 angular.module('dateInputExample', []) 20473 .controller('DateController', ['$scope', function($scope) { 20474 $scope.example = { 20475 value: new Date(2013, 9, 22) 20476 }; 20477 }]); 20478 </script> 20479 <form name="myForm" ng-controller="DateController as dateCtrl"> 20480 <label for="exampleInput">Pick a date in 2013:</label> 20481 <input type="date" id="exampleInput" name="input" ng-model="example.value" 20482 placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required /> 20483 <div role="alert">
20484 <span class="error" ng-show="myForm.input.$error.required"> 20485 Required!</span> 20486 <span class="error" ng-show="myForm.input.$error.date"> 20487 Not a valid date!</span> 20488 </div> 20489 <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/> 20490 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20491 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20492 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20493 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20494 </form> 20495 </file> 20496 <file name="protractor.js" type="protractor"> 20497 var value = element(by.binding('example.value | date: "yyyy-MM-dd"')); 20498 var valid = element(by.binding('myForm.input.$valid')); 20499 var input = element(by.model('example.value')); 20500 20501 // currently protractor/webdriver does not support 20502 // sending keys to all known HTML5 input controls 20503 // for various browsers (see https://github.com/angular/protractor/issues/562). 20504 function setInput(val) { 20505 // set the value of the element and force validation. 20506 var scr = "var ipt = document.getElementById('exampleInput'); " + 20507 "ipt.value = '" + val + "';" + 20508 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20509 browser.executeScript(scr); 20510 } 20511 20512 it('should initialize to model', function() { 20513 expect(value.getText()).toContain('2013-10-22'); 20514 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20515 }); 20516 20517 it('should be invalid if empty', function() { 20518 setInput(''); 20519 expect(value.getText()).toEqual('value ='); 20520 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20521 }); 20522 20523 it('should be invalid if over max', function() { 20524 setInput('2015-01-01'); 20525 expect(value.getText()).toContain(''); 20526 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20527 }); 20528 </file> 20529 </example> 20530 */ 20531 'date': createDateInputType('date', DATE_REGEXP, 20532 createDateParser(DATE_REGEXP, ['yyyy', 'MM', 'dd']), 20533 'yyyy-MM-dd'), 20534 20535 /** 20536 * @ngdoc input 20537 * @name input[datetime-local] 20538 * 20539 * @description 20540 * Input with datetime validation and transformation. In browsers that do not yet support 20541 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 20542 * local datetime format (yyyy-MM-ddTHH:mm:ss), for example: `2010-12-28T14:57:00`. 20543 * 20544 * The model must always be a Date object, otherwise Angular will throw an error. 20545 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20546 * 20547 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20548 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20549 * 20550 * @param {string} ngModel Assignable angular expression to data-bind to. 20551 * @param {string=} name Property name of the form under which the control is published. 20552 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 20553 * valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). 20554 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 20555 * a valid ISO datetime format (yyyy-MM-ddTHH:mm:ss). 20556 * @param {string=} required Sets `required` validation error key if the value is not entered. 20557 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20558 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20559 * `required` when you want to data-bind to the `required` attribute. 20560 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20561 * interaction with the input element. 20562 * 20563 * @example 20564 <example name="datetimelocal-input-directive" module="dateExample"> 20565 <file name="index.html"> 20566 <script> 20567 angular.module('dateExample', []) 20568 .controller('DateController', ['$scope', function($scope) { 20569 $scope.example = { 20570 value: new Date(2010, 11, 28, 14, 57) 20571 }; 20572 }]); 20573 </script> 20574 <form name="myForm" ng-controller="DateController as dateCtrl"> 20575 <label for="exampleInput">Pick a date between in 2013:</label> 20576 <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value" 20577 placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required /> 20578 <div role="alert">
20579 <span class="error" ng-show="myForm.input.$error.required"> 20580 Required!</span> 20581 <span class="error" ng-show="myForm.input.$error.datetimelocal"> 20582 Not a valid date!</span> 20583 </div> 20584 <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/> 20585 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20586 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20587 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20588 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20589 </form> 20590 </file> 20591 <file name="protractor.js" type="protractor"> 20592 var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"')); 20593 var valid = element(by.binding('myForm.input.$valid')); 20594 var input = element(by.model('example.value')); 20595 20596 // currently protractor/webdriver does not support 20597 // sending keys to all known HTML5 input controls 20598 // for various browsers (https://github.com/angular/protractor/issues/562). 20599 function setInput(val) { 20600 // set the value of the element and force validation. 20601 var scr = "var ipt = document.getElementById('exampleInput'); " + 20602 "ipt.value = '" + val + "';" + 20603 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20604 browser.executeScript(scr); 20605 } 20606 20607 it('should initialize to model', function() { 20608 expect(value.getText()).toContain('2010-12-28T14:57:00'); 20609 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20610 }); 20611 20612 it('should be invalid if empty', function() { 20613 setInput(''); 20614 expect(value.getText()).toEqual('value ='); 20615 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20616 }); 20617 20618 it('should be invalid if over max', function() { 20619 setInput('2015-01-01T23:59:00'); 20620 expect(value.getText()).toContain(''); 20621 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20622 }); 20623 </file> 20624 </example> 20625 */ 20626 'datetime-local': createDateInputType('datetimelocal', DATETIMELOCAL_REGEXP, 20627 createDateParser(DATETIMELOCAL_REGEXP, ['yyyy', 'MM', 'dd', 'HH', 'mm', 'ss', 'sss']), 20628 'yyyy-MM-ddTHH:mm:ss.sss'), 20629 20630 /** 20631 * @ngdoc input 20632 * @name input[time] 20633 * 20634 * @description 20635 * Input with time validation and transformation. In browsers that do not yet support 20636 * the HTML5 date input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 20637 * local time format (HH:mm:ss), for example: `14:57:00`. Model must be a Date object. This binding will always output a 20638 * Date object to the model of January 1, 1970, or local date `new Date(1970, 0, 1, HH, mm, ss)`. 20639 * 20640 * The model must always be a Date object, otherwise Angular will throw an error. 20641 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20642 * 20643 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20644 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20645 * 20646 * @param {string} ngModel Assignable angular expression to data-bind to. 20647 * @param {string=} name Property name of the form under which the control is published. 20648 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 20649 * valid ISO time format (HH:mm:ss). 20650 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be a 20651 * valid ISO time format (HH:mm:ss). 20652 * @param {string=} required Sets `required` validation error key if the value is not entered. 20653 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20654 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20655 * `required` when you want to data-bind to the `required` attribute. 20656 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20657 * interaction with the input element. 20658 * 20659 * @example 20660 <example name="time-input-directive" module="timeExample"> 20661 <file name="index.html"> 20662 <script> 20663 angular.module('timeExample', []) 20664 .controller('DateController', ['$scope', function($scope) { 20665 $scope.example = { 20666 value: new Date(1970, 0, 1, 14, 57, 0) 20667 }; 20668 }]); 20669 </script> 20670 <form name="myForm" ng-controller="DateController as dateCtrl"> 20671 <label for="exampleInput">Pick a between 8am and 5pm:</label> 20672 <input type="time" id="exampleInput" name="input" ng-model="example.value" 20673 placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required /> 20674 <div role="alert">
20675 <span class="error" ng-show="myForm.input.$error.required"> 20676 Required!</span> 20677 <span class="error" ng-show="myForm.input.$error.time"> 20678 Not a valid date!</span> 20679 </div> 20680 <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/> 20681 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20682 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20683 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20684 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20685 </form> 20686 </file> 20687 <file name="protractor.js" type="protractor"> 20688 var value = element(by.binding('example.value | date: "HH:mm:ss"')); 20689 var valid = element(by.binding('myForm.input.$valid')); 20690 var input = element(by.model('example.value')); 20691 20692 // currently protractor/webdriver does not support 20693 // sending keys to all known HTML5 input controls 20694 // for various browsers (https://github.com/angular/protractor/issues/562). 20695 function setInput(val) { 20696 // set the value of the element and force validation. 20697 var scr = "var ipt = document.getElementById('exampleInput'); " + 20698 "ipt.value = '" + val + "';" + 20699 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20700 browser.executeScript(scr); 20701 } 20702 20703 it('should initialize to model', function() { 20704 expect(value.getText()).toContain('14:57:00'); 20705 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20706 }); 20707 20708 it('should be invalid if empty', function() { 20709 setInput(''); 20710 expect(value.getText()).toEqual('value ='); 20711 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20712 }); 20713 20714 it('should be invalid if over max', function() { 20715 setInput('23:59:00'); 20716 expect(value.getText()).toContain(''); 20717 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20718 }); 20719 </file> 20720 </example> 20721 */ 20722 'time': createDateInputType('time', TIME_REGEXP, 20723 createDateParser(TIME_REGEXP, ['HH', 'mm', 'ss', 'sss']), 20724 'HH:mm:ss.sss'), 20725 20726 /** 20727 * @ngdoc input 20728 * @name input[week] 20729 * 20730 * @description 20731 * Input with week-of-the-year validation and transformation to Date. In browsers that do not yet support 20732 * the HTML5 week input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 20733 * week format (yyyy-W##), for example: `2013-W02`. 20734 * 20735 * The model must always be a Date object, otherwise Angular will throw an error. 20736 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20737 * 20738 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20739 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20740 * 20741 * @param {string} ngModel Assignable angular expression to data-bind to. 20742 * @param {string=} name Property name of the form under which the control is published. 20743 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be a 20744 * valid ISO week format (yyyy-W##). 20745 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must be 20746 * a valid ISO week format (yyyy-W##). 20747 * @param {string=} required Sets `required` validation error key if the value is not entered. 20748 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20749 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20750 * `required` when you want to data-bind to the `required` attribute. 20751 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20752 * interaction with the input element. 20753 * 20754 * @example 20755 <example name="week-input-directive" module="weekExample"> 20756 <file name="index.html"> 20757 <script> 20758 angular.module('weekExample', []) 20759 .controller('DateController', ['$scope', function($scope) { 20760 $scope.example = { 20761 value: new Date(2013, 0, 3) 20762 }; 20763 }]); 20764 </script> 20765 <form name="myForm" ng-controller="DateController as dateCtrl"> 20766 <label>Pick a date between in 2013: 20767 <input id="exampleInput" type="week" name="input" ng-model="example.value" 20768 placeholder="YYYY-W##" min="2012-W32" 20769 max="2013-W52" required /> 20770 </label> 20771 <div role="alert">
20772 <span class="error" ng-show="myForm.input.$error.required"> 20773 Required!</span> 20774 <span class="error" ng-show="myForm.input.$error.week"> 20775 Not a valid date!</span> 20776 </div> 20777 <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/> 20778 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20779 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20780 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20781 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20782 </form> 20783 </file> 20784 <file name="protractor.js" type="protractor"> 20785 var value = element(by.binding('example.value | date: "yyyy-Www"')); 20786 var valid = element(by.binding('myForm.input.$valid')); 20787 var input = element(by.model('example.value')); 20788 20789 // currently protractor/webdriver does not support 20790 // sending keys to all known HTML5 input controls 20791 // for various browsers (https://github.com/angular/protractor/issues/562). 20792 function setInput(val) { 20793 // set the value of the element and force validation. 20794 var scr = "var ipt = document.getElementById('exampleInput'); " + 20795 "ipt.value = '" + val + "';" + 20796 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20797 browser.executeScript(scr); 20798 } 20799 20800 it('should initialize to model', function() { 20801 expect(value.getText()).toContain('2013-W01'); 20802 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20803 }); 20804 20805 it('should be invalid if empty', function() { 20806 setInput(''); 20807 expect(value.getText()).toEqual('value ='); 20808 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20809 }); 20810 20811 it('should be invalid if over max', function() { 20812 setInput('2015-W01'); 20813 expect(value.getText()).toContain(''); 20814 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20815 }); 20816 </file> 20817 </example> 20818 */ 20819 'week': createDateInputType('week', WEEK_REGEXP, weekParser, 'yyyy-Www'), 20820 20821 /** 20822 * @ngdoc input 20823 * @name input[month] 20824 * 20825 * @description 20826 * Input with month validation and transformation. In browsers that do not yet support 20827 * the HTML5 month input, a text element will be used. In that case, the text must be entered in a valid ISO-8601 20828 * month format (yyyy-MM), for example: `2009-01`. 20829 * 20830 * The model must always be a Date object, otherwise Angular will throw an error. 20831 * Invalid `Date` objects (dates whose `getTime()` is `NaN`) will be rendered as an empty string. 20832 * If the model is not set to the first of the month, the next view to model update will set it 20833 * to the first of the month. 20834 * 20835 * The timezone to be used to read/write the `Date` instance in the model can be defined using 20836 * {@link ng.directive:ngModelOptions ngModelOptions}. By default, this is the timezone of the browser. 20837 * 20838 * @param {string} ngModel Assignable angular expression to data-bind to. 20839 * @param {string=} name Property name of the form under which the control is published. 20840 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. This must be 20841 * a valid ISO month format (yyyy-MM). 20842 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. This must 20843 * be a valid ISO month format (yyyy-MM). 20844 * @param {string=} required Sets `required` validation error key if the value is not entered. 20845 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20846 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20847 * `required` when you want to data-bind to the `required` attribute. 20848 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20849 * interaction with the input element. 20850 * 20851 * @example 20852 <example name="month-input-directive" module="monthExample"> 20853 <file name="index.html"> 20854 <script> 20855 angular.module('monthExample', []) 20856 .controller('DateController', ['$scope', function($scope) { 20857 $scope.example = { 20858 value: new Date(2013, 9, 1) 20859 }; 20860 }]); 20861 </script> 20862 <form name="myForm" ng-controller="DateController as dateCtrl"> 20863 <label for="exampleInput">Pick a month in 2013:</label> 20864 <input id="exampleInput" type="month" name="input" ng-model="example.value" 20865 placeholder="yyyy-MM" min="2013-01" max="2013-12" required /> 20866 <div role="alert">
20867 <span class="error" ng-show="myForm.input.$error.required"> 20868 Required!</span> 20869 <span class="error" ng-show="myForm.input.$error.month"> 20870 Not a valid month!</span> 20871 </div> 20872 <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/> 20873 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20874 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20875 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20876 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20877 </form> 20878 </file> 20879 <file name="protractor.js" type="protractor"> 20880 var value = element(by.binding('example.value | date: "yyyy-MM"')); 20881 var valid = element(by.binding('myForm.input.$valid')); 20882 var input = element(by.model('example.value')); 20883 20884 // currently protractor/webdriver does not support 20885 // sending keys to all known HTML5 input controls 20886 // for various browsers (https://github.com/angular/protractor/issues/562). 20887 function setInput(val) { 20888 // set the value of the element and force validation. 20889 var scr = "var ipt = document.getElementById('exampleInput'); " + 20890 "ipt.value = '" + val + "';" + 20891 "angular.element(ipt).scope().$apply(function(s) { s.myForm[ipt.name].$setViewValue('" + val + "'); });"; 20892 browser.executeScript(scr); 20893 } 20894 20895 it('should initialize to model', function() { 20896 expect(value.getText()).toContain('2013-10'); 20897 expect(valid.getText()).toContain('myForm.input.$valid = true'); 20898 }); 20899 20900 it('should be invalid if empty', function() { 20901 setInput(''); 20902 expect(value.getText()).toEqual('value ='); 20903 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20904 }); 20905 20906 it('should be invalid if over max', function() { 20907 setInput('2015-01'); 20908 expect(value.getText()).toContain(''); 20909 expect(valid.getText()).toContain('myForm.input.$valid = false'); 20910 }); 20911 </file> 20912 </example> 20913 */ 20914 'month': createDateInputType('month', MONTH_REGEXP, 20915 createDateParser(MONTH_REGEXP, ['yyyy', 'MM']), 20916 'yyyy-MM'), 20917 20918 /** 20919 * @ngdoc input 20920 * @name input[number] 20921 * 20922 * @description 20923 * Text input with number validation and transformation. Sets the `number` validation 20924 * error if not a valid number. 20925 * 20926 * <div class="alert alert-warning"> 20927 * The model must always be of type `number` otherwise Angular will throw an error. 20928 * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt} 20929 * error docs for more information and an example of how to convert your model if necessary. 20930 * </div> 20931 * 20932 * ## Issues with HTML5 constraint validation 20933 * 20934 * In browsers that follow the 20935 * [HTML5 specification](https://html.spec.whatwg.org/multipage/forms.html#number-state-%28type=number%29), 20936 * `input[number]` does not work as expected with {@link ngModelOptions `ngModelOptions.allowInvalid`}. 20937 * If a non-number is entered in the input, the browser will report the value as an empty string, 20938 * which means the view / model values in `ngModel` and subsequently the scope value 20939 * will also be an empty string. 20940 * 20941 * 20942 * @param {string} ngModel Assignable angular expression to data-bind to. 20943 * @param {string=} name Property name of the form under which the control is published. 20944 * @param {string=} min Sets the `min` validation error key if the value entered is less than `min`. 20945 * @param {string=} max Sets the `max` validation error key if the value entered is greater than `max`. 20946 * @param {string=} required Sets `required` validation error key if the value is not entered. 20947 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 20948 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 20949 * `required` when you want to data-bind to the `required` attribute. 20950 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 20951 * minlength. 20952 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 20953 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 20954 * any length. 20955 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 20956 * that contains the regular expression body that will be converted to a regular expression 20957 * as in the ngPattern directive. 20958 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 20959 * a RegExp found by evaluating the Angular expression given in the attribute value. 20960 * If the expression evaluates to a RegExp object, then this is used directly. 20961 * If the expression evaluates to a string, then it will be converted to a RegExp 20962 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 20963 * `new RegExp('^abc$')`.<br /> 20964 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 20965 * start at the index of the last search's match, thus not taking the whole input value into 20966 * account. 20967 * @param {string=} ngChange Angular expression to be executed when input changes due to user 20968 * interaction with the input element. 20969 * 20970 * @example 20971 <example name="number-input-directive" module="numberExample"> 20972 <file name="index.html"> 20973 <script> 20974 angular.module('numberExample', [])
20975 .controller('ExampleController', ['$scope', function($scope) { 20976 $scope.example = { 20977 value: 12 20978 }; 20979 }]); 20980 </script> 20981 <form name="myForm" ng-controller="ExampleController"> 20982 <label>Number: 20983 <input type="number" name="input" ng-model="example.value" 20984 min="0" max="99" required> 20985 </label> 20986 <div role="alert"> 20987 <span class="error" ng-show="myForm.input.$error.required"> 20988 Required!</span> 20989 <span class="error" ng-show="myForm.input.$error.number"> 20990 Not valid number!</span> 20991 </div> 20992 <tt>value = {{example.value}}</tt><br/> 20993 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 20994 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 20995 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 20996 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 20997 </form> 20998 </file> 20999 <file name="protractor.js" type="protractor"> 21000 var value = element(by.binding('example.value')); 21001 var valid = element(by.binding('myForm.input.$valid')); 21002 var input = element(by.model('example.value')); 21003 21004 it('should initialize to model', function() { 21005 expect(value.getText()).toContain('12'); 21006 expect(valid.getText()).toContain('true'); 21007 }); 21008 21009 it('should be invalid if empty', function() { 21010 input.clear(); 21011 input.sendKeys(''); 21012 expect(value.getText()).toEqual('value ='); 21013 expect(valid.getText()).toContain('false'); 21014 }); 21015 21016 it('should be invalid if over max', function() { 21017 input.clear(); 21018 input.sendKeys('123'); 21019 expect(value.getText()).toEqual('value ='); 21020 expect(valid.getText()).toContain('false'); 21021 }); 21022 </file> 21023 </example> 21024 */ 21025 'number': numberInputType, 21026 21027 21028 /** 21029 * @ngdoc input 21030 * @name input[url] 21031 * 21032 * @description 21033 * Text input with URL validation. Sets the `url` validation error key if the content is not a 21034 * valid URL. 21035 * 21036 * <div class="alert alert-warning"> 21037 * **Note:** `input[url]` uses a regex to validate urls that is derived from the regex 21038 * used in Chromium. If you need stricter validation, you can use `ng-pattern` or modify 21039 * the built-in validators (see the {@link guide/forms Forms guide}) 21040 * </div> 21041 * 21042 * @param {string} ngModel Assignable angular expression to data-bind to. 21043 * @param {string=} name Property name of the form under which the control is published. 21044 * @param {string=} required Sets `required` validation error key if the value is not entered. 21045 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21046 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21047 * `required` when you want to data-bind to the `required` attribute. 21048 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21049 * minlength. 21050 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21051 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21052 * any length. 21053 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21054 * that contains the regular expression body that will be converted to a regular expression 21055 * as in the ngPattern directive. 21056 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21057 * a RegExp found by evaluating the Angular expression given in the attribute value. 21058 * If the expression evaluates to a RegExp object, then this is used directly. 21059 * If the expression evaluates to a string, then it will be converted to a RegExp 21060 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21061 * `new RegExp('^abc$')`.<br /> 21062 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21063 * start at the index of the last search's match, thus not taking the whole input value into 21064 * account. 21065 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21066 * interaction with the input element. 21067 * 21068 * @example 21069 <example name="url-input-directive" module="urlExample"> 21070 <file name="index.html"> 21071 <script> 21072 angular.module('urlExample', [])
21073 .controller('ExampleController', ['$scope', function($scope) { 21074 $scope.url = { 21075 text: 'http://google.com' 21076 }; 21077 }]); 21078 </script> 21079 <form name="myForm" ng-controller="ExampleController"> 21080 <label>URL: 21081 <input type="url" name="input" ng-model="url.text" required> 21082 <label> 21083 <div role="alert"> 21084 <span class="error" ng-show="myForm.input.$error.required"> 21085 Required!</span> 21086 <span class="error" ng-show="myForm.input.$error.url"> 21087 Not valid url!</span> 21088 </div> 21089 <tt>text = {{url.text}}</tt><br/> 21090 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21091 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21092 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21093 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21094 <tt>myForm.$error.url = {{!!myForm.$error.url}}</tt><br/> 21095 </form> 21096 </file> 21097 <file name="protractor.js" type="protractor"> 21098 var text = element(by.binding('url.text')); 21099 var valid = element(by.binding('myForm.input.$valid')); 21100 var input = element(by.model('url.text')); 21101 21102 it('should initialize to model', function() { 21103 expect(text.getText()).toContain('http://google.com'); 21104 expect(valid.getText()).toContain('true'); 21105 }); 21106 21107 it('should be invalid if empty', function() { 21108 input.clear(); 21109 input.sendKeys(''); 21110 21111 expect(text.getText()).toEqual('text ='); 21112 expect(valid.getText()).toContain('false'); 21113 }); 21114 21115 it('should be invalid if not url', function() { 21116 input.clear(); 21117 input.sendKeys('box'); 21118 21119 expect(valid.getText()).toContain('false'); 21120 }); 21121 </file> 21122 </example> 21123 */ 21124 'url': urlInputType, 21125 21126 21127 /** 21128 * @ngdoc input 21129 * @name input[email] 21130 * 21131 * @description 21132 * Text input with email validation. Sets the `email` validation error key if not a valid email 21133 * address. 21134 * 21135 * <div class="alert alert-warning"> 21136 * **Note:** `input[email]` uses a regex to validate email addresses that is derived from the regex 21137 * used in Chromium. If you need stricter validation (e.g. requiring a top-level domain), you can 21138 * use `ng-pattern` or modify the built-in validators (see the {@link guide/forms Forms guide}) 21139 * </div> 21140 * 21141 * @param {string} ngModel Assignable angular expression to data-bind to. 21142 * @param {string=} name Property name of the form under which the control is published. 21143 * @param {string=} required Sets `required` validation error key if the value is not entered. 21144 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21145 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21146 * `required` when you want to data-bind to the `required` attribute. 21147 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21148 * minlength. 21149 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21150 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of 21151 * any length. 21152 * @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string 21153 * that contains the regular expression body that will be converted to a regular expression 21154 * as in the ngPattern directive. 21155 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21156 * a RegExp found by evaluating the Angular expression given in the attribute value. 21157 * If the expression evaluates to a RegExp object, then this is used directly. 21158 * If the expression evaluates to a string, then it will be converted to a RegExp 21159 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21160 * `new RegExp('^abc$')`.<br /> 21161 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21162 * start at the index of the last search's match, thus not taking the whole input value into 21163 * account. 21164 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21165 * interaction with the input element. 21166 * 21167 * @example 21168 <example name="email-input-directive" module="emailExample"> 21169 <file name="index.html"> 21170 <script> 21171 angular.module('emailExample', [])
21172 .controller('ExampleController', ['$scope', function($scope) { 21173 $scope.email = { 21174 text: '[email protected]' 21175 }; 21176 }]); 21177 </script> 21178 <form name="myForm" ng-controller="ExampleController"> 21179 <label>Email: 21180 <input type="email" name="input" ng-model="email.text" required> 21181 </label> 21182 <div role="alert"> 21183 <span class="error" ng-show="myForm.input.$error.required"> 21184 Required!</span> 21185 <span class="error" ng-show="myForm.input.$error.email"> 21186 Not valid email!</span> 21187 </div> 21188 <tt>text = {{email.text}}</tt><br/> 21189 <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/> 21190 <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/> 21191 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21192 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21193 <tt>myForm.$error.email = {{!!myForm.$error.email}}</tt><br/> 21194 </form> 21195 </file> 21196 <file name="protractor.js" type="protractor"> 21197 var text = element(by.binding('email.text')); 21198 var valid = element(by.binding('myForm.input.$valid')); 21199 var input = element(by.model('email.text')); 21200 21201 it('should initialize to model', function() { 21202 expect(text.getText()).toContain('[email protected]'); 21203 expect(valid.getText()).toContain('true'); 21204 }); 21205 21206 it('should be invalid if empty', function() { 21207 input.clear(); 21208 input.sendKeys(''); 21209 expect(text.getText()).toEqual('text ='); 21210 expect(valid.getText()).toContain('false'); 21211 }); 21212 21213 it('should be invalid if not email', function() { 21214 input.clear(); 21215 input.sendKeys('xxx'); 21216 21217 expect(valid.getText()).toContain('false'); 21218 }); 21219 </file> 21220 </example> 21221 */ 21222 'email': emailInputType, 21223 21224 21225 /** 21226 * @ngdoc input 21227 * @name input[radio] 21228 * 21229 * @description 21230 * HTML radio button. 21231 * 21232 * @param {string} ngModel Assignable angular expression to data-bind to. 21233 * @param {string} value The value to which the expression should be set when selected. 21234 * @param {string=} name Property name of the form under which the control is published. 21235 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21236 * interaction with the input element. 21237 * @param {string} ngValue Angular expression which sets the value to which the expression should 21238 * be set when selected. 21239 * 21240 * @example 21241 <example name="radio-input-directive" module="radioExample"> 21242 <file name="index.html"> 21243 <script> 21244 angular.module('radioExample', []) 21245 .controller('ExampleController', ['$scope', function($scope) { 21246 $scope.color = { 21247 name: 'blue' 21248 }; 21249 $scope.specialValue = { 21250 "id": "12345", 21251 "value": "green" 21252 }; 21253 }]); 21254 </script>
21255 <form name="myForm" ng-controller="ExampleController"> 21256 <label> 21257 <input type="radio" ng-model="color.name" value="red"> 21258 Red 21259 </label><br/> 21260 <label> 21261 <input type="radio" ng-model="color.name" ng-value="specialValue"> 21262 Green 21263 </label><br/> 21264 <label> 21265 <input type="radio" ng-model="color.name" value="blue"> 21266 Blue 21267 </label><br/> 21268 <tt>color = {{color.name | json}}</tt><br/> 21269 </form> 21270 Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`. 21271 </file> 21272 <file name="protractor.js" type="protractor"> 21273 it('should change state', function() { 21274 var color = element(by.binding('color.name')); 21275 21276 expect(color.getText()).toContain('blue'); 21277 21278 element.all(by.model('color.name')).get(0).click(); 21279 21280 expect(color.getText()).toContain('red'); 21281 }); 21282 </file> 21283 </example> 21284 */ 21285 'radio': radioInputType, 21286 21287 21288 /** 21289 * @ngdoc input 21290 * @name input[checkbox] 21291 * 21292 * @description 21293 * HTML checkbox. 21294 * 21295 * @param {string} ngModel Assignable angular expression to data-bind to. 21296 * @param {string=} name Property name of the form under which the control is published. 21297 * @param {expression=} ngTrueValue The value to which the expression should be set when selected. 21298 * @param {expression=} ngFalseValue The value to which the expression should be set when not selected. 21299 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21300 * interaction with the input element. 21301 * 21302 * @example 21303 <example name="checkbox-input-directive" module="checkboxExample"> 21304 <file name="index.html"> 21305 <script> 21306 angular.module('checkboxExample', []) 21307 .controller('ExampleController', ['$scope', function($scope) {
21308 $scope.checkboxModel = { 21309 value1 : true, 21310 value2 : 'YES' 21311 }; 21312 }]); 21313 </script> 21314 <form name="myForm" ng-controller="ExampleController"> 21315 <label>Value1: 21316 <input type="checkbox" ng-model="checkboxModel.value1"> 21317 </label><br/> 21318 <label>Value2: 21319 <input type="checkbox" ng-model="checkboxModel.value2" 21320 ng-true-value="'YES'" ng-false-value="'NO'"> 21321 </label><br/> 21322 <tt>value1 = {{checkboxModel.value1}}</tt><br/> 21323 <tt>value2 = {{checkboxModel.value2}}</tt><br/> 21324 </form> 21325 </file> 21326 <file name="protractor.js" type="protractor"> 21327 it('should change state', function() { 21328 var value1 = element(by.binding('checkboxModel.value1')); 21329 var value2 = element(by.binding('checkboxModel.value2')); 21330 21331 expect(value1.getText()).toContain('true'); 21332 expect(value2.getText()).toContain('YES'); 21333 21334 element(by.model('checkboxModel.value1')).click(); 21335 element(by.model('checkboxModel.value2')).click(); 21336 21337 expect(value1.getText()).toContain('false'); 21338 expect(value2.getText()).toContain('NO'); 21339 }); 21340 </file> 21341 </example> 21342 */ 21343 'checkbox': checkboxInputType, 21344 21345 'hidden': noop, 21346 'button': noop, 21347 'submit': noop, 21348 'reset': noop, 21349 'file': noop 21350}; 21351 21352function stringBasedInputType(ctrl) { 21353 ctrl.$formatters.push(function(value) { 21354 return ctrl.$isEmpty(value) ? value : value.toString(); 21355 }); 21356} 21357 21358function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21359 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21360 stringBasedInputType(ctrl); 21361} 21362 21363function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21364 var type = lowercase(element[0].type); 21365 21366 // In composition mode, users are still inputing intermediate text buffer, 21367 // hold the listener until composition is done. 21368 // More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent 21369 if (!$sniffer.android) { 21370 var composing = false; 21371 21372 element.on('compositionstart', function(data) { 21373 composing = true; 21374 }); 21375 21376 element.on('compositionend', function() { 21377 composing = false; 21378 listener(); 21379 }); 21380 } 21381 21382 var listener = function(ev) { 21383 if (timeout) { 21384 $browser.defer.cancel(timeout); 21385 timeout = null; 21386 } 21387 if (composing) return; 21388 var value = element.val(), 21389 event = ev && ev.type; 21390 21391 // By default we will trim the value 21392 // If the attribute ng-trim exists we will avoid trimming 21393 // If input type is 'password', the value is never trimmed 21394 if (type !== 'password' && (!attr.ngTrim || attr.ngTrim !== 'false')) { 21395 value = trim(value); 21396 } 21397 21398 // If a control is suffering from bad input (due to native validators), browsers discard its 21399 // value, so it may be necessary to revalidate (by calling $setViewValue again) even if the 21400 // control's value is the same empty value twice in a row. 21401 if (ctrl.$viewValue !== value || (value === '' && ctrl.$$hasNativeValidators)) { 21402 ctrl.$setViewValue(value, event); 21403 } 21404 }; 21405 21406 // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the 21407 // input event on backspace, delete or cut 21408 if ($sniffer.hasEvent('input')) { 21409 element.on('input', listener); 21410 } else { 21411 var timeout; 21412 21413 var deferListener = function(ev, input, origValue) { 21414 if (!timeout) { 21415 timeout = $browser.defer(function() { 21416 timeout = null; 21417 if (!input || input.value !== origValue) { 21418 listener(ev); 21419 } 21420 }); 21421 } 21422 }; 21423 21424 element.on('keydown', function(event) { 21425 var key = event.keyCode; 21426 21427 // ignore 21428 // command modifiers arrows 21429 if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return; 21430 21431 deferListener(event, this, this.value); 21432 }); 21433 21434 // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it 21435 if ($sniffer.hasEvent('paste')) { 21436 element.on('paste cut', deferListener); 21437 } 21438 } 21439 21440 // if user paste into input using mouse on older browser 21441 // or form autocomplete on newer browser, we need "change" event to catch it 21442 element.on('change', listener); 21443 21444 ctrl.$render = function() { 21445 element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); 21446 }; 21447} 21448 21449function weekParser(isoWeek, existingDate) { 21450 if (isDate(isoWeek)) { 21451 return isoWeek; 21452 } 21453 21454 if (isString(isoWeek)) { 21455 WEEK_REGEXP.lastIndex = 0; 21456 var parts = WEEK_REGEXP.exec(isoWeek); 21457 if (parts) { 21458 var year = +parts[1], 21459 week = +parts[2], 21460 hours = 0, 21461 minutes = 0, 21462 seconds = 0, 21463 milliseconds = 0, 21464 firstThurs = getFirstThursdayOfYear(year), 21465 addDays = (week - 1) * 7; 21466 21467 if (existingDate) { 21468 hours = existingDate.getHours(); 21469 minutes = existingDate.getMinutes(); 21470 seconds = existingDate.getSeconds(); 21471 milliseconds = existingDate.getMilliseconds(); 21472 } 21473 21474 return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); 21475 } 21476 } 21477 21478 return NaN; 21479} 21480 21481function createDateParser(regexp, mapping) { 21482 return function(iso, date) { 21483 var parts, map; 21484 21485 if (isDate(iso)) { 21486 return iso; 21487 } 21488 21489 if (isString(iso)) { 21490 // When a date is JSON'ified to wraps itself inside of an extra 21491 // set of double quotes. This makes the date parsing code unable 21492 // to match the date string and parse it as a date. 21493 if (iso.charAt(0) == '"' && iso.charAt(iso.length - 1) == '"') { 21494 iso = iso.substring(1, iso.length - 1); 21495 }
21496 if (ISO_DATE_REGEXP.test(iso)) { 21497 return new Date(iso); 21498 } 21499 regexp.lastIndex = 0; 21500 parts = regexp.exec(iso); 21501 21502 if (parts) { 21503 parts.shift(); 21504 if (date) { 21505 map = { 21506 yyyy: date.getFullYear(), 21507 MM: date.getMonth() + 1, 21508 dd: date.getDate(), 21509 HH: date.getHours(), 21510 mm: date.getMinutes(), 21511 ss: date.getSeconds(), 21512 sss: date.getMilliseconds() / 1000 21513 }; 21514 } else { 21515 map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; 21516 } 21517 21518 forEach(parts, function(part, index) { 21519 if (index < mapping.length) { 21520 map[mapping[index]] = +part; 21521 } 21522 }); 21523 return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss * 1000 || 0); 21524 } 21525 } 21526 21527 return NaN; 21528 }; 21529} 21530 21531function createDateInputType(type, regexp, parseDate, format) { 21532 return function dynamicDateInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter) { 21533 badInputChecker(scope, element, attr, ctrl); 21534 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21535 var timezone = ctrl && ctrl.$options && ctrl.$options.timezone; 21536 var previousDate; 21537 21538 ctrl.$$parserName = type; 21539 ctrl.$parsers.push(function(value) { 21540 if (ctrl.$isEmpty(value)) return null; 21541 if (regexp.test(value)) { 21542 // Note: We cannot read ctrl.$modelValue, as there might be a different 21543 // parser/formatter in the processing chain so that the model 21544 // contains some different data format! 21545 var parsedDate = parseDate(value, previousDate); 21546 if (timezone) { 21547 parsedDate = convertTimezoneToLocal(parsedDate, timezone); 21548 } 21549 return parsedDate; 21550 } 21551 return undefined; 21552 }); 21553 21554 ctrl.$formatters.push(function(value) { 21555 if (value && !isDate(value)) { 21556 throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value); 21557 } 21558 if (isValidDate(value)) { 21559 previousDate = value; 21560 if (previousDate && timezone) { 21561 previousDate = convertTimezoneToLocal(previousDate, timezone, true); 21562 } 21563 return $filter('date')(value, format, timezone); 21564 } else { 21565 previousDate = null; 21566 return ''; 21567 } 21568 }); 21569 21570 if (isDefined(attr.min) || attr.ngMin) { 21571 var minVal; 21572 ctrl.$validators.min = function(value) { 21573 return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal; 21574 }; 21575 attr.$observe('min', function(val) { 21576 minVal = parseObservedDateValue(val); 21577 ctrl.$validate(); 21578 }); 21579 } 21580 21581 if (isDefined(attr.max) || attr.ngMax) { 21582 var maxVal; 21583 ctrl.$validators.max = function(value) { 21584 return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal; 21585 }; 21586 attr.$observe('max', function(val) { 21587 maxVal = parseObservedDateValue(val); 21588 ctrl.$validate(); 21589 }); 21590 } 21591 21592 function isValidDate(value) { 21593 // Invalid Date: getTime() returns NaN 21594 return value && !(value.getTime && value.getTime() !== value.getTime()); 21595 } 21596 21597 function parseObservedDateValue(val) { 21598 return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined; 21599 } 21600 }; 21601} 21602 21603function badInputChecker(scope, element, attr, ctrl) { 21604 var node = element[0]; 21605 var nativeValidation = ctrl.$$hasNativeValidators = isObject(node.validity); 21606 if (nativeValidation) { 21607 ctrl.$parsers.push(function(value) { 21608 var validity = element.prop(VALIDITY_STATE_PROPERTY) || {}; 21609 // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430): 21610 // - also sets validity.badInput (should only be validity.typeMismatch). 21611 // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email) 21612 // - can ignore this case as we can still read out the erroneous email... 21613 return validity.badInput && !validity.typeMismatch ? undefined : value; 21614 }); 21615 } 21616} 21617 21618function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21619 badInputChecker(scope, element, attr, ctrl); 21620 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21621 21622 ctrl.$$parserName = 'number'; 21623 ctrl.$parsers.push(function(value) { 21624 if (ctrl.$isEmpty(value)) return null; 21625 if (NUMBER_REGEXP.test(value)) return parseFloat(value); 21626 return undefined; 21627 }); 21628 21629 ctrl.$formatters.push(function(value) { 21630 if (!ctrl.$isEmpty(value)) { 21631 if (!isNumber(value)) { 21632 throw $ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value); 21633 } 21634 value = value.toString(); 21635 } 21636 return value; 21637 }); 21638 21639 if (isDefined(attr.min) || attr.ngMin) { 21640 var minVal; 21641 ctrl.$validators.min = function(value) { 21642 return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; 21643 }; 21644 21645 attr.$observe('min', function(val) { 21646 if (isDefined(val) && !isNumber(val)) { 21647 val = parseFloat(val, 10); 21648 } 21649 minVal = isNumber(val) && !isNaN(val) ? val : undefined; 21650 // TODO(matsko): implement validateLater to reduce number of validations 21651 ctrl.$validate(); 21652 }); 21653 } 21654 21655 if (isDefined(attr.max) || attr.ngMax) { 21656 var maxVal; 21657 ctrl.$validators.max = function(value) { 21658 return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; 21659 }; 21660 21661 attr.$observe('max', function(val) { 21662 if (isDefined(val) && !isNumber(val)) { 21663 val = parseFloat(val, 10); 21664 } 21665 maxVal = isNumber(val) && !isNaN(val) ? val : undefined; 21666 // TODO(matsko): implement validateLater to reduce number of validations 21667 ctrl.$validate(); 21668 }); 21669 } 21670} 21671 21672function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21673 // Note: no badInputChecker here by purpose as `url` is only a validation 21674 // in browsers, i.e. we can always read out input.value even if it is not val
21674id! 21675 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21676 stringBasedInputType(ctrl); 21677 21678 ctrl.$$parserName = 'url'; 21679 ctrl.$validators.url = function(modelValue, viewValue) { 21680 var value = modelValue || viewValue; 21681 return ctrl.$isEmpty(value) || URL_REGEXP.test(value); 21682 }; 21683} 21684 21685function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { 21686 // Note: no badInputChecker here by purpose as `url` is only a validation 21687 // in browsers, i.e. we can always read out input.value even if it is not valid! 21688 baseInputType(scope, element, attr, ctrl, $sniffer, $browser); 21689 stringBasedInputType(ctrl); 21690 21691 ctrl.$$parserName = 'email'; 21692 ctrl.$validators.email = function(modelValue, viewValue) { 21693 var value = modelValue || viewValue; 21694 return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); 21695 }; 21696} 21697 21698function radioInputType(scope, element, attr, ctrl) { 21699 // make the name unique, if not defined 21700 if (isUndefined(attr.name)) { 21701 element.attr('name', nextUid()); 21702 } 21703 21704 var listener = function(ev) { 21705 if (element[0].checked) { 21706 ctrl.$setViewValue(attr.value, ev && ev.type); 21707 } 21708 }; 21709 21710 element.on('click', listener); 21711 21712 ctrl.$render = function() { 21713 var value = attr.value; 21714 element[0].checked = (value == ctrl.$viewValue); 21715 }; 21716 21717 attr.$observe('value', ctrl.$render); 21718} 21719 21720function parseConstantExpr($parse, context, name, expression, fallback) { 21721 var parseFn; 21722 if (isDefined(expression)) { 21723 parseFn = $parse(expression); 21724 if (!parseFn.constant) { 21725 throw minErr('ngModel')('constexpr', 'Expected constant expression for `{0}`, but saw ' + 21726 '`{1}`.', name, expression); 21727 } 21728 return parseFn(context); 21729 } 21730 return fallback; 21731} 21732 21733function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filter, $parse) { 21734 var trueValue = parseConstantExpr($parse, scope, 'ngTrueValue', attr.ngTrueValue, true); 21735 var falseValue = parseConstantExpr($parse, scope, 'ngFalseValue', attr.ngFalseValue, false); 21736 21737 var listener = function(ev) { 21738 ctrl.$setViewValue(element[0].checked, ev && ev.type); 21739 }; 21740 21741 element.on('click', listener); 21742 21743 ctrl.$render = function() { 21744 element[0].checked = ctrl.$viewValue; 21745 }; 21746 21747 // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false` 21748 // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert 21749 // it to a boolean. 21750 ctrl.$isEmpty = function(value) { 21751 return value === false; 21752 }; 21753 21754 ctrl.$formatters.push(function(value) { 21755 return equals(value, trueValue); 21756 }); 21757 21758 ctrl.$parsers.push(function(value) { 21759 return value ? trueValue : falseValue; 21760 }); 21761} 21762 21763 21764/** 21765 * @ngdoc directive 21766 * @name textarea 21767 * @restrict E 21768 * 21769 * @description 21770 * HTML textarea element control with angular data-binding. The data-binding and validation 21771 * properties of this element are exactly the same as those of the 21772 * {@link ng.directive:input input element}. 21773 * 21774 * @param {string} ngModel Assignable angular expression to data-bind to. 21775 * @param {string=} name Property name of the form under which the control is published. 21776 * @param {string=} required Sets `required` validation error key if the value is not entered. 21777 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 21778 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 21779 * `required` when you want to data-bind to the `required` attribute. 21780 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21781 * minlength. 21782 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21783 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 21784 * length. 21785 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21786 * a RegExp found by evaluating the Angular expression given in the attribute value. 21787 * If the expression evaluates to a RegExp object, then this is used directly. 21788 * If the expression evaluates to a string, then it will be converted to a RegExp 21789 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21790 * `new RegExp('^abc$')`.<br /> 21791 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21792 * start at the index of the last search's match, thus not taking the whole input value into 21793 * account. 21794 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21795 * interaction with the input element. 21796 * @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input. 21797 */ 21798 21799 21800/** 21801 * @ngdoc directive 21802 * @name input 21803 * @restrict E 21804 * 21805 * @description 21806 * HTML input element control. When used together with {@link ngModel `ngModel`}, it provides data-binding, 21807 * input state control, and validation. 21808 * Input control follows HTML5 input types and polyfills the HTML5 validation behavior for older browsers. 21809 * 21810 * <div class="alert alert-warning"> 21811 * **Note:** Not every feature offered is available for all input types. 21812 * Specifically, data binding and event handling via `ng-model` is unsupported for `input[file]`. 21813 * </div> 21814 * 21815 * @param {string} ngModel Assignable angular expression to data-bind to. 21816 * @param {string=} name Property name of the form under which the control is published. 21817 * @param {string=} required Sets `required` validation error key if the value is not entered. 21818 * @param {boolean=} ngRequired Sets `required` attribute if set to true 21819 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than 21820 * minlength. 21821 * @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than 21822 * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any 21823 * length. 21824 * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match 21825 * a RegExp found by evaluating the Angular expression given in the attribute value. 21826 * If the expression evaluates to a RegExp object, then this is used directly. 21827 * If the expression evaluates to a string, then it will be converted to a RegExp 21828 * after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to 21829 * `new RegExp('^abc$')`.<br /> 21830 * **Note:** Avoid using the `g` flag on the RegExp, as it will cause each successive search to 21831 * start at the index of the last search's match, thus not taking the whole input value into 21832 * account. 21833 * @param {string=} ngChange Angular expression to be executed when input changes due to user 21834 * interaction with the input element. 21835 * @param {boolean=} [ngTrim=true] If set to false Angular
vendor: 4,842 bytes, lines 21835-21953
21835 will not automatically trim the input. 21836 * This parameter is ignored for input[type=password] controls, which will never trim the 21837 * input. 21838 * 21839 * @example 21840 <example name="input-directive" module="inputExample"> 21841 <file name="index.html"> 21842 <script> 21843 angular.module('inputExample', []) 21844 .controller('ExampleController', ['$scope', function($scope) { 21845 $scope.user = {name: 'guest', last: 'visitor'}; 21846 }]); 21847 </script> 21848 <div ng-controller="ExampleController"> 21849 <form name="myForm"> 21850 <label> 21851 User name: 21852 <input type="text" name="userName" ng-model="user.name" required> 21853 </label> 21854 <div role="alert"> 21855 <span class="error" ng-show="myForm.userName.$error.required"> 21856 Required!</span> 21857 </div> 21858 <label> 21859 Last name: 21860 <input type="text" name="lastName" ng-model="user.last" 21861 ng-minlength="3" ng-maxlength="10"> 21862 </label> 21863 <div role="alert"> 21864 <span class="error" ng-show="myForm.lastName.$error.minlength"> 21865 Too short!</span> 21866 <span class="error" ng-show="myForm.lastName.$error.maxlength"> 21867 Too long!</span> 21868 </div> 21869 </form> 21870 <hr> 21871 <tt>user = {{user}}</tt><br/> 21872 <tt>myForm.userName.$valid = {{myForm.userName.$valid}}</tt><br/> 21873 <tt>myForm.userName.$error = {{myForm.userName.$error}}</tt><br/> 21874 <tt>myForm.lastName.$valid = {{myForm.lastName.$valid}}</tt><br/> 21875 <tt>myForm.lastName.$error = {{myForm.lastName.$error}}</tt><br/> 21876 <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 21877 <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 21878 <tt>myForm.$error.minlength = {{!!myForm.$error.minlength}}</tt><br/> 21879 <tt>myForm.$error.maxlength = {{!!myForm.$error.maxlength}}</tt><br/> 21880 </div> 21881 </file> 21882 <file name="protractor.js" type="protractor"> 21883 var user = element(by.exactBinding('user')); 21884 var userNameValid = element(by.binding('myForm.userName.$valid')); 21885 var lastNameValid = element(by.binding('myForm.lastName.$valid')); 21886 var lastNameError = element(by.binding('myForm.lastName.$error')); 21887 var formValid = element(by.binding('myForm.$valid')); 21888 var userNameInput = element(by.model('user.name')); 21889 var userLastInput = element(by.model('user.last')); 21890 21891 it('should initialize to model', function() { 21892 expect(user.getText()).toContain('{"name":"guest","last":"visitor"}'); 21893 expect(userNameValid.getText()).toContain('true'); 21894 expect(formValid.getText()).toContain('true'); 21895 }); 21896 21897 it('should be invalid if empty when required', function() { 21898 userNameInput.clear(); 21899 userNameInput.sendKeys(''); 21900 21901 expect(user.getText()).toContain('{"last":"visitor"}'); 21902 expect(userNameValid.getText()).toContain('false'); 21903 expect(formValid.getText()).toContain('false'); 21904 }); 21905 21906 it('should be valid if empty when min length is set', function() { 21907 userLastInput.clear(); 21908 userLastInput.sendKeys(''); 21909 21910 expect(user.getText()).toContain('{"name":"guest","last":""}'); 21911 expect(lastNameValid.getText()).toContain('true'); 21912 expect(formValid.getText()).toContain('true'); 21913 }); 21914 21915 it('should be invalid if less than required min length', function() { 21916 userLastInput.clear(); 21917 userLastInput.sendKeys('xx'); 21918 21919 expect(user.getText()).toContain('{"name":"guest"}'); 21920 expect(lastNameValid.getText()).toContain('false'); 21921 expect(lastNameError.getText()).toContain('minlength'); 21922 expect(formValid.getText()).toContain('false'); 21923 }); 21924 21925 it('should be invalid if longer than max length', function() { 21926 userLastInput.clear(); 21927 userLastInput.sendKeys('some ridiculously long name'); 21928 21929 expect(user.getText()).toContain('{"name":"guest"}'); 21930 expect(lastNameValid.getText()).toContain('false'); 21931 expect(lastNameError.getText()).toContain('maxlength'); 21932 expect(formValid.getText()).toContain('false'); 21933 }); 21934 </file> 21935 </example> 21936 */ 21937var inputDirective = ['$browser', '$sniffer', '$filter', '$parse', 21938 function($browser, $sniffer, $filter, $parse) { 21939 return { 21940 restrict: 'E', 21941 require: ['?ngModel'], 21942 link: { 21943 pre: function(scope, element, attr, ctrls) { 21944 if (ctrls[0]) { 21945 (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrls[0], $sniffer, 21946 $browser, $filter, $parse); 21947 } 21948 } 21949 } 21950 }; 21951}]; 21952 21953
21954 21955var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/; 21956/** 21957 * @ngdoc directive 21958 * @name ngValue 21959 * 21960 * @description 21961 * Binds the given expression to the value of `<option>` or {@link input[radio] `input[radio]`}, 21962 * so that when the element is selected, the {@link ngModel `ngModel`} of that element is set to 21963 * the bound value. 21964 * 21965 * `ngValue` is useful when dynamically generating lists of radio buttons using 21966 * {@link ngRepeat `ngRepeat`}, as shown below. 21967 * 21968 * Likewise, `ngValue` can be used to generate `<option>` elements for 21969 * the {@link select `select`} element. In that case however, only strings are supported 21970 * for the `value `attribute, so the resulting `ngModel` will always be a string. 21971 * Support for `select` models with non-string values is available via `ngOptions`. 21972 * 21973 * @element input 21974 * @param {string=} ngValue angular expression, whose value will be bound to the `value` attribute 21975 * of the `input` element 21976 * 21977 * @example 21978 <example name="ngValue-directive" module="valueExample"> 21979 <file name="index.html"> 21980 <script> 21981 angular.module('valueExample', []) 21982 .controller('ExampleController', ['$scope', function($scope) { 21983 $scope.names = ['pizza', 'unicorns', 'robots']; 21984 $scope.my = { favorite: 'unicorns' }; 21985 }]); 21986 </script>
21987 <form ng-controller="ExampleController"> 21988 <h2>Which is your favorite?</h2> 21989 <label ng-repeat="name in names" for="{{name}}"> 21990 {{name}} 21991 <input type="radio" 21992 ng-model="my.favorite" 21993 ng-value="name" 21994 id="{{name}}" 21995 name="favorite"> 21996 </label> 21997 <div>You chose {{my.favorite}}</div> 21998 </form> 21999 </file> 22000 <file name="protractor.js" type="protractor"> 22001 var favorite = element(by.binding('my.favorite')); 22002 22003 it('should initialize to model', function() { 22004 expect(favorite.getText()).toContain('unicorns'); 22005 }); 22006 it('should bind the values to the inputs', function() { 22007 element.all(by.model('my.favorite')).get(0).click(); 22008 expect(favorite.getText()).toContain('pizza'); 22009 }); 22010 </file> 22011 </example> 22012 */ 22013var ngValueDirective = function() { 22014 return { 22015 restrict: 'A', 22016 priority: 100, 22017 compile: function(tpl, tplAttr) { 22018 if (CONSTANT_VALUE_REGEXP.test(tplAttr.ngValue)) { 22019 return function ngValueConstantLink(scope, elm, attr) { 22020 attr.$set('value', scope.$eval(attr.ngValue)); 22021 }; 22022 } else { 22023 return function ngValueLink(scope, elm, attr) { 22024 scope.$watch(attr.ngValue, function valueWatchAction(value) { 22025 attr.$set('value', value); 22026 }); 22027 }; 22028 } 22029 } 22030 }; 22031}; 22032 22033/** 22034 * @ngdoc directive 22035 * @name ngBind 22036 * @restrict AC 22037 * 22038 * @description 22039 * The `ngBind` attribute tells Angular to replace the text content of the specified HTML element 22040 * with the value of a given expression, and to update the text content when the value of that 22041 * expression changes. 22042 * 22043 * Typically, you don't use `ngBind` directly, but instead you use the double curly markup like 22044 * `{{ expression }}` which is similar but less verbose. 22045 * 22046 * It is preferable to use `ngBind` instead of `{{ expression }}` if a template is momentarily 22047 * displayed by the browser in its raw state before Angular compiles it. Since `ngBind` is an 22048 * element attribute, it makes the bindings invisible to the user while the page is loading. 22049 * 22050 * An alternative solution to this problem would be using the 22051 * {@link ng.directive:ngCloak ngCloak} directive. 22052 * 22053 * 22054 * @element ANY 22055 * @param {expression} ngBind {@link guide/expression Expression} to evaluate. 22056 * 22057 * @example 22058 * Enter a name in the Live Preview text box; the greeting below the text box changes instantly. 22059 <example module="bindExample"> 22060 <file name="index.html"> 22061 <script> 22062 angular.module('bindExample', []) 22063 .controller('ExampleController', ['$scope', function($scope) { 22064 $scope.name = 'Whirled'; 22065 }]); 22066 </script>
22067 <div ng-controller="ExampleController"> 22068 <label>Enter name: <input type="text" ng-model="name"></label><br> 22069 Hello <span ng-bind="name"></span>! 22070 </div> 22071 </file> 22072 <file name="protractor.js" type="protractor"> 22073 it('should check ng-bind', function() { 22074 var nameInput = element(by.model('name')); 22075 22076 expect(element(by.binding('name')).getText()).toBe('Whirled'); 22077 nameInput.clear(); 22078 nameInput.sendKeys('world'); 22079 expect(element(by.binding('name')).getText()).toBe('world'); 22080 }); 22081 </file> 22082 </example> 22083 */ 22084var ngBindDirective = ['$compile', function($compile) { 22085 return { 22086 restrict: 'AC', 22087 compile: function ngBindCompile(templateElement) { 22088 $compile.$$addBindingClass(templateElement); 22089 return function ngBindLink(scope, element, attr) { 22090 $compile.$$addBindingInfo(element, attr.ngBind); 22091 element = element[0]; 22092 scope.$watch(attr.ngBind, function ngBindWatchAction(value) { 22093 element.textContent = value === undefined ? '' : value; 22094 }); 22095 }; 22096 } 22097 }; 22098}]; 22099 22100 22101/** 22102 * @ngdoc directive 22103 * @name ngBindTemplate 22104 * 22105 * @description 22106 * The `ngBindTemplate` directive specifies that the element 22107 * text content should be replaced with the interpolation of the template 22108 * in the `ngBindTemplate` attribute. 22109 * Unlike `ngBind`, the `ngBindTemplate` can contain multiple `{{` `}}` 22110 * expressions. This directive is needed since some HTML elements 22111 * (such as TITLE and OPTION) cannot contain SPAN elements. 22112 * 22113 * @element ANY 22114 * @param {string} ngBindTemplate template of form 22115 * <tt>{{</tt> <tt>expression</tt> <tt>}}</tt> to eval. 22116 * 22117 * @example 22118 * Try it here: enter text in text box and watch the greeting change. 22119 <example module="bindExample"> 22120 <file name="index.html"> 22121 <script> 22122 angular.module('bindExample', []) 22123 .controller('ExampleController', ['$scope', function($scope) { 22124 $scope.salutation = 'Hello'; 22125 $scope.name = 'World'; 22126 }]); 22127 </script>
22128 <div ng-controller="ExampleController"> 22129 <label>Salutation: <input type="text" ng-model="salutation"></label><br> 22130 <label>Name: <input type="text" ng-model="name"></label><br> 22131 <pre ng-bind-template="{{salutation}} {{name}}!"></pre> 22132 </div> 22133 </file> 22134 <file name="protractor.js" type="protractor"> 22135 it('should check ng-bind', function() { 22136 var salutationElem = element(by.binding('salutation')); 22137 var salutationInput = element(by.model('salutation')); 22138 var nameInput = element(by.model('name')); 22139 22140 expect(salutationElem.getText()).toBe('Hello World!'); 22141 22142 salutationInput.clear(); 22143 salutationInput.sendKeys('Greetings'); 22144 nameInput.clear(); 22145 nameInput.sendKeys('user'); 22146 22147 expect(salutationElem.getText()).toBe('Greetings user!'); 22148 }); 22149 </file> 22150 </example> 22151 */ 22152var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate, $compile) { 22153 return { 22154 compile: function ngBindTemplateCompile(templateElement) { 22155 $compile.$$addBindingClass(templateElement); 22156 return function ngBindTemplateLink(scope, element, attr) { 22157 var interpolateFn = $interpolate(element.attr(attr.$attr.ngBindTemplate)); 22158 $compile.$$addBindingInfo(element, interpolateFn.expressions); 22159 element = element[0]; 22160 attr.$observe('ngBindTemplate', function(value) { 22161 element.textContent = value === undefined ? '' : value; 22162 }); 22163 }; 22164 } 22165 }; 22166}]; 22167 22168 22169/** 22170 * @ngdoc directive 22171 * @name ngBindHtml 22172 * 22173 * @description 22174 * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default, 22175 * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service. 22176 * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link 22177 * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize} 22178 * in your module's dependencies, you need to include "angular-sanitize.js" in your application. 22179 * 22180 * You may also bypass sanitization for values you know are safe. To do so, bind to 22181 * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example 22182 * under {@link ng.$sce#show-me-an-example-using-sce- Strict Contextual Escaping (SCE)}. 22183 * 22184 * Note: If a `$sanitize` service is unavailable and the bound value isn't explicitly trusted, you 22185 * will have an exception (instead of an exploit.) 22186 * 22187 * @element ANY 22188 * @param {expression} ngBindHtml {@link guide/expression Expression} to evaluate. 22189 * 22190 * @example 22191 22192 <example module="bindHtmlExample" deps="angular-sanitize.js"> 22193 <file name="index.html">
22194 <div ng-controller="ExampleController"> 22195 <p ng-bind-html="myHTML"></p> 22196 </div> 22197 </file> 22198 22199 <file name="script.js"> 22200 angular.module('bindHtmlExample', ['ngSanitize']) 22201 .controller('ExampleController', ['$scope', function($scope) { 22202 $scope.myHTML = 22203 'I am an <code>HTML</code>string with ' + 22204 '<a href="#">links!</a> and other <em>stuff</em>'; 22205 }]); 22206 </file> 22207 22208 <file name="protractor.js" type="protractor"> 22209 it('should check ng-bind-html', function() { 22210 expect(element(by.binding('myHTML')).getText()).toBe( 22211 'I am an HTMLstring with links! and other stuff'); 22212 }); 22213 </file> 22214 </example> 22215 */ 22216var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse, $compile) { 22217 return { 22218 restrict: 'A', 22219 compile: function ngBindHtmlCompile(tElement, tAttrs) { 22220 var ngBindHtmlGetter = $parse(tAttrs.ngBindHtml); 22221 var ngBindHtmlWatch = $parse(tAttrs.ngBindHtml, function getStringValue(value) { 22222 return (value || '').toString(); 22223 }); 22224 $compile.$$addBindingClass(tElement); 22225 22226 return function ngBindHtmlLink(scope, element, attr) { 22227 $compile.$$addBindingInfo(element, attr.ngBindHtml); 22228 22229 scope.$watch(ngBindHtmlWatch, function ngBindHtmlWatchAction() { 22230 // we re-evaluate the expr because we want a TrustedValueHolderType 22231 // for $sce, not a string 22232 element.html($sce.getTrustedHtml(ngBindHtmlGetter(scope)) || ''); 22233 }); 22234 }; 22235 } 22236 }; 22237}]; 22238 22239/** 22240 * @ngdoc directive 22241 * @name ngChange 22242 * 22243 * @description 22244 * Evaluate the given expression when the user changes the input. 22245 * The expression is evaluated immediately, unlike the JavaScript onchange event 22246 * which only triggers at the end of a change (usually, when the user leaves the 22247 * form element or presses the return key). 22248 * 22249 * The `ngChange` expression is only evaluated when a change in the input value causes 22250 * a new value to be committed to the model. 22251 * 22252 * It will not be evaluated: 22253 * * if the value returned from the `$parsers` transformation pipeline has not changed 22254 * * if the input has continued to be invalid since the model will stay `null` 22255 * * if the model is changed programmatically and not by a change to the input value 22256 * 22257 * 22258 * Note, this directive requires `ngModel` to be present. 22259 * 22260 * @element input 22261 * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change 22262 * in input value. 22263 * 22264 * @example 22265 * <example name="ngChange-directive" module="changeExample"> 22266 * <file name="index.html"> 22267 * <script> 22268 * angular.module('changeExample', []) 22269 * .controller('ExampleController', ['$scope', function($scope) { 22270 * $scope.counter = 0; 22271 * $scope.change = function() { 22272 * $scope.counter++; 22273 * }; 22274 * }]); 22275 * </script>
22276 * <div ng-controller="ExampleController"> 22277 * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" /> 22278 * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" /> 22279 * <label for="ng-change-example2">Confirmed</label><br /> 22280 * <tt>debug = {{confirmed}}</tt><br/> 22281 * <tt>counter = {{counter}}</tt><br/> 22282 * </div> 22283 * </file> 22284 * <file name="protractor.js" type="protractor"> 22285 * var counter = element(by.binding('counter')); 22286 * var debug = element(by.binding('confirmed')); 22287 * 22288 * it('should evaluate the expression if changing from view', function() { 22289 * expect(counter.getText()).toContain('0'); 22290 * 22291 * element(by.id('ng-change-example1')).click(); 22292 * 22293 * expect(counter.getText()).toContain('1'); 22294 * expect(debug.getText()).toContain('true'); 22295 * }); 22296 * 22297 * it('should not evaluate the expression if changing from model', function() { 22298 * element(by.id('ng-change-example2')).click(); 22299 22300 * expect(counter.getText()).toContain('0'); 22301 * expect(debug.getText()).toContain('true'); 22302 * }); 22303 * </file> 22304 * </example> 22305 */ 22306var ngChangeDirective = valueFn({ 22307 restrict: 'A', 22308 require: 'ngModel', 22309 link: function(scope, element, attr, ctrl) { 22310 ctrl.$viewChangeListeners.push(function() { 22311 scope.$eval(attr.ngChange); 22312 }); 22313 } 22314}); 22315 22316function classDirective(name, selector) { 22317 name = 'ngClass' + name; 22318 return ['$animate', function($animate) { 22319 return { 22320 restrict: 'AC', 22321 link: function(scope, element, attr) { 22322 var oldVal; 22323 22324 scope.$watch(attr[name], ngClassWatchAction, true); 22325 22326 attr.$observe('class', function(value) { 22327 ngClassWatchAction(scope.$eval(attr[name])); 22328 }); 22329 22330 22331 if (name !== 'ngClass') { 22332 scope.$watch('$index', function($index, old$index) { 22333 // jshint bitwise: false 22334 var mod = $index & 1; 22335 if (mod !== (old$index & 1)) { 22336 var classes = arrayClasses(scope.$eval(attr[name])); 22337 mod === selector ? 22338 addClasses(classes) : 22339 removeClasses(classes); 22340 } 22341 }); 22342 } 22343 22344 function addClasses(classes) { 22345 var newClasses = digestClassCounts(classes, 1); 22346 attr.$addClass(newClasses); 22347 } 22348 22349 function removeClasses(classes) { 22350 var newClasses = digestClassCounts(classes, -1); 22351 attr.$removeClass(newClasses); 22352 } 22353 22354 function digestClassCounts(classes, count) { 22355 // Use createMap() to prevent class assumptions involving property 22356 // names in Object.prototype 22357 var classCounts = element.data('$classCounts') || createMap(); 22358 var classesToUpdate = []; 22359 forEach(classes, function(className) { 22360 if (count > 0 || classCounts[className]) { 22361 classCounts[className] = (classCounts[className] || 0) + count; 22362 if (classCounts[className] === +(count > 0)) { 22363 classesToUpdate.push(className); 22364 } 22365 } 22366 }); 22367 element.data('$classCounts', classCounts); 22368 return classesToUpdate.join(' '); 22369 } 22370 22371 function updateClasses(oldClasses, newClasses) { 22372 var toAdd = arrayDifference(newClasses, oldClasses); 22373 var toRemove = arrayDifference(oldClasses, newClasses); 22374 toAdd = digestClassCounts(toAdd, 1); 22375 toRemove = digestClassCounts(toRemove, -1); 22376 if (toAdd && toAdd.length) { 22377 $animate.addClass(element, toAdd); 22378 } 22379 if (toRemove && toRemove.length) { 22380 $animate.removeClass(element, toRemove); 22381 } 22382 } 22383 22384 function ngClassWatchAction(newVal) { 22385 if (selector === true || scope.$index % 2 === selector) { 22386 var newClasses = arrayClasses(newVal || []); 22387 if (!oldVal) { 22388 addClasses(newClasses); 22389 } else if (!equals(newVal,oldVal)) { 22390 var oldClasses = arrayClasses(oldVal);
22391 updateClasses(oldClasses, newClasses); 22392 } 22393 } 22394 oldVal = shallowCopy(newVal); 22395 } 22396 } 22397 }; 22398 22399 function arrayDifference(tokens1, tokens2) { 22400 var values = []; 22401 22402 outer: 22403 for (var i = 0; i < tokens1.length; i++) { 22404 var token = tokens1[i]; 22405 for (var j = 0; j < tokens2.length; j++) { 22406 if (token == tokens2[j]) continue outer; 22407 } 22408 values.push(token); 22409 } 22410 return values; 22411 } 22412 22413 function arrayClasses(classVal) { 22414 var classes = []; 22415 if (isArray(classVal)) { 22416 forEach(classVal, function(v) { 22417 classes = classes.concat(arrayClasses(v)); 22418 }); 22419 return classes; 22420 } else if (isString(classVal)) { 22421 return classVal.split(' '); 22422 } else if (isObject(classVal)) { 22423 forEach(classVal, function(v, k) { 22424 if (v) { 22425 classes = classes.concat(k.split(' ')); 22426 } 22427 }); 22428 return classes; 22429 } 22430 return classVal; 22431 } 22432 }]; 22433} 22434 22435/** 22436 * @ngdoc directive 22437 * @name ngClass 22438 * @restrict AC 22439 * 22440 * @description 22441 * The `ngClass` directive allows you to dynamically set CSS classes on an HTML element by databinding 22442 * an expression that represents all classes to be added. 22443 * 22444 * The directive operates in three different ways, depending on which of three types the expression 22445 * evaluates to: 22446 * 22447 * 1. If the expression evaluates to a string, the string should be one or more space-delimited class 22448 * names. 22449 * 22450 * 2. If the expression evaluates to an object, then for each key-value pair of the 22451 * object with a truthy value the corresponding key is used as a class name. 22452 * 22453 * 3. If the expression evaluates to an array, each element of the array should either be a string as in 22454 * type 1 or an object as in type 2. This means that you can mix strings and objects together in an array 22455 * to give you more control over what CSS classes appear. See the code below for an example of this. 22456 * 22457 * 22458 * The directive won't add duplicate classes if a particular class was already set. 22459 * 22460 * When the expression changes, the previously added classes are removed and only then are the 22461 * new classes added. 22462 * 22463 * @animations 22464 * **add** - happens just before the class is applied to the elements 22465 * 22466 * **remove** - happens just before the class is removed from the element 22467 * 22468 * @element ANY 22469 * @param {expression} ngClass {@link guide/expression Expression} to eval. The result 22470 * of the evaluation can be a string representing space delimited class 22471 * names, an array, or a map of class names to boolean values. In the case of a map, the 22472 * names of the properties whose values are truthy will be added as css classes to the 22473 * element. 22474 * 22475 * @example Example that demonstrates basic bindings via ngClass directive. 22476 <example> 22477 <file name="index.html"> 22478 <p ng-class="{strike: deleted, bold: important, 'has-error': error}">Map Syntax Example</p> 22479 <label> 22480 <input type="checkbox" ng-model="deleted"> 22481 deleted (apply "strike" class) 22482 </label><br> 22483 <label> 22484 <input type="checkbox" ng-model="important"> 22485 important (apply "bold" class) 22486 </label><br> 22487 <label> 22488 <input type="checkbox" ng-model="error"> 22489 error (apply "has-error" class) 22490 </label> 22491 <hr> 22492 <p ng-class="style">Using String Syntax</p> 22493 <input type="text" ng-model="style" 22494 placeholder="Type: bold strike red" aria-label="Type: bold strike red"> 22495 <hr> 22496 <p ng-class="[style1, style2, style3]">Using Array Syntax</p> 22497 <input ng-model="style1" 22498 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red"><br> 22499 <input ng-model="style2" 22500 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 2"><br> 22501 <input ng-model="style3" 22502 placeholder="Type: bold, strike or red" aria-label="Type: bold, strike or red 3"><br> 22503 <hr> 22504 <p ng-class="[style4, {orange: warning}]">Using Array and Map Syntax</p> 22505 <input ng-model="style4" placeholder="Type: bold, strike" aria-label="Type: bold, strike"><br> 22506 <label><input type="checkbox" ng-model="warning"> warning (apply "orange" class)</label> 22507 </file> 22508 <file name="style.css"> 22509 .strike { 22510 text-decoration: line-through; 22511 } 22512 .bold { 22513 font-weight: bold; 22514 } 22515 .red { 22516 color: red; 22517 } 22518 .has-error { 22519 color: red; 22520 background-color: yellow; 22521 } 22522 .orange { 22523 color: orange; 22524 } 22525 </file> 22526 <file name="protractor.js" type="protractor"> 22527 var ps = element.all(by.css('p')); 22528 22529 it('should let you toggle the class', function() { 22530 22531 expect(ps.first().getAttribute('class')).not.toMatch(/bold/); 22532 expect(ps.first().getAttribute('class')).not.toMatch(/has-error/); 22533
22534 element(by.model('important')).click(); 22535 expect(ps.first().getAttribute('class')).toMatch(/bold/); 22536 22537 element(by.model('error')).click(); 22538 expect(ps.first().getAttribute('class')).toMatch(/has-error/); 22539 }); 22540 22541 it('should let you toggle string example', function() { 22542 expect(ps.get(1).getAttribute('class')).toBe(''); 22543 element(by.model('style')).clear(); 22544 element(by.model('style')).sendKeys('red'); 22545 expect(ps.get(1).getAttribute('class')).toBe('red'); 22546 }); 22547 22548 it('array example should have 3 classes', function() { 22549 expect(ps.get(2).getAttribute('class')).toBe(''); 22550 element(by.model('style1')).sendKeys('bold'); 22551 element(by.model('style2')).sendKeys('strike'); 22552 element(by.model('style3')).sendKeys('red'); 22553 expect(ps.get(2).getAttribute('class')).toBe('bold strike red'); 22554 }); 22555 22556 it('array with map example should have 2 classes', function() { 22557 expect(ps.last().getAttribute('class')).toBe(''); 22558 element(by.model('style4')).sendKeys('bold'); 22559 element(by.model('warning')).click(); 22560 expect(ps.last().getAttribute('class')).toBe('bold orange'); 22561 }); 22562 </file> 22563 </example> 22564 22565 ## Animations 22566 22567 The example below demonstrates how to perform animations using ngClass. 22568 22569 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 22570 <file name="index.html"> 22571 <input id="setbtn" type="button" value="set" ng-click="myVar='my-class'"> 22572 <input id="clearbtn" type="button" value="clear" ng-click="myVar=''"> 22573 <br> 22574 <span class="base-class" ng-class="myVar">Sample Text</span> 22575 </file> 22576 <file name="style.css"> 22577 .base-class { 22578 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 22579 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 22580 } 22581 22582 .base-class.my-class { 22583 color: red; 22584 font-size:3em; 22585 } 22586 </file> 22587 <file name="protractor.js" type="protractor"> 22588 it('should check ng-class', function() { 22589 expect(element(by.css('.base-class')).getAttribute('class')).not. 22590 toMatch(/my-class/); 22591 22592 element(by.id('setbtn')).click(); 22593 22594 expect(element(by.css('.base-class')).getAttribute('class')). 22595 toMatch(/my-class/); 22596 22597 element(by.id('clearbtn')).click(); 22598 22599 expect(element(by.css('.base-class')).getAttribute('class')).not. 22600 toMatch(/my-class/); 22601 }); 22602 </file> 22603 </example> 22604 22605 22606 ## ngClass and pre-existing CSS3 Transitions/Animations 22607 The ngClass directive still supports CSS3 Transitions/Animations even if they do not follow the ngAnimate CSS naming structure. 22608 Upon animation ngAnimate will apply supplementary CSS classes to track the start and end of an animation, but this will not hinder 22609 any pre-existing CSS transitions already on the element. To get an idea of what happens during a class-based animation, be sure 22610 to view the step by step details of {@link $animate#addClass $animate.addClass} and 22611 {@link $animate#removeClass $animate.removeClass}. 22612 */ 22613var ngClassDirective = classDirective('', true); 22614 22615/** 22616 * @ngdoc directive 22617 * @name ngClassOdd 22618 * @restrict AC 22619 * 22620 * @description 22621 * The `ngClassOdd` and `ngClassEven` directives work exactly as 22622 * {@link ng.directive:ngClass ngClass}, except they work in 22623 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 22624 * 22625 * This directive can be applied only within the scope of an 22626 * {@link ng.directive:ngRepeat ngRepeat}. 22627 * 22628 * @element ANY 22629 * @param {expression} ngClassOdd {@link guide/expression Expression} to eval. The result 22630 * of the evaluation can be a string representing space delimited class names or an array. 22631 * 22632 * @example 22633 <example> 22634 <file name="index.html"> 22635 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 22636 <li ng-repeat="name in names"> 22637 <span ng-class-odd="'odd'" ng-class-even="'even'"> 22638 {{name}} 22639 </span> 22640 </li> 22641 </ol> 22642 </file> 22643 <file name="style.css"> 22644 .odd { 22645 color: red; 22646 } 22647 .even { 22648 color: blue; 22649 } 22650 </file> 22651 <file name="protractor.js" type="protractor"> 22652 it('should check ng-class-odd and ng-class-even', function() { 22653 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 22654 toMatch(/odd/); 22655 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 22656 toMatch(/even/); 22657 }); 22658 </file> 22659 </example> 22660 */ 22661var ngClassOddDirective = classDirective('Odd', 0); 22662 22663/** 22664 * @ngdoc directive 22665 * @name ngClassEven 22666 * @restrict AC 22667 * 22668 * @description 22669 * The `ngClassOdd` and `ngClassEven` directives work exactly as 22670 * {@link ng.directive:ngClass ngClass}, except they work in 22671 * conjunction with `ngRepeat` and take effect only on odd (even) rows. 22672 * 22673 * This directive can be applied only within the scope of an 22674 * {@link ng.directive:ngRepeat ngRepeat}. 22675 * 22676 * @element ANY 22677 * @param {expression} ngClassEven {@link guide/expression Expression} to eval. The 22678 * result of the evaluation can be a string representing space delimited class names or an array.
22679 * 22680 * @example 22681 <example> 22682 <file name="index.html"> 22683 <ol ng-init="names=['John', 'Mary', 'Cate', 'Suz']"> 22684 <li ng-repeat="name in names"> 22685 <span ng-class-odd="'odd'" ng-class-even="'even'"> 22686 {{name}} 22687 </span> 22688 </li> 22689 </ol> 22690 </file> 22691 <file name="style.css"> 22692 .odd { 22693 color: red; 22694 } 22695 .even { 22696 color: blue; 22697 } 22698 </file> 22699 <file name="protractor.js" type="protractor"> 22700 it('should check ng-class-odd and ng-class-even', function() { 22701 expect(element(by.repeater('name in names').row(0).column('name')).getAttribute('class')). 22702 toMatch(/odd/); 22703 expect(element(by.repeater('name in names').row(1).column('name')).getAttribute('class')). 22704 toMatch(/even/); 22705 }); 22706 </file> 22707 </example> 22708 */ 22709var ngClassEvenDirective = classDirective('Even', 1); 22710 22711/** 22712 * @ngdoc directive 22713 * @name ngCloak 22714 * @restrict AC 22715 * 22716 * @description 22717 * The `ngCloak` directive is used to prevent the Angular html template from being briefly 22718 * displayed by the browser in its raw (uncompiled) form while your application is loading. Use this 22719 * directive to avoid the undesirable flicker effect caused by the html template display. 22720 * 22721 * The directive can be applied to the `<body>` element, but the preferred usage is to apply 22722 * multiple `ngCloak` directives to small portions of the page to permit progressive rendering 22723 * of the browser view. 22724 * 22725 * `ngCloak` works in cooperation with the following css rule embedded within `angular.js` and 22726 * `angular.min.js`. 22727 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 22728 * 22729 * ```css 22730 * [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak { 22731 * display: none !important; 22732 * } 22733 * ``` 22734 * 22735 * When this css rule is loaded by the browser, all html elements (including their children) that 22736 * are tagged with the `ngCloak` directive are hidden. When Angular encounters this directive 22737 * during the compilation of the template it deletes the `ngCloak` element attribute, making 22738 * the compiled element visible. 22739 * 22740 * For the best result, the `angular.js` script must be loaded in the head section of the html 22741 * document; alternatively, the css rule above must be included in the external stylesheet of the 22742 * application. 22743 * 22744 * @element ANY 22745 * 22746 * @example 22747 <example> 22748 <file name="index.html"> 22749 <div id="template1" ng-cloak>{{ 'hello' }}</div> 22750 <div id="template2" class="ng-cloak">{{ 'world' }}</div> 22751 </file> 22752 <file name="protractor.js" type="protractor"> 22753 it('should remove the template directive and css class', function() { 22754 expect($('#template1').getAttribute('ng-cloak')). 22755 toBeNull(); 22756 expect($('#template2').getAttribute('ng-cloak')). 22757 toBeNull(); 22758 }); 22759 </file> 22760 </example> 22761 * 22762 */ 22763var ngCloakDirective = ngDirective({ 22764 compile: function(element, attr) { 22765 attr.$set('ngCloak', undefined); 22766 element.removeClass('ng-cloak'); 22767 } 22768}); 22769 22770/** 22771 * @ngdoc directive 22772 * @name ngController 22773 * 22774 * @description 22775 * The `ngController` directive attaches a controller class to the view. This is a key aspect of how angular 22776 * supports the principles behind the Model-View-Controller design pattern. 22777 * 22778 * MVC components in angular: 22779 * 22780 * * Model â Models are the properties of a scope; scopes are attached to the DOM where scope properties 22781 * are accessed through bindings. 22782 * * View â The template (HTML with data bindings) that is rendered into the View. 22783 * * Controller â The `ngController` directive specifies a Controller class; the class contains business 22784 * logic behind the application to decorate the scope with functions and values 22785 * 22786 * Note that you can also attach controllers to the DOM by declaring it in a route definition 22787 * via the {@link ngRoute.$route $route} service. A common mistake is to declare the controller 22788 * again using `ng-controller` in the template itself. This will cause the controller to be attached 22789 * and executed twice. 22790 * 22791 * @element ANY 22792 * @scope 22793 * @priority 500 22794 * @param {expression} ngController Name of a constructor function registered with the current 22795 * {@link ng.$controllerProvider $controllerProvider} or an {@link guide/expression expression} 22796 * that on the current scope evaluates to a constructor function. 22797 * 22798 * The controller instance can be published into a scope property by specifying 22799 * `ng-controller="as propertyName"`. 22800 * 22801 * If the current `$controllerProvider` is configured to use globals (via 22802 * {@link ng.$controllerProvider#allowGlobals `$controllerProvider.allowGlobals()` }), this may 22803 * also be the name of a globally accessible constructor function (not recommended). 22804 * 22805 * @example 22806 * Here is a simple form for editing user contact information. Adding, remov
22806ing, clearing, and 22807 * greeting are methods declared on the controller (see source tab). These methods can 22808 * easily be called from the angular markup. Any changes to the data are automatically reflected 22809 * in the View without the need for a manual update. 22810 * 22811 * Two different declaration styles are included below: 22812 * 22813 * * one binds methods and properties directly onto the controller using `this`: 22814 * `ng-controller="SettingsController1 as settings"` 22815 * * one injects `$scope` into the controller: 22816 * `ng-controller="SettingsController2"` 22817 * 22818 * The second option is more common in the Angular community, and is generally used in boilerplates 22819 * and in this guide. However, there are advantages to binding properties directly to the controller 22820 * and avoiding scope. 22821 * 22822 * * Using `controller as` makes it obvious which controller you are accessing in the template when 22823 * multiple controllers apply to an element. 22824 * * If you are writing your controllers as classes you have easier access to the properties and 22825 * methods, which will appear on the scope, from inside the controller code. 22826 * * Since there is always a `.` in the bindings, you don't have to worry about prototypal 22827 * inheritance masking primitives. 22828 * 22829 * This example demonstrates the `controller as` syntax. 22830 * 22831 * <example name="ngControllerAs" module="controllerAsExample"> 22832 * <file name="index.html"> 22833 * <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings"> 22834 * <label>Name: <input type="text" ng-model="settings.name"/></label> 22835 * <button ng-click="settings.greet()">greet</button><br/> 22836 * Contact: 22837 * <ul> 22838 * <li ng-repeat="contact in settings.contacts"> 22839 * <select ng-model="contact.type" aria-label="Contact method" id="select_{{$index}}"> 22840 * <option>phone</option> 22841 * <option>email</option> 22842 * </select> 22843 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 22844 * <button ng-click="settings.clearContact(contact)">clear</button> 22845 * <button ng-click="settings.removeContact(contact)" aria-label="Remove">X</button> 22846 * </li> 22847 * <li><button ng-click="settings.addContact()">add</button></li> 22848 * </ul> 22849 * </div> 22850 * </file> 22851 * <file name="app.js"> 22852 * angular.module('controllerAsExample', []) 22853 * .controller('SettingsController1', SettingsController1); 22854 * 22855 * function SettingsController1() { 22856 * this.name = "John Smith"; 22857 * this.contacts = [ 22858 * {type: 'phone', value: '408 555 1212'}, 22859 * {type: 'email', value: '[email protected]'} ]; 22860 * } 22861 * 22862 * SettingsController1.prototype.greet = function() { 22863 * alert(this.name); 22864 * }; 22865 * 22866 * SettingsController1.prototype.addContact = function() { 22867 * this.contacts.push({type: 'email', value: '[email protected]'}); 22868 * }; 22869 * 22870 * SettingsController1.prototype.removeContact = function(contactToRemove) { 22871 * var index = this.contacts.indexOf(contactToRemove); 22872 * this.contacts.splice(index, 1); 22873 * }; 22874 * 22875 * SettingsController1.prototype.clearContact = function(contact) { 22876 * contact.type = 'phone'; 22877 * contact.value = ''; 22878 * }; 22879 * </file> 22880 * <file name="protractor.js" type="protractor"> 22881 * it('should check controller as', function() { 22882 * var container = element(by.id('ctrl-as-exmpl')); 22883 * expect(container.element(by.model('settings.name')) 22884 * .getAttribute('value')).toBe('John Smith'); 22885 * 22886 * var firstRepeat = 22887 * container.element(by.repeater('contact in settings.contacts').row(0)); 22888 * var secondRepeat = 22889 * container.element(by.repeater('contact in settings.contacts').row(1)); 22890 * 22891 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 22892 * .toBe('408 555 1212'); 22893 * 22894 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 22895 * .toBe('[email protected]'); 22896 * 22897 * firstRepeat.element(by.buttonText('clear')).click(); 22898 * 22899 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 22900 * .toBe(''); 22901 * 22902 * container.element(by.buttonText('add')).click(); 22903 * 22904 * expect(container.element(by.repeater('contact in settings.contacts').row(2)) 22905 * .element(by.model('contact.value')) 22906 * .getAttribute('value')) 22907 * .toBe('[email protected]'); 22908 * }); 22909 * </file> 22910 * </example> 22911 * 22912 * This example demonstrates the "attach to `$scope`" style of controller. 22913 * 22914 * <example name="ngController" module="controllerExample"> 22915 * <file name="index.html"> 22916 * <div id="ctrl-exmpl" ng-controller="SettingsController2"> 22917 * <label>Name: <input type="text" ng-model="name"/></label> 22918 * <button ng-click="greet()">greet</button><br/> 22919 * Contact: 22920 * <ul> 22921 * <li ng-repeat="contact in contacts"> 22922 * <select ng-model="contact.type" id="select_{{$index}}"> 22923 * <option>phone</option> 22924 * <option>email</option> 22925 * </select> 22926 * <input type="text" ng-model="contact.value" aria-labelledby="select_{{$index}}" /> 22927 * <button ng-click="clearContact(contact)">clear</button> 22928 * <button ng-click="removeContact(contact)">X</button> 22929 * </li> 22930 * <li>[ <button ng-click="addContact()">add</button> ]</li> 22931 * </ul> 22932 * </div> 22933 * </file> 22934 * <file name="app.js">
22935 * angular.module('controllerExample', []) 22936 * .controller('SettingsController2', ['$scope', SettingsController2]); 22937 * 22938 * function SettingsController2($scope) { 22939 * $scope.name = "John Smith"; 22940 * $scope.contacts = [ 22941 * {type:'phone', value:'408 555 1212'}, 22942 * {type:'email', value:'[email protected]'} ]; 22943 * 22944 * $scope.greet = function() { 22945 * alert($scope.name); 22946 * }; 22947 * 22948 * $scope.addContact = function() { 22949 * $scope.contacts.push({type:'email', value:'[email protected]'}); 22950 * }; 22951 * 22952 * $scope.removeContact = function(contactToRemove) { 22953 * var index = $scope.contacts.indexOf(contactToRemove); 22954 * $scope.contacts.splice(index, 1); 22955 * }; 22956 * 22957 * $scope.clearContact = function(contact) { 22958 * contact.type = 'phone'; 22959 * contact.value = ''; 22960 * }; 22961 * } 22962 * </file> 22963 * <file name="protractor.js" type="protractor"> 22964 * it('should check controller', function() { 22965 * var container = element(by.id('ctrl-exmpl')); 22966 * 22967 * expect(container.element(by.model('name')) 22968 * .getAttribute('value')).toBe('John Smith'); 22969 * 22970 * var firstRepeat = 22971 * container.element(by.repeater('contact in contacts').row(0)); 22972 * var secondRepeat = 22973 * container.element(by.repeater('contact in contacts').row(1)); 22974 * 22975 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 22976 * .toBe('408 555 1212'); 22977 * expect(secondRepeat.element(by.model('contact.value')).getAttribute('value')) 22978 * .toBe('[email protected]'); 22979 * 22980 * firstRepeat.element(by.buttonText('clear')).click(); 22981 * 22982 * expect(firstRepeat.element(by.model('contact.value')).getAttribute('value')) 22983 * .toBe(''); 22984 * 22985 * container.element(by.buttonText('add')).click(); 22986 * 22987 * expect(container.element(by.repeater('contact in contacts').row(2)) 22988 * .element(by.model('contact.value')) 22989 * .getAttribute('value')) 22990 * .toBe('[email protected]'); 22991 * }); 22992 * </file> 22993 *</example> 22994 22995 */ 22996var ngControllerDirective = [function() { 22997 return { 22998 restrict: 'A', 22999 scope: true, 23000 controller: '@', 23001 priority: 500 23002 }; 23003}]; 23004 23005/** 23006 * @ngdoc directive 23007 * @name ngCsp 23008 * 23009 * @element html 23010 * @description 23011 * Enables [CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP) support. 23012 * 23013 * This is necessary when developing things like Google Chrome Extensions or Universal Windows Apps. 23014 * 23015 * CSP forbids apps to use `eval` or `Function(string)` generated functions (among other things). 23016 * For Angular to be CSP compatible there are only two things that we need to do differently: 23017 * 23018 * - don't use `Function` constructor to generate optimized value getters 23019 * - don't inject custom stylesheet into the document 23020 * 23021 * AngularJS uses `Function(string)` generated functions as a speed optimization. Applying the `ngCsp` 23022 * directive will cause Angular to use CSP compatibility mode. When this mode is on AngularJS will 23023 * evaluate all expressions up to 30% slower than in non-CSP mode, but no security violations will 23024 * be raised. 23025 * 23026 * CSP forbids JavaScript to inline stylesheet rules. In non CSP mode Angular automatically 23027 * includes some CSS rules (e.g. {@link ng.directive:ngCloak ngCloak}). 23028 * To make those directives work in CSP mode, include the `angular-csp.css` manually. 23029 * 23030 * Angular tries to autodetect if CSP is active and automatically turn on the CSP-safe mode. This 23031 * autodetection however triggers a CSP error to be logged in the console: 23032 * 23033 * ``` 23034 * Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of 23035 * script in the following Content Security Policy directive: "default-src 'self'". Note that 23036 * 'script-src' was not explicitly set, so 'default-src' is used as a fallback. 23037 * ``` 23038 * 23039 * This error is harmless but annoying. To prevent the error from showing up, put the `ngCsp` 23040 * directive on the root element of the application or on the `angular.js` script tag, whichever 23041 * appears first in the html document. 23042 * 23043 * *Note: This directive is only available in the `ng-csp` and `data-ng-csp` attribute form.* 23044 * 23045 * @example 23046 * This example shows how to apply the `ngCsp` directive to the `html` tag. 23047 ```html 23048 <!doctype html> 23049 <html ng-app ng-csp> 23050 ... 23051 ... 23052 </html> 23053 ``` 23054 * @example 23055 // Note: the suffix `.csp` in the example name triggers 23056 // csp mode in our http server! 23057 <example name="example.csp" module="cspExample" ng-csp="true"> 23058 <file name="index.html"> 23059 <div ng-controller="MainController as ctrl"> 23060 <div> 23061 <button ng-click="ctrl.inc()" id="inc">Increment</button>
23062 <span id="counter"> 23063 {{ctrl.counter}} 23064 </span> 23065 </div> 23066 23067 <div> 23068 <button ng-click="ctrl.evil()" id="evil">Evil</button> 23069 <span id="evilError"> 23070 {{ctrl.evilError}} 23071 </span> 23072 </div> 23073 </div> 23074 </file> 23075 <file name="script.js"> 23076 angular.module('cspExample', []) 23077 .controller('MainController', function() { 23078 this.counter = 0; 23079 this.inc = function() { 23080 this.counter++; 23081 }; 23082 this.evil = function() { 23083 // jshint evil:true 23084 try { 23085 eval('1+2'); 23086 } catch (e) { 23087 this.evilError = e.message; 23088 } 23089 }; 23090 }); 23091 </file> 23092 <file name="protractor.js" type="protractor"> 23093 var util, webdriver; 23094 23095 var incBtn = element(by.id('inc')); 23096 var counter = element(by.id('counter')); 23097 var evilBtn = element(by.id('evil')); 23098 var evilError = element(by.id('evilError')); 23099 23100 function getAndClearSevereErrors() { 23101 return browser.manage().logs().get('browser').then(function(browserLog) { 23102 return browserLog.filter(function(logEntry) { 23103 return logEntry.level.value > webdriver.logging.Level.WARNING.value; 23104 }); 23105 }); 23106 } 23107 23108 function clearErrors() { 23109 getAndClearSevereErrors(); 23110 } 23111 23112 function expectNoErrors() { 23113 getAndClearSevereErrors().then(function(filteredLog) { 23114 expect(filteredLog.length).toEqual(0); 23115 if (filteredLog.length) { 23116 console.log('browser console errors: ' + util.inspect(filteredLog)); 23117 } 23118 }); 23119 } 23120 23121 function expectError(regex) { 23122 getAndClearSevereErrors().then(function(filteredLog) { 23123 var found = false; 23124 filteredLog.forEach(function(log) { 23125 if (log.message.match(regex)) { 23126 found = true; 23127 } 23128 }); 23129 if (!found) { 23130 throw new Error('expected an error that matches ' + regex); 23131 } 23132 }); 23133 } 23134 23135 beforeEach(function() { 23136 util = require('util'); 23137 webdriver = require('protractor/node_modules/selenium-webdriver'); 23138 }); 23139 23140 // For now, we only test on Chrome, 23141 // as Safari does not load the page with Protractor's injected scripts, 23142 // and Firefox webdriver always disables content security policy (#6358) 23143 if (browser.params.browser !== 'chrome') { 23144 return; 23145 } 23146 23147 it('should not report errors when the page is loaded', function() { 23148 // clear errors so we are not dependent on previous tests 23149 clearErrors(); 23150 // Need to reload the page as the page is already loaded when 23151 // we come here 23152 browser.driver.getCurrentUrl().then(function(url) { 23153 browser.get(url); 23154 }); 23155 expectNoErrors(); 23156 }); 23157 23158 it('should evaluate expressions', function() { 23159 expect(counter.getText()).toEqual('0'); 23160 incBtn.click(); 23161 expect(counter.getText()).toEqual('1'); 23162 expectNoErrors(); 23163 }); 23164 23165 it('should throw and report an error when using "eval"', function() { 23166 evilBtn.click(); 23167 expect(evilError.getText()).toMatch(/Content Security Policy/); 23168 expectError(/Content Security Policy/); 23169 }); 23170 </file> 23171 </example> 23172 */ 23173 23174// ngCsp is not implemented as a proper directive any more, because we need it be processed while we 23175// bootstrap the system (before $parse is instantiated), for this reason we just have 23176// the csp.isActive() fn that looks for ng-csp attribute anywhere in the current doc 23177 23178/** 23179 * @ngdoc directive 23180 * @name ngClick 23181 * 23182 * @description 23183 * The ngClick directive allows you to specify custom behavior when 23184 * an element is clicked. 23185 * 23186 * @element ANY 23187 * @priority 0 23188 * @param {expression} ngClick {@link guide/expression Expression} to evaluate upon 23189 * click. ({@link guide/expression#-event- Event object is available as `$event`}) 23190 * 23191 * @example 23192 <example> 23193 <file name="index.html"> 23194 <button ng-click="count = count + 1" ng-init="count=0"> 23195 Increment 23196 </button>
23197 <span> 23198 count: {{count}} 23199 </span> 23200 </file> 23201 <file name="protractor.js" type="protractor"> 23202 it('should check ng-click', function() { 23203 expect(element(by.binding('count')).getText()).toMatch('0'); 23204 element(by.css('button')).click(); 23205 expect(element(by.binding('count')).getText()).toMatch('1'); 23206 }); 23207 </file> 23208 </example> 23209 */ 23210/* 23211 * A collection of directives that allows creation of custom event handlers that are defined as 23212 * angular expressions and are compiled and executed within the current scope. 23213 */ 23214var ngEventDirectives = {}; 23215 23216// For events that might fire synchronously during DOM manipulation 23217// we need to execute their event handlers asynchronously using $evalAsync, 23218// so that they are not executed in an inconsistent state. 23219var forceAsyncEvents = { 23220 'blur': true, 23221 'focus': true 23222}; 23223forEach( 23224 'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '), 23225 function(eventName) { 23226 var directiveName = directiveNormalize('ng-' + eventName); 23227 ngEventDirectives[directiveName] = ['$parse', '$rootScope', function($parse, $rootScope) { 23228 return { 23229 restrict: 'A', 23230 compile: function($element, attr) { 23231 // We expose the powerful $event object on the scope that provides access to the Window, 23232 // etc. that isn't protected by the fast paths in $parse. We explicitly request better 23233 // checks at the cost of speed since event handler expressions are not executed as 23234 // frequently as regular change detection. 23235 var fn = $parse(attr[directiveName], /* interceptorFn */ null, /* expensiveChecks */ true); 23236 return function ngEventHandler(scope, element) { 23237 element.on(eventName, function(event) { 23238 var callback = function() { 23239 fn(scope, {$event:event}); 23240 }; 23241 if (forceAsyncEvents[eventName] && $rootScope.$$phase) { 23242 scope.$evalAsync(callback); 23243 } else { 23244 scope.$apply(callback); 23245 } 23246 }); 23247 }; 23248 } 23249 }; 23250 }]; 23251 } 23252); 23253 23254/** 23255 * @ngdoc directive 23256 * @name ngDblclick 23257 * 23258 * @description 23259 * The `ngDblclick` directive allows you to specify custom behavior on a dblclick event. 23260 * 23261 * @element ANY 23262 * @priority 0 23263 * @param {expression} ngDblclick {@link guide/expression Expression} to evaluate upon 23264 * a dblclick. (The Event object is available as `$event`) 23265 * 23266 * @example 23267 <example> 23268 <file name="index.html"> 23269 <button ng-dblclick="count = count + 1" ng-init="count=0"> 23270 Increment (on double click) 23271 </button> 23272 count: {{count}} 23273 </file> 23274 </example> 23275 */ 23276 23277 23278/** 23279 * @ngdoc directive 23280 * @name ngMousedown 23281 * 23282 * @description 23283 * The ngMousedown directive allows you to specify custom behavior on mousedown event. 23284 * 23285 * @element ANY 23286 * @priority 0 23287 * @param {expression} ngMousedown {@link guide/expression Expression} to evaluate upon 23288 * mousedown. ({@link guide/expression#-event- Event object is available as `$event`}) 23289 * 23290 * @example 23291 <example> 23292 <file name="index.html"> 23293 <button ng-mousedown="count = count + 1" ng-init="count=0"> 23294 Increment (on mouse down) 23295 </button> 23296 count: {{count}} 23297 </file> 23298 </example> 23299 */ 23300 23301 23302/** 23303 * @ngdoc directive 23304 * @name ngMouseup 23305 * 23306 * @description 23307 * Specify custom behavior on mouseup event. 23308 * 23309 * @element ANY 23310 * @priority 0 23311 * @param {expression} ngMouseup {@link guide/expression Expression} to evaluate upon 23312 * mouseup. ({@link guide/expression#-event- Event object is available as `$event`}) 23313 * 23314 * @example 23315 <example> 23316 <file name="index.html"> 23317 <button ng-mouseup="count = count + 1" ng-init="count=0"> 23318 Increment (on mouse up) 23319 </button> 23320 count: {{count}} 23321 </file> 23322 </example> 23323 */ 23324 23325/** 23326 * @ngdoc directive 23327 * @name ngMouseover 23328 * 23329 * @description 23330 * Specify custom behavior on mouseover event. 23331 * 23332 * @element ANY 23333 * @priority 0 23334 * @param {expression} ngMouseover {@link guide/expression Expression} to evaluate upon 23335 * mouseover. ({@link guide/expression#-event- Event object is available as `$event`}) 23336 * 23337 * @example 23338 <example> 23339 <file name="index.html"> 23340 <button ng-mouseover="count = count + 1" ng-init="count=0"> 23341 Increment (when mouse is over) 23342 </button> 23343 count: {{count}} 23344 </file> 23345 </example> 23346 */ 23347 23348 23349/** 23350 * @ngdoc directive 23351 * @name ngMouseenter 23352 * 23353 * @description 23354 * Specify custom behavior on mouseenter event. 23355 * 23356 * @element ANY 23357 * @priority 0 23358 * @param {expression} ngMouseenter {@link guide/expression Expression} to evaluate upon 23359 * mouseenter. ({@link guide/expression#-event- Event object is available as `$event`}) 23360 * 23361 * @example 23362 <example> 23363 <file name="index.html"> 23364 <button ng-mouseenter="count = count + 1" ng-init="count=0"> 23365 Increment (when mouse enters) 23366 </button> 23367 count: {{count}} 23368 </file> 23369 </example> 23370 */ 23371 23372 23373/** 23374 * @ngdoc directive 23375 * @name ngMouseleave 23376 * 23377 * @description 23378 * Specify custom behavior on mouseleave event. 23379 * 23380 * @element ANY 23381 * @priority 0 23382 * @param {expression} ngMouseleave {@link guide/expression Expression} to evaluate upon 23383 * mouseleave. ({@link guide/expression#-event- Event object is available as `$event`}) 23384 * 23385 * @example 23386 <example> 23387 <file name="index.html"> 23388 <button ng-mouseleave="count = count + 1" ng-init="count=0"> 23389 Increment (when mouse leaves) 23390 </button> 23391 count: {{count}} 23392 </file> 23393 </example> 23394 */ 23395 23396 23397/** 23398 * @ngdoc directive 23399 * @name ngMousemove 23400 * 23401 * @description 23402 * Specify custom behavior on mousemove event. 23403 * 23404 * @element ANY 23405 * @priority 0 23406 * @param {expression} ngMousemove {@link guide/expression Expression} to evaluate upon 23407 * mousemove. ({@link guide/expression#-event- Event object is available as `$event`}) 23408 * 23409 * @example 23410 <example> 23411 <file name="index.html"> 23412 <button ng-mousemove="count = count + 1" ng-init="count=0"> 23413 Increment (when mouse moves) 23414 </button> 23415 count: {{count}} 23416 </file> 23417 </example> 23418 */ 23419 23420 23421/** 23422 * @ngdoc directive 23423 * @name ngKeydown 23424 * 23425 * @description 23426 * Specify custom behavior on keydown event. 23427 * 23428 * @element ANY 23429 * @priority 0 23430 * @param {expression} ngKeydown {@link guide/expression Expression} to evaluate upon 23431 * keydown. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 23432 * 23433 * @example 23434 <example> 23435 <file name="index.html"> 23436 <input ng-keydown="count = count + 1" ng-init="count=0"> 23437 key down count: {{count}} 23438 </file> 23439 </example> 23440 */ 23441 23442 23443/** 23444 * @ngdoc directive 23445 * @name ngKeyup 23446 * 23447 * @description 23448 * Specify custom behavior on keyup event. 23449 * 23450 * @element ANY 23451 * @priority 0 23452 * @param {expression} ngKeyup {@link guide/expression Expression} to evaluate upon 23453 * keyup. (Event object is available as `$event` and can be interrogated for keyCode, altKey, etc.) 23454 * 23455 * @example 23456 <example> 23457 <file name="index.html"> 23458 <p>Typing in the input box below updates the key count</p> 23459 <input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}} 23460 23461 <p>Typing in the input box below updates the keycode</p> 23462 <input ng-keyup="event=$event"> 23463 <p>event keyCode: {{ event.keyCode }}</p> 23464 <p>event altKey: {{ event.altKey }}</p> 23465 </file> 23466 </example> 23467 */ 23468 23469 23470/** 23471 * @ngdoc directive 23472 * @name ngKeypress 23473 * 23474 * @description 23475 * Specify custom behavior on keypress event. 23476 * 23477 * @element ANY 23478 * @param {expression} ngKeypress {@link guide/expression Expression} to evaluate upon 23479 * keypress. ({@link guide/expression#-event- Event object is available as `$event`} 23480 * and can be interrogated for keyCode, altKey, etc.) 23481 * 23482 * @example 23483 <example> 23484 <file name="index.html"> 23485 <input ng-keypress="count = count + 1" ng-init="count=0"> 23486 key press count: {{count}} 23487 </file> 23488 </example> 23489 */ 23490 23491 23492/** 23493 * @ngdoc directive 23494 * @name ngSubmit 23495 * 23496 * @description 23497 * Enables binding angular expressions to onsubmit events. 23498 * 23499 * Additionally it prevents the default action (which for form means
23499sending the request to the 23500 * server and reloading the current page), but only if the form does not contain `action`, 23501 * `data-action`, or `x-action` attributes. 23502 * 23503 * <div class="alert alert-warning"> 23504 * **Warning:** Be careful not to cause "double-submission" by using both the `ngClick` and 23505 * `ngSubmit` handlers together. See the 23506 * {@link form#submitting-a-form-and-preventing-the-default-action `form` directive documentation} 23507 * for a detailed discussion of when `ngSubmit` may be triggered. 23508 * </div> 23509 * 23510 * @element form 23511 * @priority 0 23512 * @param {expression} ngSubmit {@link guide/expression Expression} to eval. 23513 * ({@link guide/expression#-event- Event object is available as `$event`}) 23514 * 23515 * @example 23516 <example module="submitExample"> 23517 <file name="index.html"> 23518 <script> 23519 angular.module('submitExample', []) 23520 .controller('ExampleController', ['$scope', function($scope) { 23521 $scope.list = []; 23522 $scope.text = 'hello'; 23523 $scope.submit = function() { 23524 if ($scope.text) { 23525 $scope.list.push(this.text); 23526 $scope.text = ''; 23527 } 23528 }; 23529 }]); 23530 </script>
23531 <form ng-submit="submit()" ng-controller="ExampleController"> 23532 Enter text and hit enter: 23533 <input type="text" ng-model="text" name="text" /> 23534 <input type="submit" id="submit" value="Submit" /> 23535 <pre>list={{list}}</pre> 23536 </form> 23537 </file> 23538 <file name="protractor.js" type="protractor"> 23539 it('should check ng-submit', function() { 23540 expect(element(by.binding('list')).getText()).toBe('list=[]'); 23541 element(by.css('#submit')).click(); 23542 expect(element(by.binding('list')).getText()).toContain('hello'); 23543 expect(element(by.model('text')).getAttribute('value')).toBe(''); 23544 }); 23545 it('should ignore empty strings', function() { 23546 expect(element(by.binding('list')).getText()).toBe('list=[]'); 23547 element(by.css('#submit')).click(); 23548 element(by.css('#submit')).click(); 23549 expect(element(by.binding('list')).getText()).toContain('hello'); 23550 }); 23551 </file> 23552 </example> 23553 */ 23554 23555/** 23556 * @ngdoc directive 23557 * @name ngFocus 23558 * 23559 * @description 23560 * Specify custom behavior on focus event. 23561 * 23562 * Note: As the `focus` event is executed synchronously when calling `input.focus()` 23563 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 23564 * during an `$apply` to ensure a consistent state. 23565 * 23566 * @element window, input, select, textarea, a 23567 * @priority 0 23568 * @param {expression} ngFocus {@link guide/expression Expression} to evaluate upon 23569 * focus. ({@link guide/expression#-event- Event object is available as `$event`}) 23570 * 23571 * @example 23572 * See {@link ng.directive:ngClick ngClick} 23573 */ 23574 23575/** 23576 * @ngdoc directive 23577 * @name ngBlur 23578 * 23579 * @description 23580 * Specify custom behavior on blur event. 23581 * 23582 * A [blur event](https://developer.mozilla.org/en-US/docs/Web/Events/blur) fires when 23583 * an element has lost focus. 23584 * 23585 * Note: As the `blur` event is executed synchronously also during DOM manipulations 23586 * (e.g. removing a focussed input), 23587 * AngularJS executes the expression using `scope.$evalAsync` if the event is fired 23588 * during an `$apply` to ensure a consistent state. 23589 * 23590 * @element window, input, select, textarea, a 23591 * @priority 0 23592 * @param {expression} ngBlur {@link guide/expression Expression} to evaluate upon 23593 * blur. ({@link guide/expression#-event- Event object is available as `$event`}) 23594 * 23595 * @example 23596 * See {@link ng.directive:ngClick ngClick} 23597 */ 23598 23599/** 23600 * @ngdoc directive 23601 * @name ngCopy 23602 * 23603 * @description 23604 * Specify custom behavior on copy event. 23605 * 23606 * @element window, input, select, textarea, a 23607 * @priority 0 23608 * @param {expression} ngCopy {@link guide/expression Expression} to evaluate upon 23609 * copy. ({@link guide/expression#-event- Event object is available as `$event`}) 23610 * 23611 * @example 23612 <example> 23613 <file name="index.html"> 23614 <input ng-copy="copied=true" ng-init="copied=false; value='copy me'" ng-model="value"> 23615 copied: {{copied}} 23616 </file> 23617 </example> 23618 */ 23619 23620/** 23621 * @ngdoc directive 23622 * @name ngCut 23623 * 23624 * @description 23625 * Specify custom behavior on cut event. 23626 * 23627 * @element window, input, select, textarea, a 23628 * @priority 0 23629 * @param {expression} ngCut {@link guide/expression Expression} to evaluate upon 23630 * cut. ({@link guide/expression#-event- Event object is available as `$event`}) 23631 * 23632 * @example 23633 <example> 23634 <file name="index.html"> 23635 <input ng-cut="cut=true" ng-init="cut=false; value='cut me'" ng-model="value"> 23636 cut: {{cut}} 23637 </file> 23638 </example> 23639 */ 23640 23641/** 23642 * @ngdoc directive 23643 * @name ngPaste 23644 * 23645 * @description 23646 * Specify custom behavior on paste event. 23647 * 23648 * @element window, input, select, textarea, a 23649 * @priority 0 23650 * @param {expression} ngPaste {@link guide/expression Expression} to evaluate upon 23651 * paste. ({@link guide/expression#-event- Event object is available as `$event`}) 23652 * 23653 * @example 23654 <example> 23655 <file name="index.html"> 23656 <input ng-paste="paste=true" ng-init="paste=false" placeholder='paste here'> 23657 pasted: {{paste}} 23658 </file> 23659 </example> 23660 */ 23661 23662/** 23663 * @ngdoc directive 23664 * @name ngIf 23665 * @restrict A 23666 * 23667 * @description 23668 * The `ngIf` directive removes or recreates a portion of the DOM tree based on an 23669 * {expression}. If the expression assigned to `ngIf` evaluates to a false 23670 * value then the element is removed from the DOM, otherwise a clone of the 23671 * element is reinserted into the DOM. 23672 * 23673 * `ngIf` differs from `ngShow` and `ngHide` in that `ngIf` completely removes and recreates the 23674 * element in the DOM rather than changing its visibility via the `display` css property. A common 23675 * case when this difference is significant is when using css selectors that rely on an element's 23676 * position within the DOM, such as the `:first-child` or `:last-child` pseudo-classe
23676s. 23677 * 23678 * Note that when an element is removed using `ngIf` its scope is destroyed and a new scope 23679 * is created when the element is restored. The scope created within `ngIf` inherits from 23680 * its parent scope using 23681 * [prototypal inheritance](https://github.com/angular/angular.js/wiki/Understanding-Scopes#javascript-prototypal-inheritance). 23682 * An important implication of this is if `ngModel` is used within `ngIf` to bind to 23683 * a javascript primitive defined in the parent scope. In this case any modifications made to the 23684 * variable within the child scope will override (hide) the value in the parent scope. 23685 * 23686 * Also, `ngIf` recreates elements using their compiled state. An example of this behavior 23687 * is if an element's class attribute is directly modified after it's compiled, using something like 23688 * jQuery's `.addClass()` method, and the element is later removed. When `ngIf` recreates the element 23689 * the added class will be lost because the original compiled state is used to regenerate the element. 23690 * 23691 * Additionally, you can provide animations via the `ngAnimate` module to animate the `enter` 23692 * and `leave` effects. 23693 * 23694 * @animations 23695 * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container 23696 * leave - happens just before the `ngIf` contents are removed from the DOM 23697 * 23698 * @element ANY 23699 * @scope 23700 * @priority 600 23701 * @param {expression} ngIf If the {@link guide/expression expression} is falsy then 23702 * the element is removed from the DOM tree. If it is truthy a copy of the compiled 23703 * element is added to the DOM tree. 23704 * 23705 * @example 23706 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 23707 <file name="index.html"> 23708 <label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /></label><br/> 23709 Show when checked: 23710 <span ng-if="checked" class="animate-if"> 23711 This is removed when the checkbox is unchecked. 23712 </span> 23713 </file> 23714 <file name="animations.css"> 23715 .animate-if { 23716 background:white; 23717 border:1px solid black; 23718 padding:10px; 23719 } 23720 23721 .animate-if.ng-enter, .animate-if.ng-leave { 23722 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 23723 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 23724 } 23725 23726 .animate-if.ng-enter, 23727 .animate-if.ng-leave.ng-leave-active { 23728 opacity:0; 23729 } 23730 23731 .animate-if.ng-leave, 23732 .animate-if.ng-enter.ng-enter-active { 23733 opacity:1; 23734 } 23735 </file> 23736 </example> 23737 */ 23738var ngIfDirective = ['$animate', function($animate) { 23739 return { 23740 multiElement: true, 23741 transclude: 'element', 23742 priority: 600, 23743 terminal: true, 23744 restrict: 'A', 23745 $$tlb: true, 23746 link: function($scope, $element, $attr, ctrl, $transclude) { 23747 var block, childScope, previousElements; 23748 $scope.$watch($attr.ngIf, function ngIfWatchAction(value) { 23749 23750 if (value) { 23751 if (!childScope) { 23752 $transclude(function(clone, newScope) { 23753 childScope = newScope; 23754 clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' '); 23755 // Note: We only need the first/last node of the cloned nodes. 23756 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 23757 // by a directive with templateUrl when its template arrives. 23758 block = { 23759 clone: clone 23760 }; 23761 $animate.enter(clone, $element.parent(), $element); 23762 }); 23763 } 23764 } else { 23765 if (previousElements) { 23766 previousElements.remove(); 23767 previousElements = null; 23768 } 23769 if (childScope) { 23770 childScope.$destroy(); 23771 childScope = null; 23772 } 23773 if (block) { 23774 previousElements = getBlockNodes(block.clone); 23775 $animate.leave(previousElements).then(function() { 23776 previousElements = null; 23777 }); 23778 block = null; 23779 } 23780 } 23781 }); 23782 } 23783 }; 23784}]; 23785 23786/** 23787 * @ngdoc directive 23788 * @name ngInclude 23789 * @restrict ECA 23790 * 23791 * @description 23792 * Fetches, compiles and includes an external HTML fragment. 23793 * 23794 * By default, the template URL is restricted to the same domain and protocol as the 23795 * application document. This is done by calling {@link $sce#getTrustedResourceUrl 23796 * $sce.getTrustedResourceUrl} on it. To load templates from other domains or protocols 23797 * you may either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist them} or 23798 * {@link $sce#trustAsResourceUrl wrap them} as trusted values. Refer to Angular's {@link 23799 * ng.$sce Strict Contextual Escaping}. 23800 * 23801 * In addition, the browser's 23802 * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest) 23803 * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/) 23804 * policy may further restrict whether the template is successfully loaded. 23805 * For example, `ngInclude` won't work for cross-domain requests on all browsers and for `file://` 23806 * access on some browsers. 23807 * 23808 * @animations 23809 * enter - animation is used to bring new content into the browser. 23810 * leave - animation is used to animate existing content away. 23811 * 23812 * The enter and leave animation occur concurrently. 23813 * 23814 * @scope 23815 * @priority 400 23816 * 23817 * @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant, 23818 * make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`. 23819 * @param {string=} onload Expression to evaluate when a new partial is loaded. 23820 * 23821 * @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll 23822 * $anchorScroll} to scroll the viewport after the content is loaded. 23823 * 23824 * - If the attribute is not set, disable scrolling. 23825 * - If the attribute is set without value, enable scrolling. 23826 * - Otherwise enable scrolling only if the expression evaluates to truthy value. 23827 * 23828 * @example 23829 <example module="includeExample" deps="angular-animate.js" animations="true"> 23830 <file name="index.html">
23831 <div ng-controller="ExampleController"> 23832 <select ng-model="template" ng-options="t.name for t in templates"> 23833 <option value="">(blank)</option> 23834 </select> 23835 url of the template: <code>{{template.url}}</code> 23836 <hr/> 23837 <div class="slide-animate-container"> 23838 <div class="slide-animate" ng-include="template.url"></div> 23839 </div> 23840 </div> 23841 </file> 23842 <file name="script.js"> 23843 angular.module('includeExample', ['ngAnimate']) 23844 .controller('ExampleController', ['$scope', function($scope) { 23845 $scope.templates = 23846 [ { name: 'template1.html', url: 'template1.html'}, 23847 { name: 'template2.html', url: 'template2.html'} ]; 23848 $scope.template = $scope.templates[0]; 23849 }]); 23850 </file> 23851 <file name="template1.html"> 23852 Content of template1.html 23853 </file> 23854 <file name="template2.html"> 23855 Content of template2.html 23856 </file> 23857 <file name="animations.css"> 23858 .slide-animate-container { 23859 position:relative; 23860 background:white; 23861 border:1px solid black; 23862 height:40px; 23863 overflow:hidden; 23864 } 23865 23866 .slide-animate { 23867 padding:10px; 23868 } 23869 23870 .slide-animate.ng-enter, .slide-animate.ng-leave { 23871 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 23872 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 23873 23874 position:absolute; 23875 top:0; 23876 left:0; 23877 right:0; 23878 bottom:0; 23879 display:block; 23880 padding:10px; 23881 } 23882 23883 .slide-animate.ng-enter { 23884 top:-50px; 23885 } 23886 .slide-animate.ng-enter.ng-enter-active { 23887 top:0; 23888 } 23889 23890 .slide-animate.ng-leave { 23891 top:0; 23892 } 23893 .slide-animate.ng-leave.ng-leave-active { 23894 top:50px; 23895 } 23896 </file> 23897 <file name="protractor.js" type="protractor"> 23898 var templateSelect = element(by.model('template')); 23899 var includeElem = element(by.css('[ng-include]')); 23900 23901 it('should load template1.html', function() { 23902 expect(includeElem.getText()).toMatch(/Content of template1.html/); 23903 }); 23904 23905 it('should load template2.html', function() { 23906 if (browser.params.browser == 'firefox') { 23907 // Firefox can't handle using selects 23908 // See https://github.com/angular/protractor/issues/480 23909 return; 23910 } 23911 templateSelect.click(); 23912 templateSelect.all(by.css('option')).get(2).click(); 23913 expect(includeElem.getText()).toMatch(/Content of template2.html/); 23914 }); 23915 23916 it('should change to blank', function() { 23917 if (browser.params.browser == 'firefox') { 23918 // Firefox can't handle using selects 23919 return; 23920 } 23921 templateSelect.click(); 23922 templateSelect.all(by.css('option')).get(0).click(); 23923 expect(includeElem.isPresent()).toBe(false); 23924 }); 23925 </file> 23926 </example> 23927 */ 23928 23929 23930/** 23931 * @ngdoc event 23932 * @name ngInclude#$includeContentRequested 23933 * @eventType emit on the scope ngInclude was declared in 23934 * @description 23935 * Emitted every time the ngInclude content is requested. 23936 * 23937 * @param {Object} angularEvent Synthetic event object. 23938 * @param {String} src URL of content to load. 23939 */ 23940 23941 23942/** 23943 * @ngdoc event 23944 * @name ngInclude#$includeContentLoaded 23945 * @eventType emit on the current ngInclude scope 23946 * @description 23947 * Emitted every time the ngInclude content is reloaded. 23948 * 23949 * @param {Object} angularEvent Synthetic event object. 23950 * @param {String} src URL of content to load. 23951 */ 23952 23953 23954/** 23955 * @ngdoc event 23956 * @name ngInclude#$includeContentError 23957 * @eventType emit on the scope ngInclude was declared in 23958 * @description 23959 * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299) 23960 * 23961 * @param {Object} angularEvent Synthetic event object. 23962 * @param {String} src URL of content to load. 23963 */ 23964var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', '$sce', 23965 function($templateRequest, $anchorScroll, $animate, $sce) { 23966 return { 23967 restrict: 'ECA', 23968 priority: 400, 23969 terminal: true, 23970 transclude: 'element', 23971 controller: angular.noop, 23972 compile: function(element, attr) { 23973 var srcExp = attr.ngInclude || attr.src, 23974 onloadExp = attr.onload || '', 23975 autoScrollExp = attr.autoscroll; 23976 23977 return function(scope, $element, $attr, ctrl, $transclude) { 23978 var changeCounter = 0, 23979 currentScope, 23980 previousElement, 23981 currentElement; 23982 23983 var cleanupLastIncludeContent = function() { 23984 if (previousElement) { 23985 previousElement.remove(); 23986 previousElement = null; 23987 } 23988 if (currentScope) { 23989 currentScope.$destroy(); 23990 currentScope = null; 23991 } 23992 if (currentElement) { 23993 $animate.leave(currentElement).then(function() { 23994 previousElement = null; 23995 }); 23996 previousElement = currentElement; 23997 currentElement = null; 23998 } 23999 }; 24000 24001 scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) { 24002 var afterAnimation = function() { 24003 if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) { 24004 $anchorScroll(); 24005 } 24006 }; 24007 var thisChangeId = ++changeCounter; 24008 24009 if (src) { 24010 //set the 2nd param to true to ignore the template request error so that the inner 24011 //contents and scope can be cleaned up. 24012 $templateRequest(src, true).then(function(response) { 24013 if (thisChangeId !== changeCounter) return; 24014 var newScope = scope.$new(); 24015 ctrl.template = response; 24016 24017 // Note: This will also link all children of ng-include that were contained in the original 24018 // html. If that content contains controllers, ... they could pollute/change the scope. 24019 // However, using ng-include on an element with additional content does not make sense... 24020 // Note: We can't remove them in the cloneAttchFn of $transclude as that 24021 // function is called before linking the content, which would apply child 24022 // directives to non existing elements. 24023 var clone = $transclude(newScope, function(clone) { 24024 cleanupLastIncludeContent(); 24025 $animate.enter(clone, null, $element).then(afterAnimation); 24026 }); 24027 24028 currentScope = newScope; 24029 currentElement = clone; 24030 24031 currentScope.$emit('$includeContentLoaded', src); 24032 scope.$eval(onloadExp); 24033 }, function() { 24034 if (thisChangeId === changeCounter) { 24035 cleanupLastIncludeContent();
vendor: 2,651 bytes, lines 24036-24116
24036 scope.$emit('$includeContentError', src); 24037 } 24038 }); 24039 scope.$emit('$includeContentRequested', src); 24040 } else { 24041 cleanupLastIncludeContent(); 24042 ctrl.template = null; 24043 } 24044 }); 24045 }; 24046 } 24047 }; 24048}]; 24049 24050// This directive is called during the $transclude call of the first `ngInclude` directive. 24051// It will replace and compile the content of the element with the loaded template. 24052// We need this directive so that the element content is already filled when 24053// the link function of another directive on the same element as ngInclude 24054// is called. 24055var ngIncludeFillContentDirective = ['$compile', 24056 function($compile) { 24057 return { 24058 restrict: 'ECA', 24059 priority: -400, 24060 require: 'ngInclude', 24061 link: function(scope, $element, $attr, ctrl) { 24062 if (/SVG/.test($element[0].toString())) { 24063 // WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not 24064 // support innerHTML, so detect this here and try to generate the contents 24065 // specially. 24066 $element.empty(); 24067 $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope, 24068 function namespaceAdaptedClone(clone) { 24069 $element.append(clone); 24070 }, {futureParentElement: $element}); 24071 return; 24072 } 24073 24074 $element.html(ctrl.template); 24075 $compile($element.contents())(scope); 24076 } 24077 }; 24078 }]; 24079 24080/** 24081 * @ngdoc directive 24082 * @name ngInit 24083 * @restrict AC 24084 * 24085 * @description 24086 * The `ngInit` directive allows you to evaluate an expression in the 24087 * current scope. 24088 * 24089 * <div class="alert alert-danger"> 24090 * The only appropriate use of `ngInit` is for aliasing special properties of 24091 * {@link ng.directive:ngRepeat `ngRepeat`}, as seen in the demo below. Besides this case, you 24092 * should use {@link guide/controller controllers} rather than `ngInit` 24093 * to initialize values on a scope. 24094 * </div> 24095 * <div class="alert alert-warning"> 24096 * **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make 24097 * sure you have parenthesis for correct precedence: 24098 * <pre class="prettyprint"> 24099 * `<div ng-init="test1 = (data | orderBy:'name')"></div>` 24100 * </pre> 24101 * </div> 24102 * 24103 * @priority 450 24104 * 24105 * @element ANY 24106 * @param {expression} ngInit {@link guide/expression Expression} to eval. 24107 * 24108 * @example 24109 <example module="initExample"> 24110 <file name="index.html"> 24111 <script> 24112 angular.module('initExample', []) 24113 .controller('ExampleController', ['$scope', function($scope) { 24114 $scope.list = [['a', 'b'], ['c', 'd']]; 24115 }]); 24116 </script>
24117 <div ng-controller="ExampleController"> 24118 <div ng-repeat="innerList in list" ng-init="outerIndex = $index"> 24119 <div ng-repeat="value in innerList" ng-init="innerIndex = $index"> 24120 <span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span> 24121 </div> 24122 </div> 24123 </div> 24124 </file> 24125 <file name="protractor.js" type="protractor"> 24126 it('should alias index positions', function() { 24127 var elements = element.all(by.css('.example-init')); 24128 expect(elements.get(0).getText()).toBe('list[ 0 ][ 0 ] = a;'); 24129 expect(elements.get(1).getText()).toBe('list[ 0 ][ 1 ] = b;'); 24130 expect(elements.get(2).getText()).toBe('list[ 1 ][ 0 ] = c;'); 24131 expect(elements.get(3).getText()).toBe('list[ 1 ][ 1 ] = d;'); 24132 }); 24133 </file> 24134 </example> 24135 */ 24136var ngInitDirective = ngDirective({ 24137 priority: 450, 24138 compile: function() { 24139 return { 24140 pre: function(scope, element, attrs) { 24141 scope.$eval(attrs.ngInit); 24142 } 24143 }; 24144 } 24145}); 24146 24147/** 24148 * @ngdoc directive 24149 * @name ngList 24150 * 24151 * @description 24152 * Text input that converts between a delimited string and an array of strings. The default 24153 * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom 24154 * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`. 24155 * 24156 * The behaviour of the directive is affected by the use of the `ngTrim` attribute. 24157 * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each 24158 * list item is respected. This implies that the user of the directive is responsible for 24159 * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a 24160 * tab or newline character. 24161 * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected 24162 * when joining the list items back together) and whitespace around each list item is stripped 24163 * before it is added to the model. 24164 * 24165 * ### Example with Validation 24166 * 24167 * <example name="ngList-directive" module="listExample"> 24168 * <file name="app.js"> 24169 * angular.module('listExample', []) 24170 * .controller('ExampleController', ['$scope', function($scope) { 24171 * $scope.names = ['morpheus', 'neo', 'trinity']; 24172 * }]); 24173 * </file> 24174 * <file name="index.html">
24175 * <form name="myForm" ng-controller="ExampleController"> 24176 * <label>List: <input name="namesInput" ng-model="names" ng-list required></label> 24177 * <span role="alert"> 24178 * <span class="error" ng-show="myForm.namesInput.$error.required"> 24179 * Required!</span> 24180 * </span> 24181 * <br> 24182 * <tt>names = {{names}}</tt><br/> 24183 * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/> 24184 * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/> 24185 * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/> 24186 * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/> 24187 * </form> 24188 * </file> 24189 * <file name="protractor.js" type="protractor"> 24190 * var listInput = element(by.model('names')); 24191 * var names = element(by.exactBinding('names')); 24192 * var valid = element(by.binding('myForm.namesInput.$valid')); 24193 * var error = element(by.css('span.error')); 24194 * 24195 * it('should initialize to model', function() { 24196 * expect(names.getText()).toContain('["morpheus","neo","trinity"]'); 24197 * expect(valid.getText()).toContain('true'); 24198 * expect(error.getCssValue('display')).toBe('none'); 24199 * }); 24200 * 24201 * it('should be invalid if empty', function() { 24202 * listInput.clear(); 24203 * listInput.sendKeys(''); 24204 * 24205 * expect(names.getText()).toContain(''); 24206 * expect(valid.getText()).toContain('false'); 24207 * expect(error.getCssValue('display')).not.toBe('none'); 24208 * }); 24209 * </file> 24210 * </example> 24211 * 24212 * ### Example - splitting on whitespace 24213 * <example name="ngList-directive-newlines"> 24214 * <file name="index.html"> 24215 * <textarea ng-model="list" ng-list=" " ng-trim="false"></textarea> 24216 * <pre>{{ list | json }}</pre> 24217 * </file> 24218 * <file name="protractor.js" type="protractor"> 24219 * it("should split the text by newlines", function() { 24220 * var listInput = element(by.model('list')); 24221 * var output = element(by.binding('list | json')); 24222 * listInput.sendKeys('abc\ndef\nghi'); 24223 * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]'); 24224 * }); 24225 * </file> 24226 * </example> 24227 * 24228 * @element input 24229 * @param {string=} ngList optional delimiter that should be used to split the value. 24230 */ 24231var ngListDirective = function() { 24232 return { 24233 restrict: 'A', 24234 priority: 100, 24235 require: 'ngModel', 24236 link: function(scope, element, attr, ctrl) { 24237 // We want to control whitespace trimming so we use this convoluted approach 24238 // to access the ngList attribute, which doesn't pre-trim the attribute 24239 var ngList = element.attr(attr.$attr.ngList) || ', '; 24240 var trimValues = attr.ngTrim !== 'false'; 24241 var separator = trimValues ? trim(ngList) : ngList; 24242 24243 var parse = function(viewValue) { 24244 // If the viewValue is invalid (say required but empty) it will be `undefined` 24245 if (isUndefined(viewValue)) return; 24246 24247 var list = []; 24248 24249 if (viewValue) { 24250 forEach(viewValue.split(separator), function(value) { 24251 if (value) list.push(trimValues ? trim(value) : value); 24252 }); 24253 } 24254 24255 return list; 24256 }; 24257 24258 ctrl.$parsers.push(parse); 24259 ctrl.$formatters.push(function(value) { 24260 if (isArray(value)) { 24261 return value.join(ngList); 24262 } 24263 24264 return undefined; 24265 }); 24266 24267 // Override the standard $isEmpty because an empty array means the input is empty. 24268 ctrl.$isEmpty = function(value) { 24269 return !value || !value.length; 24270 }; 24271 } 24272 }; 24273}; 24274 24275/* global VALID_CLASS: true, 24276 INVALID_CLASS: true, 24277 PRISTINE_CLASS: true, 24278 DIRTY_CLASS: true, 24279 UNTOUCHED_CLASS: true, 24280 TOUCHED_CLASS: true, 24281*/ 24282 24283var VALID_CLASS = 'ng-valid', 24284 INVALID_CLASS = 'ng-invalid', 24285 PRISTINE_CLASS = 'ng-pristine', 24286 DIRTY_CLASS = 'ng-dirty', 24287 UNTOUCHED_CLASS = 'ng-untouched', 24288 TOUCHED_CLASS = 'ng-touched', 24289 PENDING_CLASS = 'ng-pending'; 24290 24291 24292var $ngModelMinErr = new minErr('ngModel'); 24293 24294/** 24295 * @ngdoc type 24296 * @name ngModel.NgModelController 24297 * 24298 * @property {string} $viewValue Actual string value in the view. 24299 * @property {*} $modelValue The value in the model that the control is bound to. 24300 * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever 24301 the control reads value from the DOM. The functions are called in array order, each passing 24302 its return value through to the next. The last return value is forwarded to the 24303 {@link ngModel.NgModelController#$validators `$validators`} collection. 24304 24305Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue 24306`$viewValue`}. 24307 24308Returning `undefined` from a parser means a parse error occurred. In that case, 24309no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel` 24310will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`} 24311is set to `true`. The parse error is stored in `ngModel.$error.parse`. 24312 24313 * 24314 * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever 24315 the model value changes. The functions are called in reverse array order, each passing the value through to the 24316 next. The last return value is used as the actual DOM value. 24317 Used to format / convert values for display in the control. 24318 * ```js 24319 * function formatter(value) { 24320 * if (value) { 24321 * return value.toUpperCase(); 24322 * } 24323 * } 24324 * ngModel.$formatters.push(formatter); 24325 * ``` 24326 * 24327 * @property {Object.<string, function>} $validators A collection of validators that are applied 24328 * whenever the model value changes. The key value within the object refers to the name of the 24329 * validator while the function refers to the validation operation. The validation operation is 24330 * provided with the model value as an argument and must return a true or false value depending 24331 * on the response of that validation. 24332 * 24333 * ```js 24334 * ngModel.$validators.validCharacters = function(modelValue, viewValue) { 24335 * var value = modelValue || viewValue; 24336 * return /[0-9]+/.test(value) && 24337 * /[a-z]+/.test(value) && 24338 * /[A-Z]+/.test(value) && 24339 * /\W+/.test(value); 24340 * }; 24341 * ``` 24342 * 24343 * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to 24344 * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided 24345 * is expected to return a promise when it is run during the model validation process. Once the promise 24346 * is delivered then the validation status will be set to true when fulfilled and false when rejected. 24347 * When the asynchronous validators are triggered, each of the validators will run in parallel and the model 24348 * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator 24349 * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators 24350 * will only run once all synchronous validators have passed. 24351 * 24352 * Please note that if $http is used then it is important that the server returns a success HTTP response code 24353 * in order to fulfill the validation and a status level of `4xx` in order to reject the validation. 24354 * 24355 * ```js 24356 * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) { 24357 * var value = modelValue || viewValue; 24358 * 24359 * // Lookup user by username 24360 * return $http.get('/api/users/' + value). 24361 * then(function resolved() { 24362 * //username exists, this means validation fails 24363 * return $q.reject('exists'); 24364 * }, function rejected() { 24365 * //username does not exist, therefore this validation passes 24366 * return true; 24367 * }); 24368 * }; 24369 * ``` 24370 * 24371 * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the 24372 * view value has changed. It is called with no arguments, and its return value is ignored. 24373 * This can be used in place of additional $watches against the model value. 24374 * 24375 * @property {Object} $error An object hash with all failing validator ids as keys. 24376 * @property {Object} $pending An object hash with all pending validator ids as keys. 24377 * 24378 * @property {boolean} $untouched True if control has not lost focus yet. 24379 * @property {boolean} $touched True if control has lost focus. 24380 * @property {boolean} $pristine True if user has not interacted with the control yet. 24381 * @property {boolean} $dirty True if user has already interacted with the control. 24382 * @property {boolean} $valid True if there is no error. 24383 * @property {boolean}
24383 $invalid True if at least one error on the control. 24384 * @property {string} $name The name attribute of the control. 24385 * 24386 * @description 24387 * 24388 * `NgModelController` provides API for the {@link ngModel `ngModel`} directive. 24389 * The controller contains services for data-binding, validation, CSS updates, and value formatting 24390 * and parsing. It purposefully does not contain any logic which deals with DOM rendering or 24391 * listening to DOM events. 24392 * Such DOM related logic should be provided by other directives which make use of 24393 * `NgModelController` for data-binding to control elements. 24394 * Angular provides this DOM logic for most {@link input `input`} elements. 24395 * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example 24396 * custom control example} that uses `ngModelController` to bind to `contenteditable` elements. 24397 * 24398 * @example 24399 * ### Custom Control Example 24400 * This example shows how to use `NgModelController` with a custom control to achieve 24401 * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) 24402 * collaborate together to achieve the desired result. 24403 * 24404 * `contenteditable` is an HTML5 attribute, which tells the browser to let the element 24405 * contents be edited in place by the user. 24406 * 24407 * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} 24408 * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`). 24409 * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks 24410 * that content using the `$sce` service. 24411 * 24412 * <example name="NgModelController" module="customControl" deps="angular-sanitize.js"> 24413 <file name="style.css"> 24414 [contenteditable] { 24415 border: 1px solid black; 24416 background-color: white; 24417 min-height: 20px; 24418 } 24419 24420 .ng-invalid { 24421 border: 1px solid red; 24422 } 24423 24424 </file> 24425 <file name="script.js"> 24426 angular.module('customControl', ['ngSanitize']). 24427 directive('contenteditable', ['$sce', function($sce) { 24428 return { 24429 restrict: 'A', // only activate on element attribute 24430 require: '?ngModel', // get a hold of NgModelController 24431 link: function(scope, element, attrs, ngModel) { 24432 if (!ngModel) return; // do nothing if no ng-model 24433 24434 // Specify how UI should be updated 24435 ngModel.$render = function() { 24436 element.html($sce.getTrustedHtml(ngModel.$viewValue || '')); 24437 }; 24438 24439 // Listen for change events to enable binding 24440 element.on('blur keyup change', function() { 24441 scope.$evalAsync(read); 24442 }); 24443 read(); // initialize 24444 24445 // Write data to the model 24446 function read() { 24447 var html = element.html(); 24448 // When we clear the content editable the browser leaves a <br> behind 24449 // If strip-br attribute is provided then we strip this out 24450 if ( attrs.stripBr && html == '<br>' ) { 24451 html = ''; 24452 } 24453 ngModel.$setViewValue(html); 24454 } 24455 } 24456 }; 24457 }]); 24458 </file> 24459 <file name="index.html"> 24460 <form name="myForm"> 24461 <div contenteditable 24462 name="myWidget" ng-model="userContent" 24463 strip-br="true" 24464 required>Change me!</div> 24465 <span ng-show="myForm.myWidget.$error.required">Required!</span> 24466 <hr> 24467 <textarea ng-model="userContent" aria-label="Dynamic textarea"></textarea> 24468 </form> 24469 </file> 24470 <file name="protractor.js" type="protractor"> 24471 it('should data-bind and become invalid', function() { 24472 if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') { 24473 // SafariDriver can't handle contenteditable 24474 // and Firefox driver can't clear contenteditables very well 24475 return; 24476 } 24477 var contentEditable = element(by.css('[contenteditable]')); 24478 var content = 'Change me!'; 24479 24480 expect(contentEditable.getText()).toEqual(content); 24481 24482 contentEditable.clear(); 24483 contentEditable.sendKeys(protractor.Key.BACK_SPACE); 24484 expect(contentEditable.getText()).toEqual(''); 24485 expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/); 24486 }); 24487 </file> 24488 * </example> 24489 * 24490 * 24491 */ 24492var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate', 24493 function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { 24494 this.$viewValue = Number.NaN; 24495 this.$modelValue = Number.NaN; 24496 this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity. 24497 this.$validators = {}; 24498 this.$asyncValidators = {}; 24499 this.$parsers = []; 24500 this.$formatters = []; 24501 this.$viewChangeListeners = []; 24502 this.$untouched = true; 24503 this.$touched = false;
24504 this.$pristine = true; 24505 this.$dirty = false; 24506 this.$valid = true; 24507 this.$invalid = false; 24508 this.$error = {}; // keep invalid keys here 24509 this.$$success = {}; // keep valid keys here 24510 this.$pending = undefined; // keep pending keys here 24511 this.$name = $interpolate($attr.name || '', false)($scope); 24512 24513 24514 var parsedNgModel = $parse($attr.ngModel), 24515 parsedNgModelAssign = parsedNgModel.assign, 24516 ngModelGet = parsedNgModel, 24517 ngModelSet = parsedNgModelAssign, 24518 pendingDebounce = null, 24519 parserValid, 24520 ctrl = this; 24521 24522 this.$$setOptions = function(options) { 24523 ctrl.$options = options; 24524 if (options && options.getterSetter) { 24525 var invokeModelGetter = $parse($attr.ngModel + '()'), 24526 invokeModelSetter = $parse($attr.ngModel + '($$$p)'); 24527 24528 ngModelGet = function($scope) { 24529 var modelValue = parsedNgModel($scope); 24530 if (isFunction(modelValue)) { 24531 modelValue = invokeModelGetter($scope); 24532 } 24533 return modelValue; 24534 }; 24535 ngModelSet = function($scope, newValue) { 24536 if (isFunction(parsedNgModel($scope))) { 24537 invokeModelSetter($scope, {$$$p: ctrl.$modelValue}); 24538 } else { 24539 parsedNgModelAssign($scope, ctrl.$modelValue); 24540 } 24541 }; 24542 } else if (!parsedNgModel.assign) { 24543 throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}", 24544 $attr.ngModel, startingTag($element)); 24545 } 24546 }; 24547 24548 /** 24549 * @ngdoc method 24550 * @name ngModel.NgModelController#$render 24551 * 24552 * @description 24553 * Called when the view needs to be updated. It is expected that the user of the ng-model 24554 * directive will implement this method. 24555 * 24556 * The `$render()` method is invoked in the following situations: 24557 * 24558 * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last 24559 * committed value then `$render()` is called to update the input control. 24560 * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and 24561 * the `$viewValue` are different from last time. 24562 * 24563 * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of 24564 * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue` 24565 * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be 24566 * invoked if you only change a property on the objects. 24567 */ 24568 this.$render = noop; 24569 24570 /** 24571 * @ngdoc method 24572 * @name ngModel.NgModelController#$isEmpty 24573 * 24574 * @description 24575 * This is called when we need to determine if the value of an input is empty. 24576 * 24577 * For instance, the required directive does this to work out if the input has data or not. 24578 * 24579 * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`. 24580 * 24581 * You can override this for input directives whose concept of being empty is different from the 24582 * default. The `checkboxInputType` directive does this because in its case a value of `false` 24583 * implies empty. 24584 * 24585 * @param {*} value The value of the input to check for emptiness. 24586 * @returns {boolean} True if `value` is "empty". 24587 */ 24588 this.$isEmpty = function(value) { 24589 return isUndefined(value) || value === '' || value === null || value !== value; 24590 }; 24591 24592 var parentForm = $element.inheritedData('$formController') || nullFormCtrl, 24593 currentValidationRunId = 0; 24594 24595 /** 24596 * @ngdoc method 24597 * @name ngModel.NgModelController#$setValidity 24598 * 24599 * @description 24600 * Change the validity state, and notify the form. 24601 * 24602 * This method can be called within $parsers/$formatters or a custom validation implementation. 24603 * However, in most cases it should be sufficient to use the `ngModel.$validators` and 24604 * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically. 24605 * 24606 * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned 24607 * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]` 24608 * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
24609 * The `validationErrorKey` should be in camelCase and will get converted into dash-case 24610 * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` 24611 * class and can be bound to as `{{someForm.someControl.$error.myError}}` . 24612 * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined), 24613 * or skipped (null). Pending is used for unfulfilled `$asyncValidators`. 24614 * Skipped is used by Angular when validators do not run because of parse errors and 24615 * when `$asyncValidators` do not run because any of the `$validators` failed. 24616 */ 24617 addSetValidityMethod({ 24618 ctrl: this, 24619 $element: $element, 24620 set: function(object, property) { 24621 object[property] = true; 24622 }, 24623 unset: function(object, property) { 24624 delete object[property]; 24625 }, 24626 parentForm: parentForm, 24627 $animate: $animate 24628 }); 24629 24630 /** 24631 * @ngdoc method 24632 * @name ngModel.NgModelController#$setPristine 24633 * 24634 * @description 24635 * Sets the control to its pristine state. 24636 * 24637 * This method can be called to remove the `ng-dirty` class and set the control to its pristine 24638 * state (`ng-pristine` class). A model is considered to be pristine when the control 24639 * has not been changed from when first compiled. 24640 */ 24641 this.$setPristine = function() { 24642 ctrl.$dirty = false; 24643 ctrl.$pristine = true; 24644 $animate.removeClass($element, DIRTY_CLASS); 24645 $animate.addClass($element, PRISTINE_CLASS); 24646 }; 24647 24648 /** 24649 * @ngdoc method 24650 * @name ngModel.NgModelController#$setDirty 24651 * 24652 * @description 24653 * Sets the control to its dirty state. 24654 * 24655 * This method can be called to remove the `ng-pristine` class and set the control to its dirty 24656 * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed 24657 * from when first compiled. 24658 */ 24659 this.$setDirty = function() { 24660 ctrl.$dirty = true; 24661 ctrl.$pristine = false; 24662 $animate.removeClass($element, PRISTINE_CLASS); 24663 $animate.addClass($element, DIRTY_CLASS); 24664 parentForm.$setDirty(); 24665 }; 24666 24667 /** 24668 * @ngdoc method 24669 * @name ngModel.NgModelController#$setUntouched 24670 * 24671 * @description 24672 * Sets the control to its untouched state. 24673 * 24674 * This method can be called to remove the `ng-touched` class and set the control to its 24675 * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched 24676 * by default, however this function can be used to restore that state if the model has 24677 * already been touched by the user. 24678 */ 24679 this.$setUntouched = function() { 24680 ctrl.$touched = false; 24681 ctrl.$untouched = true; 24682 $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS); 24683 }; 24684 24685 /** 24686 * @ngdoc method 24687 * @name ngModel.NgModelController#$setTouched 24688 * 24689 * @description 24690 * Sets the control to its touched state. 24691 * 24692 * This method can be called to remove the `ng-untouched` class and set the control to its 24693 * touched state (`ng-touched` class). A model is considered to be touched when the user has 24694 * first focused the control element and then shifted focus away from the control (blur event). 24695 */ 24696 this.$setTouched = function() { 24697 ctrl.$touched = true; 24698 ctrl.$untouched = false; 24699 $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS); 24700 }; 24701 24702 /** 24703 * @ngdoc method 24704 * @name ngModel.NgModelController#$rollbackViewValue 24705 * 24706 * @description 24707 * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`, 24708 * which may be caused by a pending debounced event or because the input is waiting for a some 24709 * future event. 24710 * 24711 * If you have an input that uses `ng-model-options` to set up debounced events or events such 24712 * as blur you can have a situation where there is a period when the `$viewValue` 24713 * is out of synch with the ngModel's `$modelValue`. 24714 * 24715 * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue` 24716 * programmatically before these debounced/future events have resolved/occurred, because Angular's 24717 * dirty checking mechanism is not able to tell whether the model has actually changed or not. 24718 * 24719 * The `$rollbackViewValue()` method should be called before programmatically changing the model of an 24720 * input which may have such events pending. This is important in order to make sure that the 24721 * input field will be updated with the new model value and any pending operations are cancelled. 24722 * 24723 * <example name="ng-model-cancel-update" module="cancel-update-example"> 24724 * <file name="app.js">
24725 * angular.module('cancel-update-example', []) 24726 * 24727 * .controller('CancelUpdateController', ['$scope', function($scope) { 24728 * $scope.resetWithCancel = function(e) { 24729 * if (e.keyCode == 27) { 24730 * $scope.myForm.myInput1.$rollbackViewValue(); 24731 * $scope.myValue = ''; 24732 * } 24733 * }; 24734 * $scope.resetWithoutCancel = function(e) { 24735 * if (e.keyCode == 27) { 24736 * $scope.myValue = ''; 24737 * } 24738 * }; 24739 * }]); 24740 * </file> 24741 * <file name="index.html"> 24742 * <div ng-controller="CancelUpdateController"> 24743 * <p>Try typing something in each input. See that the model only updates when you 24744 * blur off the input. 24745 * </p> 24746 * <p>Now see what happens if you start typing then press the Escape key</p> 24747 * 24748 * <form name="myForm" ng-model-options="{ updateOn: 'blur' }"> 24749 * <p id="inputDescription1">With $rollbackViewValue()</p> 24750 * <input name="myInput1" aria-describedby="inputDescription1" ng-model="myValue" 24751 * ng-keydown="resetWithCancel($event)"><br/> 24752 * myValue: "{{ myValue }}" 24753 * 24754 * <p id="inputDescription2">Without $rollbackViewValue()</p> 24755 * <input name="myInput2" aria-describedby="inputDescription2" ng-model="myValue" 24756 * ng-keydown="resetWithoutCancel($event)"><br/> 24757 * myValue: "{{ myValue }}" 24758 * </form> 24759 * </div> 24760 * </file> 24761 * </example> 24762 */ 24763 this.$rollbackViewValue = function() { 24764 $timeout.cancel(pendingDebounce); 24765 ctrl.$viewValue = ctrl.$$lastCommittedViewValue; 24766 ctrl.$render(); 24767 }; 24768 24769 /** 24770 * @ngdoc method 24771 * @name ngModel.NgModelController#$validate 24772 * 24773 * @description 24774 * Runs each of the registered validators (first synchronous validators and then 24775 * asynchronous validators). 24776 * If the validity changes to invalid, the model will be set to `undefined`, 24777 * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`. 24778 * If the validity changes to valid, it will set the model to the last available valid 24779 * `$modelValue`, i.e. either the last parsed value or the last value set from the scope. 24780 */ 24781 this.$validate = function() { 24782 // ignore $validate before model is initialized 24783 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 24784 return; 24785 } 24786 24787 var viewValue = ctrl.$$lastCommittedViewValue; 24788 // Note: we use the $$rawModelValue as $modelValue might have been 24789 // set to undefined during a view -> model update that found validation 24790 // errors. We can't parse the view here, since that could change 24791 // the model although neither viewValue nor the model on the scope changed 24792 var modelValue = ctrl.$$rawModelValue; 24793 24794 var prevValid = ctrl.$valid; 24795 var prevModelValue = ctrl.$modelValue; 24796 24797 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 24798 24799 ctrl.$$runValidators(modelValue, viewValue, function(allValid) { 24800 // If there was no change in validity, don't update the model 24801 // This prevents changing an invalid modelValue to undefined 24802 if (!allowInvalid && prevValid !== allValid) { 24803 // Note: Don't check ctrl.$valid here, as we could have 24804 // external validators (e.g. calculated on the server), 24805 // that just call $setValidity and need the model value 24806 // to calculate their validity. 24807 ctrl.$modelValue = allValid ? modelValue : undefined; 24808 24809 if (ctrl.$modelValue !== prevModelValue) { 24810 ctrl.$$writeModelToScope(); 24811 } 24812 } 24813 }); 24814 24815 }; 24816 24817 this.$$runValidators = function(modelValue, viewValue, doneCallback) { 24818 currentValidationRunId++; 24819 var localValidationRunId = currentValidationRunId; 24820 24821 // check parser error 24822 if (!processParseErrors()) { 24823 validationDone(false); 24824 return; 24825 } 24826 if (!processSyncValidators()) { 24827 validationDone(false); 24828 return; 24829 } 24830 processAsyncValidators(); 24831 24832 function processParseErrors() { 24833 var errorKey = ctrl.$$parserName || 'parse'; 24834 if (parserValid === undefined) {
24835 setValidity(errorKey, null); 24836 } else { 24837 if (!parserValid) { 24838 forEach(ctrl.$validators, function(v, name) { 24839 setValidity(name, null); 24840 }); 24841 forEach(ctrl.$asyncValidators, function(v, name) { 24842 setValidity(name, null); 24843 }); 24844 } 24845 // Set the parse error last, to prevent unsetting it, should a $validators key == parserName 24846 setValidity(errorKey, parserValid); 24847 return parserValid; 24848 } 24849 return true; 24850 } 24851 24852 function processSyncValidators() { 24853 var syncValidatorsValid = true; 24854 forEach(ctrl.$validators, function(validator, name) { 24855 var result = validator(modelValue, viewValue); 24856 syncValidatorsValid = syncValidatorsValid && result; 24857 setValidity(name, result); 24858 }); 24859 if (!syncValidatorsValid) { 24860 forEach(ctrl.$asyncValidators, function(v, name) { 24861 setValidity(name, null); 24862 }); 24863 return false; 24864 } 24865 return true; 24866 } 24867 24868 function processAsyncValidators() { 24869 var validatorPromises = []; 24870 var allValid = true; 24871 forEach(ctrl.$asyncValidators, function(validator, name) { 24872 var promise = validator(modelValue, viewValue); 24873 if (!isPromiseLike(promise)) { 24874 throw $ngModelMinErr("$asyncValidators", 24875 "Expected asynchronous validator to return a promise but got '{0}' instead.", promise); 24876 } 24877 setValidity(name, undefined); 24878 validatorPromises.push(promise.then(function() { 24879 setValidity(name, true); 24880 }, function(error) { 24881 allValid = false; 24882 setValidity(name, false); 24883 })); 24884 }); 24885 if (!validatorPromises.length) { 24886 validationDone(true); 24887 } else { 24888 $q.all(validatorPromises).then(function() { 24889 validationDone(allValid); 24890 }, noop); 24891 } 24892 } 24893 24894 function setValidity(name, isValid) { 24895 if (localValidationRunId === currentValidationRunId) { 24896 ctrl.$setValidity(name, isValid); 24897 } 24898 } 24899 24900 function validationDone(allValid) { 24901 if (localValidationRunId === currentValidationRunId) { 24902 24903 doneCallback(allValid); 24904 } 24905 } 24906 }; 24907 24908 /** 24909 * @ngdoc method 24910 * @name ngModel.NgModelController#$commitViewValue 24911 * 24912 * @description 24913 * Commit a pending update to the `$modelValue`. 24914 * 24915 * Updates may be pending by a debounced event or because the input is waiting for a some future 24916 * event defined in `ng-model-options`. this method is rarely needed as `NgModelController` 24917 * usually handles calling this in response to input events. 24918 */ 24919 this.$commitViewValue = function() { 24920 var viewValue = ctrl.$viewValue; 24921 24922 $timeout.cancel(pendingDebounce); 24923 24924 // If the view value has not changed then we should just exit, except in the case where there is 24925 // a native validator on the element. In this case the validation state may have changed even though 24926 // the viewValue has stayed empty. 24927 if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) { 24928 return; 24929 } 24930 ctrl.$$lastCommittedViewValue = viewValue; 24931 24932 // change to dirty 24933 if (ctrl.$pristine) { 24934 this.$setDirty(); 24935 } 24936 this.$$parseAndValidate(); 24937 }; 24938 24939 this.$$parseAndValidate = function() { 24940 var viewValue = ctrl.$$lastCommittedViewValue; 24941 var modelValue = viewValue; 24942 parserValid = isUndefined(modelValue) ? undefined : true; 24943 24944 if (parserValid) { 24945 for (var i = 0; i < ctrl.$parsers.length; i++) { 24946 modelValue = ctrl.$parsers[i](modelValue); 24947 if (isUndefined(modelValue)) { 24948 parserValid = false; 24949 break; 24950 } 24951 } 24952 } 24953 if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { 24954 // ctrl.$modelValue has not been touched yet... 24955 ctrl.$modelValue = ngModelGet($scope); 24956 } 24957 var prevModelValue = ctrl.$modelValue; 24958 var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; 24959 ctrl.$$rawModelValue = modelValue; 24960 24961 if (allowInvalid) { 24962 ctrl.$modelValue = modelValue; 24963 writeToModelIfNeeded(); 24964 } 24965 24966 // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. 24967 // This can happen if e.g. $setViewValue is called from inside a parser 24968 ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { 24969 if (!allowInvalid) { 24970 // Note: Don't check ctrl.$valid here, as we could have 24971 // external validators (e.g. calculated on the server), 24972 // that just call $setValidity and need the model value 24973 // to calculate their validity. 24974 ctrl.$modelValue = allValid ? modelValue : undefined; 24975 writeToModelIfNeeded(); 24976 } 24977 }); 24978 24979 function writeToModelIfNeeded() { 24980 if (ctrl.$modelValue !== prevModelValue) { 24981 ctrl.$$writeModelToScope(); 24982 } 24983 } 24984 }; 24985 24986 this.$$writeModelToScope = function() { 24987 ngModelSet($scope, ctrl.$modelValue);
24988 forEach(ctrl.$viewChangeListeners, function(listener) { 24989 try { 24990 listener(); 24991 } catch (e) { 24992 $exceptionHandler(e); 24993 } 24994 }); 24995 }; 24996 24997 /** 24998 * @ngdoc method 24999 * @name ngModel.NgModelController#$setViewValue 25000 * 25001 * @description 25002 * Update the view value. 25003 * 25004 * This method should be called when an input directive want to change the view value; typically, 25005 * this is done from within a DOM event handler. 25006 * 25007 * For example {@link ng.directive:input input} calls it when the value of the input changes and 25008 * {@link ng.directive:select select} calls it when an option is selected. 25009 * 25010 * If the new `value` is an object (rather than a string or a number), we should make a copy of the 25011 * object before passing it to `$setViewValue`. This is because `ngModel` does not perform a deep 25012 * watch of objects, it only looks for a change of identity. If you only change the property of 25013 * the object then ngModel will not realise that the object has changed and will not invoke the 25014 * `$parsers` and `$validators` pipelines. 25015 * 25016 * For this reason, you should not change properties of the copy once it has been passed to 25017 * `$setViewValue`. Otherwise you may cause the model value on the scope to change incorrectly. 25018 * 25019 * When this method is called, the new `value` will be staged for committing through the `$parsers` 25020 * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged 25021 * value sent directly for processing, finally to be applied to `$modelValue` and then the 25022 * **expression** specified in the `ng-model` attribute. 25023 * 25024 * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called. 25025 * 25026 * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn` 25027 * and the `default` trigger is not listed, all those actions will remain pending until one of the 25028 * `updateOn` events is triggered on the DOM element. 25029 * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions} 25030 * directive is used with a custom debounce for this particular event. 25031 * 25032 * Note that calling this function does not trigger a `$digest`. 25033 * 25034 * @param {string} value Value from the view. 25035 * @param {string} trigger Event that triggered the update. 25036 */ 25037 this.$setViewValue = function(value, trigger) { 25038 ctrl.$viewValue = value; 25039 if (!ctrl.$options || ctrl.$options.updateOnDefault) { 25040 ctrl.$$debounceViewValueCommit(trigger); 25041 } 25042 }; 25043 25044 this.$$debounceViewValueCommit = function(trigger) { 25045 var debounceDelay = 0, 25046 options = ctrl.$options, 25047 debounce; 25048 25049 if (options && isDefined(options.debounce)) { 25050 debounce = options.debounce; 25051 if (isNumber(debounce)) { 25052 debounceDelay = debounce; 25053 } else if (isNumber(debounce[trigger])) { 25054 debounceDelay = debounce[trigger]; 25055 } else if (isNumber(debounce['default'])) { 25056 debounceDelay = debounce['default']; 25057 } 25058 } 25059 25060 $timeout.cancel(pendingDebounce); 25061 if (debounceDelay) { 25062 pendingDebounce = $timeout(function() { 25063 ctrl.$commitViewValue(); 25064 }, debounceDelay); 25065 } else if ($rootScope.$$phase) { 25066 ctrl.$commitViewValue(); 25067 } else { 25068 $scope.$apply(function() { 25069 ctrl.$commitViewValue(); 25070 }); 25071 } 25072 }; 25073 25074 // model -> value 25075 // Note: we cannot use a normal scope.$watch as we want to detect the following: 25076 // 1. scope value is 'a' 25077 // 2. user enters 'b' 25078 // 3. ng-change kicks in and reverts scope value to 'a' 25079 // -> scope value did not change since the last digest as 25080 // ng-change executes in apply phase 25081 // 4. view should be changed back to 'a' 25082 $scope.$watch(function ngModelWatch() { 25083 var modelValue = ngModelGet($scope); 25084 25085 // if scope model value and ngModel value are out of sync 25086 // TODO(perf): why not move this to the action fn? 25087 if (modelValue !== ctrl.$modelValue && 25088 // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator 25089 (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue) 25090 ) { 25091 ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; 25092 parserValid = undefined; 25093 25094 var formatters = ctrl.$formatters, 25095 idx = formatters.length; 25096 25097 var viewValue = modelValue; 25098 while (idx--) { 25099 viewValue = formatters[idx](viewValue); 25100 } 25101 if (ctrl.$viewValue !== viewValue) { 25102 ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; 25103 ctrl.$render(); 25104 25105 ctrl.$$runValidators(modelValue, viewValue, noop); 25106 } 25107 } 25108 25109 return modelValue; 25110 }); 25111}]; 25112 25113 25114/** 25115 * @ngdoc directive 25116 * @name ngModel 25117 * 25118 * @element input 25119 * @priority 1 25120 * 25121 * @description 25122 * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a 25123 * property on the scope using {@link ngModel.NgModelController NgModelController}, 25124 * which is created and exposed by this directive. 25125 * 25126 * `ngModel` is responsible for: 25127 * 25128 * - Binding the view into the model, which other directives such as `input`, `textarea` or `select` 25129 * require. 25130 * - Providing validation behavior (i.e. required, number, email, url). 25131 * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors). 25132 * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations. 25133 * - Registering the control with its parent {@link ng.directive:form form}. 25134 *
25135 * Note: `ngModel` will try to bind to the property given by evaluating the expression on the 25136 * current scope. If the property doesn't already exist on this scope, it will be created 25137 * implicitly and added to the scope. 25138 * 25139 * For best practices on using `ngModel`, see: 25140 * 25141 * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes) 25142 * 25143 * For basic examples, how to use `ngModel`, see: 25144 * 25145 * - {@link ng.directive:input input} 25146 * - {@link input[text] text} 25147 * - {@link input[checkbox] checkbox} 25148 * - {@link input[radio] radio} 25149 * - {@link input[number] number} 25150 * - {@link input[email] email} 25151 * - {@link input[url] url} 25152 * - {@link input[date] date} 25153 * - {@link input[datetime-local] datetime-local} 25154 * - {@link input[time] time} 25155 * - {@link input[month] month} 25156 * - {@link input[week] week} 25157 * - {@link ng.directive:select select} 25158 * - {@link ng.directive:textarea textarea} 25159 * 25160 * # CSS classes 25161 * The following CSS classes are added and removed on the associated input/select/textarea element 25162 * depending on the validity of the model. 25163 * 25164 * - `ng-valid`: the model is valid 25165 * - `ng-invalid`: the model is invalid 25166 * - `ng-valid-[key]`: for each valid key added by `$setValidity` 25167 * - `ng-invalid-[key]`: for each invalid key added by `$setValidity` 25168 * - `ng-pristine`: the control hasn't been interacted with yet 25169 * - `ng-dirty`: the control has been interacted with 25170 * - `ng-touched`: the control has been blurred 25171 * - `ng-untouched`: the control hasn't been blurred 25172 * - `ng-pending`: any `$asyncValidators` are unfulfilled 25173 * 25174 * Keep in mind that ngAnimate can detect each of these classes when added and removed. 25175 * 25176 * ## Animation Hooks 25177 * 25178 * Animations within models are triggered when any of the associated CSS classes are added and removed 25179 * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`, 25180 * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself. 25181 * The animations that are triggered within ngModel are similar to how they work in ngClass and 25182 * animations can be hooked into using CSS transitions, keyframes as well as JS animations. 25183 * 25184 * The following example shows a simple way to utilize CSS transitions to style an input element 25185 * that has been rendered as invalid after it has been validated: 25186 * 25187 * <pre> 25188 * //be sure to include ngAnimate as a module to hook into more 25189 * //advanced animations 25190 * .my-input { 25191 * transition:0.5s linear all; 25192 * background: white; 25193 * } 25194 * .my-input.ng-invalid { 25195 * background: red; 25196 * color:white; 25197 * } 25198 * </pre> 25199 * 25200 * @example 25201 * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample"> 25202 <file name="index.html"> 25203 <script> 25204 angular.module('inputExample', []) 25205 .controller('ExampleController', ['$scope', function($scope) { 25206 $scope.val = '1'; 25207 }]); 25208 </script> 25209 <style> 25210 .my-input { 25211 -webkit-transition:all linear 0.5s; 25212 transition:all linear 0.5s; 25213 background: transparent; 25214 } 25215 .my-input.ng-invalid { 25216 color:white; 25217 background: red; 25218 } 25219 </style> 25220 <p id="inputDescription"> 25221 Update input to see transitions when valid/invalid. 25222 Integer is a valid value. 25223 </p>
25224 <form name="testForm" ng-controller="ExampleController"> 25225 <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" 25226 aria-describedby="inputDescription" /> 25227 </form> 25228 </file> 25229 * </example> 25230 * 25231 * ## Binding to a getter/setter 25232 * 25233 * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a 25234 * function that returns a representation of the model when called with zero arguments, and sets 25235 * the internal state of a model when called with an argument. It's sometimes useful to use this 25236 * for models that have an internal representation that's different from what the model exposes 25237 * to the view. 25238 * 25239 * <div class="alert alert-success"> 25240 * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more 25241 * frequently than other parts of your code. 25242 * </div> 25243 * 25244 * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that 25245 * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to 25246 * a `<form>`, which will enable this behavior for all `<input>`s within it. See 25247 * {@link ng.directive:ngModelOptions `ngModelOptions`} for more. 25248 * 25249 * The following example shows how to use `ngModel` with a getter/setter: 25250 * 25251 * @example 25252 * <example name="ngModel-getter-setter" module="getterSetterExample"> 25253 <file name="index.html">
25254 <div ng-controller="ExampleController"> 25255 <form name="userForm"> 25256 <label>Name: 25257 <input type="text" name="userName" 25258 ng-model="user.name" 25259 ng-model-options="{ getterSetter: true }" /> 25260 </label> 25261 </form> 25262 <pre>user.name = <span ng-bind="user.name()"></span></pre> 25263 </div> 25264 </file> 25265 <file name="app.js"> 25266 angular.module('getterSetterExample', []) 25267 .controller('ExampleController', ['$scope', function($scope) { 25268 var _name = 'Brian'; 25269 $scope.user = { 25270 name: function(newName) { 25271 // Note that newName can be undefined for two reasons: 25272 // 1. Because it is called as a getter and thus called with no arguments 25273 // 2. Because the property should actually be set to undefined. This happens e.g. if the 25274 // input is invalid 25275 return arguments.length ? (_name = newName) : _name; 25276 } 25277 }; 25278 }]); 25279 </file> 25280 * </example> 25281 */ 25282var ngModelDirective = ['$rootScope', function($rootScope) { 25283 return { 25284 restrict: 'A', 25285 require: ['ngModel', '^?form', '^?ngModelOptions'], 25286 controller: NgModelController, 25287 // Prelink needs to run before any input directive 25288 // so that we can set the NgModelOptions in NgModelController 25289 // before anyone else uses it. 25290 priority: 1, 25291 compile: function ngModelCompile(element) { 25292 // Setup initial state of the control 25293 element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS); 25294 25295 return { 25296 pre: function ngModelPreLink(scope, element, attr, ctrls) { 25297 var modelCtrl = ctrls[0], 25298 formCtrl = ctrls[1] || nullFormCtrl; 25299 25300 modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options); 25301 25302 // notify others, especially parent forms 25303 formCtrl.$addControl(modelCtrl); 25304 25305 attr.$observe('name', function(newValue) { 25306 if (modelCtrl.$name !== newValue) { 25307 formCtrl.$$renameControl(modelCtrl, newValue); 25308 } 25309 }); 25310 25311 scope.$on('$destroy', function() { 25312 formCtrl.$removeControl(modelCtrl); 25313 }); 25314 }, 25315 post: function ngModelPostLink(scope, element, attr, ctrls) { 25316 var modelCtrl = ctrls[0]; 25317 if (modelCtrl.$options && modelCtrl.$options.updateOn) { 25318 element.on(modelCtrl.$options.updateOn, function(ev) { 25319 modelCtrl.$$debounceViewValueCommit(ev && ev.type); 25320 }); 25321 } 25322 25323 element.on('blur', function(ev) { 25324 if (modelCtrl.$touched) return; 25325 25326 if ($rootScope.$$phase) { 25327 scope.$evalAsync(modelCtrl.$setTouched); 25328 } else { 25329 scope.$apply(modelCtrl.$setTouched); 25330 } 25331 }); 25332 } 25333 }; 25334 } 25335 }; 25336}]; 25337 25338var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/; 25339 25340/** 25341 * @ngdoc directive 25342 * @name ngModelOptions 25343 * 25344 * @description 25345 * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of 25346 * events that will trigger a model update and/or a debouncing delay so that the actual update only 25347 * takes place when a timer expires; this timer will be reset after another change takes place. 25348 * 25349 * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might 25350 * be different from the value in the actual model. This means that if you update the model you 25351 * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in 25352 * order to make sure it is synchronized with the model and that any debounced action is canceled. 25353 * 25354 * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`} 25355 * method is by making sure the input is placed inside a form that has a `name` attribute. This is 25356 * important because `form` controllers are published to the related scope under the name in their 25357 * `name` attribute. 25358 * 25359 * Any pending changes will take place immediately when an enclosing form is submitted via the 25360 * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit` 25361 * to have access to the updated model. 25362 * 25363 * `ngModelOptions` has an effect on the element it's declared on and its desce
25363ndants. 25364 * 25365 * @param {Object} ngModelOptions options to apply to the current model. Valid keys are: 25366 * - `updateOn`: string specifying which event should the input be bound to. You can set several 25367 * events using an space delimited list. There is a special event called `default` that 25368 * matches the default events belonging of the control. 25369 * - `debounce`: integer value which contains the debounce model update value in milliseconds. A 25370 * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a 25371 * custom value for each event. For example: 25372 * `ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 500, 'blur': 0 } }"` 25373 * - `allowInvalid`: boolean value which indicates that the model can be set with values that did 25374 * not validate correctly instead of the default behavior of setting the model to undefined. 25375 * - `getterSetter`: boolean value which determines whether or not to treat functions bound to 25376 `ngModel` as getters/setters. 25377 * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for 25378 * `<input type="date">`, `<input type="time">`, ... . It understands UTC/GMT and the 25379 * continental US time zone abbreviations, but for general use, use a time zone offset, for 25380 * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian) 25381 * If not specified, the timezone of the browser will be used. 25382 * 25383 * @example 25384 25385 The following example shows how to override immediate updates. Changes on the inputs within the 25386 form will update the model only when the control loses focus (blur event). If `escape` key is 25387 pressed while the input field is focused, the value is reset to the value in the current model. 25388 25389 <example name="ngModelOptions-directive-blur" module="optionsExample"> 25390 <file name="index.html">
25391 <div ng-controller="ExampleController"> 25392 <form name="userForm"> 25393 <label>Name: 25394 <input type="text" name="userName" 25395 ng-model="user.name" 25396 ng-model-options="{ updateOn: 'blur' }" 25397 ng-keyup="cancel($event)" /> 25398 </label><br /> 25399 <label>Other data: 25400 <input type="text" ng-model="user.data" /> 25401 </label><br /> 25402 </form> 25403 <pre>user.name = <span ng-bind="user.name"></span></pre> 25404 </div> 25405 </file> 25406 <file name="app.js"> 25407 angular.module('optionsExample', []) 25408 .controller('ExampleController', ['$scope', function($scope) { 25409 $scope.user = { name: 'say', data: '' }; 25410 25411 $scope.cancel = function(e) { 25412 if (e.keyCode == 27) { 25413 $scope.userForm.userName.$rollbackViewValue(); 25414 } 25415 }; 25416 }]); 25417 </file> 25418 <file name="protractor.js" type="protractor"> 25419 var model = element(by.binding('user.name')); 25420 var input = element(by.model('user.name')); 25421 var other = element(by.model('user.data')); 25422 25423 it('should allow custom events', function() { 25424 input.sendKeys(' hello'); 25425 input.click(); 25426 expect(model.getText()).toEqual('say'); 25427 other.click(); 25428 expect(model.getText()).toEqual('say hello'); 25429 }); 25430 25431 it('should $rollbackViewValue when model changes', function() { 25432 input.sendKeys(' hello'); 25433 expect(input.getAttribute('value')).toEqual('say hello'); 25434 input.sendKeys(protractor.Key.ESCAPE); 25435 expect(input.getAttribute('value')).toEqual('say'); 25436 other.click(); 25437 expect(model.getText()).toEqual('say'); 25438 }); 25439 </file> 25440 </example> 25441 25442 This one shows how to debounce model changes. Model will be updated only 1 sec after last change. 25443 If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty. 25444 25445 <example name="ngModelOptions-directive-debounce" module="optionsExample"> 25446 <file name="index.html">
25447 <div ng-controller="ExampleController"> 25448 <form name="userForm"> 25449 <label>Name: 25450 <input type="text" name="userName" 25451 ng-model="user.name" 25452 ng-model-options="{ debounce: 1000 }" /> 25453 </label> 25454 <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button> 25455 <br /> 25456 </form> 25457 <pre>user.name = <span ng-bind="user.name"></span></pre> 25458 </div> 25459 </file> 25460 <file name="app.js"> 25461 angular.module('optionsExample', []) 25462 .controller('ExampleController', ['$scope', function($scope) { 25463 $scope.user = { name: 'say' }; 25464 }]); 25465 </file> 25466 </example> 25467 25468 This one shows how to bind to getter/setters: 25469 25470 <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample"> 25471 <file name="index.html"> 25472 <div ng-controller="ExampleController"> 25473 <form name="userForm"> 25474 <label>Name: 25475 <input type="text" name="userName" 25476 ng-model="user.name" 25477 ng-model-options="{ getterSetter: true }" /> 25478 </label> 25479 </form> 25480 <pre>user.name = <span ng-bind="user.name()"></span></pre> 25481 </div> 25482 </file> 25483 <file name="app.js"> 25484 angular.module('getterSetterExample', []) 25485 .controller('ExampleController', ['$scope', function($scope) { 25486 var _name = 'Brian'; 25487 $scope.user = { 25488 name: function(newName) { 25489 // Note that newName can be undefined for two reasons: 25490 // 1. Because it is called as a getter and thus called with no arguments 25491 // 2. Because the property should actually be set to undefined. This happens e.g. if the 25492 // input is invalid 25493 return arguments.length ? (_name = newName) : _name; 25494 } 25495 }; 25496 }]); 25497 </file> 25498 </example> 25499 */ 25500var ngModelOptionsDirective = function() { 25501 return { 25502 restrict: 'A', 25503 controller: ['$scope', '$attrs', function($scope, $attrs) { 25504 var that = this; 25505 this.$options = copy($scope.$eval($attrs.ngModelOptions)); 25506 // Allow adding/overriding bound events 25507 if (this.$options.updateOn !== undefined) { 25508 this.$options.updateOnDefault = false;
25509 // extract "default" pseudo-event from list of events that can trigger a model update 25510 this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() { 25511 that.$options.updateOnDefault = true; 25512 return ' '; 25513 })); 25514 } else { 25515 this.$options.updateOnDefault = true; 25516 } 25517 }] 25518 }; 25519}; 25520 25521 25522 25523// helper methods 25524function addSetValidityMethod(context) { 25525 var ctrl = context.ctrl, 25526 $element = context.$element, 25527 classCache = {}, 25528 set = context.set, 25529 unset = context.unset, 25530 parentForm = context.parentForm, 25531 $animate = context.$animate; 25532 25533 classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS)); 25534 25535 ctrl.$setValidity = setValidity; 25536 25537 function setValidity(validationErrorKey, state, controller) { 25538 if (state === undefined) { 25539 createAndSet('$pending', validationErrorKey, controller); 25540 } else { 25541 unsetAndCleanup('$pending', validationErrorKey, controller); 25542 } 25543 if (!isBoolean(state)) { 25544 unset(ctrl.$error, validationErrorKey, controller); 25545 unset(ctrl.$$success, validationErrorKey, controller); 25546 } else { 25547 if (state) { 25548 unset(ctrl.$error, validationErrorKey, controller); 25549 set(ctrl.$$success, validationErrorKey, controller); 25550 } else { 25551 set(ctrl.$error, validationErrorKey, controller); 25552 unset(ctrl.$$success, validationErrorKey, controller); 25553 } 25554 } 25555 if (ctrl.$pending) { 25556 cachedToggleClass(PENDING_CLASS, true); 25557 ctrl.$valid = ctrl.$invalid = undefined; 25558 toggleValidationCss('', null); 25559 } else { 25560 cachedToggleClass(PENDING_CLASS, false); 25561 ctrl.$valid = isObjectEmpty(ctrl.$error); 25562 ctrl.$invalid = !ctrl.$valid; 25563 toggleValidationCss('', ctrl.$valid); 25564 } 25565 25566 // re-read the state as the set/unset methods could have 25567 // combined state in ctrl.$error[validationError] (used for forms), 25568 // where setting/unsetting only increments/decrements the value, 25569 // and does not replace it. 25570 var combinedState; 25571 if (ctrl.$pending && ctrl.$pending[validationErrorKey]) { 25572 combinedState = undefined; 25573 } else if (ctrl.$error[validationErrorKey]) { 25574 combinedState = false; 25575 } else if (ctrl.$$success[validationErrorKey]) { 25576 combinedState = true; 25577 } else { 25578 combinedState = null; 25579 } 25580
25581 toggleValidationCss(validationErrorKey, combinedState); 25582 parentForm.$setValidity(validationErrorKey, combinedState, ctrl); 25583 } 25584 25585 function createAndSet(name, value, controller) { 25586 if (!ctrl[name]) { 25587 ctrl[name] = {}; 25588 } 25589 set(ctrl[name], value, controller); 25590 } 25591 25592 function unsetAndCleanup(name, value, controller) { 25593 if (ctrl[name]) { 25594 unset(ctrl[name], value, controller); 25595 } 25596 if (isObjectEmpty(ctrl[name])) { 25597 ctrl[name] = undefined; 25598 } 25599 } 25600 25601 function cachedToggleClass(className, switchValue) { 25602 if (switchValue && !classCache[className]) { 25603 $animate.addClass($element, className); 25604 classCache[className] = true; 25605 } else if (!switchValue && classCache[className]) { 25606 $animate.removeClass($element, className); 25607 classCache[className] = false; 25608 } 25609 } 25610 25611 function toggleValidationCss(validationErrorKey, isValid) { 25612 validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : ''; 25613 25614 cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true); 25615 cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false); 25616 } 25617} 25618 25619function isObjectEmpty(obj) { 25620 if (obj) { 25621 for (var prop in obj) { 25622 if (obj.hasOwnProperty(prop)) { 25623 return false; 25624 } 25625 } 25626 } 25627 return true; 25628} 25629 25630/** 25631 * @ngdoc directive 25632 * @name ngNonBindable 25633 * @restrict AC 25634 * @priority 1000 25635 * 25636 * @description 25637 * The `ngNonBindable` directive tells Angular not to compile or bind the contents of the current 25638 * DOM element. This is useful if the element contains what appears to be Angular directives and 25639 * bindings but which should be ignored by Angular. This could be the case if you have a site that 25640 * displays snippets of code, for instance. 25641 * 25642 * @element ANY 25643 * 25644 * @example 25645 * In this example there are two locations where a simple interpolation binding (`{{}}`) is present, 25646 * but the one wrapped in `ngNonBindable` is left alone. 25647 * 25648 * @example 25649 <example> 25650 <file name="index.html"> 25651 <div>Normal: {{1 + 2}}</div> 25652 <div ng-non-bindable>Ignored: {{1 + 2}}</div> 25653 </file> 25654 <file name="protractor.js" type="protractor"> 25655 it('should check ng-non-bindable', function() { 25656 expect(element(by.binding('1 + 2')).getText()).toContain('3'); 25657 expect(element.all(by.css('div')).last().getText()).toMatch(/1 \+ 2/); 25658 }); 25659 </file> 25660 </example> 25661 */ 25662var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); 25663 25664/* global jqLiteRemove */ 25665 25666var ngOptionsMinErr = minErr('ngOptions'); 25667 25668/** 25669 * @ngdoc directive 25670 * @name ngOptions 25671 * @restrict A 25672 * 25673 * @description 25674 * 25675 * The `ngOptions` attribute can be used to dynamically generate a list of `<option>` 25676 * elements for the `<select>` element using the array or object obtained by evaluating the 25677 * `ngOptions` comprehension expression. 25678 * 25679 * In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a 25680 * similar result. However, `ngOptions` provides some benefits such as reducing memory and 25681 * increasing speed by not creating a new scope for each repeated instance, as well as providing 25682 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 25683 * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound 25684 * to a non-string value. This is because an option element can only be bound to string values at 25685 * present. 25686 * 25687 * When an item in the `<select>` menu is selected, the array element or object property 25688 * represented by the selected option will be bound to the model identified by the `ngModel` 25689 * directive. 25690 * 25691 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 25692 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 25693 * option. See example below for demonstration. 25694 * 25695 * ## Complex Models (objects or collections) 25696 * 25697 * **Note:** By default, `ngModel` watches the model by reference, not value. This is important when 25698 * binding any input directive to a model that is an object or a collection. 25699 * 25700 * Since this is a common situation for `ngOptions` the directive additionally watches the model using 25701 * `$watchCollection` when the select has the `multiple` attribute or when there is a `track by` clause in 25702 * the options expression. This allows ngOptions to trigger a re-rendering of the options even if the actual 25703 * object/collection has not changed identity but only a property on the object or an item in the collection 25704 * changes. 25705 *
25706 * Note that `$watchCollection` does a shallow comparison of the properties of the object (or the items in the collection 25707 * if the model is an array). This means that changing a property deeper inside the object/collection that the 25708 * first level will not trigger a re-rendering. 25709 * 25710 * 25711 * ## `select` **`as`** 25712 * 25713 * Using `select` **`as`** will bind the result of the `select` expression to the model, but 25714 * the value of the `<select>` and `<option>` html elements will be either the index (for array data sources) 25715 * or property name (for object data sources) of the value within the collection. If a **`track by`** expression 25716 * is used, the result of that expression will be set as the value of the `option` and `select` elements. 25717 * 25718 * 25719 * ### `select` **`as`** and **`track by`** 25720 * 25721 * <div class="alert alert-warning"> 25722 * Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together. 25723 * </div> 25724 * 25725 * Consider the following example: 25726 * 25727 * ```html 25728 * <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"> 25729 * ``` 25730 * 25731 * ```js 25732 * $scope.values = [{ 25733 * id: 1, 25734 * label: 'aLabel', 25735 * subItem: { name: 'aSubItem' } 25736 * }, { 25737 * id: 2, 25738 * label: 'bLabel', 25739 * subItem: { name: 'bSubItem' } 25740 * }]; 25741 * 25742 * $scope.selected = { name: 'aSubItem' }; 25743 * ``` 25744 * 25745 * With the purpose of preserving the selection, the **`track by`** expression is always applied to the element 25746 * of the data source (to `item` in this example). To calculate whether an element is selected, we do the 25747 * following: 25748 * 25749 * 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]` 25750 * 2. Apply **`track by`** to the already selected value in `ngModel`. 25751 * In the example: this is not possible as **`track by`** refers to `item.id`, but the selected 25752 * value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to 25753 * a wrong object, the selected element can't be found, `<select>` is always reset to the "not 25754 * selected" option. 25755 * 25756 * 25757 * @param {string} ngModel Assignable angular expression to data-bind to. 25758 * @param {string=} name Property name of the form under which the control is published. 25759 * @param {string=} required The control is considered valid only if value is entered. 25760 * @param {string=} ngRequired Adds `required` attribute and `required` validation constraint to 25761 * the element when the ngRequired expression evaluates to true. Use `ngRequired` instead of 25762 * `required` when you want to data-bind to the `required` attribute. 25763 * @param {comprehension_expression=} ngOptions in one of the following forms: 25764 * 25765 * * for array data sources: 25766 * * `label` **`for`** `value` **`in`** `array` 25767 * * `select` **`as`** `label` **`for`** `value` **`in`** `array` 25768 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` 25769 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` 25770 * * `label` **`group by`** `group` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 25771 * * `label` **`disable when`** `disable` **`for`** `value` **`in`** `array` **`track by`** `trackexpr` 25772 * * `label` **`for`** `value` **`in`** `array` | orderBy:`orderexpr` **`track by`** `trackexpr` 25773 * (for including a filter with `track by`) 25774 * * for object data sources: 25775 * * `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 25776 * * `select` **`as`** `label` **`for (`**`key` **`,`** `value`**`) in`** `object` 25777 * * `label` **`group by`** `group` **`for (`**`key`**`,`** `value`**`) in`** `object` 25778 * * `label` **`disable when`** `disable` **`for (`**`key`**`,`** `value`**`) in`** `object` 25779 * * `select` **`as`** `label` **`group by`** `group` 25780 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 25781 * * `select` **`as`** `label` **`disable when`** `disable` 25782 * **`for` `(`**`key`**`,`** `value`**`) in`** `object` 25783 * 25784 * Where: 25785 * 25786 * * `array` / `object`: an expression which evaluates to an array / object to iterate over. 25787 * * `value`: local variable which will refer to each item in the `array` or each property value 25788 * of `object` during iteration. 25789 * * `key`: local variable which will refer to a property name in `object` during iteration. 25790 * * `label`: The result of this expression will be the label for `<option>` element. The 25791 * `expression` will most likely refer to the `value` variable (e.g. `value.propertyName`). 25792 * * `select`: The result of this expression will be bound to the model of the parent `<select>` 25793 * element. If not specified, `select` expression will default to `value`. 25794 * * `group`: The result of this expression will be used to group options using the `<optgroup>` 25795 * DOM element. 25796 * * `disable`: The result of this expression will be used to disable the rendered `<option>` 25797 * element. Return `true` to disable. 25798 * * `trackexpr`: Used when working with an array of objects. The result of this expression will be 25799 * used to identify the objects in the array. The `trackexpr` will most likely refer to the 25800 * `value` variable (e.g. `value.propertyName`). With this the selection is preserved 25801 * even when the options are recreated (e.g. reloaded from the server). 25802 * 25803 * @example 25804 <example module="selectExample"> 25805 <file name="index.html"> 25806 <script> 25807 angular.module('selectExample', [])
25808 .controller('ExampleController', ['$scope', function($scope) { 25809 $scope.colors = [ 25810 {name:'black', shade:'dark'}, 25811 {name:'white', shade:'light', notAnOption: true}, 25812 {name:'red', shade:'dark'}, 25813 {name:'blue', shade:'dark', notAnOption: true}, 25814 {name:'yellow', shade:'light', notAnOption: false} 25815 ]; 25816 $scope.myColor = $scope.colors[2]; // red 25817 }]); 25818 </script> 25819 <div ng-controller="ExampleController"> 25820 <ul> 25821 <li ng-repeat="color in colors"> 25822 <label>Name: <input ng-model="color.name"></label> 25823 <label><input type="checkbox" ng-model="color.notAnOption"> Disabled?</label> 25824 <button ng-click="colors.splice($index, 1)" aria-label="Remove">X</button> 25825 </li> 25826 <li> 25827 <button ng-click="colors.push({})">add</button> 25828 </li> 25829 </ul> 25830 <hr/> 25831 <label>Color (null not allowed): 25832 <select ng-model="myColor" ng-options="color.name for color in colors"></select> 25833 </label><br/> 25834 <label>Color (null allowed): 25835 <span class="nullable"> 25836 <select ng-model="myColor" ng-options="color.name for color in colors"> 25837 <option value="">-- choose color --</option> 25838 </select> 25839 </span></label><br/> 25840 25841 <label>Color grouped by shade: 25842 <select ng-model="myColor" ng-options="color.name group by color.shade for color in colors"> 25843 </select> 25844 </label><br/> 25845 25846 <label>Color grouped by shade, with some disabled: 25847 <select ng-model="myColor" 25848 ng-options="color.name group by color.shade disable when color.notAnOption for color in colors"> 25849 </select> 25850 </label><br/> 25851 25852 25853 25854 Select <button ng-click="myColor = { name:'not in list', shade: 'other' }">bogus</button>. 25855 <br/> 25856 <hr/> 25857 Currently selected: {{ {selected_color:myColor} }} 25858 <div style="border:solid 1px black; height:20px" 25859 ng-style="{'background-color':myColor.name}"> 25860 </div> 25861 </div> 25862 </file> 25863 <file name="protractor.js" type="protractor"> 25864 it('should check ng-options', function() { 25865 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('red'); 25866 element.all(by.model('myColor')).first().click(); 25867 element.all(by.css('select[ng-model="myColor"] option')).first().click(); 25868 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('black'); 25869 element(by.css('.nullable select[ng-model="myColor"]')).click(); 25870 element.all(by.css('.nullable select[ng-model="myColor"] option')).first().click(); 25871 expect(element(by.binding('{selected_color:myColor}')).getText()).toMatch('null'); 25872 }); 25873 </file> 25874 </example> 25875 */ 25876 25877// jshint maxlen: false 25878// //00001111111111000000000002222222222000000000000000000000333333333300000000000000000000000004444444444400000000000005555555555555550000000006666666666666660000000777777777777777000000000000000888888888800000000000000000009999999999 25879var NG_OPTIONS_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\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]+?))?$/; 25880 // 1: value expression (valueFn) 25881 // 2: label expression (displayFn) 25882 // 3: group by expression (groupByFn) 25883 // 4: disable when expression (disableWhenFn) 25884 // 5: array item variable name 25885 // 6: object item key variable name 25886 // 7: object item value variable name 25887 // 8: collection expression 25888 // 9: track by expression 25889// jshint maxlen: 100 25890 25891 25892var ngOptionsDirective = ['$compile', '$parse', function($compile, $parse) { 25893 25894 function parseOptionsExpression(optionsExp, selectElement, scope) { 25895 25896 var match = optionsExp.match(NG_OPTIONS_REGEXP); 25897 if (!(match)) { 25898 throw ngOptionsMinErr('iexp', 25899 "Expected expression in form of " + 25900 "'_select_ (as _label_)? for (_key_,)?_value_ in _collection_'" + 25901 " but got '{0}'. Element: {1}", 25902 optionsExp, startingTag(selectElement)); 25903 } 25904 25905 // Extract the parts from the ngOptions expression 25906 25907 // The variable name for the value of the item in the collection 25908 var valueName = match[5] || match[7]; 25909 // The variable name for the key of the item in the collection 25910 var keyName = match[6]; 25911 25912 // An expression that generates the viewValue for an option if there is a label expression 25913 var selectAs = / as /.test(match[0]) && match[1]; 25914 // An expression that is used to track the id of each object in the options collection 25915 var trackBy = match[9]; 25916 // An expression that generates the viewValue for an option if there is no label expression 25917 var valueFn = $parse(match[2] ? match[1] : valueName); 25918 var selectAsFn = selectAs && $parse(selectAs); 25919 var viewValueFn = selectAsFn || valueFn; 25920 var trackByFn = trackBy && $parse(trackBy); 25921 25922 // Get the value by which we are going to track the option 25923 // if we have a trackFn then use that (passing scope and locals) 25924 // otherwise just hash the given viewValue 25925 var getTrackByValueFn = trackBy ? 25926 function(value, locals) { return trackByFn(scope, locals); } : 25927 function getHashOfValue(value) { return hashKey(value); }; 25928 var getTrackByValue = function(value, key) {
25929 return getTrackByValueFn(value, getLocals(value, key)); 25930 }; 25931 25932 var displayFn = $parse(match[2] || match[1]); 25933 var groupByFn = $parse(match[3] || ''); 25934 var disableWhenFn = $parse(match[4] || ''); 25935 var valuesFn = $parse(match[8]); 25936 25937 var locals = {}; 25938 var getLocals = keyName ? function(value, key) { 25939 locals[keyName] = key; 25940 locals[valueName] = value; 25941 return locals; 25942 } : function(value) { 25943 locals[valueName] = value; 25944 return locals; 25945 }; 25946 25947 25948 function Option(selectValue, viewValue, label, group, disabled) { 25949 this.selectValue = selectValue; 25950 this.viewValue = viewValue; 25951 this.label = label; 25952 this.group = group; 25953 this.disabled = disabled; 25954 } 25955 25956 return { 25957 trackBy: trackBy, 25958 getTrackByValue: getTrackByValue, 25959 getWatchables: $parse(valuesFn, function(values) { 25960 // Create a collection of things that we would like to watch (watchedArray) 25961 // so that they can all be watched using a single $watchCollection 25962 // that only runs the handler once if anything changes 25963 var watchedArray = []; 25964 values = values || []; 25965 25966 Object.keys(values).forEach(function getWatchable(key) { 25967 if (key.charAt(0) === '$') return; 25968 var locals = getLocals(values[key], key); 25969 var selectValue = getTrackByValueFn(values[key], locals); 25970 watchedArray.push(selectValue); 25971 25972 // Only need to watch the displayFn if there is a specific label expression 25973 if (match[2] || match[1]) { 25974 var label = displayFn(scope, locals); 25975 watchedArray.push(label); 25976 } 25977 25978 // Only need to watch the disableWhenFn if there is a specific disable expression 25979 if (match[4]) { 25980 var disableWhen = disableWhenFn(scope, locals); 25981 watchedArray.push(disableWhen); 25982 } 25983 }); 25984 return watchedArray; 25985 }), 25986 25987 getOptions: function() { 25988 25989 var optionItems = []; 25990 var selectValueMap = {}; 25991 25992 // The option values were already computed in the `getWatchables` fn, 25993 // which must have been called to trigger `getOptions` 25994 var optionValues = valuesFn(scope) || []; 25995 var optionValuesKeys; 25996 25997 25998 if (!keyName && isArrayLike(optionValues)) { 25999 optionValuesKeys = optionValues; 26000 } else { 26001 // if object, extract keys, in enumeration order, unsorted 26002 optionValuesKeys = []; 26003 for (var itemKey in optionValues) { 26004 if (optionValues.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 26005 optionValuesKeys.push(itemKey); 26006 } 26007 } 26008 } 26009 26010 var optionValuesLength = optionValuesKeys.length; 26011 26012 for (var index = 0; index < optionValuesLength; index++) { 26013 var key = (optionValues === optionValuesKeys) ? index : optionValuesKeys[index]; 26014 var value = optionValues[key]; 26015 var locals = getLocals(value, key); 26016 var viewValue = viewValueFn(scope, locals); 26017 var selectValue = getTrackByValueFn(viewValue, locals); 26018 var label = displayFn(scope, locals); 26019 var group = groupByFn(scope, locals); 26020 var disabled = disableWhenFn(scope, locals); 26021 var optionItem = new Option(selectValue, viewValue, label, group, disabled); 26022 26023 optionItems.push(optionItem); 26024 selectValueMap[selectValue] = optionItem; 26025 } 26026 26027 return { 26028 items: optionItems, 26029 selectValueMap: selectValueMap, 26030 getOptionFromViewValue: function(value) { 26031 return selectValueMap[getTrackByValue(value)]; 26032 }, 26033 getViewValueFromOption: function(option) { 26034 // If the viewValue could be an object that may be mutated by the application, 26035 // we need to make a copy and not return the reference to the value on the option. 26036 return trackBy ? angular.copy(option.viewValue) : option.viewValue; 26037 } 26038 }; 26039 } 26040 }; 26041 } 26042 26043 26044 // we can't just jqLite('<option>') since jqLite is not smart enough 26045 // to create it in <select> and IE barfs otherwise. 26046 var optionTemplate = document.createElement('option'), 26047 optGroupTemplate = document.createElement('optgroup'); 26048 26049 return { 26050 restrict: 'A', 26051 terminal: true, 26052 require: ['select', '?ngModel'], 26053 link: function(scope, selectElement, attr, ctrls) { 26054 26055 // if ngModel is not defined, we don't need to do anything 26056 var ngModelCtrl = ctrls[1]; 26057 if (!ngModelCtrl) return; 26058 26059 var selectCtrl = ctrls[0]; 26060 var multiple = attr.multiple; 26061 26062 // The emptyOption allows the application developer to provide their own custom "empty" 26063 // option when the viewValue does not match any of the option values. 26064 var emptyOption; 26065 for (var i = 0, children = selectElement.children(), ii = children.length; i < ii; i++) { 26066 if (children[i].value === '') { 26067 emptyOption = children.eq(i); 26068 break; 26069 } 26070 } 26071 26072 var providedEmptyOption = !!emptyOption; 26073 26074 var unknownOption = jqLite(optionTemplate.cloneNode(false)); 26075 unknownOption.val('?'); 26076 26077 var options; 26078 var ngOptions = parseOptionsExpression(attr.ngOptions, selectElement, scope); 26079 26080 26081 var renderEmptyOption = function() { 26082 if (!providedEmptyOption) {
26083 selectElement.prepend(emptyOption); 26084 } 26085 selectElement.val(''); 26086 emptyOption.prop('selected', true); // needed for IE 26087 emptyOption.attr('selected', true); 26088 }; 26089 26090 var removeEmptyOption = function() { 26091 if (!providedEmptyOption) { 26092 emptyOption.remove(); 26093 } 26094 }; 26095 26096 26097 var renderUnknownOption = function() { 26098 selectElement.prepend(unknownOption); 26099 selectElement.val('?'); 26100 unknownOption.prop('selected', true); // needed for IE 26101 unknownOption.attr('selected', true); 26102 }; 26103 26104 var removeUnknownOption = function() { 26105 unknownOption.remove(); 26106 }; 26107 26108 26109 // Update the controller methods for multiple selectable options 26110 if (!multiple) { 26111 26112 selectCtrl.writeValue = function writeNgOptionsValue(value) { 26113 var option = options.getOptionFromViewValue(value); 26114 26115 if (option && !option.disabled) { 26116 if (selectElement[0].value !== option.selectValue) { 26117 removeUnknownOption(); 26118 removeEmptyOption(); 26119 26120 selectElement[0].value = option.selectValue; 26121 option.element.selected = true; 26122 option.element.setAttribute('selected', 'selected'); 26123 } 26124 } else { 26125 if (value === null || providedEmptyOption) { 26126 removeUnknownOption(); 26127 renderEmptyOption(); 26128 } else { 26129 removeEmptyOption(); 26130 renderUnknownOption(); 26131 } 26132 } 26133 }; 26134 26135 selectCtrl.readValue = function readNgOptionsValue() { 26136 26137 var selectedOption = options.selectValueMap[selectElement.val()]; 26138 26139 if (selectedOption && !selectedOption.disabled) { 26140 removeEmptyOption(); 26141 removeUnknownOption(); 26142 return options.getViewValueFromOption(selectedOption); 26143 } 26144 return null; 26145 }; 26146 26147 // If we are using `track by` then we must watch the tracked value on the model 26148 // since ngModel only watches for object identity change 26149 if (ngOptions.trackBy) { 26150 scope.$watch( 26151 function() { return ngOptions.getTrackByValue(ngModelCtrl.$viewValue); }, 26152 function() { ngModelCtrl.$render(); } 26153 ); 26154 } 26155 26156 } else { 26157 26158 ngModelCtrl.$isEmpty = function(value) { 26159 return !value || value.length === 0; 26160 }; 26161 26162 26163 selectCtrl.writeValue = function writeNgOptionsMultiple(value) { 26164 options.items.forEach(function(option) { 26165 option.element.selected = false; 26166 }); 26167 26168 if (value) { 26169 value.forEach(function(item) { 26170 var option = options.getOptionFromViewValue(item); 26171 if (option && !option.disabled) option.element.selected = true; 26172 }); 26173 } 26174 }; 26175 26176 26177 selectCtrl.readValue = function readNgOptionsMultiple() { 26178 var selectedValues = selectElement.val() || [], 26179 selections = []; 26180 26181 forEach(selectedValues, function(value) { 26182 var option = options.selectValueMap[value]; 26183 if (!option.disabled) selections.push(options.getViewValueFromOption(option)); 26184 }); 26185 26186 return selections; 26187 }; 26188 26189 // If we are using `track by` then we must watch these tracked values on the model 26190 // since ngModel only watches for object identity change 26191 if (ngOptions.trackBy) { 26192 26193 scope.$watchCollection(function() { 26194 if (isArray(ngModelCtrl.$viewValue)) { 26195 return ngModelCtrl.$viewValue.map(function(value) { 26196 return ngOptions.getTrackByValue(value); 26197 }); 26198 } 26199 }, function() { 26200 ngModelCtrl.$render(); 26201 }); 26202 26203 } 26204 } 26205 26206 26207 if (providedEmptyOption) { 26208 26209 // we need to remove it before calling selectElement.empty() because otherwise IE will 26210 // remove the label from the element. wtf? 26211 emptyOption.remove(); 26212 26213 // compile the element since there might be bindings in it 26214 $compile(emptyOption)(scope); 26215 26216 // remove the class, which is added automatically because we recompile the element and it 26217 // becomes the compilation root 26218 emptyOption.removeClass('ng-scope'); 26219 } else { 26220 emptyOption = jqLite(optionTemplate.cloneNode(false)); 26221 } 26222 26223 // We need to do this here to ensure that the options object is defined 26224 // when we first hit it in writeNgOptionsValue 26225 updateOptions(); 26226 26227 // We will re-render the option elements if the option values or labels change 26228 scope.$watchCollection(ngOptions.getWatchables, updateOptions); 26229 26230 // ------------------------------------------------------------------ // 26231 26232 26233 function updateOptionElement(option, element) { 26234 option.element = element; 26235 element.disabled = option.disabled; 26236 if (option.value !== element.value) element.value = option.selectValue; 26237 if (option.label !== element.label) { 26238 element.label = option.label; 26239 element.textContent = option.label; 26240 } 26241 } 26242 26243 function addOrReuseElement(parent, current, type, templateElement) { 26244 var element; 26245 // Check whether we can reuse the next element 26246 if (current && lowercase(current.nodeName) === type) { 26247 // The next element is the right type so reuse it 26248 element = current; 26249 } else { 26250 // The next element is not the right type so create a new one 26251 element = templateElement.cloneNode(false); 26252 if (!current) { 26253 // There are no more elements so just append it to the select 26254 parent.appendChild(element); 26255 } else { 26256 // The next element is not a group so insert the new one 26257 parent.insertBefore(element, current); 26258 } 26259 } 26260 return element; 26261 } 26262 26263 26264 function removeExcessElements(current) { 26265 var next; 26266 while (current) { 26267 next = current.nextSibling; 26268 jqLiteRemove(current); 26269 current = next; 26270 } 26271 } 26272 26273 26274 function skipEmptyAndUnknownOptions(current) { 26275 var emptyOption_ = emptyOption && emptyOption[0]; 26276 var unknownOption_ = unknownOption && unknownOption[0]; 26277 26278 if (emptyOption_ || unknownOption_) { 26279 while (current && 26280 (current === emptyOption_ || 26281 current === unknownOption_)) { 26282 current = current.nextSibling; 26283 } 26284 } 26285 return current; 26286 } 26287 26288 26289 function updateOptions() { 26290 26291 var previousValue = options && selectCtrl.readValue(); 26292 26293 options = ngOptions.getOptions(); 26294 26295 var groupMap = {}; 26296 var currentElement = selectElement[0].firstChild; 26297 26298 // Ensure that the empty option is always there if it was explicitly provided 26299 if (providedEmptyOption) {
26300 selectElement.prepend(emptyOption); 26301 } 26302 26303 currentElement = skipEmptyAndUnknownOptions(currentElement); 26304 26305 options.items.forEach(function updateOption(option) { 26306 var group; 26307 var groupElement; 26308 var optionElement; 26309 26310 if (option.group) { 26311 26312 // This option is to live in a group 26313 // See if we have already created this group 26314 group = groupMap[option.group]; 26315 26316 if (!group) { 26317 26318 // We have not already created this group 26319 groupElement = addOrReuseElement(selectElement[0], 26320 currentElement, 26321 'optgroup', 26322 optGroupTemplate); 26323 // Move to the next element 26324 currentElement = groupElement.nextSibling; 26325 26326 // Update the label on the group element 26327 groupElement.label = option.group; 26328 26329 // Store it for use later 26330 group = groupMap[option.group] = { 26331 groupElement: groupElement, 26332 currentOptionElement: groupElement.firstChild 26333 }; 26334 26335 } 26336 26337 // So now we have a group for this option we add the option to the group 26338 optionElement = addOrReuseElement(group.groupElement, 26339 group.currentOptionElement, 26340 'option', 26341 optionTemplate); 26342 updateOptionElement(option, optionElement); 26343 // Move to the next element 26344 group.currentOptionElement = optionElement.nextSibling; 26345 26346 } else { 26347 26348 // This option is not in a group 26349 optionElement = addOrReuseElement(selectElement[0], 26350 currentElement, 26351 'option', 26352 optionTemplate); 26353 updateOptionElement(option, optionElement); 26354 // Move to the next element 26355 currentElement = optionElement.nextSibling; 26356 } 26357 }); 26358 26359 26360 // Now remove all excess options and group 26361 Object.keys(groupMap).forEach(function(key) { 26362 removeExcessElements(groupMap[key].currentOptionElement); 26363 }); 26364 removeExcessElements(currentElement); 26365 26366 ngModelCtrl.$render(); 26367 26368 // Check to see if the value has changed due to the update to the options 26369 if (!ngModelCtrl.$isEmpty(previousValue)) { 26370 var nextValue = selectCtrl.readValue(); 26371 if (ngOptions.trackBy ? !equals(previousValue, nextValue) : previousValue !== nextValue) { 26372 ngModelCtrl.$setViewValue(nextValue); 26373 ngModelCtrl.$render(); 26374 } 26375 } 26376 26377 } 26378 26379 } 26380 }; 26381}]; 26382 26383/** 26384 * @ngdoc directive 26385 * @name ngPluralize 26386 * @restrict EA 26387 * 26388 * @description 26389 * `ngPluralize` is a directive that displays messages according to en-US localization rules. 26390 * These rules are bundled with angular.js, but can be overridden 26391 * (see {@link guide/i18n Angular i18n} dev guide). You configure ngPluralize directive 26392 * by specifying the mappings between 26393 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 26394 * and the strings to be displayed. 26395 * 26396 * # Plural categories and explicit number rules 26397 * There are two 26398 * [plural categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) 26399 * in Angular's default en-US locale: "one" and "other". 26400 * 26401 * While a plural category may match many numbers (for example, in en-US locale, "other" can match 26402 * any number that is not 1), an explicit number rule can only match one number. For example, the 26403 * explicit number rule for "3" matches the number 3. There are examples of plural categories 26404 * and explicit number rules throughout the rest of this documentation. 26405 * 26406 * # Configuring ngPluralize 26407 * You configure ngPluralize by providing 2 attributes: `count` and `when`. 26408 * You can also provide an optional attribute, `offset`. 26409 * 26410 * The value of the `count` attribute can be either a string or an {@link guide/expression 26411 * Angular expression}; these are evaluated on the current scope for its bound value. 26412 * 26413 * The `when` attribute specifies the mappings between plural categories and the actual 26414 * string to be displayed. The value of the attribute should be a JSON object. 26415 * 26416 * The following example shows how to configure ngPluralize: 26417 * 26418 * ```html 26419 * <ng-pluralize count="personCount"
26420 when="{'0': 'Nobody is viewing.', 26421 * 'one': '1 person is viewing.', 26422 * 'other': '{} people are viewing.'}"> 26423 * </ng-pluralize> 26424 *``` 26425 * 26426 * In the example, `"0: Nobody is viewing."` is an explicit number rule. If you did not 26427 * specify this rule, 0 would be matched to the "other" category and "0 people are viewing" 26428 * would be shown instead of "Nobody is viewing". You can specify an explicit number rule for 26429 * other numbers, for example 12, so that instead of showing "12 people are viewing", you can 26430 * show "a dozen people are viewing". 26431 * 26432 * You can use a set of closed braces (`{}`) as a placeholder for the number that you want substituted 26433 * into pluralized strings. In the previous example, Angular will replace `{}` with 26434 * <span ng-non-bindable>`{{personCount}}`</span>. The closed braces `{}` is a placeholder 26435 * for <span ng-non-bindable>{{numberExpression}}</span>. 26436 * 26437 * If no rule is defined for a category, then an empty string is displayed and a warning is generated. 26438 * Note that some locales define more categories than `one` and `other`. For example, fr-fr defines `few` and `many`. 26439 * 26440 * # Configuring ngPluralize with offset 26441 * The `offset` attribute allows further customization of pluralized text, which can result in 26442 * a better user experience. For example, instead of the message "4 people are viewing this document", 26443 * you might display "John, Kate and 2 others are viewing this document". 26444 * The offset attribute allows you to offset a number by any desired value. 26445 * Let's take a look at an example: 26446 * 26447 * ```html 26448 * <ng-pluralize count="personCount" offset=2 26449 * when="{'0': 'Nobody is viewing.', 26450 * '1': '{{person1}} is viewing.', 26451 * '2': '{{person1}} and {{person2}} are viewing.', 26452 * 'one': '{{person1}}, {{person2}} and one other person are viewing.', 26453 * 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 26454 * </ng-pluralize> 26455 * ``` 26456 * 26457 * Notice that we are still using two plural categories(one, other), but we added 26458 * three explicit number rules 0, 1 and 2. 26459 * When one person, perhaps John, views the document, "John is viewing" will be shown. 26460 * When three people view the document, no explicit number rule is found, so 26461 * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. 26462 * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing" 26463 * is shown. 26464 * 26465 * Note that when you specify offsets, you must provide explicit number rules for 26466 * numbers from 0 up to and including the offset. If you use an offset of 3, for example, 26467 * you must provide explicit number rules for 0, 1, 2 and 3. You must also provide plural strings for 26468 * plural categories "one" and "other". 26469 * 26470 * @param {string|expression} count The variable to be bound to. 26471 * @param {string} when The mapping between plural category to its corresponding strings. 26472 * @param {number=} offset Offset to deduct from the total number. 26473 * 26474 * @example 26475 <example module="pluralizeExampl
vendor: 4,913 bytes, lines 26475-26589
26475e"> 26476 <file name="index.html"> 26477 <script> 26478 angular.module('pluralizeExample', []) 26479 .controller('ExampleController', ['$scope', function($scope) { 26480 $scope.person1 = 'Igor'; 26481 $scope.person2 = 'Misko'; 26482 $scope.personCount = 1; 26483 }]); 26484 </script> 26485 <div ng-controller="ExampleController"> 26486 <label>Person 1:<input type="text" ng-model="person1" value="Igor" /></label><br/> 26487 <label>Person 2:<input type="text" ng-model="person2" value="Misko" /></label><br/> 26488 <label>Number of People:<input type="text" ng-model="personCount" value="1" /></label><br/> 26489 26490 <!--- Example with simple pluralization rules for en locale ---> 26491 Without Offset: 26492 <ng-pluralize count="personCount" 26493 when="{'0': 'Nobody is viewing.', 26494 'one': '1 person is viewing.', 26495 'other': '{} people are viewing.'}"> 26496 </ng-pluralize><br> 26497 26498 <!--- Example with offset ---> 26499 With Offset(2): 26500 <ng-pluralize count="personCount" offset=2 26501 when="{'0': 'Nobody is viewing.', 26502 '1': '{{person1}} is viewing.', 26503 '2': '{{person1}} and {{person2}} are viewing.', 26504 'one': '{{person1}}, {{person2}} and one other person are viewing.', 26505 'other': '{{person1}}, {{person2}} and {} other people are viewing.'}"> 26506 </ng-pluralize> 26507 </div> 26508 </file> 26509 <file name="protractor.js" type="protractor"> 26510 it('should show correct pluralized string', function() { 26511 var withoutOffset = element.all(by.css('ng-pluralize')).get(0); 26512 var withOffset = element.all(by.css('ng-pluralize')).get(1); 26513 var countInput = element(by.model('personCount')); 26514 26515 expect(withoutOffset.getText()).toEqual('1 person is viewing.'); 26516 expect(withOffset.getText()).toEqual('Igor is viewing.'); 26517 26518 countInput.clear(); 26519 countInput.sendKeys('0'); 26520 26521 expect(withoutOffset.getText()).toEqual('Nobody is viewing.'); 26522 expect(withOffset.getText()).toEqual('Nobody is viewing.'); 26523 26524 countInput.clear(); 26525 countInput.sendKeys('2'); 26526 26527 expect(withoutOffset.getText()).toEqual('2 people are viewing.'); 26528 expect(withOffset.getText()).toEqual('Igor and Misko are viewing.'); 26529 26530 countInput.clear(); 26531 countInput.sendKeys('3'); 26532 26533 expect(withoutOffset.getText()).toEqual('3 people are viewing.'); 26534 expect(withOffset.getText()).toEqual('Igor, Misko and one other person are viewing.'); 26535 26536 countInput.clear(); 26537 countInput.sendKeys('4'); 26538 26539 expect(withoutOffset.getText()).toEqual('4 people are viewing.'); 26540 expect(withOffset.getText()).toEqual('Igor, Misko and 2 other people are viewing.'); 26541 }); 26542 it('should show data-bound names', function() { 26543 var withOffset = element.all(by.css('ng-pluralize')).get(1); 26544 var personCount = element(by.model('personCount')); 26545 var person1 = element(by.model('person1')); 26546 var person2 = element(by.model('person2')); 26547 personCount.clear(); 26548 personCount.sendKeys('4'); 26549 person1.clear(); 26550 person1.sendKeys('Di'); 26551 person2.clear(); 26552 person2.sendKeys('Vojta'); 26553 expect(withOffset.getText()).toEqual('Di, Vojta and 2 other people are viewing.'); 26554 }); 26555 </file> 26556 </example> 26557 */ 26558var ngPluralizeDirective = ['$locale', '$interpolate', '$log', function($locale, $interpolate, $log) { 26559 var BRACE = /{}/g, 26560 IS_WHEN = /^when(Minus)?(.+)$/; 26561 26562 return { 26563 link: function(scope, element, attr) { 26564 var numberExp = attr.count, 26565 whenExp = attr.$attr.when && element.attr(attr.$attr.when), // we have {{}} in attrs 26566 offset = attr.offset || 0, 26567 whens = scope.$eval(whenExp) || {}, 26568 whensExpFns = {}, 26569 startSymbol = $interpolate.startSymbol(), 26570 endSymbol = $interpolate.endSymbol(), 26571 braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol, 26572 watchRemover = angular.noop, 26573 lastCount; 26574 26575 forEach(attr, function(expression, attributeName) { 26576 var tmpMatch = IS_WHEN.exec(attributeName); 26577 if (tmpMatch) { 26578 var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]); 26579 whens[whenKey] = element.attr(attr.$attr[attributeName]); 26580 } 26581 }); 26582 forEach(whens, function(expression, key) { 26583 whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement)); 26584 26585 }); 26586 26587 scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) { 26588 var count = parseFloat(newVal); 26589 var countIsNaN = isN
vendor: 10,711 bytes, lines 26589-26825
26589aN(count); 26590 26591 if (!countIsNaN && !(count in whens)) { 26592 // If an explicit number rule such as 1, 2, 3... is defined, just use it. 26593 // Otherwise, check it against pluralization rules in $locale service. 26594 count = $locale.pluralCat(count - offset); 26595 } 26596 26597 // If both `count` and `lastCount` are NaN, we don't need to re-register a watch. 26598 // In JS `NaN !== NaN`, so we have to exlicitly check. 26599 if ((count !== lastCount) && !(countIsNaN && isNumber(lastCount) && isNaN(lastCount))) { 26600 watchRemover(); 26601 var whenExpFn = whensExpFns[count]; 26602 if (isUndefined(whenExpFn)) { 26603 if (newVal != null) { 26604 $log.debug("ngPluralize: no rule defined for '" + count + "' in " + whenExp); 26605 } 26606 watchRemover = noop; 26607 updateElementText(); 26608 } else { 26609 watchRemover = scope.$watch(whenExpFn, updateElementText); 26610 } 26611 lastCount = count; 26612 } 26613 }); 26614 26615 function updateElementText(newText) { 26616 element.text(newText || ''); 26617 } 26618 } 26619 }; 26620}]; 26621 26622/** 26623 * @ngdoc directive 26624 * @name ngRepeat 26625 * 26626 * @description 26627 * The `ngRepeat` directive instantiates a template once per item from a collection. Each template 26628 * instance gets its own scope, where the given loop variable is set to the current collection item, 26629 * and `$index` is set to the item index or key. 26630 * 26631 * Special properties are exposed on the local scope of each template instance, including: 26632 * 26633 * | Variable | Type | Details | 26634 * |-----------|-----------------|-----------------------------------------------------------------------------| 26635 * | `$index` | {@type number} | iterator offset of the repeated element (0..length-1) | 26636 * | `$first` | {@type boolean} | true if the repeated element is first in the iterator. | 26637 * | `$middle` | {@type boolean} | true if the repeated element is between the first and last in the iterator. | 26638 * | `$last` | {@type boolean} | true if the repeated element is last in the iterator. | 26639 * | `$even` | {@type boolean} | true if the iterator position `$index` is even (otherwise false). | 26640 * | `$odd` | {@type boolean} | true if the iterator position `$index` is odd (otherwise false). | 26641 * 26642 * Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}. 26643 * This may be useful when, for instance, nesting ngRepeats. 26644 * 26645 * 26646 * # Iterating over object properties 26647 * 26648 * It is possible to get `ngRepeat` to iterate over the properties of an object using the following 26649 * syntax: 26650 * 26651 * ```js 26652 * <div ng-repeat="(key, value) in myObj"> ... </div> 26653 * ``` 26654 * 26655 * You need to be aware that the JavaScript specification does not define the order of keys 26656 * returned for an object. (To mitigate this in Angular 1.3 the `ngRepeat` directive 26657 * used to sort the keys alphabetically.) 26658 * 26659 * Version 1.4 removed the alphabetic sorting. We now rely on the order returned by the browser 26660 * when running `for key in myObj`. It seems that browsers generally follow the strategy of providing 26661 * keys in the order in which they were defined, although there are exceptions when keys are deleted 26662 * and reinstated. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#Cross-browser_issues 26663 * 26664 * If this is not desired, the recommended workaround is to convert your object into an array 26665 * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could 26666 * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter) 26667 * or implement a `$watch` on the object yourself. 26668 * 26669 * 26670 * # Tracking and Duplicates 26671 * 26672 * When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM: 26673 * 26674 * * When an item is added, a new instance of the template is added to the DOM. 26675 * * When an item is removed, its template instance is removed from the DOM. 26676 * * When items are reordered, their respective templates are reordered in the DOM. 26677 * 26678 * By default, `ngRepeat` does not allow duplicate items in arrays. This is because when 26679 * there are duplicates, it is not possible to maintain a one-to-one mapping between collection 26680 * items and DOM elements. 26681 * 26682 * If you do need to repeat duplicate items, you can substitute the default tracking behavior 26683 * with your own using the `track by` expression. 26684 * 26685 * For example, you may track items by the index of each item in the collection, using the 26686 * special scope property `$index`: 26687 * ```html 26688 * <div ng-repeat="n in [42, 42, 43, 43] track by $index"> 26689 * {{n}} 26690 * </div> 26691 * ``` 26692 * 26693 * You may use arbitrary expressions in `track by`, including references to custom functions 26694 * on the scope: 26695 * ```html 26696 * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)"> 26697 * {{n}} 26698 * </div> 26699 * ``` 26700 * 26701 * If you are working with objects that have an identifier property, you can track 26702 * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat` 26703 * will not have to rebuild the DOM elements for items it has already rendered, even if the 26704 * JavaScript objects in the collection have been substituted for new ones: 26705 * ```html 26706 * <div ng-repeat="model in collection track by model.id"> 26707 * {{model.name}} 26708 * </div> 26709 * ``` 26710 * 26711 * When no `track by` expression is provided, it is equivalent to tracking by the built-in 26712 * `$id` function, which tracks items by their identity: 26713 * ```html 26714 * <div ng-repeat="obj in collection track by $id(obj)"> 26715 * {{obj.prop}} 26716 * </div> 26717 * ``` 26718 * 26719 * <div class="alert alert-warning"> 26720 * **Note:** `track by` must always be the last expression: 26721 * </div> 26722 * ``` 26723 * <div ng-repeat="model in collection | orderBy: 'id' as filtered_result track by model.id"> 26724 * {{model.name}} 26725 * </div> 26726 * ``` 26727 * 26728 * # Special repeat start and end points 26729 * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending 26730 * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. 26731 * 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) 26732 * up to and including the ending HTML tag where **ng-repeat-end** is placed. 26733 * 26734 * The example below makes use of this feature: 26735 * ```html 26736 * <header ng-repeat-start="item in items"> 26737 * Header {{ item }} 26738 * </header> 26739 * <div class="body"> 26740 * Body {{ item }} 26741 * </div> 26742 * <footer ng-repeat-end> 26743 * Footer {{ item }} 26744 * </footer> 26745 * ``` 26746 * 26747 * And with an input of {@type ['A','B']} for the items variable in the example above, the output will evaluate to: 26748 * ```html 26749 * <header> 26750 * Header A 26751 * </header> 26752 * <div class="body"> 26753 * Body A 26754 * </div> 26755 * <footer> 26756 * Footer A 26757 * </footer> 26758 * <header> 26759 * Header B 26760 * </header> 26761 * <div class="body"> 26762 * Body B 26763 * </div> 26764 * <footer> 26765 * Footer B 26766 * </footer> 26767 * ``` 26768 * 26769 * The custom start and end points for ngRepeat also support all other HTML directive syntax flavors provided in AngularJS (such 26770 * as **data-ng-repeat-start**, **x-ng-repeat-start** and **ng:repeat-start**). 26771 * 26772 * @animations 26773 * **.enter** - when a new item is added to the list or when an item is revealed after a filter 26774 * 26775 * **.leave** - when an item is removed from the list or when an item is filtered out 26776 * 26777 * **.move** - when an adjacent item is filtered out causing a reorder or when the item contents are reordered 26778 * 26779 * @element ANY 26780 * @scope 26781 * @priority 1000 26782 * @param {repeat_expression} ngRepeat The expression indicating how to enumerate a collection. These 26783 * formats are currently supported: 26784 * 26785 * * `variable in expression` â where variable is the user defined loop variable and `expression` 26786 * is a scope expression giving the collection to enumerate. 26787 * 26788 * For example: `album in artist.albums`. 26789 * 26790 * * `(key, value) in expression` â where `key` and `value` can be any user defined identifiers, 26791 * and `expression` is the scope expression giving the collection to enumerate. 26792 * 26793 * For example: `(name, age) in {'adam':10, 'amalie':12}`. 26794 * 26795 * * `variable in expression track by tracking_expression` â You can also provide an optional tracking expression 26796 * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression 26797 * is specified, ng-repeat associates elements by identity. It is an error to have 26798 * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are 26799 * mapped to the same DOM element, which is not possible.) 26800 * 26801 * Note that the tracking expression must come last, after any filters, and the alias expression. 26802 * 26803 * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements 26804 * will be associated by item identity in the array. 26805 * 26806 * For example: `item in items track by $id(item)`. A built in `$id()` function can be used to assign a unique 26807 * `$$hashKey` property to each item in the array. This property is then used as a key to associated DOM elements 26808 * with the corresponding item in the array by identity. Moving the same object in array would move the DOM 26809 * element in the same way in the DOM. 26810 * 26811 * For example: `item in items track by item.id` is a typical pattern when the items come from the database. In this 26812 * case the object identity does not matter. Two objects are considered equivalent as long as their `id` 26813 * property is same. 26814 * 26815 * For example: `item in items | filter:searchText track by item.id` is a pattern that might be used to apply a filter 26816 * to items in conjunction with a tracking expression. 26817 * 26818 * * `variable in expression as alias_expression` â You can also provide an optional alias expression which will then store the 26819 * intermediate results of the repeater after the filters have been applied. Typically this is used to render a special message 26820 * when a filter is active on the repeater, but the filtered result set is empty. 26821 * 26822 * For example: `item in items | filter:x as results` will store the fragment of the repeated items as `results`, but only after 26823 * the items have been processed through the filter. 26824 * 26825 * Please note that `as [variable name] is not an operator but rather a part of ngRepeat micro-syntax s
26825o it can be used only at the end 26826 * (and not as operator, inside an expression). 26827 * 26828 * For example: `item in items | filter : x | orderBy : order | limitTo : limit as results` . 26829 * 26830 * @example 26831 * This example initializes the scope to a list of names and 26832 * then uses `ngRepeat` to display every person: 26833 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 26834 <file name="index.html"> 26835 <div ng-init="friends = [ 26836 {name:'John', age:25, gender:'boy'}, 26837 {name:'Jessie', age:30, gender:'girl'}, 26838 {name:'Johanna', age:28, gender:'girl'}, 26839 {name:'Joy', age:15, gender:'girl'}, 26840 {name:'Mary', age:28, gender:'girl'}, 26841 {name:'Peter', age:95, gender:'boy'}, 26842 {name:'Sebastian', age:50, gender:'boy'}, 26843 {name:'Erika', age:27, gender:'girl'}, 26844 {name:'Patrick', age:40, gender:'boy'}, 26845 {name:'Samantha', age:60, gender:'girl'} 26846 ]"> 26847 I have {{friends.length}} friends. They are: 26848 <input type="search" ng-model="q" placeholder="filter friends..." aria-label="filter friends" /> 26849 <ul class="example-animate-container"> 26850 <li class="animate-repeat" ng-repeat="friend in friends | filter:q as results"> 26851 [{{$index + 1}}] {{friend.name}} who is {{friend.age}} years old. 26852 </li> 26853 <li class="animate-repeat" ng-if="results.length == 0"> 26854 <strong>No results found...</strong> 26855 </li> 26856 </ul> 26857 </div> 26858 </file> 26859 <file name="animations.css"> 26860 .example-animate-container { 26861 background:white; 26862 border:1px solid black; 26863 list-style:none; 26864 margin:0; 26865 padding:0 10px; 26866 } 26867 26868 .animate-repeat { 26869 line-height:40px; 26870 list-style:none; 26871 box-sizing:border-box; 26872 } 26873 26874 .animate-repeat.ng-move, 26875 .animate-repeat.ng-enter, 26876 .animate-repeat.ng-leave { 26877 -webkit-transition:all linear 0.5s; 26878 transition:all linear 0.5s; 26879 } 26880 26881 .animate-repeat.ng-leave.ng-leave-active, 26882 .animate-repeat.ng-move, 26883 .animate-repeat.ng-enter { 26884 opacity:0; 26885 max-height:0; 26886 } 26887 26888 .animate-repeat.ng-leave, 26889 .animate-repeat.ng-move.ng-move-active, 26890 .animate-repeat.ng-enter.ng-enter-active { 26891 opacity:1; 26892 max-height:40px; 26893 } 26894 </file> 26895 <file name="protractor.js" type="protractor"> 26896 var friends = element.all(by.repeater('friend in friends')); 26897 26898 it('should render initial data set', function() { 26899 expect(friends.count()).toBe(10); 26900 expect(friends.get(0).getText()).toEqual('[1] John who is 25 years old.'); 26901 expect(friends.get(1).getText()).toEqual('[2] Jessie who is 30 years old.'); 26902 expect(friends.last().getText()).toEqual('[10] Samantha who is 60 years old.'); 26903 expect(element(by.binding('friends.length')).getText()) 26904 .toMatch("I have 10 friends. They are:"); 26905 }); 26906 26907 it('should update repeater when filter predicate changes', function() { 26908 expect(friends.count()).toBe(10); 26909 26910 element(by.model('q')).sendKeys('ma'); 26911 26912 expect(friends.count()).toBe(2); 26913 expect(friends.get(0).getText()).toEqual('[1] Mary who is 28 years old.'); 26914 expect(friends.last().getText()).toEqual('[2] Samantha who is 60 years old.'); 26915 }); 26916 </file> 26917 </example> 26918 */ 26919var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) { 26920 var NG_REMOVED = '$$NG_REMOVED'; 26921 var ngRepeatMinErr = minErr('ngRepeat'); 26922 26923 var updateScope = function(scope, index, valueIdentifier, value, keyIdentifier, key, arrayLength) { 26924 // TODO(perf): generate setters to shave off ~40ms or 1-1.5% 26925 scope[valueIdentifier] = value; 26926 if (keyIdentifier) scope[keyIdentifier] = key; 26927 scope.$index = index; 26928 scope.$first = (index === 0); 26929 scope.$last = (index === (arrayLength - 1)); 26930 scope.$middle = !(scope.$first || scope.$last); 26931 // jshint bitwise: false 26932 scope.$odd = !(scope.$even = (index&1) === 0); 26933 // jshint bitwise: true 26934 }; 26935 26936 var getBlockStart = function(block) { 26937 return block.clone[0]; 26938 }; 26939 26940 var getBlockEnd = function(block) { 26941 return block.clone[block.clone.length - 1]; 26942 }; 26943 26944 26945 return { 26946 restrict: 'A', 26947 multiElement: true, 26948 transclude: 'element', 26949 priority: 1000, 26950 terminal: true, 26951 $$tlb: true, 26952 compile: function ngRepeatCompile($element, $attr) { 26953 var expression = $attr.ngRepeat; 26954 var ngRepeatEndComment = document.createComment(' end ngRepeat: ' + expression + ' '); 26955 26956 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*$/); 26957 26958 if (!match) { 26959 throw ngRepeatMinErr('iexp', "Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.", 26960 expression); 26961 } 26962 26963 var lhs = match[1]; 26964 var rhs = match[2]; 26965 var aliasAs = match[3]; 26966 var trackByExp = match[4]; 26967 26968 match = lhs.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/); 26969 26970 if (!match) { 26971 throw ngRepeatMinErr('iidexp', "'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'.", 26972 lhs); 26973 } 26974 var valueIdentifier = match[3] || match[1]; 26975 var keyIdentifier = match[2]; 26976 26977 if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) || 26978 /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) { 26979 throw ngRepeatMinErr('badident', "alias '{0}
vendor: 11,910 bytes, lines 26979-27250
26979' is invalid --- must be a valid JS identifier which is not a reserved name.", 26980 aliasAs); 26981 } 26982 26983 var trackByExpGetter, trackByIdExpFn, trackByIdArrayFn, trackByIdObjFn; 26984 var hashFnLocals = {$id: hashKey}; 26985 26986 if (trackByExp) { 26987 trackByExpGetter = $parse(trackByExp); 26988 } else { 26989 trackByIdArrayFn = function(key, value) { 26990 return hashKey(value); 26991 }; 26992 trackByIdObjFn = function(key) { 26993 return key; 26994 }; 26995 } 26996 26997 return function ngRepeatLink($scope, $element, $attr, ctrl, $transclude) { 26998 26999 if (trackByExpGetter) { 27000 trackByIdExpFn = function(key, value, index) { 27001 // assign key, value, and $index to the locals so that they can be used in hash functions 27002 if (keyIdentifier) hashFnLocals[keyIdentifier] = key; 27003 hashFnLocals[valueIdentifier] = value; 27004 hashFnLocals.$index = index; 27005 return trackByExpGetter($scope, hashFnLocals); 27006 }; 27007 } 27008 27009 // Store a list of elements from previous run. This is a hash where key is the item from the 27010 // iterator, and the value is objects with following properties. 27011 // - scope: bound scope 27012 // - element: previous element. 27013 // - index: position 27014 // 27015 // We are using no-proto object so that we don't need to guard against inherited props via 27016 // hasOwnProperty. 27017 var lastBlockMap = createMap(); 27018 27019 //watch props 27020 $scope.$watchCollection(rhs, function ngRepeatAction(collection) { 27021 var index, length, 27022 previousNode = $element[0], // node that cloned nodes should be inserted after 27023 // initialized to the comment node anchor 27024 nextNode, 27025 // Same as lastBlockMap but it has the current state. It will become the 27026 // lastBlockMap on the next iteration. 27027 nextBlockMap = createMap(), 27028 collectionLength, 27029 key, value, // key/value of iteration 27030 trackById, 27031 trackByIdFn, 27032 collectionKeys, 27033 block, // last object information {scope, element, id} 27034 nextBlockOrder, 27035 elementsToRemove; 27036 27037 if (aliasAs) { 27038 $scope[aliasAs] = collection; 27039 } 27040 27041 if (isArrayLike(collection)) { 27042 collectionKeys = collection; 27043 trackByIdFn = trackByIdExpFn || trackByIdArrayFn; 27044 } else { 27045 trackByIdFn = trackByIdExpFn || trackByIdObjFn; 27046 // if object, extract keys, in enumeration order, unsorted 27047 collectionKeys = []; 27048 for (var itemKey in collection) { 27049 if (collection.hasOwnProperty(itemKey) && itemKey.charAt(0) !== '$') { 27050 collectionKeys.push(itemKey); 27051 } 27052 } 27053 } 27054 27055 collectionLength = collectionKeys.length; 27056 nextBlockOrder = new Array(collectionLength); 27057 27058 // locate existing items 27059 for (index = 0; index < collectionLength; index++) { 27060 key = (collection === collectionKeys) ? index : collectionKeys[index]; 27061 value = collection[key]; 27062 trackById = trackByIdFn(key, value, index); 27063 if (lastBlockMap[trackById]) { 27064 // found previously seen block 27065 block = lastBlockMap[trackById]; 27066 delete lastBlockMap[trackById]; 27067 nextBlockMap[trackById] = block; 27068 nextBlockOrder[index] = block; 27069 } else if (nextBlockMap[trackById]) { 27070 // if collision detected. restore lastBlockMap and throw an error 27071 forEach(nextBlockOrder, function(block) { 27072 if (block && block.scope) lastBlockMap[block.id] = block; 27073 }); 27074 throw ngRepeatMinErr('dupes', 27075 "Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}", 27076 expression, trackById, value); 27077 } else { 27078 // new never before seen block 27079 nextBlockOrder[index] = {id: trackById, scope: undefined, clone: undefined}; 27080 nextBlockMap[trackById] = true; 27081 } 27082 } 27083 27084 // remove leftover items 27085 for (var blockKey in lastBlockMap) { 27086 block = lastBlockMap[blockKey]; 27087 elementsToRemove = getBlockNodes(block.clone); 27088 $animate.leave(elementsToRemove); 27089 if (elementsToRemove[0].parentNode) { 27090 // if the element was not removed yet because of pending animation, mark it as deleted 27091 // so that we can ignore it later 27092 for (index = 0, length = elementsToRemove.length; index < length; index++) { 27093 elementsToRemove[index][NG_REMOVED] = true; 27094 } 27095 } 27096 block.scope.$destroy(); 27097 } 27098 27099 // we are not using forEach for perf reasons (trying to avoid #call) 27100 for (index = 0; index < collectionLength; index++) { 27101 key = (collection === collectionKeys) ? index : collectionKeys[index]; 27102 value = collection[key]; 27103 block = nextBlockOrder[index]; 27104 27105 if (block.scope) { 27106 // if we have already seen this object, then we need to reuse the 27107 // associated scope/element 27108 27109 nextNode = previousNode; 27110 27111 // skip nodes that are already pending removal via leave animation 27112 do { 27113 nextNode = nextNode.nextSibling; 27114 } while (nextNode && nextNode[NG_REMOVED]); 27115 27116 if (getBlockStart(block) != nextNode) { 27117 // existing item which got moved 27118 $animate.move(getBlockNodes(block.clone), null, jqLite(previousNode)); 27119 } 27120 previousNode = getBlockEnd(block); 27121 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 27122 } else { 27123 // new item which we don't know about 27124 $transclude(function ngRepeatTransclude(clone, scope) { 27125 block.scope = scope; 27126 // http://jsperf.com/clone-vs-createcomment 27127 var endNode = ngRepeatEndComment.cloneNode(false); 27128 clone[clone.length++] = endNode; 27129 27130 // TODO(perf): support naked previousNode in `enter` to avoid creation of jqLite wrapper? 27131 $animate.enter(clone, null, jqLite(previousNode)); 27132 previousNode = endNode; 27133 // Note: We only need the first/last node of the cloned nodes. 27134 // However, we need to keep the reference to the jqlite wrapper as it might be changed later 27135 // by a directive with templateUrl when its template arrives. 27136 block.clone = clone; 27137 nextBlockMap[block.id] = block; 27138 updateScope(block.scope, index, valueIdentifier, value, keyIdentifier, key, collectionLength); 27139 }); 27140 } 27141 } 27142 lastBlockMap = nextBlockMap; 27143 }); 27144 }; 27145 } 27146 }; 27147}]; 27148 27149var NG_HIDE_CLASS = 'ng-hide'; 27150var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; 27151/** 27152 * @ngdoc directive 27153 * @name ngShow 27154 * 27155 * @description 27156 * The `ngShow` directive shows or hides the given HTML element based on the expression 27157 * provided to the `ngShow` attribute. The element is shown or hidden by removing or adding 27158 * the `.ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 27159 * in AngularJS and sets the display style to none (using an !important flag). 27160 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 27161 * 27162 * ```html 27163 * <!-- when $scope.myValue is truthy (element is visible) --> 27164 * <div ng-show="myValue"></div> 27165 * 27166 * <!-- when $scope.myValue is falsy (element is hidden) --> 27167 * <div ng-show="myValue" class="ng-hide"></div> 27168 * ``` 27169 * 27170 * When the `ngShow` expression evaluates to a falsy value then the `.ng-hide` CSS class is added to the class 27171 * attribute on the element causing it to become hidden. When truthy, the `.ng-hide` CSS class is removed 27172 * from the element causing the element not to appear hidden. 27173 * 27174 * ## Why is !important used? 27175 * 27176 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 27177 * can be easily overridden by heavier selectors. For example, something as simple 27178 * as changing the display style on a HTML list item would make hidden elements appear visible. 27179 * This also becomes a bigger issue when dealing with CSS frameworks. 27180 * 27181 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 27182 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 27183 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 27184 * 27185 * ### Overriding `.ng-hide` 27186 * 27187 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 27188 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 27189 * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope 27190 * with extra animation classes that can be added. 27191 * 27192 * ```css 27193 * .ng-hide:not(.ng-hide-animate) { 27194 * /* this is just another form of hiding an element */ 27195 * display: block!important; 27196 * position: absolute; 27197 * top: -9999px; 27198 * left: -9999px; 27199 * } 27200 * ``` 27201 * 27202 * By default you don't need to override in CSS anything and the animations will work around the display style. 27203 * 27204 * ## A note about animations with `ngShow` 27205 * 27206 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 27207 * is true and false. This system works like the animation system present with ngClass except that 27208 * you must also include the !important flag to override the display property 27209 * so that you can perform an animation when the element is hidden during the time of the animation. 27210 * 27211 * ```css 27212 * // 27213 * //a working example can be found at the bottom of this page 27214 * // 27215 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 27216 * /* this is required as of 1.3x to properly 27217 * apply all styling in a show/hide animation */ 27218 * transition: 0s linear all; 27219 * } 27220 * 27221 * .my-element.ng-hide-add-active, 27222 * .my-element.ng-hide-remove-active { 27223 * /* the transition is defined in the active class */ 27224 * transition: 1s linear all; 27225 * } 27226 * 27227 * .my-element.ng-hide-add { ... } 27228 * .my-element.ng-hide-add.ng-hide-add-active { ... } 27229 * .my-element.ng-hide-remove { ... } 27230 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 27231 * ``` 27232 * 27233 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 27234 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 27235 * 27236 * @animations 27237 * addClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a truthy value and the just before contents are set to visible 27238 * removeClass: `.ng-hide` - happens after the `ngShow` expression evaluates to a non truthy value and just before the contents are set to hidden 27239 * 27240 * @element ANY 27241 * @param {expression} ngShow If the {@link guide/expression expression} is truthy 27242 * then the element is shown or hidden respectively. 27243 * 27244 * @example 27245 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 27246 <file name="index.html"> 27247 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngHide"><br/> 27248 <div> 27249 Show: 27250 <div class="check-element animate-show" ng-show="checked">
27251 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 27252 </div> 27253 </div> 27254 <div> 27255 Hide: 27256 <div class="check-element animate-show" ng-hide="checked"> 27257 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 27258 </div> 27259 </div> 27260 </file> 27261 <file name="glyphicons.css"> 27262 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 27263 </file> 27264 <file name="animations.css"> 27265 .animate-show { 27266 line-height: 20px; 27267 opacity: 1; 27268 padding: 10px; 27269 border: 1px solid black; 27270 background: white; 27271 } 27272 27273 .animate-show.ng-hide-add.ng-hide-add-active, 27274 .animate-show.ng-hide-remove.ng-hide-remove-active { 27275 -webkit-transition: all linear 0.5s; 27276 transition: all linear 0.5s; 27277 } 27278 27279 .animate-show.ng-hide { 27280 line-height: 0; 27281 opacity: 0; 27282 padding: 0 10px; 27283 } 27284 27285 .check-element { 27286 padding: 10px; 27287 border: 1px solid black; 27288 background: white; 27289 } 27290 </file> 27291 <file name="protractor.js" type="protractor"> 27292 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 27293 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 27294 27295 it('should check ng-show / ng-hide', function() { 27296 expect(thumbsUp.isDisplayed()).toBeFalsy(); 27297 expect(thumbsDown.isDisplayed()).toBeTruthy(); 27298 27299 element(by.model('checked')).click(); 27300 27301 expect(thumbsUp.isDisplayed()).toBeTruthy(); 27302 expect(thumbsDown.isDisplayed()).toBeFalsy(); 27303 }); 27304 </file> 27305 </example> 27306 */ 27307var ngShowDirective = ['$animate', function($animate) { 27308 return { 27309 restrict: 'A', 27310 multiElement: true, 27311 link: function(scope, element, attr) { 27312 scope.$watch(attr.ngShow, function ngShowWatchAction(value) { 27313 // we're adding a temporary, animation-specific class for ng-hide since this way 27314 // we can control when the element is actually displayed on screen without having 27315 // to have a global/greedy CSS selector that breaks when other animations are run. 27316 // Read: https://github.com/angular/angular.js/issues/9103#issuecomment-58335845 27317 $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, { 27318 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 27319 }); 27320 }); 27321 } 27322 }; 27323}]; 27324 27325 27326/** 27327 * @ngdoc directive 27328 * @name ngHide 27329 * 27330 * @description 27331 * The `ngHide` directive shows or hides the given HTML element based on the expression 27332 * provided to the `ngHide` attribute. The element is shown or hidden by removing or adding 27333 * the `ng-hide` CSS class onto the element. The `.ng-hide` CSS class is predefined 27334 * in AngularJS and sets the display style to none (using an !important flag). 27335 * For CSP mode please add `angular-csp.css` to your html file (see {@link ng.directive:ngCsp ngCsp}). 27336 * 27337 * ```html 27338 * <!-- when $scope.myValue is truthy (element is hidden) --> 27339 * <div ng-hide="myValue" class="ng-hide"></div> 27340 * 27341 * <!-- when $scope.myValue is falsy (element is visible) --> 27342 * <div ng-hide="myValue"></div> 27343 * ``` 27344 * 27345 * When the `ngHide` expression evaluates to a truthy value then the `.ng-hide` CSS class is added to the class 27346 * attribute on the element causing it to become hidden. When falsy, the `.ng-hide` CSS class is removed 27347 * from the element causing the element not to appear hidden. 27348 * 27349 * ## Why is !important used? 27350 * 27351 * You may be wondering why !important is used for the `.ng-hide` CSS class. This is because the `.ng-hide` selector 27352 * can be easily overridden by heavier selectors. For example, something as simple 27353 * as changing the display style on a HTML list item would make hidden elements appear visible. 27354 * This also becomes a bigger issue when dealing with CSS frameworks. 27355 * 27356 * By using !important, the show and hide behavior will work as expected despite any clash between CSS selector 27357 * specificity (when !important isn't used with any conflicting styles). If a developer chooses to override the 27358 * styling to change how to hide an element then it is just a matter of using !important in their own CSS code. 27359 * 27360 * ### Overriding `.ng-hide` 27361 * 27362 * By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change 27363 * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide` 27364 * class in CSS: 27365 * 27366 * ```css 27367 * .ng-hide { 27368 * /* this is just another form of hiding an element */ 27369 * display: block!important; 27370 * position: absolute; 27371 * top: -9999px; 27372 * left: -9999px; 27373 * } 27374 * ``` 27375 * 27376 * By default you don't need to override in CSS anything and the animations will work around the display style. 27377 * 27378 * ## A note about animations with `ngHide` 27379 * 27380 * Animations in ngShow/ngHide work with the show and hide events that are triggered when the directive expression 27381 * is true and false. This system works like the animation system present with ngClass, except that the `.ng-hide` 27382 * CSS class is added and removed for you instead of your own CSS class. 27383 * 27384 * ```css 27385 * // 27386 * //a working example can be found at the bottom of this page 27387 * // 27388 * .my-element.ng-hide-add, .my-element.ng-hide-remove { 27389 * transition: 0.5s linear all; 27390 * } 27391 * 27392 * .my-element.ng-hide-add { ... } 27393 * .my-element.ng-hide-add.ng-hide-add-active { ... } 27394 * .my-element.ng-hide-remove { ... } 27395 * .my-element.ng-hide-remove.ng-hide-remove-active { ... } 27396 * ``` 27397 * 27398 * Keep in mind that, as of AngularJS version 1.3.0-beta.11, there is no need to change the display 27399 * property to block during animation states--ngAnimate will handle the style toggling automatically for you. 27400 * 27401 * @animations 27402 * removeClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a truthy value and just before the contents are set to hidden 27403 * addClass: `.ng-hide` - happens after the `ngHide` expression evaluates to a non truthy value and just before the contents are set to visible 27404 * 27405 * @element ANY 27406 * @param {expression} ngHide If the {@link guide/expression expression} is truthy then 27407 * the element is shown or hidden respectively. 27408 * 27409 * @example 27410 <example module="ngAnimate" deps="angular-animate.js" animations="true"> 27411 <file name="index.html"> 27412 Click me: <input type="checkbox" ng-model="checked" aria-label="Toggle ngShow"><br/> 27413 <div> 27414 Show: 27415 <div class="check-element animate-hide" ng-show="checked">
27416 <span class="glyphicon glyphicon-thumbs-up"></span> I show up when your checkbox is checked. 27417 </div> 27418 </div> 27419 <div> 27420 Hide: 27421 <div class="check-element animate-hide" ng-hide="checked"> 27422 <span class="glyphicon glyphicon-thumbs-down"></span> I hide when your checkbox is checked. 27423 </div> 27424 </div> 27425 </file> 27426 <file name="glyphicons.css"> 27427 @import url(../../components/bootstrap-3.1.1/css/bootstrap.css); 27428 </file> 27429 <file name="animations.css"> 27430 .animate-hide { 27431 -webkit-transition: all linear 0.5s; 27432 transition: all linear 0.5s; 27433 line-height: 20px; 27434 opacity: 1; 27435 padding: 10px; 27436 border: 1px solid black; 27437 background: white; 27438 } 27439 27440 .animate-hide.ng-hide { 27441 line-height: 0; 27442 opacity: 0; 27443 padding: 0 10px; 27444 } 27445 27446 .check-element { 27447 padding: 10px; 27448 border: 1px solid black; 27449 background: white; 27450 } 27451 </file> 27452 <file name="protractor.js" type="protractor"> 27453 var thumbsUp = element(by.css('span.glyphicon-thumbs-up')); 27454 var thumbsDown = element(by.css('span.glyphicon-thumbs-down')); 27455 27456 it('should check ng-show / ng-hide', function() { 27457 expect(thumbsUp.isDisplayed()).toBeFalsy(); 27458 expect(thumbsDown.isDisplayed()).toBeTruthy(); 27459 27460 element(by.model('checked')).click(); 27461 27462 expect(thumbsUp.isDisplayed()).toBeTruthy(); 27463 expect(thumbsDown.isDisplayed()).toBeFalsy(); 27464 }); 27465 </file> 27466 </example> 27467 */ 27468var ngHideDirective = ['$animate', function($animate) { 27469 return { 27470 restrict: 'A', 27471 multiElement: true, 27472 link: function(scope, element, attr) { 27473 scope.$watch(attr.ngHide, function ngHideWatchAction(value) { 27474 // The comment inside of the ngShowDirective explains why we add and 27475 // remove a temporary class for the show/hide animation 27476 $animate[value ? 'addClass' : 'removeClass'](element,NG_HIDE_CLASS, { 27477 tempClasses: NG_HIDE_IN_PROGRESS_CLASS 27478 }); 27479 }); 27480 } 27481 }; 27482}]; 27483 27484/** 27485 * @ngdoc directive 27486 * @name ngStyle 27487 * @restrict AC 27488 * 27489 * @description 27490 * The `ngStyle` directive allows you to set CSS style on an HTML element conditionally. 27491 * 27492 * @element ANY 27493 * @param {expression} ngStyle 27494 * 27495 * {@link guide/expression Expression} which evals to an 27496 * object whose keys are CSS style names and values are corresponding values for those CSS 27497 * keys. 27498 * 27499 * Since some CSS style names are not valid keys for an object, they must be quoted. 27500 * See the 'background-color' style in the example below. 27501 * 27502 * @example 27503 <example> 27504 <file name="index.html"> 27505 <input type="button" value="set color" ng-click="myStyle={color:'red'}"> 27506 <input type="button" value="set background" ng-click="myStyle={'background-color':'blue'}"> 27507 <input type="button" value="clear" ng-click="myStyle={}"> 27508 <br/> 27509 <span ng-style="myStyle">Sample Text</span> 27510 <pre>myStyle={{myStyle}}</pre> 27511 </file> 27512 <file name="style.css"> 27513 span { 27514 color: black; 27515 } 27516 </file> 27517 <file name="protractor.js" type="protractor"> 27518 var colorSpan = element(by.css('span')); 27519 27520 it('should check ng-style', function() { 27521 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 27522 element(by.css('input[value=\'set color\']')).click(); 27523 expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); 27524 element(by.css('input[value=clear]')).click(); 27525 expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); 27526 }); 27527 </file> 27528 </example> 27529 */ 27530var ngStyleDirective = ngDirective(function(scope, element, attr) { 27531 scope.$watch(attr.ngStyle, function ngStyleWatchAction(newStyles, oldStyles) { 27532 if (oldStyles && (newStyles !== oldStyles)) { 27533 forEach(oldStyles, function(val, style) { element.css(style, '');}); 27534 } 27535 if (newStyles) element.css(newStyles); 27536 }, true); 27537}); 27538 27539/** 27540 * @ngdoc directive 27541 * @name ngSwitch 27542 * @restrict EA 27543 * 27544 * @description 27545 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression. 27546 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location 27547 * as specified in the template. 27548 * 27549 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it 27550 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element 27551 * matches the value obtained from the evaluated expression. In other words, you define a container element 27552 * (where you place the directive), place an expression on the **`on="..."` attribute** 27553 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place 27554 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on 27555 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default 27556 * attribute is displayed. 27557 * 27558 * <div class="alert alert-info"> 27559 * Be aware that the attribute values to match against cannot be expressions. They are interpreted 27560 * as literal string values to match against. 27561 * For example, **`ng-switch-when="someVal"`** will match against the string `"s
27561omeVal"` not against the 27562 * value of the expression `$scope.someVal`. 27563 * </div> 27564 27565 * @animations 27566 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container 27567 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM 27568 * 27569 * @usage 27570 * 27571 * ``` 27572 * <ANY ng-switch="expression"> 27573 * <ANY ng-switch-when="matchValue1">...</ANY> 27574 * <ANY ng-switch-when="matchValue2">...</ANY> 27575 * <ANY ng-switch-default>...</ANY> 27576 * </ANY> 27577 * ``` 27578 * 27579 * 27580 * @scope 27581 * @priority 1200 27582 * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>. 27583 * On child elements add: 27584 * 27585 * * `ngSwitchWhen`: the case statement to match against. If match then this 27586 * case will be displayed. If the same match appears multiple times, all the 27587 * elements will be displayed. 27588 * * `ngSwitchDefault`: the default case when no other case match. If there 27589 * are multiple default cases, all of them will be displayed when no other 27590 * case match. 27591 * 27592 * 27593 * @example 27594 <example module="switchExample" deps="angular-animate.js" animations="true"> 27595 <file name="index.html">
27596 <div ng-controller="ExampleController"> 27597 <select ng-model="selection" ng-options="item for item in items"> 27598 </select> 27599 <code>selection={{selection}}</code> 27600 <hr/> 27601 <div class="animate-switch-container" 27602 ng-switch on="selection"> 27603 <div class="animate-switch" ng-switch-when="settings">Settings Div</div> 27604 <div class="animate-switch" ng-switch-when="home">Home Span</div> 27605 <div class="animate-switch" ng-switch-default>default</div> 27606 </div> 27607 </div> 27608 </file> 27609 <file name="script.js"> 27610 angular.module('switchExample', ['ngAnimate']) 27611 .controller('ExampleController', ['$scope', function($scope) { 27612 $scope.items = ['settings', 'home', 'other']; 27613 $scope.selection = $scope.items[0]; 27614 }]); 27615 </file> 27616 <file name="animations.css"> 27617 .animate-switch-container { 27618 position:relative; 27619 background:white; 27620 border:1px solid black; 27621 height:40px; 27622 overflow:hidden; 27623 } 27624 27625 .animate-switch { 27626 padding:10px; 27627 } 27628 27629 .animate-switch.ng-animate { 27630 -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 27631 transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s; 27632 27633 position:absolute; 27634 top:0; 27635 left:0; 27636 right:0; 27637 bottom:0; 27638 } 27639 27640 .animate-switch.ng-leave.ng-leave-active, 27641 .animate-switch.ng-enter { 27642 top:-50px; 27643 } 27644 .animate-switch.ng-leave, 27645 .animate-switch.ng-enter.ng-enter-active { 27646 top:0; 27647 } 27648 </file> 27649 <file name="protractor.js" type="protractor"> 27650 var switchElem = element(by.css('[ng-switch]')); 27651 var select = element(by.model('selection')); 27652 27653 it('should start in settings', function() { 27654 expect(switchElem.getText()).toMatch(/Settings Div/); 27655 }); 27656 it('should change to home', function() { 27657 select.all(by.css('option')).get(1).click(); 27658 expect(switchElem.getText()).toMatch(/Home Span/); 27659 }); 27660 it('should select default', function() { 27661 select.all(by.css('option')).get(2).click(); 27662 expect(switchElem.getText()).toMatch(/default/); 27663 }); 27664 </file> 27665 </example> 27666 */ 27667var ngSwitchDirective = ['$animate', function($animate) { 27668 return { 27669 require: 'ngSwitch', 27670 27671 // asks for $scope to fool the BC controller module 27672 controller: ['$scope', function ngSwitchController() { 27673 this.cases = {}; 27674 }], 27675 link: function(scope, element, attr, ngSwitchController) { 27676 var watchExpr = attr.ngSwitch || attr.on, 27677 selectedTranscludes = [], 27678 selectedElements = [], 27679 previousLeaveAnimations = [], 27680 selectedScopes = []; 27681 27682 var spliceFactory = function(array, index) { 27683 return function() { array.splice(index, 1); }; 27684 }; 27685 27686 scope.$watch(watchExpr, function ngSwitchWatchAction(value) { 27687 var i, ii; 27688 for (i = 0, ii = previousLeaveAnimations.length; i < ii; ++i) { 27689 $animate.cancel(previousLeaveAnimations[i]); 27690 } 27691 previousLeaveAnimations.length = 0; 27692 27693 for (i = 0, ii = selectedScopes.length; i < ii; ++i) { 27694 var selected = getBlockNodes(selectedElements[i].clone); 27695 selectedScopes[i].$destroy(); 27696 var promise = previousLeaveAnimations[i] = $animate.leave(selected); 27697 promise.then(spliceFactory(previousLeaveAnimations, i)); 27698 } 27699 27700 selectedElements.length = 0; 27701 selectedScopes.length = 0; 27702 27703 if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) { 27704 forEach(selectedTranscludes, function(selectedTransclude) { 27705 selectedTransclude.transclude(function(caseElement, selectedScope) { 27706 selectedScopes.push(selectedScope); 27707 var anchor = selectedTransclude.element; 27708 caseElement[caseElement.length++] = document.createComment(' end ngSwitchWhen: '); 27709 var block = { clone: caseElement }; 27710 27711 selectedElements.push(block);
27712 $animate.enter(caseElement, anchor.parent(), anchor); 27713 }); 27714 }); 27715 } 27716 }); 27717 } 27718 }; 27719}]; 27720 27721var ngSwitchWhenDirective = ngDirective({ 27722 transclude: 'element', 27723 priority: 1200, 27724 require: '^ngSwitch', 27725 multiElement: true, 27726 link: function(scope, element, attrs, ctrl, $transclude) { 27727 ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []); 27728 ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element }); 27729 } 27730}); 27731 27732var ngSwitchDefaultDirective = ngDirective({ 27733 transclude: 'element', 27734 priority: 1200, 27735 require: '^ngSwitch', 27736 multiElement: true, 27737 link: function(scope, element, attr, ctrl, $transclude) { 27738 ctrl.cases['?'] = (ctrl.cases['?'] || []); 27739 ctrl.cases['?'].push({ transclude: $transclude, element: element }); 27740 } 27741}); 27742 27743/** 27744 * @ngdoc directive 27745 * @name ngTransclude 27746 * @restrict EAC 27747 * 27748 * @description 27749 * Directive that marks the insertion point for the transcluded DOM of the nearest parent directive that uses transclusion. 27750 * 27751 * Any existing content of the element that this directive is placed on will be removed before the transcluded content is inserted. 27752 * 27753 * @element ANY 27754 * 27755 * @example 27756 <example module="transcludeExample"> 27757 <file name="index.html"> 27758 <script> 27759 angular.module('transcludeExample', []) 27760 .directive('pane', function(){ 27761 return { 27762 restrict: 'E', 27763 transclude: true, 27764 scope: { title:'@' }, 27765 template: '<div style="border: 1px solid black;">' + 27766 '<div style="background-color: gray">{{title}}</div>' + 27767 '<ng-transclude></ng-transclude>' + 27768 '</div>' 27769 }; 27770 }) 27771 .controller('ExampleController', ['$scope', function($scope) { 27772 $scope.title = 'Lorem Ipsum'; 27773 $scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...'; 27774 }]); 27775 </script>
27776 <div ng-controller="ExampleController"> 27777 <input ng-model="title" aria-label="title"> <br/> 27778 <textarea ng-model="text" aria-label="text"></textarea> <br/> 27779 <pane title="{{title}}">{{text}}</pane> 27780 </div> 27781 </file> 27782 <file name="protractor.js" type="protractor"> 27783 it('should have transcluded', function() { 27784 var titleElement = element(by.model('title')); 27785 titleElement.clear(); 27786 titleElement.sendKeys('TITLE'); 27787 var textElement = element(by.model('text')); 27788 textElement.clear(); 27789 textElement.sendKeys('TEXT'); 27790 expect(element(by.binding('title')).getText()).toEqual('TITLE'); 27791 expect(element(by.binding('text')).getText()).toEqual('TEXT'); 27792 }); 27793 </file> 27794 </example> 27795 * 27796 */ 27797var ngTranscludeDirective = ngDirective({ 27798 restrict: 'EAC', 27799 link: function($scope, $element, $attrs, controller, $transclude) { 27800 if (!$transclude) { 27801 throw minErr('ngTransclude')('orphan', 27802 'Illegal use of ngTransclude directive in the template! ' + 27803 'No parent directive that requires a transclusion found. ' + 27804 'Element: {0}', 27805 startingTag($element)); 27806 } 27807 27808 $transclude(function(clone) { 27809 $element.empty(); 27810 $element.append(clone); 27811 }); 27812 } 27813}); 27814 27815/** 27816 * @ngdoc directive 27817 * @name script 27818 * @restrict E 27819 * 27820 * @description 27821 * Load the content of a `<script>` element into {@link ng.$templateCache `$templateCache`}, so that the 27822 * template can be used by {@link ng.directive:ngInclude `ngInclude`}, 27823 * {@link ngRoute.directive:ngView `ngView`}, or {@link guide/directive directives}. The type of the 27824 * `<script>` element must be specified as `text/ng-template`, and a cache name for the template must be 27825 * assigned through the element's `id`, which can then be used as a directive's `templateUrl`. 27826 * 27827 * @param {string} type Must be set to `'text/ng-template'`. 27828 * @param {string} id Cache name of the template. 27829 * 27830 * @example 27831 <example> 27832 <file name="index.html"> 27833 <script type="text/ng-template" id="/tpl.html"> 27834 Content of the template. 27835 </script> 27836 27837 <a ng-click="currentTpl='/tpl.html'" id="tpl-link">Load inlined template</a> 27838 <div id="tpl-content" ng-include src="currentTpl"></div> 27839 </file> 27840 <file name="protractor.js" type="protractor"> 27841 it('should load template defined inside script tag', function() { 27842 element(by.css('#tpl-link')).click(); 27843 expect(element(by.css('#tpl-content')).getText()).toMatch(/Content of the template/); 27844 }); 27845 </file> 27846 </example> 27847 */ 27848var scriptDirective = ['$templateCache', function($templateCache) { 27849 return { 27850 restrict: 'E', 27851 terminal: true, 27852 compile: function(element, attr) { 27853 if (attr.type == 'text/ng-template') { 27854 var templateUrl = attr.id, 27855 text = element[0].text; 27856 27857 $templateCache.put(templateUrl, text); 27858 } 27859 } 27860 }; 27861}]; 27862 27863var noopNgModelController = { $setViewValue: noop, $render: noop }; 27864 27865/** 27866 * @ngdoc type 27867 * @name select.SelectController 27868 * @description 27869 * The controller for the `<select>` directive. This provides support for reading 27870 * and writing the selected value(s) of the control and also coordinates dynamically 27871 * added `<option>` elements, perhaps by an `ngRepeat` directive. 27872 */ 27873var SelectController = 27874 ['$element', '$scope', '$attrs', function($element, $scope, $attrs) { 27875 27876 var self = this, 27877 optionsMap = new HashMap(); 27878 27879 // If the ngModel doesn't get provided then provide a dummy noop version to prevent errors 27880 self.ngModelCtrl = noopNgModelController; 27881 27882 // The "unknown" option is one that is prepended to the list if the viewValue 27883 // does not match any of the options. When it is rendered the value of the unknown 27884 // option is '? XXX ?' where XXX is the hashKey of the value that is not known. 27885 // 27886 // We can't just jqLite('<option>') since jqLite is not smart enough 27887 // to create it in <select> and IE barfs otherwise. 27888 self.unknownOption = jqLite(document.createElement('option')); 27889 self.renderUnknownOption = function(val) { 27890 var unknownVal = '? ' + hashKey(val) + ' ?'; 27891 self.unknownOption.val(unknownVal);
27892 $element.prepend(self.unknownOption); 27893 $element.val(unknownVal); 27894 }; 27895 27896 $scope.$on('$destroy', function() { 27897 // disable unknown option so that we don't do work when the whole select is being destroyed 27898 self.renderUnknownOption = noop; 27899 }); 27900 27901 self.removeUnknownOption = function() { 27902 if (self.unknownOption.parent()) self.unknownOption.remove(); 27903 }; 27904 27905 27906 // Read the value of the select control, the implementation of this changes depending 27907 // upon whether the select can have multiple values and whether ngOptions is at work. 27908 self.readValue = function readSingleValue() { 27909 self.removeUnknownOption(); 27910 return $element.val(); 27911 }; 27912 27913 27914 // Write the value to the select control, the implementation of this changes depending 27915 // upon whether the select can have multiple values and whether ngOptions is at work. 27916 self.writeValue = function writeSingleValue(value) { 27917 if (self.hasOption(value)) { 27918 self.removeUnknownOption(); 27919 $element.val(value); 27920 if (value === '') self.emptyOption.prop('selected', true); // to make IE9 happy 27921 } else { 27922 if (value == null && self.emptyOption) { 27923 self.removeUnknownOption(); 27924 $element.val(''); 27925 } else { 27926 self.renderUnknownOption(value); 27927 } 27928 } 27929 }; 27930 27931 27932 // Tell the select control that an option, with the given value, has been added 27933 self.addOption = function(value, element) { 27934 assertNotHasOwnProperty(value, '"option value"'); 27935 if (value === '') { 27936 self.emptyOption = element; 27937 } 27938 var count = optionsMap.get(value) || 0; 27939 optionsMap.put(value, count + 1); 27940 }; 27941 27942 // Tell the select control that an option, with the given value, has been removed 27943 self.removeOption = function(value) { 27944 var count = optionsMap.get(value); 27945 if (count) { 27946 if (count === 1) { 27947 optionsMap.remove(value); 27948 if (value === '') { 27949 self.emptyOption = undefined; 27950 } 27951 } else { 27952 optionsMap.put(value, count - 1); 27953 } 27954 } 27955 }; 27956 27957 // Check whether the select control has an option matching the given value 27958 self.hasOption = function(value) { 27959 return !!optionsMap.get(value); 27960 }; 27961}]; 27962 27963/** 27964 * @ngdoc directive 27965 * @name select 27966 * @restrict E 27967 * 27968 * @description 27969 * HTML `SELECT` element with angular data-binding. 27970 * 27971 * In many cases, `ngRepeat` can be used on `<option>` elements instead of {@link ng.directive:ngOptions 27972 * ngOptions} to achieve a similar result. However, `ngOptions` provides some benefits such as reducing 27973 * memory and increasing speed by not creating a new scope for each repeated instance, as well as providing 27974 * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the 27975 * comprehension expression. 27976 * 27977 * When an item in the `<select>` menu is selected, the array element or object property 27978 * represented by the selected option will be bound to the model identified by the `ngModel` 27979 * directive. 27980 * 27981 * If the viewValue contains a value that doesn't match any of the options then the control 27982 * will automatically add an "unknown" option, which it then removes when this is resolved. 27983 * 27984 * Optionally, a single hard-coded `<option>` element, with the value set to an empty string, can 27985 * be nested into the `<select>` element. This element will then represent the `null` or "not selected" 27986 * option. See example below for demonstration. 27987 * 27988 * <div class="alert alert-info"> 27989 * The value of a `select` directive used without `ngOptions` is always a string. 27990 * When the model needs to be bound to a non-string value, you must either explictly convert it 27991 * using a directive (see example below) or use `ngOptions` to specify the set of options. 27992 * This is because an option element can only be bound to string values at present. 27993 * </div> 27994 * 27995 * ### Example (binding `select` to a non-string value) 27996 * 27997 * <example name="select-with-non-string-options" module="nonStringSelect"> 27998 * <file name="index.html"> 27999 * <select ng-model="model.id" convert-to-number> 28000 * <option value="0">Zero</option> 28001 * <option value="1">One</option> 28002 * <option value="2">Two</option> 28003 * </select> 28004 * {{ model }} 28005 * </file> 28006 * <file name="app.js"> 28007 * angular.module('nonStringSelect', []) 28008 * .run(function($rootScope) { 28009 * $rootScope.model = { id: 2 }; 28010 * }) 28011 * .directive('convertToNumber', function() { 28012 * return { 28013 * require: 'ngModel', 28014 * link: function(scope, element, attrs, ngModel) { 28015 * ngModel.$parsers.push(function(val) { 28016 * return parseInt(val, 10); 28017 * }); 28018 * ngModel.$formatters.push(function(val) { 28019 * return '' + val; 28020 * }); 28021 * } 28022 * }; 28023 * }); 28024 * </file> 28025 * <file name="protractor.js" type="protractor"> 28026 * it('should initialize to model', function() { 28027 * var select = element(by.css('select')); 28028 * expect(element(by.model('model.id')).$('option:checked').getText()).toEqual('Two'); 28029 * }); 28030 * </file> 28031 * </example> 28032 * 28033 */ 28034var selectDirective = function() { 28035 28036 return { 28037 restrict: 'E', 28038 require: ['select', '?ngModel'], 28039 controller: SelectController, 28040 link: function(scope, element, attr, ctrls) { 28041 28042 // if ngModel is not defined, we don't need to do anything 28043 var ngModelCtrl = ctrls[1]; 28044 if (!ngModelCtrl) return; 28045 28046 var selectCtrl = ctrls[0]; 28047 28048 selectCtrl.ngModelCtrl = ngModelCtrl; 28049 28050 // We delegate rendering to the `writeValue` method, which can be changed 28051 // if the select can have multiple selected values or if the options are being 28052 // generated by `ngOptions` 28053 ngModelCtrl.$render = function() { 28054 selectCtrl.writeValue(ngModelCtrl.$viewValue); 28055 }; 28056 28057 // When the selected item(s) changes we delegate getting the value of the select control 28058 // to the `readValue` method, which can be changed if the select can have multiple 28059 // selected values or if the options are being generated by `ngOptions` 28060 element.on('change', function() { 28061 scope.$apply(function() { 28062 ngModelCtrl.$setViewValue(selectCtrl.readValue()); 28063 }); 28064 }); 28065 28066 // If the select allows multiple values then we need to modify how we read and write 28067 // values from and to the control; also what it means for the value to be empty and
28068 // we have to add an extra watch since ngModel doesn't work well with arrays - it 28069 // doesn't trigger rendering if only an item in the array changes. 28070 if (attr.multiple) { 28071 28072 // Read value now needs to check each option to see if it is selected 28073 selectCtrl.readValue = function readMultipleValue() { 28074 var array = []; 28075 forEach(element.find('option'), function(option) { 28076 if (option.selected) { 28077 array.push(option.value); 28078 } 28079 }); 28080 return array; 28081 }; 28082 28083 // Write value now needs to set the selected property of each matching option 28084 selectCtrl.writeValue = function writeMultipleValue(value) { 28085 var items = new HashMap(value); 28086 forEach(element.find('option'), function(option) { 28087 option.selected = isDefined(items.get(option.value)); 28088 }); 28089 }; 28090 28091 // we have to do it on each watch since ngModel watches reference, but 28092 // we need to work of an array, so we need to see if anything was inserted/removed 28093 var lastView, lastViewRef = NaN; 28094 scope.$watch(function selectMultipleWatch() { 28095 if (lastViewRef === ngModelCtrl.$viewValue && !equals(lastView, ngModelCtrl.$viewValue)) { 28096 lastView = shallowCopy(ngModelCtrl.$viewValue); 28097 ngModelCtrl.$render(); 28098 } 28099 lastViewRef = ngModelCtrl.$viewValue; 28100 }); 28101 28102 // If we are a multiple select then value is now a collection 28103 // so the meaning of $isEmpty changes 28104 ngModelCtrl.$isEmpty = function(value) { 28105 return !value || value.length === 0; 28106 }; 28107 28108 } 28109 } 28110 }; 28111}; 28112 28113 28114// The option directive is purely designed to communicate the existence (or lack of) 28115// of dynamically created (and destroyed) option elements to their containing select 28116// directive via its controller. 28117var optionDirective = ['$interpolate', function($interpolate) { 28118 28119 function chromeHack(optionElement) { 28120 // Workaround for https://code.google.com/p/chromium/issues/detail?id=381459 28121 // Adding an <option selected="selected"> element to a <select required="required"> should 28122 // automatically select the new element 28123 if (optionElement[0].hasAttribute('selected')) { 28124 optionElement[0].selected = true; 28125 } 28126 } 28127 28128 return { 28129 restrict: 'E', 28130 priority: 100, 28131 compile: function(element, attr) { 28132 28133 // If the value attribute is not defined then we fall back to the 28134 // text content of the option element, which may be interpolated 28135 if (isUndefined(attr.value)) { 28136 var interpolateFn = $interpolate(element.text(), true); 28137 if (!interpolateFn) { 28138 attr.$set('value', element.text()); 28139 } 28140 } 28141 28142 return function(scope, element, attr) { 28143 28144 // This is an optimization over using ^^ since we don't want to have to search 28145 // all the way to the root of the DOM for every single option element 28146 var selectCtrlName = '$selectController', 28147 parent = element.parent(), 28148 selectCtrl = parent.data(selectCtrlName) || 28149 parent.parent().data(selectCtrlName); // in case we are in optgroup 28150 28151 // Only update trigger option updates if this is an option within a `select` 28152 // that also has `ngModel` attached 28153 if (selectCtrl && selectCtrl.ngModelCtrl) { 28154 28155 if (interpolateFn) { 28156 scope.$watch(interpolateFn, function interpolateWatchAction(newVal, oldVal) { 28157 attr.$set('value', newVal); 28158 if (oldVal !== newVal) { 28159 selectCtrl.removeOption(oldVal); 28160 } 28161 selectCtrl.addOption(newVal, element); 28162 selectCtrl.ngModelCtrl.$render(); 28163 chromeHack(element); 28164 }); 28165 } else { 28166 selectCtrl.addOption(attr.value, element); 28167 selectCtrl.ngModelCtrl.$render(); 28168 chromeHack(element); 28169 } 28170 28171 element.on('$destroy', function() { 28172 selectCtrl.removeOption(attr.value); 28173 selectCtrl.ngModelCtrl.$render(); 28174 }); 28175 } 28176 }; 28177 } 28178 }; 28179}]; 28180 28181var styleDirective = valueFn({ 28182 restrict: 'E', 28183 terminal: false 28184}); 28185 28186var requiredDirective = function() { 28187 return { 28188 restrict: 'A', 28189 require: '?ngModel', 28190 link: function(scope, elm, attr, ctrl) { 28191 if (!ctrl) return; 28192 attr.required = true; // force truthy in case we are on non input element 28193 28194 ctrl.$validators.required = function(modelValue, viewValue) { 28195 return !attr.required || !ctrl.$isEmpty(viewValue); 28196 }; 28197 28198 attr.$observe('required', function() { 28199 ctrl.$validate(); 28200 }); 28201 } 28202 }; 28203}; 28204 28205 28206var patternDirective = function() { 28207 return { 28208 restrict: 'A', 28209 require: '?ngModel', 28210 link: function(scope, elm, attr, ctrl) { 28211 if (!ctrl) return; 28212 28213 var regexp, patternExp = attr.ngPattern || attr.pattern; 28214 attr.$observe('pattern', function(regex) { 28215 if (isString(regex) && regex.length > 0) { 28216 regex = new RegExp('^' + regex + '$'); 28217 } 28218 28219 if (regex && !regex.test) { 28220 throw minErr('ngPattern')('noregexp', 28221 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp, 28222 regex, startingTag(elm)); 28223 } 28224 28225 regexp = regex || undefined; 28226 ctrl.$validate(); 28227 }); 28228 28229 ctrl.$validators.pattern = function(value) { 28230 return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value); 28231 }; 28232 } 28233 }; 28234}; 28235 28236 28237var maxlengthDirective = function() { 28238 return { 28239 restrict: 'A', 28240 require: '?ngModel', 28241 link: function(scope, elm, attr, ctrl) { 28242 if (!ctrl) return; 28243 28244 var maxlength = -1; 28245 attr.$observe('maxlength', function(value) { 28246 var intVal = toInt(value); 28247 maxlength = isNaN(intVal) ? -1 : intVal; 28248 ctrl.$validate(); 28249 }); 28250 ctrl.$validators.maxlength = function(modelValue, viewValue) { 28251 return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength); 28252 }; 28253 } 28254 }; 28255}; 28256 28257var minlengthDirective = function() { 28258 return { 28259 restrict: 'A', 28260 require: '?ngModel', 28261 link: function(scope, elm, attr, ctrl) { 28262 if (!ctrl) return; 28263 28264 var minlength = 0; 28265 attr.$observe('minlength', function(value) { 28266 minlength = toInt(value) || 0; 28267 ctrl.$validate(); 28268 }); 28269 ctrl.$validators.minlength = function(modelValue, viewValue) { 28270 return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength; 28271 }; 28272 } 28273 }; 28274}; 28275 28276 if (window.angular.bootstrap) { 28277 //AngularJS is already loaded, so we can return here... 28278 console.log('WARNING: Tried to load angular more than once.'); 28279 return; 28280 } 28281 28282 //try to bind to jquery now so that one can write jqLite(document).ready() 28283 //but we will rebind on bootstrap again. 28284 bindJQuery(); 28285 28286 publishExternalAPI(angular); 28287 28288 jqLite(document).ready(function() { 28289 angularInit(document, bootstrap); 28290 }); 28291 28292})(window, document); 28293 28294!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;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</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.